Dayward AI
Week 1 · D5About 5 hours

Voiceover, Subtitles, and Audio Tracks: Multi-Character Voices, Timeline Alignment, and Subtitle Files

Give every character their own voice, use voice duration to correct shot duration in return, generate subtitle files with a correct timeline, and get the relationship between background music and dialogue right.

Today's goals 0/3

Sign in to tick these off and save your progress.

Today's Goals

  1. Assign a voice per character and batch-synthesize dialogue, producing audio files ready to use directly
  2. Align audio duration with shot duration, and generate subtitle files with a correct timeline
  3. Explain the parameters in speech synthesis that most affect the finished feel, and the boundaries for handling background music

Yesterday you got a batch of moving shots. Today you give them voices — and settle a question the first four days kept deferring: how long should this shot actually be? When you finish, scroll back up and tick the three goals off.

Plain-Language Walkthrough

Break one default assumption first, or today's whole chapter reads as the only option available.

Not every video model produces a silent film. Some return picture only, with an empty audio track; some include ambient sound; some will generate lip-sync along with spoken lines written into the prompt, and the official docs show exactly that form, sometimes allowing a reference audio clip to fix the timbre. So "once you have the shot you must dub it" depends on which model you used — before you start, check whether the output even has an audio stream.

If the model can speak for itself, why does this course still run a separate synthesis step? Four engineering differences:

  1. The lines are controllable word for word. The script's dialogue is fixed. Let the model improvise and it will never say exactly that line — and in drama, one wrong word is a wrong line.
  2. The timbre stays stable across shots. A character appears in six shots; if each clip generates its own voice, one person ends up with six voices. This is the audio version of the face-swap problem, and audiences are more sensitive to voice than to face.
  3. The timeline becomes computable. The whole pipeline's timeline is derived backwards from voice duration (that is the second half of today). With speech baked into the video you cannot measure when each line starts or how long it runs — subtitle timecodes cannot be generated, and music ducking cannot be aligned.
  4. Changing one word differs by four orders of magnitude. Re-synthesizing a line costs a fraction of a cent; re-generating a shot costs dollars.

Conversely, if your piece explicitly wants no subtitles, the lines need not be exact, and lip-sync matters more, letting the video model speak is the easier choice — its lip movement is generated with the picture, and post cannot match that. Today teaches the controllable path; knowing the other one exists is how you know what you are choosing.

One voice per character

In a recording booth the voice director's first job is not to roll, it is casting: who voices the lead, who voices the second female. Once decided, it goes on the wall chart and does not change all season. Audiences remember voices better than you expect — change a character's voice in episode two and the comments immediately ask who this is.

So step one of dubbing is an assignment table, and that table should not be improvised on dubbing day. It belongs to the character card defined on D2: each character has a voiceId field holding the vendor's system voice identifier. Cross-episode consistency comes from that record, not from a temporary map inside the dubbing code — which is why this course calls the character card a record rather than a parameter.

MiniMax's system voices include a set tuned for short drama, named after the archetype: badao_shaoye the domineering heir, junlang_nanyou the handsome boyfriend, lengdan_xiongzhang the aloof senior, chunzhen_xuedi the innocent junior, bingjiao_didi the possessive little brother; on the female side wumei_yujie, danya_xuejie, tianxin_xiaoling, qiaopi_mengmei, diadia_xuemei. There is also a general set such as male-qn-jingying and female-chengshu, good for narration and background characters.

Beyond the voice itself, three parameters directly shape the finished feel:

Emotion (emotion) is the most overlooked and the most audible. Its values are a fixed enumeration: happy, sad, angry, fearful, disgusted, surprised, calm, fluent, whisper. The same line — "that phone is not yours" — is a cool warning under calm and a confrontation under angry; two completely different scenes. The engineering move is to give each character a baseline emotion and let individual lines override it. Short-form drama's emotional turns live in those overrides.

Speed (speed) ranges from 0.5 to 2, and it has a hidden effect: speed changes audio duration, and duration propagates into the shot. So speed cannot be nudged casually at this step; nudge it and you re-align the timeline.

Volume and pitch (vol, pitch) usually stay at their defaults. To make a character sound younger or heavier, change the voice, do not pull the pitch — pitched audio has an obvious synthetic edge.

