@lingjingai/scriptctl 0.57.1 → 0.58.0

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 (40) hide show
  1. package/changes/0.58.0.md +14 -0
  2. package/dist/cli/register-workflows.js +17 -0
  3. package/dist/cli/register-workflows.js.map +1 -1
  4. package/dist/domain/creative-draft/compose.d.ts +18 -0
  5. package/dist/domain/creative-draft/compose.js +403 -0
  6. package/dist/domain/creative-draft/compose.js.map +1 -0
  7. package/dist/domain/ingest/video-serial-md.js.map +1 -1
  8. package/dist/domain/ingest/video-source-evolution.d.ts +4 -0
  9. package/dist/domain/ingest/video-source-evolution.js +0 -3
  10. package/dist/domain/ingest/video-source-evolution.js.map +1 -1
  11. package/dist/domain/ingest/video-state-evidence.d.ts +66 -0
  12. package/dist/domain/ingest/video-state-evidence.js +385 -0
  13. package/dist/domain/ingest/video-state-evidence.js.map +1 -0
  14. package/dist/infra/ingest/video-discovery.js +10 -4
  15. package/dist/infra/ingest/video-discovery.js.map +1 -1
  16. package/dist/infra/ingest/video-source-snapshot.d.ts +1 -1
  17. package/dist/infra/ingest/video-source-snapshot.js +26 -3
  18. package/dist/infra/ingest/video-source-snapshot.js.map +1 -1
  19. package/dist/infra/ingest/video-state-evidence.d.ts +14 -0
  20. package/dist/infra/ingest/video-state-evidence.js +140 -0
  21. package/dist/infra/ingest/video-state-evidence.js.map +1 -0
  22. package/dist/llm/config.js +1 -1
  23. package/dist/llm/router.js +4 -6
  24. package/dist/llm/router.js.map +1 -1
  25. package/dist/llm/tasks/ingest/video-serial.js +7 -0
  26. package/dist/llm/tasks/ingest/video-serial.js.map +1 -1
  27. package/dist/llm/tasks/schemas.js +3 -0
  28. package/dist/llm/tasks/schemas.js.map +1 -1
  29. package/dist/usecases/ingest/generate-video-state-evidence.d.ts +23 -0
  30. package/dist/usecases/ingest/generate-video-state-evidence.js +124 -0
  31. package/dist/usecases/ingest/generate-video-state-evidence.js.map +1 -0
  32. package/dist/usecases/ingest/video-pipeline.d.ts +6 -1
  33. package/dist/usecases/ingest/video-pipeline.js +32 -10
  34. package/dist/usecases/ingest/video-pipeline.js.map +1 -1
  35. package/dist/usecases/script/compose-creative-draft.d.ts +3 -0
  36. package/dist/usecases/script/compose-creative-draft.js +106 -0
  37. package/dist/usecases/script/compose-creative-draft.js.map +1 -0
  38. package/package.json +1 -1
  39. package/skills/scriptctl/SKILL.md +30 -9
  40. package/skills/scriptctl/references/ingest-workflow.md +4 -2
