export class VoiceActor { private static readonly SUPPORTED_VOICES = ['jean', 'thierry', 'sylvie', 'antoine', 'fr-CA-JeanNeural', 'fr-FR-VivienneNeural']; private constructor(private readonly _value: string) {} static create(value: string): VoiceActor { if (!this.SUPPORTED_VOICES.includes(value)) { throw new Error(`Voice actor not supported: ${value}`); } return new VoiceActor(value); } get value(): string { return this._value; } } class SpeechRate { private constructor(private readonly _value: string) {} static create(value: string): SpeechRate { // Validate format like "+10%", "-20%", "0%" const rateRegex = /^[+-]?\d+%$/; if (!rateRegex.test(value)) { throw new Error('Invalid Speech Rate format (expected [+-]N%)'); } return new SpeechRate(value); } get value(): string { return this._value; } } export class NarrationContent { private static readonly EMOTION_REGEX = /\[(CRI|MURMURE|DEMON|COLERE|PEUR)\]/g; private constructor(private readonly _value: string) {} static create(value: string): NarrationContent { return new NarrationContent(value || ''); } get value(): string { return this._value; } get hasEmotions(): boolean { return /\[(CRI|MURMURE|DEMON|COLERE|PEUR)\]/.test(this._value); } get emotions(): string[] { const regex = /\[(CRI|MURMURE|DEMON|COLERE|PEUR)\]/g; const matches = this._value.match(regex); return matches ? Array.from(new Set(matches)) : []; } get plainText(): string { const regex = /\[(CRI|MURMURE|DEMON|COLERE|PEUR)\]/g; return this._value.replace(regex, '').replace(/\s+/g, ' ').trim(); } } export interface NarrationConfigProps { voice: string; rate: string; } export class NarrationConfig { private constructor( public readonly voice: VoiceActor, public readonly rate: SpeechRate ) {} static create(props: NarrationConfigProps): NarrationConfig { return new NarrationConfig( VoiceActor.create(props.voice), SpeechRate.create(props.rate) ); } static createDefault(): NarrationConfig { return this.create({ voice: 'jean', rate: '0%' }); } }