Finally one return-value detail you must know: data.audio is a hex string by default, not base64. In Node that is Buffer.from(json.data.audio, "hex"). Decoding it as base64 gives you a file that saves fine and will not play, and the error message points nowhere near the cause.

tts.js
async function synthesize({ text, voiceId, emotion, speed, outPath }) {
  const res = await fetch(`${process.env.MINIMAX_BASE_URL}/v1/t2a_v2`, {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.MINIMAX_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      model: 'speech-2.8-hd',
      text,
      stream: false,
      voice_setting: { voice_id: voiceId, speed, vol: 1, pitch: 0, emotion },
      audio_setting: { sample_rate: 32000, bitrate: 128000, format: 'mp3', channel: 1 },
    }),
  })
  const json = await res.json()
  if (json.base_resp?.status_code) throw new Error(`synthesis failed ${json.base_resp.status_code}`)
  // data.audio is a hex string by default, not base64 - decoding as base64 gives an unplayable file
  await writeFile(outPath, Buffer.from(json.data.audio, 'hex'))
  // audio_length is in milliseconds and goes straight into the timeline
  return { path: outPath, durationMs: json.extra_info.audio_length }
}

Voices cast, parameters tuned, audio on disk — and now the real problem appears: this clip runs 3.3 seconds, but the shot you generated yesterday is 3 seconds. Who gives way?

Duration is a two-way constraint

This is the core of the chapter, and the reason the pipeline is ordered the way it is.

Shot duration has two sources. One comes from the script: the writer marked this as a quick cut, planned at 3 seconds. The other comes from the line: saying it takes 3.3 seconds. When the two disagree, one must give — and that choice decides the shape of your pipeline.

This course's ruling is dialogue first: stretch the shot, never trim the speech.

The reason is direct: an audience can hear a line being cut, and cannot see that a shot ran 0.8 seconds longer than planned. A clipped half-sentence is a defect; eight-tenths of a second is a slightly slower beat. And speeding up delivery to protect the planned length gives you an episode that sounds rushed, with speed-induced artifacts that are especially audible on voice.

Hence the order of the line: generate the picture, then synthesize the speech, then write shot duration back from speech duration. That looks roundabout — why not synthesize first and then generate a video of exactly that length? Because video duration comes in fixed tiers (six seconds by default); you cannot ask for exactly 6.34 seconds. So the practical arrangement is: video generated at planned length, timeline laid out by speech duration, and any shortfall in picture made up on the editing bay (freeze or extend) — D6's craft.

The arithmetic: a shot's required duration is lead-in silence + total speech + inter-line gaps + tail silence. The silences are not optional decoration — if the line starts the instant the shot does and the cut lands the instant it ends, the episode feels throttled. The final duration is the larger of planned and required, and any stretched shot gets flagged for the human review desk on D10.

One iron rule: the duration written into the timeline must be measured from the file on disk, never estimated. Character count times a coefficient is only good enough for scheduling. Real speech varies with speed, emotion and punctuation pauses, and a few hundred milliseconds per line is normal — while subtitle drift accumulates line by line: 200 milliseconds off on line one is two seconds off by line ten, and the audience sees subtitles racing the picture.

align.js
const LEAD_MS = 250 // lead-in silence
const TAIL_MS = 400 // tail silence
const GAP_MS = 180 // pause between two lines inside one shot
 
// Dialogue first: stretch the shot, never trim the speech
export function planShot(shot, clips) {
  const plannedMs = Math.round(shot.durationSec * 1000)
  // durationMs in clips comes from the file on disk, not a character-count estimate
  const spoken = clips.reduce((sum, c) => sum + c.durationMs, 0)
  const gaps = Math.max(0, clips.length - 1) * GAP_MS
  const requiredMs = clips.length === 0 ? plannedMs : LEAD_MS + spoken + gaps + TAIL_MS
  return {
    shotId: shot.id,
    plannedMs,
    requiredMs,
    finalMs: Math.max(plannedMs, requiredMs),
    conflict: requiredMs > plannedMs, // stretched shots get flagged for human review
  }
}

Where subtitle timing comes from

Subtitle timestamps have two possible sources, and this comes up in interviews more often than you would think.

