@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.
- package/changes/0.58.0.md +14 -0
- package/changes/0.58.1.md +17 -0
- package/dist/cli/register-workflows.js +17 -0
- package/dist/cli/register-workflows.js.map +1 -1
- package/dist/domain/creative-draft/compose.d.ts +18 -0
- package/dist/domain/creative-draft/compose.js +403 -0
- package/dist/domain/creative-draft/compose.js.map +1 -0
- package/dist/domain/ingest/video-serial-md.js +3 -3
- package/dist/domain/ingest/video-serial-md.js.map +1 -1
- package/dist/domain/ingest/video-source-evolution.d.ts +4 -0
- package/dist/domain/ingest/video-source-evolution.js +0 -3
- package/dist/domain/ingest/video-source-evolution.js.map +1 -1
- package/dist/domain/ingest/video-state-evidence.d.ts +66 -0
- package/dist/domain/ingest/video-state-evidence.js +385 -0
- package/dist/domain/ingest/video-state-evidence.js.map +1 -0
- package/dist/domain/script/v4/validation.js +62 -0
- package/dist/domain/script/v4/validation.js.map +1 -1
- package/dist/infra/ingest/video-discovery.js +10 -4
- package/dist/infra/ingest/video-discovery.js.map +1 -1
- package/dist/infra/ingest/video-source-snapshot.d.ts +2 -1
- package/dist/infra/ingest/video-source-snapshot.js +55 -4
- package/dist/infra/ingest/video-source-snapshot.js.map +1 -1
- package/dist/infra/ingest/video-state-evidence.d.ts +14 -0
- package/dist/infra/ingest/video-state-evidence.js +140 -0
- package/dist/infra/ingest/video-state-evidence.js.map +1 -0
- package/dist/llm/config.js +1 -1
- package/dist/llm/router.js +4 -6
- package/dist/llm/router.js.map +1 -1
- package/dist/llm/tasks/ingest/video-serial.d.ts +2 -2
- package/dist/llm/tasks/ingest/video-serial.js +102 -21
- package/dist/llm/tasks/ingest/video-serial.js.map +1 -1
- package/dist/llm/tasks/schemas.js +5 -2
- package/dist/llm/tasks/schemas.js.map +1 -1
- package/dist/usecases/ingest/failure.js +2 -0
- package/dist/usecases/ingest/failure.js.map +1 -1
- package/dist/usecases/ingest/generate-video-state-evidence.d.ts +23 -0
- package/dist/usecases/ingest/generate-video-state-evidence.js +114 -0
- package/dist/usecases/ingest/generate-video-state-evidence.js.map +1 -0
- package/dist/usecases/ingest/skip-video-episode.js +2 -2
- package/dist/usecases/ingest/skip-video-episode.js.map +1 -1
- package/dist/usecases/ingest/video-pipeline.d.ts +8 -3
- package/dist/usecases/ingest/video-pipeline.js +48 -15
- package/dist/usecases/ingest/video-pipeline.js.map +1 -1
- package/dist/usecases/script/compose-creative-draft.d.ts +3 -0
- package/dist/usecases/script/compose-creative-draft.js +106 -0
- package/dist/usecases/script/compose-creative-draft.js.map +1 -0
- package/package.json +1 -1
- package/skills/scriptctl/SKILL.md +30 -9
- package/skills/scriptctl/references/ingest-workflow.md +10 -4
- package/skills/scriptctl/references/production-writing-standard.md +6 -3
- 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":"
|
|
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 };
|