@lingjingai/scriptctl 0.57.1 → 0.58.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/changes/0.58.0.md +14 -0
  2. package/changes/0.58.1.md +17 -0
  3. package/dist/cli/register-workflows.js +17 -0
  4. package/dist/cli/register-workflows.js.map +1 -1
  5. package/dist/domain/creative-draft/compose.d.ts +18 -0
  6. package/dist/domain/creative-draft/compose.js +403 -0
  7. package/dist/domain/creative-draft/compose.js.map +1 -0
  8. package/dist/domain/ingest/video-serial-md.js +3 -3
  9. package/dist/domain/ingest/video-serial-md.js.map +1 -1
  10. package/dist/domain/ingest/video-source-evolution.d.ts +4 -0
  11. package/dist/domain/ingest/video-source-evolution.js +0 -3
  12. package/dist/domain/ingest/video-source-evolution.js.map +1 -1
  13. package/dist/domain/ingest/video-state-evidence.d.ts +66 -0
  14. package/dist/domain/ingest/video-state-evidence.js +385 -0
  15. package/dist/domain/ingest/video-state-evidence.js.map +1 -0
  16. package/dist/domain/script/v4/validation.js +62 -0
  17. package/dist/domain/script/v4/validation.js.map +1 -1
  18. package/dist/infra/ingest/video-discovery.js +10 -4
  19. package/dist/infra/ingest/video-discovery.js.map +1 -1
  20. package/dist/infra/ingest/video-source-snapshot.d.ts +2 -1
  21. package/dist/infra/ingest/video-source-snapshot.js +55 -4
  22. package/dist/infra/ingest/video-source-snapshot.js.map +1 -1
  23. package/dist/infra/ingest/video-state-evidence.d.ts +14 -0
  24. package/dist/infra/ingest/video-state-evidence.js +140 -0
  25. package/dist/infra/ingest/video-state-evidence.js.map +1 -0
  26. package/dist/llm/config.js +1 -1
  27. package/dist/llm/router.js +4 -6
  28. package/dist/llm/router.js.map +1 -1
  29. package/dist/llm/tasks/ingest/video-serial.d.ts +2 -2
  30. package/dist/llm/tasks/ingest/video-serial.js +102 -21
  31. package/dist/llm/tasks/ingest/video-serial.js.map +1 -1
  32. package/dist/llm/tasks/schemas.js +5 -2
  33. package/dist/llm/tasks/schemas.js.map +1 -1
  34. package/dist/usecases/ingest/failure.js +2 -0
  35. package/dist/usecases/ingest/failure.js.map +1 -1
  36. package/dist/usecases/ingest/generate-video-state-evidence.d.ts +23 -0
  37. package/dist/usecases/ingest/generate-video-state-evidence.js +114 -0
  38. package/dist/usecases/ingest/generate-video-state-evidence.js.map +1 -0
  39. package/dist/usecases/ingest/skip-video-episode.js +2 -2
  40. package/dist/usecases/ingest/skip-video-episode.js.map +1 -1
  41. package/dist/usecases/ingest/video-pipeline.d.ts +8 -3
  42. package/dist/usecases/ingest/video-pipeline.js +48 -15
  43. package/dist/usecases/ingest/video-pipeline.js.map +1 -1
  44. package/dist/usecases/script/compose-creative-draft.d.ts +3 -0
  45. package/dist/usecases/script/compose-creative-draft.js +106 -0
  46. package/dist/usecases/script/compose-creative-draft.js.map +1 -0
  47. package/package.json +1 -1
  48. package/skills/scriptctl/SKILL.md +30 -9
  49. package/skills/scriptctl/references/ingest-workflow.md +10 -4
  50. package/skills/scriptctl/references/production-writing-standard.md +6 -3
  51. package/skills/scriptctl/references/video-transcript-repair.md +16 -5
@@ -4,6 +4,10 @@ export interface VideoSourceEpisodeIdentity {
4
4
  ext: string;
5
5
  mime: string;
6
6
  sha256: string;
7
+ size_bytes?: number;
8
+ mtime_ms?: number;
9
+ /** Container duration used to reject impossible evidence timestamps. */
10
+ duration_ms?: number;
7
11
  }