One: the API gives them. The speech API has a subtitle_enable switch and a subtitle_type (per sentence or per word), and returns a download link to a subtitle file. That sounds convenient, but it has two practical problems: you spend an extra HTTP request fetching the content, and more importantly its timestamps are relative to that one audio clip, while your subtitles must sit on the episode's timeline, separated by the offset of "which second this shot starts at." So even using it, you cannot skip laying things out yourself.

Two: local alignment. You already hold each clip's real duration and each shot's start time; accumulate them and you have the subtitle timeline. It needs no extra request, depends on no vendor field, and you control the line breaks — one line of dialogue per cue, which is exactly the rhythm short drama wants.

This course fixes on the second path and computes every subtitle from the local timeline. The reason is practical: the field-by-field structure of the downloaded subtitle file is not published in the official docs, and we do not build a course on unverified fields. If you want to use it in your own project, measure it first.

Alignment has exactly one key: the subtitle cursor and the shot cursor must be the same variable. In pseudocode: the episode has one cursor starting at 0; for each shot, record its start, then lay out cues from "start + lead-in," advancing the cursor by each line's real duration plus the inter-line gap; when the shot is done, advance the episode cursor by that shot's final duration. Two cursors sharing one origin is the only guarantee against drift.

One easily missed self-check: every cue must fall inside the shot it belongs to. An out-of-bounds cue raises no error; it just floats the previous shot's line over the next shot's picture, and the audience sees words appearing while no mouth moves. The check is cheap and belongs hard-wired into the pipeline.

Subtitle file format details

SRT is the most universal subtitle format, simple enough to hand-write: an index starting at 1, a line of timecode, one or two lines of text, and a blank line between entries.

TextText
1
00:00:00,250 --> 00:00:02,230
Why did the phone light up by itself
 
2
00:00:06,250 --> 00:00:09,550
News from five minutes in the future,
pushed to me right now

It looks simple, but three mistakes make players reject the file, and usually with no error at all — just no subtitles. Those are painfully slow to diagnose, because you first suspect the encoding, then the path, then the player.

First, the millisecond separator is a comma, not a period. 00:00:01.320 is WebVTT; SRT needs 00:00:01,320. This is the most common one.

Second, hours, minutes and seconds must be zero-padded, and milliseconds must be three digits. Most players reject 0:0:1,32 outright.

Third, indexes start at 1 and stay contiguous. Start at 0 or skip a number and some players silently drop every subsequent cue.

srt.js
// SRT timecode: HH:MM:SS,mmm - the separator is a comma, not a period, and every part is padded
export function formatTimecode(ms) {
  const p = (n, w = 2) => String(n).padStart(w, '0')
  const h = Math.floor(ms / 3_600_000)
  const m = Math.floor((ms % 3_600_000) / 60_000)
  const s = Math.floor((ms % 60_000) / 1000)
  return `${p(h)}:${p(m)}:${p(s)},${p(ms % 1000, 3)}`
}
 
export function toSrt(cues) {
  const blocks = cues.map((c) =>
    // Indexes start at 1 and stay contiguous; a gap makes some players drop everything after it
    [`${c.index}`, `${formatTimecode(c.startMs)} --> ${formatTimecode(c.endMs)}`, wrap(c.text)].join('\n')
  )
  return blocks.join('\n\n') + '\n'
}

Then line breaking. A vertical frame is narrow — roughly fourteen CJK characters, or about thirty Latin ones, fill a line, and past that the text either smears across the frame or gets cropped at both ends. Long lines break into two, preferring a punctuation mark as the break point (comma, period), falling back to a hard break. Hard breaks have one classic ugly outcome: a second line holding a single orphaned word. So search backwards for punctuation all the way to a sensible minimum line length — uneven lines beat an orphan.

Stacking tracks: voice, ambience, music

An episode's sound usually has three layers: voice is the lead, ambience (rain, street noise, the hum of a convenience-store cooler) makes the picture feel real, and music drives emotion.

Their relationship in one sentence: voice is always loudest, ambience sits where you can feel it without it competing, music sits a step below that and drops further whenever someone speaks. That last move is called ducking, and it is what makes dialogue intelligible — without it viewers feel "the music is too loud, I cannot hear them" but cannot say why.