@@ -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":"AACA,OAAO,EAAE,aAAa,EAAmB,MAAM,oBAAoB,CAAC;AAoFpE,MAAM,2BAA2B,GAAG,EAAE,CAAC;AACvC,MAAM,mBAAmB,GAAG,CAAC,CAAC;AAC9B,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,KAAK,CAAU,CAAC;AACrD,MAAM,YAAY,GAAG,oDAAoD,CAAC;AAC1E,MAAM,WAAW,GAAG,+BAA+B,CAAC;AACpD,MAAM,QAAQ,GAAG,mFAAmF,CAAC;AACrG,MAAM,QAAQ,GAAG,0CAA0C,CAAC;AAC5D,MAAM,cAAc,GAAG,8CAA8C,CAAC;AACtE,MAAM,eAAe,GAAG,uBAAuB,CAAC;AAChD,MAAM,oBAAoB,GAAG,yCAAyC,CAAC;AAEvE,SAAS,IAAI,CAAC,KAAa;IACzB,OAAO,KAAK,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;AAC5D,CAAC;AAED,SAAS,0BAA0B,CAAC,KAAa;IAC/C,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,oDAAoD,CAAC,CAAC;IACvF,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IACpC,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACjC,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACjC,IAAI,OAAO,IAAI,EAAE,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACzD,IAAI,OAAO,IAAI,EAAE;QAAE,OAAO,IAAI,CAAC;IAC/B,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACvD,OAAO,CAAC,CAAC,KAAK,GAAG,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,GAAG,IAAI,GAAG,MAAM,CAAC,QAAQ,IAAI,CAAC,CAAC,CAAC;AAChF,CAAC;AAED;;;;GAIG;AACH,SAAS,sCAAsC,CAAC,KAAa;IAC3D,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;IACpD,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;AAC1D,CAAC;AAED,SAAS,qBAAqB,CAAC,KAAa,EAAE,OAAsB,OAAO;IACzE,OAAO,IAAI,KAAK,qBAAqB;QACnC,CAAC,CAAC,sCAAsC,CAAC,KAAK,CAAC;QAC/C,CAAC,CAAC,0BAA0B,CAAC,KAAK,CAAC,CAAC;AACxC,CAAC;AAED,SAAS,aAAa,CAAC,YAA2B,EAAE,UAAmB;IACrE,IAAI,YAAY,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IACxC,OAAO,UAAU,KAAK,SAAS,IAAI,YAAY,IAAI,UAAU,CAAC;AAChE,CAAC;AAED,SAAS,oBAAoB,CAC3B,OAAqC,EACrC,UAAmB;IAEnB,IAAI,UAAU,KAAK,SAAS;QAAE,OAAO,OAAO,CAAC;IAC7C,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACzG,MAAM,UAAU,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,aAAa,CAAC,0BAA0B,CAAC,KAAK,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC;IACrH,MAAM,WAAW,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,aAAa,CAAC,sCAAsC,CAAC,KAAK,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC;IAClI,2EAA2E;IAC3E,0EAA0E;IAC1E,OAAO,WAAW,GAAG,UAAU,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,OAAO,CAAC;AACpE,CAAC;AAED,SAAS,yBAAyB,CAAC,YAAoB;IACrD,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,GAAG,MAAM,CAAC,CAAC;IAClD,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,YAAY,GAAG,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC;IAC5D,MAAM,QAAQ,GAAG,YAAY,GAAG,KAAK,CAAC;IACtC,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;AACxH,CAAC;AAED,SAAS,eAAe,CAAC,MAAgB;IACvC,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC;IACvC,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;AACvD,CAAC;AAED,SAAS,aAAa,CAAC,KAAc,EAAE,cAAmC;IACxE,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,UAAU,EAAE,GAAG,CAAC,KAAK,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;IAC9F,KAAK,MAAM,CAAC,KAAK,EAAE,SAAS,CAAC,IAAI,cAAc,EAAE,CAAC;QAChD,IAAI,IAAI,CAAC,SAAS,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC;YAAE,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;IACzE,CAAC;IACD,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC;AACtE,CAAC;AAED,SAAS,YAAY,CAAC,OAAe,EAAE,QAA2B;IAChE,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC;IAClG,OAAO,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACxD,CAAC;AAED,SAAS,YAAY,CAAC,OAAe,EAAE,MAA0B,EAAE,eAAsC;IACvG,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,YAAY,CAAC,OAAO,EAAE,eAAe,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC;AAChH,CAAC;AAED,SAAS,oBAAoB,CAAC,EAC5B,MAAM,EACN,KAAK,EACL,UAAU,EACV,SAAS,EACT,QAAQ,EACR,eAAe,EACf,SAAS,GASV;IACC,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,KAAK,QAAQ,IAAI,MAAM,CAAC,IAAI,KAAK,YAAY,CAAC;IAC3E,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS;QAC5B,CAAC,CAAC,MAAM,CAAC,OAAO;QAChB,CAAC,CAAC,eAAe,IAAI,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,aAAa,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACjE,MAAM,WAAW,GAAG,YAAY,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IACpD,MAAM,aAAa,GAAG,WAAW,IAAI,CAAC,IAAI,WAAW,IAAI,EAAE,CAAC;IAC5D,MAAM,aAAa,GAAG,WAAW,IAAI,CAAC,CAAC;IAEvC,8EAA8E;IAC9E,8EAA8E;IAC9E,2EAA2E;IAC3E,+EAA+E;IAC/E,iEAAiE;IACjE,MAAM,YAAY,GAAG,MAAM,CAAC,IAAI,KAAK,UAAU,IAAI,SAAS,IAAI,MAAM,IAAI,UAAU,IAAI,MAAM,CAAC,OAAO;WACjG,MAAM,CAAC,OAAO,CAAC,QAAQ,KAAK,KAAK,CAAC,QAAQ,CAAC;IAChD,MAAM,cAAc,GAAG,YAAY,IAAI,CAAC,oBAAoB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC3E,MAAM,0BAA0B,GAAG,UAAU,KAAK,CAAC,IAAI,CACrD,CAAC,SAAS,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;WAC9B,CAAC,cAAc,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAChG,CAAC;IACF,IAAI,CAAC,aAAa,IAAI,CAAC,cAAc,IAAI,CAAC,0BAA0B;QAAE,OAAO,IAAI,CAAC;IAElF,MAAM,SAAS,GAA2B,aAAa,IAAI,cAAc,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,gBAAgB,CAAC;IAC/G,8EAA8E;IAC9E,6EAA6E;IAC7E,+EAA+E;IAC/E,IAAI,UAAU,GAAG,cAAc,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACrF,IAAI,aAAa,GAAG,cAAc,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACxF,IAAI,UAAU,KAAK,CAAC,EAAE,CAAC;QACrB,UAAU,IAAI,EAAE,CAAC;QACjB,aAAa,IAAI,EAAE,CAAC;IACtB,CAAC;IACD,MAAM,eAAe,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC;IACxC,IAAI,eAAe,IAAI,eAAe,KAAK,SAAS,IAAI,eAAe,KAAK,IAAI,IAAI,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC;QACtH,UAAU,IAAI,EAAE,CAAC;IACnB,CAAC;IACD,IAAI,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,UAAU,IAAI,EAAE,CAAC;IACjD,IAAI,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QAC9B,UAAU,IAAI,EAAE,CAAC;QACjB,aAAa,IAAI,EAAE,CAAC;IACtB,CAAC;SAAM,IAAI,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QAClC,UAAU,IAAI,EAAE,CAAC;QACjB,aAAa,IAAI,CAAC,CAAC;IACrB,CAAC;IACD,IAAI,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QAC3B,aAAa,IAAI,EAAE,CAAC;QACpB,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC;YAAE,UAAU,IAAI,EAAE,CAAC;IACnD,CAAC;IACD,IAAI,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QACjC,UAAU,IAAI,EAAE,CAAC;QACjB,aAAa,IAAI,EAAE,CAAC;IACtB,CAAC;IACD,IAAI,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,UAAU,IAAI,EAAE,CAAC;IACpD,IAAI,YAAY,CAAC,OAAO,EAAE,SAAS,EAAE,eAAe,CAAC,IAAI,CAAC,EAAE,CAAC;QAC3D,UAAU,IAAI,EAAE,CAAC;QACjB,aAAa,IAAI,EAAE,CAAC;IACtB,CAAC;IACD,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,SAAS,EAAE,CAAC;AAClD,CAAC;AAED,SAAS,0BAA0B,CACjC,MAAgB,EAChB,SAA6B,EAC7B,eAAsC;IAEtC,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,KAAK,QAAQ,IAAI,MAAM,CAAC,IAAI,KAAK,YAAY,CAAC;IAC3E,MAAM,aAAa,GAAG,eAAe,IAAI,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,aAAa,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACxF,IAAI,CAAC,SAAS,IAAI,oBAAoB,CAAC,IAAI,CAAC,aAAa,CAAC;QAAE,OAAO,IAAI,CAAC;IACxE,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC;IAEjE,0EAA0E;IAC1E,+EAA+E;IAC/E,6EAA6E;IAC7E,gFAAgF;IAChF,IAAI,UAAU,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACrC,UAAU,IAAI,YAAY,CAAC,OAAO,EAAE,SAAS,EAAE,eAAe,CAAC,GAAG,EAAE,CAAC;IACrE,IAAI,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,UAAU,IAAI,EAAE,CAAC;IACjD,IAAI,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,UAAU,IAAI,EAAE,CAAC;SAC3C,IAAI,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,UAAU,IAAI,CAAC,CAAC;IACjD,IAAI,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,UAAU,IAAI,EAAE,CAAC;IACnD,IAAI,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,UAAU,IAAI,EAAE,CAAC;IACpD,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,CAAC,iBAAiB,EAAE,SAAS,EAAE,gBAAgB,EAAE,CAAC;AAC9F,CAAC;AAED,SAAS,cAAc,CACrB,SAAoB,EACpB,QAAwC,EACxC,MAAgC;IAEhC,MAAM,MAAM,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC,OAAO,IAAI,SAAS,CAAC,YAAY,EAAE,CAAC,EAAE,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;IAC7F,OAAO,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACjD,CAAC;AAED,SAAS,cAAc,CACrB,UAAgC,EAChC,QAAwC,EACxC,MAAgC,EAChC,KAAa,EACb,YAA2B;IAE3B,MAAM,YAAY,GAAG,IAAI,GAAG,EAAqB,CAAC;IAClD,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,MAAM,GAAG,GAAG,GAAG,SAAS,CAAC,OAAO,IAAI,SAAS,CAAC,YAAY,EAAE,CAAC;QAC7D,MAAM,QAAQ,GAAG,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACvC,IAAI,CAAC,QAAQ,IAAI,cAAc,CAAC,SAAS,EAAE,QAAQ,EAAE,MAAM,CAAC,GAAG,cAAc,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,CAAC;YAC1G,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;QACnC,CAAC;IACH,CAAC;IACD,MAAM,MAAM,GAAG,CAAC,GAAG,YAAY,CAAC,MAAM,EAAE,CAAC;SACtC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,cAAc,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,CAAC,GAAG,cAAc,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,CAAC;WAClG,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC,OAAO;WAC5B,IAAI,CAAC,YAAY,GAAG,KAAK,CAAC,YAAY,CAAC;SAC3C,MAAM,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,YAAY,KAAK,IAAI,IAAI,cAAc,CAAC,SAAS,EAAE,QAAQ,EAAE,MAAM,CAAC,IAAI,YAAY,CAAC,CAAC;IAE/G,6EAA6E;IAC7E,8EAA8E;IAC9E,wEAAwE;IACxE,MAAM,QAAQ,GAAgB,EAAE,CAAC;IACjC,KAAK,MAAM,SAAS,IAAI,MAAM,EAAE,CAAC;QAC/B,MAAM,QAAQ,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,KAAK,SAAS,CAAC,OAAO;eACxE,KAAK,CAAC,OAAO,KAAK,SAAS,CAAC,OAAO;eACnC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,YAAY,GAAG,SAAS,CAAC,YAAY,CAAC,GAAG,KAAK,CAAC,CAAC;QACpE,IAAI,QAAQ;YAAE,SAAS;QACvB,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACzB,IAAI,QAAQ,CAAC,MAAM,IAAI,KAAK;YAAE,MAAM;IACtC,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,SAAS,aAAa,CACpB,UAAgC,EAChC,QAAwC,EACxC,MAAgC;IAEhC,2EAA2E;IAC3E,0EAA0E;IAC1E,OAAO,cAAc,CAAC,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;AAC1E,CAAC;AAED,SAAS,iBAAiB,CAAC,SAAiB;IAC1C,OAAO,SAAS,CAAC,OAAO,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,IAAI,SAAS,CAAC;AAC/E,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,2BAA2B,CACzC,IAAgC,EAChC,MAAgB,EAChB,OAAmB,EACnB,OAA8C,EAC9C,YAAoB;IAEpB,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;IACnF,MAAM,cAAc,GAAG,aAAa,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACvD,MAAM,SAAS,GAAG,IAAI,GAAG,CAAkB,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IAClG,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,QAAQ,EAAE,aAAa,CAAC,KAAK,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC;IACtH,MAAM,kBAAkB,GAAG,IAAI,GAAG,EAAkB,CAAC;IACrD,MAAM,kBAAkB,GAAG,IAAI,GAAG,EAAuB,CAAC;IAC1D,MAAM,eAAe,GAAG,IAAI,GAAG,EAAuB,CAAC;IACvD,MAAM,MAAM,GAAG,IAAI,GAAG,EAAuB,CAAC;IAE9C,KAAK,IAAI,YAAY,GAAG,CAAC,EAAE,YAAY,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,YAAY,IAAI,CAAC,EAAE,CAAC;QACpF,MAAM,OAAO,GAAG,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAE,CAAC;QAC/C,MAAM,SAAS,GAAG,YAAY,GAAG,CAAC,CAAC;QACnC,MAAM,MAAM,GAAG,eAAe,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAC9C,IAAI,CAAC,MAAM;YAAE,SAAS;QACtB,MAAM,aAAa,GAAG,oBAAoB,CAAC,OAAO,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC;QACxE,KAAK,IAAI,UAAU,GAAG,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,IAAI,CAAC,EAAE,CAAC;YAC7E,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,UAAU,CAAE,CAAC;YAC1C,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,EAAoB,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;YAC1H,KAAK,MAAM,GAAG,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;gBAC/B,kBAAkB,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,kBAAkB,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;gBACtF,MAAM,KAAK,GAAG,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;gBAC1C,IAAI,CAAC,KAAK;oBAAE,SAAS;gBACrB,MAAM,QAAQ,GAAG,eAAe,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;gBAC3E,MAAM,KAAK,GAAgB,EAAE,CAAC;gBAC9B,MAAM,OAAO,GAAG,GAAG,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC,IAAI,CAAC;gBACtE,MAAM,KAAK,GAAG,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;gBAChG,KAAK,MAAM,MAAM,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;oBACnC,MAAM,SAAS,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;oBAC1C,MAAM,YAAY,GAAG,qBAAqB,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;oBACrE,IAAI,CAAC,aAAa,CAAC,YAAY,EAAE,MAAM,CAAC,WAAW,CAAC;wBAAE,SAAS;oBAC/D,MAAM,aAAa,GAAG,oBAAoB,CAAC;wBACzC,MAAM;wBACN,KAAK;wBACL,UAAU,EAAE,MAAM,CAAC,MAAM;wBACzB,SAAS,EAAE,MAAM;wBACjB,QAAQ;wBACR,eAAe;wBACf,SAAS,EAAE,KAAK,EAAE,UAAU,IAAI,SAAS;qBAC1C,CAAC,CAAC;oBACH,MAAM,MAAM,GAAG,aAAa,IAAI,CAAC,KAAK;wBACpC,CAAC,CAAC,0BAA0B,CAAC,MAAM,EAAE,MAAM,EAAE,eAAe,CAAC;wBAC7D,CAAC,CAAC,IAAI,CAAC,CAAC;oBACV,IAAI,CAAC,MAAM;wBAAE,SAAS;oBACtB,MAAM,SAAS,GAAc;wBAC3B,OAAO,EAAE,KAAK,CAAC,QAAQ;wBACvB,OAAO,EAAE,SAAS;wBAClB,SAAS,EAAE,OAAO,CAAC,UAAU;wBAC7B,OAAO,EAAE,KAAK,CAAC,QAAQ;wBACvB,SAAS,EAAE,yBAAyB,CAAC,YAAY,CAAC;wBAClD,YAAY;wBACZ,UAAU,EAAE,MAAM,CAAC,WAAW;wBAC9B,UAAU,EAAE,MAAM,CAAC,UAAU;wBAC7B,aAAa,EAAE,MAAM,CAAC,aAAa;wBACnC,SAAS,EAAE,MAAM,CAAC,SAAS;wBAC3B,UAAU,EAAE,MAAM,CAAC,IAAI;wBACvB,YAAY,EAAE,MAAM,CAAC,MAAM;qBAC5B,CAAC;oBACF,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;oBACtB,IAAI,aAAa,EAAE,CAAC;wBAClB,MAAM,QAAQ,GAAG,GAAG,SAAS,IAAI,YAAY,EAAE,CAAC;wBAChD,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,IAAI,GAAG,EAAU,CAAC;wBAC3D,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;wBAC7B,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;oBACjC,CAAC;gBACH,CAAC;gBACD,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBACrB,MAAM,QAAQ,GAAG,kBAAkB,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;oBAC9D,QAAQ,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC;oBACxB,kBAAkB,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;oBACjD,uEAAuE;oBACvE,wEAAwE;oBACxE,IAAI,KAAK,EAAE,CAAC;wBACV,MAAM,GAAG,GAAG,GAAG,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;wBAClD,MAAM,MAAM,GAAG,eAAe,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;wBAC9C,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC;wBACtB,eAAe,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;oBACnC,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,oBAAoB,GAAG,CAAC,MAAc,EAAE,OAAe,EAAU,EAAE;QACvE,MAAM,IAAI,GAAG,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACnC,MAAM,KAAK,GAAG,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACrC,MAAM,cAAc,GAAG,IAAI,EAAE,UAAU,KAAK,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACjE,MAAM,eAAe,GAAG,KAAK,EAAE,UAAU,KAAK,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACnE,OAAO,cAAc,GAAG,eAAe;eAClC,CAAC,kBAAkB,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,kBAAkB,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;eAC9E,CAAC,IAAI,EAAE,UAAU,IAAI,MAAM,CAAC,CAAC,aAAa,CAAC,KAAK,EAAE,UAAU,IAAI,OAAO,EAAE,OAAO,CAAC,CAAC;IACzF,CAAC,CAAC;IAEF,MAAM,KAAK,GAAG,CAAC,GAAG,eAAe,CAAC,OAAO,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,EAAE,MAAM,CAAC,EAAoC,EAAE;QACvG,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC1C,MAAM,KAAK,GAAG,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAC3D,MAAM,KAAK,GAAG,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC;QAChF,MAAM,MAAM,GAAG,aAAa,CAAC,MAAM,EAAE,YAAY,EAAE,MAAM,CAAC,CAAC;QAC3D,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,IAAI,CAAC,MAAM;YAAE,OAAO,EAAE,CAAC;QAC3C,MAAM,UAAU,GAAG,SAAS,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;QAC/D,MAAM,OAAO,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE;YAC9C,MAAM,uBAAuB,GAAG,MAAM,CAAC,UAAU,KAAK,SAAS;gBAC7D,CAAC,CAAC,MAAM,CAAC,iBAAiB;gBAC1B,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,UAAU,GAAG,EAAE,CAAC,CAAC;YACxC,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,uBAAuB,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,YAAY,GAAG,MAAM,CAAC,CAAC,CAAC;YAClG,MAAM,SAAS,GAAG,yBAAyB,CAAC,YAAY,CAAC,CAAC;YAC1D,OAAO;gBACL,WAAW,EAAE,GAAG,UAAU,IAAI,MAAM,CAAC,OAAO,IAAI,SAAS,EAAE;gBAC3D,OAAO,EAAE,MAAM,CAAC,OAAO;gBACvB,UAAU,EAAE,MAAM,CAAC,SAAS;gBAC5B,QAAQ,EAAE,MAAM,CAAC,OAAO;gBACxB,SAAS;gBACT,SAAS,EAAE,MAAM;gBACjB,SAAS,EAAE,MAAM,CAAC,SAAS;gBAC3B,aAAa,EAAE,MAAM,CAAC,YAAY;gBAClC,WAAW,EAAE,MAAM,CAAC,UAAU;gBAC9B,UAAU,EAAE,mBAAmB,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,QAAQ,OAAO,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,iBAAiB,CAAC,SAAS,CAAC,MAAM;gBACnJ,YAAY,EAAE,SAAkB;aACjC,CAAC;QACJ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,SAAS,KAAK,MAAM,CAAC,SAAS,CAAC,KAAK,KAAK,CAAC,CAAC;QACpH,OAAO,CAAC;gBACN,WAAW,EAAE,UAAU;gBACvB,UAAU,EAAE,OAAO;gBACnB,QAAQ,EAAE,KAAK,CAAC,QAAQ;gBACxB,UAAU,EAAE,KAAK,CAAC,UAAU;gBAC5B,QAAQ,EAAE,KAAK,CAAC,QAAQ;gBACxB,UAAU,EAAE,KAAK,CAAC,UAAU;gBAC5B,OAAO;aACR,CAAC,CAAC;IACL,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,oBAAoB,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC;WACvE,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,KAAK,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC;IAE/D,MAAM,UAAU,GAAG,CAAC,GAAG,kBAAkB,CAAC,OAAO,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,EAAE,MAAM,CAAC,EAAuC,EAAE;QACtH,MAAM,KAAK,GAAG,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACrC,MAAM,OAAO,GAAG,cAAc,CAC5B,MAAM,EACN,eAAe,EACf,MAAM,EACN,mBAAmB,EACnB,2BAA2B,CAC5B,CAAC;QACF,IAAI,CAAC,KAAK,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QAC9C,MAAM,UAAU,GAAG,YAAY,KAAK,CAAC,QAAQ,EAAE,CAAC;QAChD,OAAO,CAAC;gBACN,WAAW,EAAE,UAAU;gBACvB,UAAU,EAAE,OAAO;gBACnB,QAAQ,EAAE,KAAK,CAAC,QAAQ;gBACxB,UAAU,EAAE,KAAK,CAAC,UAAU;gBAC5B,2EAA2E;gBAC3E,4EAA4E;gBAC5E,qDAAqD;gBACrD,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;oBAChC,WAAW,EAAE,GAAG,UAAU,IAAI,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,SAAS,EAAE;oBAClE,OAAO,EAAE,MAAM,CAAC,OAAO;oBACvB,UAAU,EAAE,MAAM,CAAC,SAAS;oBAC5B,QAAQ,EAAE,MAAM,CAAC,OAAO;oBACxB,SAAS,EAAE,MAAM,CAAC,SAAS;oBAC3B,SAAS,EAAE,CAAC;oBACZ,SAAS,EAAE,MAAM,CAAC,SAAS;oBAC3B,aAAa,EAAE,MAAM,CAAC,YAAY;oBAClC,WAAW,EAAE,MAAM,CAAC,UAAU;oBAC9B,UAAU,EAAE,mBAAmB,KAAK,CAAC,QAAQ,gBAAgB,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,iBAAiB,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM;oBACjJ,YAAY,EAAE,SAAkB;iBACjC,CAAC,CAAC;aACJ,CAAC,CAAC;IACL,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,oBAAoB,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC;IAE9E,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,aAAa,EAAE,YAAY,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC;AACxE,CAAC;AAED,MAAM,UAAU,2BAA2B,CAAC,KAAa;IACvD,OAAO,qBAAqB,CAAC,KAAK,CAAC,CAAC;AACtC,CAAC"}
@@ -12,13 +12,19 @@ function ext(p) {
12
12
  function mimeFor(p) {
13
13
  return VIDEO_MIME[ext(p)] ?? "application/octet-stream";
14
14
  }
15
- // First run of digits in the basename, or null when the name carries no number.
16
- // ponytail: first-integer heuristic; if real datasets name files with non-episode
17
- // digits (dates, resolutions) switch to an explicit `ep(\d+)` pattern.
15
+ // Prefer an explicit episode marker before falling back to the first run of
16
+ // digits. Real series names commonly carry unrelated numbers (for example
17
+ // `749局-第35集.mp4`); treating the title number as the episode collapses an
18
+ // entire directory into one duplicate episode.
18
19
  function episodeNumber(file) {
19
20
  // Strip the extension first — `.mp4`/`.mpeg` carry digits that are not episode
20
21
  // numbers.
21
- const m = path.basename(file, path.extname(file)).match(/\d+/);
22
+ const stem = path.basename(file, path.extname(file));
23
+ const explicit = stem.match(/第\s*0*(\d+)\s*[集话回]/u)
24
+ ?? stem.match(/(?:^|[._\-\s])(?:ep(?:isode)?|e)\s*[._\-\s]*0*(\d+)(?=$|[._\-\s])/iu);
25
+ if (explicit)
26
+ return Number(explicit[1]);
27
+ const m = stem.match(/\d+/);
22
28
  return m ? Number(m[0]) : null;
23
29
  }
24
30
  function discoveryError(message, received) {
@@ -1 +1 @@
1
- {"version":3,"file":"video-discovery.js","sourceRoot":"","sources":["../../../src/infra/ingest/video-discovery.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"video-discovery.js","sourceRoot":"","sources":["../../../src/infra/ingest/video-discovery.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
1
  import type { VideoSourceEpisodeIdentity } from "../../domain/ingest/video-source-evolution.js";
2
2
  import type { VideoEpisode } from "./video-discovery.js";
3
- export declare function snapshotVideoSource(episodes: VideoEpisode[]): VideoSourceEpisodeIdentity[];
3
+ export declare function snapshotVideoSource(episodes: VideoEpisode[], previous?: readonly VideoSourceEpisodeIdentity[], previousVerifiedAt?: string): VideoSourceEpisodeIdentity[];
4
4
  export declare function videoSourceFingerprint(episodes: VideoSourceEpisodeIdentity[]): string;
@@ -1,21 +1,44 @@
1
+ import * as fs from "node:fs";
1
2
  import * as path from "node:path";
2
3
  import { sha256File, sha256Text } from "../../common.js";
3
- export function snapshotVideoSource(episodes) {
4
+ export function snapshotVideoSource(episodes, previous = [], previousVerifiedAt) {
5
+ const priorByEpisode = new Map(previous.map((item) => [item.episode, item]));
6
+ const verifiedAtMs = previousVerifiedAt ? Date.parse(previousVerifiedAt) : Number.NaN;
4
7
  return episodes.map((episode) => {
5
8
  const absolutePath = path.resolve(episode.path);
9
+ const stat = fs.statSync(absolutePath);
10
+ const sizeBytes = stat.size;
11
+ const mtimeMs = Math.trunc(stat.mtimeMs);
12
+ const prior = priorByEpisode.get(episode.episode);
13
+ const unchangedByMetadata = prior
14
+ && prior.size_bytes === sizeBytes
15
+ && prior.mtime_ms === mtimeMs;
16
+ const unchangedSinceLegacyVerification = prior
17
+ && prior.size_bytes === undefined
18
+ && prior.mtime_ms === undefined
19
+ && prior.path === absolutePath
20
+ && Number.isFinite(verifiedAtMs)
21
+ && stat.mtimeMs <= verifiedAtMs;
22
+ const sha256 = prior
23
+ && prior.ext === episode.ext
24
+ && prior.mime === episode.mime
25
+ && (unchangedByMetadata || unchangedSinceLegacyVerification)
26
+ ? prior.sha256
27
+ : sha256File(absolutePath);
6
28
  return {
7
29
  episode: episode.episode,
8
30
  path: absolutePath,
9
31
  ext: episode.ext,
10
32
  mime: episode.mime,
11
- sha256: sha256File(absolutePath),
33
+ sha256,
34
+ size_bytes: sizeBytes,
35
+ mtime_ms: mtimeMs,
12
36
  };
13
37
  });
14
38
  }
15
39
  export function videoSourceFingerprint(episodes) {
16
40
  return sha256Text(JSON.stringify(episodes.map((episode) => ({
17
41
  episode: episode.episode,
18
- path: episode.path,
19
42
  ext: episode.ext,
20
43
  mime: episode.mime,
21
44
  sha256: episode.sha256,
@@ -1 +1 @@
1
- {"version":3,"file":"video-source-snapshot.js","sourceRoot":"","sources":["../../../src/infra/ingest/video-source-snapshot.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAIzD,MAAM,UAAU,mBAAmB,CAAC,QAAwB;IAC1D,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE;QAC9B,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAChD,OAAO;YACL,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,IAAI,EAAE,YAAY;YAClB,GAAG,EAAE,OAAO,CAAC,GAAG;YAChB,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,MAAM,EAAE,UAAU,CAAC,YAAY,CAAC;SACjC,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,QAAsC;IAC3E,OAAO,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QAC1D,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,GAAG,EAAE,OAAO,CAAC,GAAG;QAChB,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,MAAM,EAAE,OAAO,CAAC,MAAM;KACvB,CAAC,CAAC,CAAC,CAAC,CAAC;AACR,CAAC"}
1
+ {"version":3,"file":"video-source-snapshot.js","sourceRoot":"","sources":["../../../src/infra/ingest/video-source-snapshot.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAIzD,MAAM,UAAU,mBAAmB,CACjC,QAAwB,EACxB,WAAkD,EAAE,EACpD,kBAA2B;IAE3B,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;IAC7E,MAAM,YAAY,GAAG,kBAAkB,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;IACtF,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE;QAC9B,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAChD,MAAM,IAAI,GAAG,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;QACvC,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC;QAC5B,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACzC,MAAM,KAAK,GAAG,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAClD,MAAM,mBAAmB,GAAG,KAAK;eAC5B,KAAK,CAAC,UAAU,KAAK,SAAS;eAC9B,KAAK,CAAC,QAAQ,KAAK,OAAO,CAAC;QAChC,MAAM,gCAAgC,GAAG,KAAK;eACzC,KAAK,CAAC,UAAU,KAAK,SAAS;eAC9B,KAAK,CAAC,QAAQ,KAAK,SAAS;eAC5B,KAAK,CAAC,IAAI,KAAK,YAAY;eAC3B,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC;eAC7B,IAAI,CAAC,OAAO,IAAI,YAAY,CAAC;QAClC,MAAM,MAAM,GAAG,KAAK;eACf,KAAK,CAAC,GAAG,KAAK,OAAO,CAAC,GAAG;eACzB,KAAK,CAAC,IAAI,KAAK,OAAO,CAAC,IAAI;eAC3B,CAAC,mBAAmB,IAAI,gCAAgC,CAAC;YAC5D,CAAC,CAAC,KAAK,CAAC,MAAM;YACd,CAAC,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;QAC7B,OAAO;YACL,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,IAAI,EAAE,YAAY;YAClB,GAAG,EAAE,OAAO,CAAC,GAAG;YAChB,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,MAAM;YACN,UAAU,EAAE,SAAS;YACrB,QAAQ,EAAE,OAAO;SAClB,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,QAAsC;IAC3E,OAAO,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QAC1D,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,GAAG,EAAE,OAAO,CAAC,GAAG;QAChB,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,MAAM,EAAE,OAAO,CAAC,MAAM;KACvB,CAAC,CAAC,CAAC,CAAC,CAAC;AACR,CAAC"}
@@ -0,0 +1,14 @@
1
+ import { type VideoStateEvidenceManifest, type VideoStateEvidencePlan } from "../../domain/ingest/video-state-evidence.js";
2
+ import type { WorkspaceStore } from "./workspace-store.js";
3
+ export interface FrameExtractionResult {
4
+ ok: boolean;
5
+ error?: string;
6
+ }
7
+ export type VideoFrameExtractor = (sourcePath: string, timestamp: string, outputPath: string, signal?: AbortSignal) => Promise<FrameExtractionResult>;
8
+ export interface VideoStateEvidenceMaterializeOptions {
9
+ /** Reuse a ready frame already present at the deterministic output path. */
10
+ reuseExistingFrames?: boolean;
11
+ }
12
+ export declare const extractVideoEvidenceFrame: VideoFrameExtractor;
13
+ /** Materialize a plan without making frame-tooling failures fatal to ingest. */
14
+ export declare function materializeVideoStateEvidence(store: WorkspaceStore, plan: VideoStateEvidencePlan, signal?: AbortSignal, extractor?: VideoFrameExtractor, options?: VideoStateEvidenceMaterializeOptions): Promise<VideoStateEvidenceManifest>;