8
12
  export type VideoSourceEvolution = {
9
13
  kind: "unchanged";
@@ -2,9 +2,6 @@ function identityMismatch(previous, current) {
2
2
  if (previous.episode !== current.episode) {
3
3
  return `episode ${previous.episode} was replaced by episode ${current.episode}`;
4
4
  }
5
- if (previous.path !== current.path) {
6
- return `episode ${previous.episode} moved from ${previous.path} to ${current.path}`;
7
- }
8
5
  if (previous.ext !== current.ext || previous.mime !== current.mime) {
9
6
  return `episode ${previous.episode} changed media type`;
10
7
  }
@@ -1 +1 @@
1
- {"version":3,"file":"video-source-evolution.js","sourceRoot":"","sources":["../../../src/domain/ingest/video-source-evolution.ts"],"names":[],"mappings":"AAaA,SAAS,gBAAgB,CACvB,QAAoC,EACpC,OAAmC;IAEnC,IAAI,QAAQ,CAAC,OAAO,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC;QACzC,OAAO,WAAW,QAAQ,CAAC,OAAO,4BAA4B,OAAO,CAAC,OAAO,EAAE,CAAC;IAClF,CAAC;IACD,IAAI,QAAQ,CAAC,IAAI,KAAK,OAAO,CAAC,IAAI,EAAE,CAAC;QACnC,OAAO,WAAW,QAAQ,CAAC,OAAO,eAAe,QAAQ,CAAC,IAAI,OAAO,OAAO,CAAC,IAAI,EAAE,CAAC;IACtF,CAAC;IACD,IAAI,QAAQ,CAAC,GAAG,KAAK,OAAO,CAAC,GAAG,IAAI,QAAQ,CAAC,IAAI,KAAK,OAAO,CAAC,IAAI,EAAE,CAAC;QACnE,OAAO,WAAW,QAAQ,CAAC,OAAO,qBAAqB,CAAC;IAC1D,CAAC;IACD,IAAI,QAAQ,CAAC,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC;QACvC,OAAO,WAAW,QAAQ,CAAC,OAAO,kBAAkB,CAAC;IACvD,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,4BAA4B,CAC1C,QAAsC,EACtC,OAAqC;IAErC,IAAI,OAAO,CAAC,MAAM,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC;QACrC,OAAO;YACL,IAAI,EAAE,cAAc;YACpB,MAAM,EAAE,kBAAkB,QAAQ,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,iCAAiC;SAC5F,CAAC;IACJ,CAAC;IAED,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QACxD,MAAM,QAAQ,GAAG,gBAAgB,CAAC,QAAQ,CAAC,KAAK,CAAE,EAAE,OAAO,CAAC,KAAK,CAAE,CAAC,CAAC;QACrE,IAAI,QAAQ;YAAE,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;IAClE,CAAC;IAED,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAChD,OAAO,QAAQ,CAAC,MAAM,KAAK,CAAC;QAC1B,CAAC,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,EAAE,EAAE;QACrC,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC;AACnC,CAAC;AAED,MAAM,UAAU,iCAAiC,CAC/C,QAA4C;IAE5C,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QACxD,MAAM,QAAQ,GAAG,KAAK,GAAG,CAAC,CAAC;QAC3B,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAE,CAAC,OAAO,CAAC;QACxC,IAAI,MAAM,KAAK,QAAQ,EAAE,CAAC;YACxB,OAAO,oBAAoB,QAAQ,mBAAmB,MAAM,EAAE,CAAC;QACjE,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
1
+ {"version":3,"file":"video-source-evolution.js","sourceRoot":"","sources":["../../../src/domain/ingest/video-source-evolution.ts"],"names":[],"mappings":"AAiBA,SAAS,gBAAgB,CACvB,QAAoC,EACpC,OAAmC;IAEnC,IAAI,QAAQ,CAAC,OAAO,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC;QACzC,OAAO,WAAW,QAAQ,CAAC,OAAO,4BAA4B,OAAO,CAAC,OAAO,EAAE,CAAC;IAClF,CAAC;IACD,IAAI,QAAQ,CAAC,GAAG,KAAK,OAAO,CAAC,GAAG,IAAI,QAAQ,CAAC,IAAI,KAAK,OAAO,CAAC,IAAI,EAAE,CAAC;QACnE,OAAO,WAAW,QAAQ,CAAC,OAAO,qBAAqB,CAAC;IAC1D,CAAC;IACD,IAAI,QAAQ,CAAC,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC;QACvC,OAAO,WAAW,QAAQ,CAAC,OAAO,kBAAkB,CAAC;IACvD,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,4BAA4B,CAC1C,QAAsC,EACtC,OAAqC;IAErC,IAAI,OAAO,CAAC,MAAM,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC;QACrC,OAAO;YACL,IAAI,EAAE,cAAc;YACpB,MAAM,EAAE,kBAAkB,QAAQ,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,iCAAiC;SAC5F,CAAC;IACJ,CAAC;IAED,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QACxD,MAAM,QAAQ,GAAG,gBAAgB,CAAC,QAAQ,CAAC,KAAK,CAAE,EAAE,OAAO,CAAC,KAAK,CAAE,CAAC,CAAC;QACrE,IAAI,QAAQ;YAAE,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;IAClE,CAAC;IAED,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAChD,OAAO,QAAQ,CAAC,MAAM,KAAK,CAAC;QAC1B,CAAC,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,EAAE,EAAE;QACrC,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC;AACnC,CAAC;AAED,MAAM,UAAU,iCAAiC,CAC/C,QAA4C;IAE5C,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QACxD,MAAM,QAAQ,GAAG,KAAK,GAAG,CAAC,CAAC;QAC3B,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAE,CAAC,OAAO,CAAC;QACxC,IAAI,MAAM,KAAK,QAAQ,EAAE,CAAC;YACxB,OAAO,oBAAoB,QAAQ,mBAAmB,MAAM,EAAE,CAAC;QACjE,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,66 @@
1
+ import type { VideoEpisodeDoc } from "./video-records.js";
2
+ import { type RunContext } from "./video-context.js";
3
+ import type { VideoSourceEpisodeIdentity } from "./video-source-evolution.js";
4
+ import type { ScriptV4 } from "../script/v4/types.js";
5
+ /** `model` remains readable for version-1 compatibility; new plans never emit it. */
6
+ export type VideoStateEvidenceSelection = "model" | "target_anchor" | "scene_fallback";
7
+ export type VideoStateEvidenceFrameStatus = "pending" | "ready" | "unavailable";
8
+ export interface VideoStateEvidenceSample {
9
+ evidence_id: string;
10
+ episode: number;
11
+ episode_id: string;
12
+ scene_id: string;
13
+ timestamp: string;
14
+ offset_ms: -1000 | 0 | 1000;
15
+ selection: VideoStateEvidenceSelection;
16
+ source_sha256: string;
17
+ frame_path: string;
18
+ frame_status: VideoStateEvidenceFrameStatus;
19
+ error?: string;
20
+ }
21
+ export interface VideoActorStateEvidence {
22
+ evidence_id: string;
23
+ asset_kind: "actor";
24
+ actor_id: string;
25
+ actor_name: string;
26
+ state_id: string;
27
+ state_name: string;
28
+ samples: VideoStateEvidenceSample[];
29
+ }
30
+ export interface VideoActorIdentityEvidence {
31
+ evidence_id: string;
32
+ asset_kind: "actor";
33
+ actor_id: string;
34
+ actor_name: string;
35
+ samples: VideoStateEvidenceSample[];
36
+ }
37
+ export interface VideoStateEvidenceManifest {
38
+ version: 2;
39
+ generated_at: string;
40
+ source_sha256: string;
41
+ items: VideoActorStateEvidence[];
42
+ identities?: VideoActorIdentityEvidence[];
43
+ }
44
+ export interface PlannedVideoStateEvidenceSample extends VideoStateEvidenceSample {
45
+ source_path: string;
46
+ }
47
+ export interface PlannedVideoActorStateEvidence extends Omit<VideoActorStateEvidence, "samples"> {
48
+ samples: PlannedVideoStateEvidenceSample[];
49
+ }
50
+ export interface PlannedVideoActorIdentityEvidence extends Omit<VideoActorIdentityEvidence, "samples"> {
51
+ samples: PlannedVideoStateEvidenceSample[];
52
+ }
53
+ export interface VideoStateEvidencePlan {
54
+ version: 2;
55
+ source_sha256: string;
56
+ items: PlannedVideoActorStateEvidence[];
57
+ identities: PlannedVideoActorIdentityEvidence[];
58
+ }
59
+ /**
60
+ * Build evidence only after final Script v4 identities are stable. Gemini does
61
+ * not return any evidence fields: candidates come from final Action timestamps,
62
+ * visible-action text and single-person scene ranges, then ffmpeg materializes
63
+ * the selected frames locally.
64
+ */
65
+ export declare function buildVideoStateEvidencePlan(docs: readonly VideoEpisodeDoc[], script: ScriptV4, context: RunContext, sources: readonly VideoSourceEpisodeIdentity[], sourceSha256: string): VideoStateEvidencePlan;
66
+ export declare function parseVideoEvidenceTimestamp(value: string): number | null;
@@ -0,0 +1,385 @@
1
+ import { buildAliasMap } from "./video-context.js";
2
+ const IDENTITY_MIN_EVIDENCE_SCORE = 60;
3
+ const IDENTITY_BOARD_SIZE = 6;
4
+ const FRAME_OFFSETS_MS = [-1_000, 0, 1_000];
5
+ const WARDROBE_CUE = /(?:身穿|穿着|服装|造型|衣|裙|袍|衫|夹克|制服|西装|盔甲|装甲|防弹服|套装|裤|靴)/u;
6
+ const FRAMING_CUE = /(?:全身|半身|上半身|四分之三身|躯干|胸前|腰部)/u;
7
+ const BODY_CUE = /(?:站(?:在|立)|坐(?:在|下|起)|跪(?:在|下|倒)?|蹲(?:在|下)?|走(?:在|出|向)|行走|迈步|奔跑|跳起|跃起|转身|抬头|转头)/u;
8
+ const FACE_CUE = /(?:特写|近景|脸|面容|五官|正脸|侧脸|侧前脸|双眼|眼眶|眉头|嘴角)/u;
9
+ const BAD_VISUAL_CUE = /(?:背对|背影|远处|极远|模糊|运动模糊|脸被遮|面部遮挡|只露出|纯道具|空镜)/u;
10
+ const DETAIL_ONLY_CUE = /(?:双脚|脚部|膝盖|伤口|手部特写)/u;
11
+ const OFFSCREEN_SPEECH_CUE = /(?:画外音|旁白|未入镜|镜外|电话音|广播|\bO\.?S\.?\b)/iu;
12
+ function norm(value) {
13
+ return value.normalize("NFC").replace(/\s+/g, " ").trim();
14
+ }
15
+ function clockTimestampMilliseconds(value) {
16
+ const match = value.trim().match(/^(?:(\d{1,2}):)?(\d{1,2}):(\d{2})(?:\.(\d{1,3}))?$/);
17
+ if (!match)
18
+ return null;
19
+ const hours = Number(match[1] ?? 0);
20
+ const minutes = Number(match[2]);
21
+ const seconds = Number(match[3]);
22
+ if (minutes >= 60 && match[1] !== undefined)
23
+ return null;
24
+ if (seconds >= 60)
25
+ return null;
26
+ const fraction = String(match[4] ?? "").padEnd(3, "0");
27
+ return ((hours * 60 + minutes) * 60 + seconds) * 1000 + Number(fraction || 0);
28
+ }
29
+ /**
30
+ * Some imported projects used `seconds:centiseconds` (for example `29:00`
31
+ * means 29 seconds) before Script v4 standardized on clock timestamps. Keep
32
+ * compatibility at the episode level so ordinary `00:33` remains 33 seconds.
33
+ */
34
+ function legacyCentisecondTimestampMilliseconds(value) {
35
+ const match = value.trim().match(/^(\d+):(\d{2})$/);
36
+ if (!match)
37
+ return null;
38
+ return Number(match[1]) * 1_000 + Number(match[2]) * 10;
39
+ }
40
+ function timestampMilliseconds(value, mode = "clock") {
41
+ return mode === "legacy_centiseconds"
42
+ ? legacyCentisecondTimestampMilliseconds(value)
43
+ : clockTimestampMilliseconds(value);
44
+ }
45
+ function inSourceRange(milliseconds, durationMs) {
46
+ if (milliseconds === null)
47
+ return false;
48
+ return durationMs === undefined || milliseconds <= durationMs;
49
+ }
50
+ function episodeTimestampMode(episode, durationMs) {
51
+ if (durationMs === undefined)
52
+ return "clock";
53
+ const timestamps = episode.scenes.flatMap((scene) => scene.actions.map(actionTimestamp)).filter(Boolean);
54
+ const clockValid = timestamps.filter((value) => inSourceRange(clockTimestampMilliseconds(value), durationMs)).length;
55
+ const legacyValid = timestamps.filter((value) => inSourceRange(legacyCentisecondTimestampMilliseconds(value), durationMs)).length;
56
+ // Ties intentionally remain standard clock timestamps. A legacy episode is
57
+ // selected only when the source duration proves it explains more Actions.
58
+ return legacyValid > clockValid ? "legacy_centiseconds" : "clock";
59
+ }
60
+ function timestampFromMilliseconds(milliseconds) {
61
+ const minutes = Math.floor(milliseconds / 60_000);
62
+ const seconds = Math.floor((milliseconds % 60_000) / 1_000);
63
+ const fraction = milliseconds % 1_000;
64
+ return `${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}.${String(fraction).padStart(3, "0")}`;
65
+ }
66
+ function actionTimestamp(action) {
67
+ const value = action.extend?.timestamp;
68
+ return typeof value === "string" ? value.trim() : "";
69
+ }
70
+ function actorSurfaces(actor, contextAliases) {
71
+ const names = new Set([actor.actor_name, ...(actor.aliases ?? [])].map(norm).filter(Boolean));
72
+ for (const [alias, canonical] of contextAliases) {
73
+ if (norm(canonical) === norm(actor.actor_name))
74
+ names.add(norm(alias));
75
+ }
76
+ return [...names].sort((left, right) => right.length - left.length);
77
+ }
78
+ function mentionIndex(content, surfaces) {
79
+ const indices = surfaces.map((surface) => content.indexOf(surface)).filter((index) => index >= 0);
80
+ return indices.length > 0 ? Math.min(...indices) : -1;
81
+ }
82
+ function visibleNames(content, actors, surfacesByActor) {
83
+ return actors.filter((actor) => mentionIndex(content, surfacesByActor.get(actor.actor_id) ?? []) >= 0).length;
84
+ }
85
+ function scoreActionCandidate({ action, actor, actorCount, allActors, surfaces, surfacesByActor, stateName, }) {
86
+ const narrative = action.type === "action" || action.type === "transition";
87
+ const content = norm(narrative
88
+ ? action.content
89
+ : "parenthetical" in action ? action.parenthetical ?? "" : "");
90
+ const directIndex = mentionIndex(content, surfaces);
91
+ const directSubject = directIndex >= 0 && directIndex <= 12;
92
+ const directMention = directIndex >= 0;
93
+ // For a visual contact sheet, a normal spoken line in a scene where the final
94
+ // Script already binds this actor + state is the best cheap proxy for a clean
95
+ // character shot. It is not identity proof (reverse shots still exist), so
96
+ // the client keeps the neighbouring frames and human annotation path. Explicit
97
+ // off-screen/voice-over speech is never used as a visual anchor.
98
+ const actorSpeaker = action.type === "dialogue" && "speaker" in action && "actor_id" in action.speaker
99
+ && action.speaker.actor_id === actor.actor_id;
100
+ const visibleSpeaker = actorSpeaker && !OFFSCREEN_SPEECH_CUE.test(content);
101
+ const singleActorVisibleFallback = actorCount === 1 && ((narrative && content.length > 0)
102
+ || (visibleSpeaker && content.length > 0 && (BODY_CUE.test(content) || FACE_CUE.test(content))));
103
+ if (!directMention && !visibleSpeaker && !singleActorVisibleFallback)
104
+ return null;
105
+ const selection = directSubject || visibleSpeaker ? "target_anchor" : "scene_fallback";
106
+ // Deliberately keep ordinary on-screen dialogue above even a very descriptive
107
+ // wardrobe Action: the scene-level state reference already establishes which
108
+ // outfit is active, while the speaking beat is more likely to show the person.
109
+ let stateScore = visibleSpeaker ? 250 : directSubject ? 65 : directMention ? 50 : 45;
110
+ let identityScore = visibleSpeaker ? 250 : directSubject ? 65 : directMention ? 50 : 45;
111
+ if (actorCount === 1) {
112
+ stateScore += 20;
113
+ identityScore += 20;
114
+ }
115
+ const normalizedState = norm(stateName);
116
+ if (normalizedState && normalizedState !== "default" && normalizedState !== "默认" && content.includes(normalizedState)) {
117
+ stateScore += 35;
118
+ }
119
+ if (WARDROBE_CUE.test(content))
120
+ stateScore += 30;
121
+ if (FRAMING_CUE.test(content)) {
122
+ stateScore += 25;
123
+ identityScore += 15;
124
+ }
125
+ else if (BODY_CUE.test(content)) {
126
+ stateScore += 15;
127
+ identityScore += 5;
128
+ }
129
+ if (FACE_CUE.test(content)) {
130
+ identityScore += 30;
131
+ if (!FRAMING_CUE.test(content))
132
+ stateScore -= 20;
133
+ }
134
+ if (BAD_VISUAL_CUE.test(content)) {
135
+ stateScore -= 70;
136
+ identityScore -= 70;
137
+ }
138
+ if (DETAIL_ONLY_CUE.test(content))
139
+ stateScore -= 40;
140
+ if (visibleNames(content, allActors, surfacesByActor) >= 2) {
141
+ stateScore -= 15;
142
+ identityScore -= 15;
143
+ }
144
+ return { stateScore, identityScore, selection };
145
+ }
146
+ function scoreStateCoverageFallback(action, allActors, surfacesByActor) {
147
+ const narrative = action.type === "action" || action.type === "transition";
148
+ const parenthetical = "parenthetical" in action ? norm(action.parenthetical ?? "") : "";
149
+ if (!narrative && OFFSCREEN_SPEECH_CUE.test(parenthetical))
150
+ return null;
151
+ const content = norm(narrative ? action.content : parenthetical);
152
+ // A scene-level timestamp cannot prove identity, but it is still a useful
153
+ // last-resort state anchor when the final scene reference binds one costume to
154
+ // the actor. Keep it below every actor-specific candidate so quality scoring
155
+ // only chooses the best frame; it must not make a referenced costume disappear.
156
+ let stateScore = narrative ? 0 : -40;
157
+ stateScore -= visibleNames(content, allActors, surfacesByActor) * 20;
158
+ if (WARDROBE_CUE.test(content))
159
+ stateScore += 10;
160
+ if (FRAMING_CUE.test(content))
161
+ stateScore += 10;
162
+ else if (BODY_CUE.test(content))
163
+ stateScore += 5;
164
+ if (BAD_VISUAL_CUE.test(content))
165
+ stateScore -= 20;
166
+ if (DETAIL_ONLY_CUE.test(content))
167
+ stateScore -= 10;
168
+ return { stateScore, identityScore: Number.NEGATIVE_INFINITY, selection: "scene_fallback" };
169
+ }
170
+ function effectiveScore(candidate, scoreKey, owners) {
171
+ const shared = (owners.get(`${candidate.episode}:${candidate.milliseconds}`)?.size ?? 0) > 1;
172
+ return candidate[scoreKey] - (shared ? 35 : 0);
173
+ }
174
+ function bestCandidates(candidates, scoreKey, owners, limit, minimumScore) {
175
+ const deduplicated = new Map();
176
+ for (const candidate of candidates) {
177
+ const key = `${candidate.episode}:${candidate.milliseconds}`;
178
+ const existing = deduplicated.get(key);
179
+ if (!existing || effectiveScore(candidate, scoreKey, owners) > effectiveScore(existing, scoreKey, owners)) {
180
+ deduplicated.set(key, candidate);
181
+ }
182
+ }
183
+ const ranked = [...deduplicated.values()]
184
+ .sort((left, right) => effectiveScore(right, scoreKey, owners) - effectiveScore(left, scoreKey, owners)
185
+ || left.episode - right.episode
186
+ || left.milliseconds - right.milliseconds)
187
+ .filter((candidate) => minimumScore === null || effectiveScore(candidate, scoreKey, owners) >= minimumScore);
188
+ // A useful contact sheet needs visual variety, not six near-identical frames
189
+ // from one shot. Prefer different scenes; only reuse a scene when its anchors
190
+ // are at least three seconds apart and the board still has empty cells.
191
+ const selected = [];
192
+ for (const candidate of ranked) {
193
+ const tooClose = selected.some((entry) => entry.episode === candidate.episode
194
+ && entry.sceneId === candidate.sceneId
195
+ && Math.abs(entry.milliseconds - candidate.milliseconds) < 3_000);
196
+ if (tooClose)
197
+ continue;
198
+ selected.push(candidate);
199
+ if (selected.length >= limit)
200
+ break;
201
+ }
202
+ return selected;
203
+ }
204
+ function bestCandidate(candidates, scoreKey, owners) {
205
+ // State evidence is coverage-first: every actor-specific or neutral visual
206
+ // candidate remains eligible, while scores only determine which one wins.
207
+ return bestCandidates(candidates, scoreKey, owners, 1, null)[0] ?? null;
208
+ }
209
+ function timestampFilePart(timestamp) {
210
+ return timestamp.replace(/[^0-9]+/g, "-").replace(/^-|-$/g, "") || "unknown";
211
+ }
212
+ /**
213
+ * Build evidence only after final Script v4 identities are stable. Gemini does
214
+ * not return any evidence fields: candidates come from final Action timestamps,
215
+ * visible-action text and single-person scene ranges, then ffmpeg materializes
216
+ * the selected frames locally.
217
+ */
218
+ export function buildVideoStateEvidencePlan(docs, script, context, sources, sourceSha256) {
219
+ const sourceByEpisode = new Map(sources.map((source) => [source.episode, source]));
220
+ const contextAliases = buildAliasMap(context.registry);
221
+ const actorById = new Map(script.actors.map((actor) => [actor.actor_id, actor]));
222
+ const surfacesByActor = new Map(script.actors.map((actor) => [actor.actor_id, actorSurfaces(actor, contextAliases)]));
223
+ const appearancesByActor = new Map();
224
+ const identityCandidates = new Map();
225
+ const stateCandidates = new Map();
226
+ const owners = new Map();
227
+ for (let episodeIndex = 0; episodeIndex < script.episodes.length; episodeIndex += 1) {
228
+ const episode = script.episodes[episodeIndex];
229
+ const episodeNo = episodeIndex + 1;
230
+ const source = sourceByEpisode.get(episodeNo);
231
+ if (!source)
232
+ continue;
233
+ const timestampMode = episodeTimestampMode(episode, source.duration_ms);
234
+ for (let sceneIndex = 0; sceneIndex < episode.scenes.length; sceneIndex += 1) {
235
+ const scene = episode.scenes[sceneIndex];
236
+ const actors = scene.actors.map((ref) => actorById.get(ref.actor_id)).filter((actor) => Boolean(actor));
237
+ for (const ref of scene.actors) {
238
+ appearancesByActor.set(ref.actor_id, (appearancesByActor.get(ref.actor_id) ?? 0) + 1);
239
+ const actor = actorById.get(ref.actor_id);
240
+ if (!actor)
241
+ continue;
242
+ const surfaces = surfacesByActor.get(actor.actor_id) ?? [actor.actor_name];
243
+ const local = [];
244
+ const stateId = ref.state_ids.length === 1 ? ref.state_ids[0] : null;
245
+ const state = stateId ? actor.states.find((candidate) => candidate.state_id === stateId) : null;
246
+ for (const action of scene.actions) {
247
+ const timestamp = actionTimestamp(action);
248
+ const milliseconds = timestampMilliseconds(timestamp, timestampMode);
249
+ if (!inSourceRange(milliseconds, source.duration_ms))
250
+ continue;
251
+ const actorSpecific = scoreActionCandidate({
252
+ action,
253
+ actor,
254
+ actorCount: actors.length,
255
+ allActors: actors,
256
+ surfaces,
257
+ surfacesByActor,
258
+ stateName: state?.state_name ?? "default",
259
+ });
260
+ const scored = actorSpecific ?? (state
261
+ ? scoreStateCoverageFallback(action, actors, surfacesByActor)
262
+ : null);
263
+ if (!scored)
264
+ continue;
265
+ const candidate = {
266
+ actorId: actor.actor_id,
267
+ episode: episodeNo,
268
+ episodeId: episode.episode_id,
269
+ sceneId: scene.scene_id,
270
+ timestamp: timestampFromMilliseconds(milliseconds),
271
+ milliseconds,
272
+ durationMs: source.duration_ms,
273
+ stateScore: scored.stateScore,
274
+ identityScore: scored.identityScore,
275
+ selection: scored.selection,
276
+ sourcePath: source.path,
277
+ sourceSha256: source.sha256,
278
+ };
279
+ local.push(candidate);
280
+ if (actorSpecific) {
281
+ const ownerKey = `${episodeNo}:${milliseconds}`;
282
+ const ownerSet = owners.get(ownerKey) ?? new Set();
283
+ ownerSet.add(actor.actor_id);
284
+ owners.set(ownerKey, ownerSet);
285
+ }
286
+ }
287
+ if (local.length > 0) {
288
+ const identity = identityCandidates.get(actor.actor_id) ?? [];
289
+ identity.push(...local);
290
+ identityCandidates.set(actor.actor_id, identity);
291
+ // A scene-level state_ids set is unordered. Only a single state can be
292
+ // attributed to every Action in the scene without inventing chronology.
293
+ if (state) {
294
+ const key = `${actor.actor_id}:${state.state_id}`;
295
+ const values = stateCandidates.get(key) ?? [];
296
+ values.push(...local);
297
+ stateCandidates.set(key, values);
298
+ }
299
+ }
300
+ }
301
+ }
302
+ }
303
+ const compareActorPriority = (leftId, rightId) => {
304
+ const left = actorById.get(leftId);
305
+ const right = actorById.get(rightId);
306
+ const leftBackground = left?.importance === "background" ? 1 : 0;
307
+ const rightBackground = right?.importance === "background" ? 1 : 0;
308
+ return leftBackground - rightBackground
309
+ || (appearancesByActor.get(rightId) ?? 0) - (appearancesByActor.get(leftId) ?? 0)
310
+ || (left?.actor_name ?? leftId).localeCompare(right?.actor_name ?? rightId, "zh-CN");
311
+ };
312
+ const items = [...stateCandidates.entries()].flatMap(([key, values]) => {
313
+ const [actorId, stateId] = key.split(":");
314
+ const actor = actorId ? actorById.get(actorId) : undefined;
315
+ const state = actor?.states.find((candidate) => candidate.state_id === stateId);
316
+ const anchor = bestCandidate(values, "stateScore", owners);
317
+ if (!actor || !state || !anchor)
318
+ return [];
319
+ const evidenceId = `state:${actor.actor_id}:${state.state_id}`;
320
+ const samples = FRAME_OFFSETS_MS.map((offset) => {
321
+ const lastSeekableMillisecond = anchor.durationMs === undefined
322
+ ? Number.POSITIVE_INFINITY
323
+ : Math.max(0, anchor.durationMs - 50);
324
+ const milliseconds = Math.min(lastSeekableMillisecond, Math.max(0, anchor.milliseconds + offset));
325
+ const timestamp = timestampFromMilliseconds(milliseconds);
326
+ return {
327
+ evidence_id: `${evidenceId}:${anchor.episode}:${timestamp}`,
328
+ episode: anchor.episode,
329
+ episode_id: anchor.episodeId,
330
+ scene_id: anchor.sceneId,
331
+ timestamp,
332
+ offset_ms: offset,
333
+ selection: anchor.selection,
334
+ source_sha256: anchor.sourceSha256,
335
+ source_path: anchor.sourcePath,
336
+ frame_path: `evidence/frames/${actor.actor_id}/${state.state_id}/ep_${String(anchor.episode).padStart(3, "0")}_${timestampFilePart(timestamp)}.jpg`,
337
+ frame_status: "pending",
338
+ };
339
+ }).filter((sample, index, all) => all.findIndex((candidate) => candidate.timestamp === sample.timestamp) === index);
340
+ return [{
341
+ evidence_id: evidenceId,
342
+ asset_kind: "actor",
343
+ actor_id: actor.actor_id,
344
+ actor_name: actor.actor_name,
345
+ state_id: state.state_id,
346
+ state_name: state.state_name,
347
+ samples,
348
+ }];
349
+ }).sort((left, right) => compareActorPriority(left.actor_id, right.actor_id)
350
+ || left.state_name.localeCompare(right.state_name, "zh-CN"));
351
+ const identities = [...identityCandidates.entries()].flatMap(([actorId, values]) => {
352
+ const actor = actorById.get(actorId);
353
+ const anchors = bestCandidates(values, "identityScore", owners, IDENTITY_BOARD_SIZE, IDENTITY_MIN_EVIDENCE_SCORE);
354
+ if (!actor || anchors.length === 0)
355
+ return [];
356
+ const evidenceId = `identity:${actor.actor_id}`;
357
+ return [{
358
+ evidence_id: evidenceId,
359
+ asset_kind: "actor",
360
+ actor_id: actor.actor_id,
361
+ actor_name: actor.actor_name,
362
+ // Ranked, cross-scene identity/appearance frames. The client renders these
363
+ // as a compact 3x2 contact sheet; the first sample remains the single-frame
364
+ // fallback for older clients and inline hover cards.
365
+ samples: anchors.map((anchor) => ({
366
+ evidence_id: `${evidenceId}:${anchor.episode}:${anchor.timestamp}`,
367
+ episode: anchor.episode,
368
+ episode_id: anchor.episodeId,
369
+ scene_id: anchor.sceneId,
370
+ timestamp: anchor.timestamp,
371
+ offset_ms: 0,
372
+ selection: anchor.selection,
373
+ source_sha256: anchor.sourceSha256,
374
+ source_path: anchor.sourcePath,
375
+ frame_path: `evidence/frames/${actor.actor_id}/identity/ep_${String(anchor.episode).padStart(3, "0")}_${timestampFilePart(anchor.timestamp)}.jpg`,
376
+ frame_status: "pending",
377
+ })),
378
+ }];
379
+ }).sort((left, right) => compareActorPriority(left.actor_id, right.actor_id));
380
+ return { version: 2, source_sha256: sourceSha256, items, identities };
381
+ }
382
+ export function parseVideoEvidenceTimestamp(value) {
383
+ return timestampMilliseconds(value);
384
+ }
385
+ //# sourceMappingURL=video-state-evidence.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"video-state-evidence.js","sourceRoot":"","sources":["../../../src/domain/ingest/video-state-evidence.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  import { ACTION_TYPES, IMPORTANCE_TIERS, NON_ACTOR_SPEAKER_KINDS, ROLE_TYPES, } from "../shared/types.js";
2
2
  import { isV4Id } from "./ids.js";
3
3
  import { isSceneTime } from "../../ingest/environment.js";
4
+ import { parseTimestamp, TimestampParseError } from "../timestamp.js";
4
5
  const isObj = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
5
6
  const isStr = (value) => typeof value === "string";
6
7
  const isArr = (value) => Array.isArray(value);
@@ -216,6 +217,7 @@ export function validateScriptV4(doc) {
216
217
  err("scenes", `${episodePath}.scenes`, "scenes must be an array");
217
218
  return;
218
219
  }
220
+ const timeline = [];
219
221
  episode.scenes.forEach((scene, sceneIndex) => {
220
222
  const scenePath = `${episodePath}.scenes[${sceneIndex}]`;
221
223
  if (!isObj(scene)) {
@@ -316,6 +318,10 @@ export function validateScriptV4(doc) {
316
318
  if ("emotion" in action)
317
319
  err("v3_field", `${actionPath}.emotion`, "Script v4 does not allow emotion");
318
320
  validateExtend(action.extend, `${actionPath}.extend`, err);
321
+ timeline.push({
322
+ path: `${actionPath}.extend.timestamp`,
323
+ value: isObj(action.extend) ? action.extend.timestamp : undefined,
324
+ });
319
325
  const speaking = action.type === "dialogue" || action.type === "inner_thought";
320
326
  if (speaking) {
321
327
  if (action.parenthetical !== undefined && !isStr(action.parenthetical)) {
@@ -402,6 +408,62 @@ export function validateScriptV4(doc) {
402
408
  }
403
409
  });
404
410
  });
411
+ const hasDuration = isObj(episode.extend)
412
+ && Object.hasOwn(episode.extend, "source_duration_ms");
413
+ const durationValue = hasDuration
414
+ ? episode.extend.source_duration_ms
415
+ : undefined;
416
+ const sourceDurationMs = typeof durationValue === "number"
417
+ && Number.isFinite(durationValue)
418
+ && durationValue > 0
419
+ ? durationValue
420
+ : undefined;
421
+ if (hasDuration && sourceDurationMs === undefined) {
422
+ err("source_duration_invalid", `${episodePath}.extend.source_duration_ms`, "source_duration_ms must be a positive finite number");
423
+ }
424
+ if (hasDuration || timeline.some((item) => item.value !== undefined)) {
425
+ let previousMs;
426
+ let previousPath;
427
+ for (const item of timeline) {
428
+ if (item.value === undefined) {
429
+ warn("timestamp_missing", item.path, "timestamped episodes require a timestamp on every Action");
430
+ continue;
431
+ }
432
+ if (!isStr(item.value)) {
433
+ err("timestamp_invalid", item.path, "timestamp must be a string in M:SS or M:SS.mmm form");
434
+ continue;
435
+ }
436
+ let currentMs;
437
+ try {
438
+ currentMs = parseTimestamp(item.value).milliseconds;
439
+ }
440
+ catch (error) {
441
+ if (!(error instanceof TimestampParseError))
442
+ throw error;
443
+ err("timestamp_invalid", item.path, error.message);
444
+ continue;
445
+ }
446
+ if (previousMs === undefined && currentMs > 15_000) {
447
+ warn("timestamp_gap_too_long", item.path, `video start to first Action is ${currentMs}ms; maximum is 15000ms`);
448
+ }
449
+ if (previousMs !== undefined && currentMs <= previousMs) {
450
+ err("timestamp_not_increasing", item.path, `timestamp must be greater than the preceding Action at ${previousPath ?? "unknown"}`);
451
+ }
452
+ else if (previousMs !== undefined && currentMs - previousMs > 15_000) {
453
+ warn("timestamp_gap_too_long", item.path, `adjacent Action gap is ${currentMs - previousMs}ms; maximum is 15000ms`);
454
+ }
455
+ if (sourceDurationMs !== undefined && currentMs > sourceDurationMs) {
456
+ err("timestamp_exceeds_source", item.path, `timestamp ${currentMs}ms exceeds source duration ${sourceDurationMs}ms`);
457
+ }
458
+ previousMs = currentMs;
459
+ previousPath = item.path;
460
+ }
461
+ if (sourceDurationMs !== undefined
462
+ && previousMs !== undefined
463
+ && sourceDurationMs - previousMs > 15_000) {
464
+ warn("timestamp_gap_too_long", `${episodePath}.extend.source_duration_ms`, `final Action to video end is ${sourceDurationMs - previousMs}ms; maximum is 15000ms`);
465
+ }
466
+ }
405
467
  });
406
468
  }
407
469
  return { ok: errors.length === 0, errors, warnings };