How many decibels apart is not a single fixed answer, and it should not be hard-coded into a command today. Today's job is to record the intent: voice is the 0 dB reference, ambience one step down, music one more step down, with music pushed further down during speech. Those numbers travel with the timeline to the editing bay, and D6 turns them into actual ffmpeg parameters — the timeline and the mix plan are data, and the command is a function of the data. That division of labor recurs throughout the second half of this course.

One diagnostic aside: if the finished piece sounds muffled on the voice, the first suspect is not the loudness balance but mismatched sample rates. If synthesis outputs one sample rate and the video's audio track another, the resampling on mix eats the high end. Unifying the sample rate helps far more than adjusting volume.

This section has the least technology in it and is the likeliest place on this pipeline to actually blow up.

Almost all background music is copyrighted. Ripped from a video site, recorded off someone else's drama, or even downloaded from a stock site marked "free" — any of it may carry terms that forbid commercial use. Once a drama is out and has views, a rights holder showing up is a realistic outcome, and the platform's usual response is a takedown plus throttling. What you lose is not the song; it is the whole piece's reach.

There are only three safe options: use a library with explicit commercial licensing and keep the license records; use a generative music service and confirm its terms allow commercial use; or use ambience only and no score. Short drama's emotion is largely carried by dialogue and pacing, so going without a score is not fatal.

The same family has one more red line: do not generate the likeness or the voice of a real person. Describing a character's appearance by naming a public figure, or cloning a real person's timbre, touches likeness and voice rights, and is more serious than the music issue. Every character in this course is fictional, and the appearance notes in the character card use generic features only.

What labeling generated content requires, what metadata exports should carry, and what filings short drama needs before release form a whole compliance topic that D11 covers, with the official text as the authority there. Today, just hold those two red lines and make "where did this music come from" a required field in your project — traceable provenance is the precondition for compliance at all.

Source Reading

Hands-On Lab

🧪 D5 lab: a dubbing stage that batch-voices by character and emits aligned subtitle files

Code location: labs/ai-drama-pipeline/day-05-voice-subtitle

Acceptance criteria:

  1. In step 1 the assignment table gives the two characters two different voices, matching the fields in their character cards.
  2. In step 2 the duration printed for each line comes from the file on disk, not a character-count estimate.
  3. In step 3 the shot whose lines do not fit is marked as stretched, and the storyboard record's duration has actually been changed to the corrected value.
  4. Step 5 prints a passing self-check: shots are contiguous, cues do not overlap, every cue sits inside its own shot, and every timecode is well-formed.
  5. pnpm typecheck passes with no any.

starter/ leaves four exercises: two in dubbing (voice assignment, real duration) and two in the timeline (duration correction, timecode format). Everything runs offline under MOCK=1; placeholder audio length is derived from the line's character count, but the duration is still measured out of the file with ffprobe — so no line of the alignment logic is bypassed. Run it once unmodified first: the last step lists a string of problems — out-of-bounds cues, malformed timecodes, two characters sharing one voice — and that is your to-do list, shrinking as each exercise lands.

  1. Wire the character card's voice field into the assignment table, run once, and confirm the two characters have different voice identifiers.
  2. Batch-synthesize every line to disk, print each clip's real duration, and compare it against the character-count estimate.
  3. Correct shot durations with the real values, find the stretched shot, and confirm the storyboard record was actually rewritten.
  4. Generate the subtitle file from the timeline, load it in a player, and confirm the subtitles track the picture with no global offset.
  5. Run the timecode self-check and drive out-of-bounds, overlap and format problems all to zero.

Interview Questions

Today's 3 questions are in the question bank below, focused on how synthesis parameters relate to finished feel, the ordering of the two-way duration constraint, and the two sources of subtitle timestamps. Read the analysis before the key points — question 1's answer must include why that side gives way; a bare conclusion invites a follow-up. The cn / global labels let you pick by target market.

Checklist and Tomorrow

  • I can assign a voice per character and batch-synthesize dialogue, producing audio files ready to use directly
  • I can align audio duration with shot duration, and generate subtitle files with a correct timeline
  • I can explain the parameters in speech synthesis that most affect the finished feel, and the boundaries for handling background music
  • I can explain why the timeline must use measured audio duration rather than a character-count estimate
  • I can name the three easiest mistakes in SRT timecodes
  • All 5 lab acceptance criteria pass
  • I can answer at least 2 of the 3 interview questions without looking at the key points

Tomorrow (D6) we enter the editing bay: shot videos, voice, subtitles and music get assembled along today's timeline into one complete vertical cut, plus a cover frame. The reason the timeline comes before the edit is that the editing bay should not compute time itself — it only translates a structured table into a string of ffmpeg parameters. You will also meet a very practical engineering habit: the program probes whether the local ffmpeg has a given capability before choosing burned-in or soft subtitles, rather than assuming the reader's environment matches yours.

Interview questions

  • When the synthesized speech and the shot duration disagree, which side do you adjust, and why?语音时长和画面时长对不上,你会调哪一边?为什么?
    Common in ChinaCommon overseasIntermediate#timeline#tts#pipeline-design

    How to reason about it · think before answering

    1. Answering 'stretch the shot' alone scores nothing; the hinge is 'why'. They want the reasoning for which side yields, and whether you see that this choice fixes the order of the whole pipeline.
    2. State the criterion: which distortion does the audience notice? Clipped or sped-up dialogue is audible immediately; a shot running 0.8 seconds long is not. So the picture yields.
    3. Derive the pipeline order from that: generate video at the planned duration, synthesize speech, write the measured duration back onto the shot, and let the editor pad the picture. Why not synthesize first and generate video to fit? Because video APIs expose discrete duration options — you cannot ask for exactly 6.34 seconds.
    4. Add the engineering detail that cannot be skipped: the timeline must use durations measured from the rendered files, never character-count estimates. Estimation error accumulates line by line, and by the tenth line the subtitles visibly race the picture.
    5. Then the exception, which earns points: if a shot has intrinsic rhythm — a beat cut, a transition, an action match — the picture cannot simply be stretched, and the right fix is a shorter line in the script. That is why stretched shots should be flagged for human review rather than silently rewritten.
    6. Expect the follow-up: can't you just nudge the speaking rate? You can, but it costs you — rate changes affect timbre and delivery, and they change duration again, turning a one-way flow into a loop. Make the lead-in and tail padding adjustable and spend that budget before touching the rate.

    分析过程 · 先想清楚再作答

    1. 这题只答「调画面」拿不到分,题眼在「为什么」——面试官要的是让步理由,以及你有没有意识到这个选择会决定整条流水线的排列顺序。
    2. 先给判断依据:哪一边的失真观众察觉得到。台词被切掉、或者被加速到语气变形,观众立刻听得出来;一镜比原计划长零点八秒,观众感觉不到。所以让步的是画面。
    3. 由这条判断反推流水线顺序:画面先按计划时长生成,语音合成完之后由真实时长回写镜头时长,剪辑台再去补足画面。为什么不倒过来先合成语音再按语音时长生成视频?因为视频接口的时长是有限档位的,你没法要求它精确生成 6.34 秒。
    4. 补一条不能省的工程细节:写进时间轴的必须是从落盘文件量出来的真实时长,不能是字数估算。估算误差是逐句累加的,第一句差两百毫秒,第十句就差两秒,成片上表现为字幕跟画面赛跑。
    5. 再说例外,这是加分项:如果这一镜的画面本身有强节奏(比如卡点、转场、动作衔接),画面就不能被随意拉长,这时候要回头改剧本把台词写短,而不是硬拉画面。所以被顶长的镜头应该被标记出来交给人复核,而不是程序默默改掉。
    6. 可以预期的追问:那不能微调语速吗?可以,但语速是有代价的——语速改变会同时改变音质与情绪表现,而且它会反过来再改一次时长,等于把一个单向流程变成了循环。留一点余量的做法是给留白参数一个可调区间,先动留白再动语速。

    Key points

    • Stretch the picture: clipped or sped-up dialogue is instantly audible, while a fraction of a second of extra shot length is not
    • That fixes the pipeline order: generate video at planned duration, synthesize speech, write measured duration back, pad in the edit
    • You cannot invert it and generate video to match speech, because video APIs only expose discrete durations
    • The timeline must use durations measured from rendered files; character-count estimates accumulate error line by line
    • Shots with intrinsic rhythm are the exception, so flag stretched shots for human review instead of silently rewriting them

    答题要点

    • 调画面:台词被切或被加速观众立刻察觉,镜头长零点几秒观众感觉不到
    • 由此定下流水线顺序:画面按计划时长生成,语音合成后回写真实时长,剪辑台补足画面
    • 不能倒过来按语音时长生成视频,因为视频接口的时长只有有限档位
    • 时间轴必须用落盘文件量出的真实时长,字数估算的误差会逐句累加
    • 画面有强节奏的镜头是例外,这类冲突应标记出来交人复核而不是程序默默改掉
  • Where do you get subtitle timestamps from, and what do you do when the API does not provide them?字幕的时间戳你会怎么拿?接口不给时间戳时有什么替代方案?
    Common in ChinaCommon overseasIntermediate#subtitles#timeline

    How to reason about it · think before answering

    1. This tests whether you would take a dependency on an optional vendor field. Name both paths and their costs; giving only one invites a follow-up you will not enjoy.
    2. Path one is the API: TTS endpoints often expose a subtitle flag returning sentence- or word-level timestamps. Three problems — it costs an extra request to fetch, the timestamps are relative to that single audio segment, and the field structure varies by vendor. The third is the worst, because it welds your subtitle module to one provider.
    3. Path two is local alignment: you already hold every clip's measured duration and every shot's start time, so accumulating them gives the episode timeline. Zero extra requests, zero vendor coupling, and you control segmentation — one line of dialogue per cue, which is exactly the rhythm short drama wants.
    4. The key insight is that path one does not free you from path two: API timestamps are segment-relative, so you still add the shot's offset within the episode. Since you must write the alignment code anyway, make it the single source of truth.
    5. The implementation has one rule: the subtitle cursor and the shot cursor share one origin and advance together. Add a check that every cue falls inside its own shot — overflow raises no error, it just floats the previous shot's line over the next shot's picture.
    6. Expect the follow-up: what about karaoke-style word-level subtitles? That genuinely requires word-level timestamps from the API. Treat it as an optional enhancement over a local-alignment main path, degrading to sentence level when word data is unavailable.

    分析过程 · 先想清楚再作答

    1. 这题在考你会不会为一个可有可无的厂商字段引入依赖。两条路都要说得出来,还要说清各自的代价,只答一条会被追问到底。
    2. 第一条是接口给:语音合成接口通常有一个字幕开关,返回按句或按词的时间戳。它的问题有三个——要多发一次请求去取内容、时间戳是相对单段音频的、字段结构随厂商变化。第三条最要命,因为它让你的字幕模块和某一家厂商绑死了。
    3. 第二条是本地对齐:你手里已经有每段音频的真实时长和每一镜的起始时刻,累加就是整集时间轴。它零额外请求、零厂商依赖,而且断句由你自己控制——按台词行断,一句一条,天然符合短剧节奏。
    4. 关键在于**就算用第一条也逃不掉第二条**:接口给的是段内相对时间,你仍然要加上这一镜在整集里的偏移。所以本地对齐这套代码无论如何都要写,那不如让它成为唯一的真相来源。
    5. 对齐的实现只有一个要点:字幕游标和镜头游标必须共用同一个原点,逐镜推进。再配一个自检——每条字幕必须落在它所属的那一镜内,越界不会报错,只会让上一镜的台词飘到下一镜的画面上。
    6. 可以预期的追问:那按词级时间戳做卡拉OK式字幕呢?那种效果确实必须依赖接口的词级时间戳,本地对齐做不了。这时的正确做法是把它做成一个可选增强,主链路仍然走本地对齐,拿不到词级数据就降级成句级。

    Key points

    • Two sources: timestamps returned by the API, and local alignment accumulated from measured audio durations
    • The API path costs an extra request, gives segment-relative timestamps, and couples you to one vendor's field structure
    • Local alignment needs no extra request and no vendor coupling, and lets you segment per line of dialogue
    • Even with API timestamps you must add each shot's offset within the episode, so the alignment code is unavoidable anyway
    • The implementation rule is one shared origin for the subtitle and shot cursors, plus a check that each cue stays inside its own shot

    答题要点

    • 两条来源:接口返回的时间戳,以及由音频真实时长本地累加对齐
    • 接口那条的代价是多一次请求、时间戳只相对单段音频、字段结构跟厂商绑定
    • 本地对齐零额外请求零厂商依赖,断句按台词行控制,符合短剧节奏
    • 即使用接口时间戳也仍要自己加上这一镜在整集里的偏移,所以本地对齐代码无论如何都得写
    • 实现要点是字幕游标与镜头游标共用同一原点,并自检每条字幕是否落在它所属的镜头内
  • In a multi-character pipeline, how do you guarantee the same character keeps the same voice across episodes?多角色配音里,怎么保证同一个角色跨集用的是同一个声音?
    Common in ChinaCommon overseasBasic#tts#consistency#provider-abstraction

    How to reason about it · think before answering

    1. This looks like a voice question but is really about where state lives. 'Hardcode it in config' is not wrong, but stopping there shows no engineering judgment.
    2. Name the risk first: voice is part of a character's identity, and audiences are about as sensitive to it as to a face. Inconsistency across episodes has three usual causes — running each episode as an independent pipeline, picking voices from an ad-hoc or random mapping, and someone tweaking a character's global parameters while fixing the delivery of one line.
    3. The fix is to file the voice in the character record rather than in code: the record carries a voice id, and the dubbing step only reads it. Consistency then holds regardless of episode, run or operator — the same pattern as pinning appearance to a base reference image.
    4. Storing the voice id alone is not enough. Perceived sameness also depends on the baseline emotion and the speaking rate; the same voice at two different rates sounds like a different state of a person. Keep all three in the record, and allow per-line overrides of emotion only, never of rate.
    5. Add a defensive layer: record the voice id together with the model name in the artifact metadata. Vendors do retire and rename voices, and you want to be able to answer 'why does season two sound different' from data rather than memory.
    6. Expect the follow-up: what if the vendor retires that voice? Make voice selection part of the provider abstraction — the record stores the character's voice archetype, and the mapping to a concrete vendor voice lives in the adapter, so swapping vendors never touches the character records.

    分析过程 · 先想清楚再作答

    1. 这题看着像配音问题,其实考的是状态该存在哪里。答「配置里写死」不算错,但只答到这一层看不出工程判断。
    2. 先说清楚风险来自哪:声音是角色身份的一部分,观众对它的敏感度不低于脸。跨集不一致的典型成因有三个——每集独立跑一次流程、音色靠临时映射或随机挑选、以及某次为了改一句台词的语气顺手改了这个角色的全局参数。
    3. 解法是把音色归档而不是归代码:角色档案里带一个音色字段,配音环节只读不写。这样一致性由档案保证,跟哪一集、哪一次运行、谁跑的都无关。这跟角色形象靠基准图归档是同一套思路。
    4. 但只存音色标识还不够,跨集听感一致还依赖另外两项:基调情绪与语速。同一个音色用两种语速念,听起来像两个人的状态。所以档案里要一起存这三项,单条台词只允许覆盖情绪,不允许覆盖语速。
    5. 再补一层防御:把音色标识连同模型名一起记进产物元数据。厂商下线或重命名一个音色是会发生的,你要能查出「第二季为什么听起来不一样」,而不是只能凭记忆猜。
    6. 可以预期的追问:如果厂商真的下线了那个音色怎么办?答案是把音色选择也做成 provider 抽象的一部分:档案里存的是角色的音色角色定位,映射到具体厂商音色的表放在适配层,换厂商或补映射时不动档案。

    Key points

    • Store the voice in the character record and have the dubbing step read it only, so consistency is independent of episode, run or operator
    • Keep voice id, baseline emotion and speaking rate together; allow per-line emotion overrides but never rate overrides
    • Write the voice id and model name into artifact metadata so you can explain why a later season sounds different
    • Typical causes of drift are per-episode independent runs, ad-hoc mappings, and global tweaks made while fixing one line
    • Fold voice selection into the provider abstraction: records hold the archetype, the adapter maps it to a concrete vendor voice

    答题要点

    • 把音色存进角色档案,配音环节只读不写,一致性与集数、运行次数、操作人无关
    • 档案里要同时存音色标识、基调情绪与语速;单条台词只允许覆盖情绪,不允许覆盖语速
    • 把音色标识与模型名一起写进产物元数据,便于回答「为什么这一季听起来不一样」
    • 跨集不一致的典型成因是每集独立跑、临时映射、以及改一句台词时顺手改了全局参数
    • 音色选择应纳入 provider 抽象:档案存角色的音色定位,具体厂商音色的映射放在适配层

Comments