@lingjingai/scriptctl 0.35.0 → 0.36.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.
- package/README.md +21 -5
- package/changes/0.36.0.md +22 -0
- package/dist/bin.js +0 -0
- package/dist/cli.d.ts +2 -1
- package/dist/cli.js +52 -128
- package/dist/cli.js.map +1 -1
- package/dist/common.d.ts +0 -16
- package/dist/common.js +2 -342
- package/dist/common.js.map +1 -1
- package/dist/domain/ingest/apply-plan.d.ts +23 -0
- package/dist/domain/ingest/apply-plan.js +210 -0
- package/dist/domain/ingest/apply-plan.js.map +1 -0
- package/dist/domain/ingest/assembler.d.ts +57 -0
- package/dist/domain/ingest/assembler.js +181 -0
- package/dist/domain/ingest/assembler.js.map +1 -0
- package/dist/domain/ingest/chunker.d.ts +9 -0
- package/dist/domain/ingest/chunker.js +38 -0
- package/dist/domain/ingest/chunker.js.map +1 -0
- package/dist/domain/ingest/evidence.d.ts +5 -0
- package/dist/domain/ingest/evidence.js +66 -0
- package/dist/domain/ingest/evidence.js.map +1 -0
- package/dist/domain/ingest/govern.d.ts +3 -0
- package/dist/domain/ingest/govern.js +127 -0
- package/dist/domain/ingest/govern.js.map +1 -0
- package/dist/domain/ingest/jsonl-parser.d.ts +6 -0
- package/dist/domain/ingest/jsonl-parser.js +187 -0
- package/dist/domain/ingest/jsonl-parser.js.map +1 -0
- package/dist/domain/ingest/markdown-parser.d.ts +6 -0
- package/dist/domain/ingest/markdown-parser.js +186 -0
- package/dist/domain/ingest/markdown-parser.js.map +1 -0
- package/dist/domain/ingest/records.d.ts +71 -0
- package/dist/domain/ingest/records.js +9 -0
- package/dist/domain/ingest/records.js.map +1 -0
- package/dist/domain/ingest/renumber.d.ts +14 -0
- package/dist/domain/ingest/renumber.js +130 -0
- package/dist/domain/ingest/renumber.js.map +1 -0
- package/dist/domain/ingest/script-v3.d.ts +94 -0
- package/dist/domain/ingest/script-v3.js +7 -0
- package/dist/domain/ingest/script-v3.js.map +1 -0
- package/dist/domain/ingest/state-reconcile.d.ts +11 -0
- package/dist/domain/ingest/state-reconcile.js +90 -0
- package/dist/domain/ingest/state-reconcile.js.map +1 -0
- package/dist/domain/ingest/validation.d.ts +11 -0
- package/dist/domain/ingest/validation.js +168 -0
- package/dist/domain/ingest/validation.js.map +1 -0
- package/dist/domain/ingest/video-aggregate.d.ts +23 -0
- package/dist/domain/ingest/video-aggregate.js +89 -0
- package/dist/domain/ingest/video-aggregate.js.map +1 -0
- package/dist/domain/ingest/video-apply.d.ts +37 -0
- package/dist/domain/ingest/video-apply.js +236 -0
- package/dist/domain/ingest/video-apply.js.map +1 -0
- package/dist/domain/ingest/video-md-parser.d.ts +2 -0
- package/dist/domain/ingest/video-md-parser.js +233 -0
- package/dist/domain/ingest/video-md-parser.js.map +1 -0
- package/dist/domain/ingest/video-records.d.ts +56 -0
- package/dist/domain/ingest/video-records.js +9 -0
- package/dist/domain/ingest/video-records.js.map +1 -0
- package/dist/domain/loom/index.d.ts +2 -1
- package/dist/domain/loom/index.js +12 -10
- package/dist/domain/loom/index.js.map +1 -1
- package/dist/domain/loom/types.d.ts +3 -2
- package/dist/domain/script/lookups.d.ts +3 -2
- package/dist/domain/script/lookups.js +25 -8
- package/dist/domain/script/lookups.js.map +1 -1
- package/dist/domain/script/patch/apply.js +2 -2
- package/dist/domain/script/patch/apply.js.map +1 -1
- package/dist/domain/script/patch/helpers.d.ts +3 -7
- package/dist/domain/script/patch/helpers.js +23 -96
- package/dist/domain/script/patch/helpers.js.map +1 -1
- package/dist/domain/script/patch/ops-action.d.ts +0 -2
- package/dist/domain/script/patch/ops-action.js +34 -100
- package/dist/domain/script/patch/ops-action.js.map +1 -1
- package/dist/domain/script/patch/ops-asset.d.ts +1 -0
- package/dist/domain/script/patch/ops-asset.js +102 -155
- package/dist/domain/script/patch/ops-asset.js.map +1 -1
- package/dist/domain/script/patch/ops-dialogue.d.ts +1 -4
- package/dist/domain/script/patch/ops-dialogue.js +43 -220
- package/dist/domain/script/patch/ops-dialogue.js.map +1 -1
- package/dist/domain/script/patch/ops-extend.js +8 -2
- package/dist/domain/script/patch/ops-extend.js.map +1 -1
- package/dist/domain/script/patch/ops-meta.js.map +1 -1
- package/dist/domain/script/patch/ops-scene.js +26 -39
- package/dist/domain/script/patch/ops-scene.js.map +1 -1
- package/dist/domain/script/patch/ops-state.d.ts +3 -3
- package/dist/domain/script/patch/ops-state.js +33 -49
- package/dist/domain/script/patch/ops-state.js.map +1 -1
- package/dist/domain/script/patch/registry.js +9 -13
- package/dist/domain/script/patch/registry.js.map +1 -1
- package/dist/domain/script/refs.js +23 -111
- package/dist/domain/script/refs.js.map +1 -1
- package/dist/domain/script/scene-refs.d.ts +3 -0
- package/dist/domain/script/scene-refs.js +35 -0
- package/dist/domain/script/scene-refs.js.map +1 -0
- package/dist/domain/script/schema.js +17 -21
- package/dist/domain/script/schema.js.map +1 -1
- package/dist/domain/script/text-util.d.ts +7 -0
- package/dist/domain/script/text-util.js +91 -0
- package/dist/domain/script/text-util.js.map +1 -0
- package/dist/help-text.js +117 -410
- package/dist/help-text.js.map +1 -1
- package/dist/infra/converters.js +1 -1
- package/dist/infra/converters.js.map +1 -1
- package/dist/infra/episode-title.d.ts +4 -0
- package/dist/infra/episode-title.js +129 -0
- package/dist/infra/episode-title.js.map +1 -0
- package/dist/infra/ingest/review-renderer.d.ts +1 -0
- package/dist/infra/ingest/review-renderer.js +431 -0
- package/dist/infra/ingest/review-renderer.js.map +1 -0
- package/dist/infra/ingest/source-resolver.d.ts +21 -0
- package/dist/infra/ingest/source-resolver.js +51 -0
- package/dist/infra/ingest/source-resolver.js.map +1 -0
- package/dist/infra/ingest/text-reader.d.ts +3 -0
- package/dist/infra/ingest/text-reader.js +27 -0
- package/dist/infra/ingest/text-reader.js.map +1 -0
- package/dist/infra/ingest/video-discovery.d.ts +8 -0
- package/dist/infra/ingest/video-discovery.js +60 -0
- package/dist/infra/ingest/video-discovery.js.map +1 -0
- package/dist/infra/ingest/workspace-store.d.ts +18 -0
- package/dist/infra/ingest/workspace-store.js +70 -0
- package/dist/infra/ingest/workspace-store.js.map +1 -0
- package/dist/infra/llm/anthropic-model.d.ts +11 -0
- package/dist/infra/llm/anthropic-model.js +109 -0
- package/dist/infra/llm/anthropic-model.js.map +1 -0
- package/dist/infra/llm/gemini-upload.d.ts +9 -0
- package/dist/infra/llm/gemini-upload.js +94 -0
- package/dist/infra/llm/gemini-upload.js.map +1 -0
- package/dist/infra/llm/gemini-video-model.d.ts +47 -0
- package/dist/infra/llm/gemini-video-model.js +167 -0
- package/dist/infra/llm/gemini-video-model.js.map +1 -0
- package/dist/infra/llm/openai-compatible-model.d.ts +13 -0
- package/dist/infra/llm/openai-compatible-model.js +143 -0
- package/dist/infra/llm/openai-compatible-model.js.map +1 -0
- package/dist/infra/llm/proxy.d.ts +1 -0
- package/dist/infra/llm/proxy.js +30 -0
- package/dist/infra/llm/proxy.js.map +1 -0
- package/dist/infra/llm/retry.d.ts +5 -0
- package/dist/infra/llm/retry.js +20 -0
- package/dist/infra/llm/retry.js.map +1 -0
- package/dist/llm/composition.d.ts +5 -0
- package/dist/llm/composition.js +5 -0
- package/dist/llm/composition.js.map +1 -0
- package/dist/llm/config.d.ts +26 -0
- package/dist/llm/config.js +201 -0
- package/dist/llm/config.js.map +1 -0
- package/dist/llm/registry.d.ts +44 -0
- package/dist/llm/registry.js +48 -0
- package/dist/llm/registry.js.map +1 -0
- package/dist/llm/router.d.ts +23 -0
- package/dist/llm/router.js +0 -0
- package/dist/llm/router.js.map +1 -0
- package/dist/llm/tasks/ingest/asset-curate.d.ts +13 -0
- package/dist/llm/tasks/ingest/asset-curate.js +45 -0
- package/dist/llm/tasks/ingest/asset-curate.js.map +1 -0
- package/dist/llm/tasks/ingest/asset-extract.d.ts +12 -0
- package/dist/llm/tasks/ingest/asset-extract.js +51 -0
- package/dist/llm/tasks/ingest/asset-extract.js.map +1 -0
- package/dist/llm/tasks/ingest/asset-govern.d.ts +22 -0
- package/dist/llm/tasks/ingest/asset-govern.js +143 -0
- package/dist/llm/tasks/ingest/asset-govern.js.map +1 -0
- package/dist/llm/tasks/ingest/mention-resolve.d.ts +9 -0
- package/dist/llm/tasks/ingest/mention-resolve.js +39 -0
- package/dist/llm/tasks/ingest/mention-resolve.js.map +1 -0
- package/dist/llm/tasks/ingest/plot-speaker.d.ts +11 -0
- package/dist/llm/tasks/ingest/plot-speaker.js +72 -0
- package/dist/llm/tasks/ingest/plot-speaker.js.map +1 -0
- package/dist/llm/tasks/ingest/roster-merge.d.ts +46 -0
- package/dist/llm/tasks/ingest/roster-merge.js +235 -0
- package/dist/llm/tasks/ingest/roster-merge.js.map +1 -0
- package/dist/llm/tasks/ingest/state-merge.d.ts +10 -0
- package/dist/llm/tasks/ingest/state-merge.js +84 -0
- package/dist/llm/tasks/ingest/state-merge.js.map +1 -0
- package/dist/llm/tasks/ingest/state-reconcile.d.ts +8 -0
- package/dist/llm/tasks/ingest/state-reconcile.js +44 -0
- package/dist/llm/tasks/ingest/state-reconcile.js.map +1 -0
- package/dist/llm/tasks/ingest/text-normalize.d.ts +10 -0
- package/dist/llm/tasks/ingest/text-normalize.js +89 -0
- package/dist/llm/tasks/ingest/text-normalize.js.map +1 -0
- package/dist/llm/tasks/ingest/video-correct.d.ts +30 -0
- package/dist/llm/tasks/ingest/video-correct.js +144 -0
- package/dist/llm/tasks/ingest/video-correct.js.map +1 -0
- package/dist/llm/tasks/ingest/video-transcribe.d.ts +4 -0
- package/dist/llm/tasks/ingest/video-transcribe.js +105 -0
- package/dist/llm/tasks/ingest/video-transcribe.js.map +1 -0
- package/dist/llm/tasks/schemas.d.ts +5 -0
- package/dist/llm/tasks/schemas.js +107 -0
- package/dist/llm/tasks/schemas.js.map +1 -0
- package/dist/llm/types.d.ts +97 -0
- package/dist/llm/types.js +13 -0
- package/dist/llm/types.js.map +1 -0
- package/dist/usecases/doctor.d.ts +1 -1
- package/dist/usecases/doctor.js +36 -9
- package/dist/usecases/doctor.js.map +1 -1
- package/dist/usecases/ingest/command.d.ts +5 -0
- package/dist/usecases/ingest/command.js +57 -0
- package/dist/usecases/ingest/command.js.map +1 -0
- package/dist/usecases/ingest/fanout.d.ts +10 -0
- package/dist/usecases/ingest/fanout.js +23 -0
- package/dist/usecases/ingest/fanout.js.map +1 -0
- package/dist/usecases/ingest/pass4-mention-resolve.d.ts +11 -0
- package/dist/usecases/ingest/pass4-mention-resolve.js +52 -0
- package/dist/usecases/ingest/pass4-mention-resolve.js.map +1 -0
- package/dist/usecases/ingest/pass5-extract.d.ts +7 -0
- package/dist/usecases/ingest/pass5-extract.js +50 -0
- package/dist/usecases/ingest/pass5-extract.js.map +1 -0
- package/dist/usecases/ingest/pass5-govern.d.ts +13 -0
- package/dist/usecases/ingest/pass5-govern.js +155 -0
- package/dist/usecases/ingest/pass5-govern.js.map +1 -0
- package/dist/usecases/ingest/pipeline.d.ts +28 -0
- package/dist/usecases/ingest/pipeline.js +144 -0
- package/dist/usecases/ingest/pipeline.js.map +1 -0
- package/dist/usecases/ingest/publish-command.d.ts +4 -0
- package/dist/usecases/ingest/publish-command.js +77 -0
- package/dist/usecases/ingest/publish-command.js.map +1 -0
- package/dist/usecases/ingest/status.d.ts +39 -0
- package/dist/usecases/ingest/status.js +155 -0
- package/dist/usecases/ingest/status.js.map +1 -0
- package/dist/usecases/ingest/video-pass3.d.ts +26 -0
- package/dist/usecases/ingest/video-pass3.js +0 -0
- package/dist/usecases/ingest/video-pass3.js.map +1 -0
- package/dist/usecases/ingest/video-pipeline.d.ts +32 -0
- package/dist/usecases/ingest/video-pipeline.js +202 -0
- package/dist/usecases/ingest/video-pipeline.js.map +1 -0
- package/dist/usecases/ingest/view-command.d.ts +4 -0
- package/dist/usecases/ingest/view-command.js +51 -0
- package/dist/usecases/ingest/view-command.js.map +1 -0
- package/dist/usecases/script/actions.js +2 -2
- package/dist/usecases/script/actions.js.map +1 -1
- package/dist/usecases/script/actor.js.map +1 -1
- package/dist/usecases/script/actors.js.map +1 -1
- package/dist/usecases/script/add-actor.js.map +1 -1
- package/dist/usecases/script/add-episode.js.map +1 -1
- package/dist/usecases/script/add-location.js.map +1 -1
- package/dist/usecases/script/add-prop.js.map +1 -1
- package/dist/usecases/script/alias.js.map +1 -1
- package/dist/usecases/script/assets.js.map +1 -1
- package/dist/usecases/script/context.js.map +1 -1
- package/dist/usecases/script/create.js +3 -6
- package/dist/usecases/script/create.js.map +1 -1
- package/dist/usecases/script/delete.js.map +1 -1
- package/dist/usecases/script/describe.js.map +1 -1
- package/dist/usecases/script/dialogue.js +6 -13
- package/dist/usecases/script/dialogue.js.map +1 -1
- package/dist/usecases/script/do.js +1 -1
- package/dist/usecases/script/do.js.map +1 -1
- package/dist/usecases/script/episodes.js.map +1 -1
- package/dist/usecases/script/{add-speaker.d.ts → importance.d.ts} +1 -1
- package/dist/usecases/script/importance.js +17 -0
- package/dist/usecases/script/importance.js.map +1 -0
- package/dist/usecases/script/insert.js.map +1 -1
- package/dist/usecases/script/issues.js.map +1 -1
- package/dist/usecases/script/lib.d.ts +8 -6
- package/dist/usecases/script/lib.js +81 -97
- package/dist/usecases/script/lib.js.map +1 -1
- package/dist/usecases/script/locations.js.map +1 -1
- package/dist/usecases/script/merge.js.map +1 -1
- package/dist/usecases/script/move.js.map +1 -1
- package/dist/usecases/script/props.js.map +1 -1
- package/dist/usecases/script/refs.js.map +1 -1
- package/dist/usecases/script/rename.js.map +1 -1
- package/dist/usecases/script/replace.js.map +1 -1
- package/dist/usecases/script/role.js.map +1 -1
- package/dist/usecases/script/scenes.js +8 -15
- package/dist/usecases/script/scenes.js.map +1 -1
- package/dist/usecases/script/script-patch.js +1 -1
- package/dist/usecases/script/script-patch.js.map +1 -1
- package/dist/usecases/script/script-validate.js +1 -1
- package/dist/usecases/script/script-validate.js.map +1 -1
- package/dist/usecases/script/session.d.ts +4 -4
- package/dist/usecases/script/session.js +57 -78
- package/dist/usecases/script/session.js.map +1 -1
- package/dist/usecases/script/split.js.map +1 -1
- package/dist/usecases/script/state-add.js.map +1 -1
- package/dist/usecases/script/state-delete.js.map +1 -1
- package/dist/usecases/script/state-rename.js.map +1 -1
- package/dist/usecases/script/summary.js.map +1 -1
- package/dist/usecases/script/synopsis-generate.d.ts +4 -5
- package/dist/usecases/script/synopsis-generate.js +30 -46
- package/dist/usecases/script/synopsis-generate.js.map +1 -1
- package/dist/usecases/script/transition.js.map +1 -1
- package/dist/usecases/script/type.js.map +1 -1
- package/dist/usecases/script/worldview.js.map +1 -1
- package/package.json +22 -17
- package/skills/scriptctl/SKILL.md +140 -120
- package/skills/scriptctl/references/atomic-write-workflow.md +12 -9
- package/skills/scriptctl/references/ingest-workflow.md +85 -0
- package/skills/scriptctl/references/state-reference-repair.md +26 -39
- package/dist/domain/direct/runner.d.ts +0 -25
- package/dist/domain/direct/runner.js +0 -88
- package/dist/domain/direct/runner.js.map +0 -1
- package/dist/domain/direct/stage.d.ts +0 -108
- package/dist/domain/direct/stage.js +0 -134
- package/dist/domain/direct/stage.js.map +0 -1
- package/dist/domain/direct/stages/asset-curation.d.ts +0 -2
- package/dist/domain/direct/stages/asset-curation.js +0 -10
- package/dist/domain/direct/stages/asset-curation.js.map +0 -1
- package/dist/domain/direct/stages/batch-extract.d.ts +0 -2
- package/dist/domain/direct/stages/batch-extract.js +0 -12
- package/dist/domain/direct/stages/batch-extract.js.map +0 -1
- package/dist/domain/direct/stages/batch-plan.d.ts +0 -2
- package/dist/domain/direct/stages/batch-plan.js +0 -9
- package/dist/domain/direct/stages/batch-plan.js.map +0 -1
- package/dist/domain/direct/stages/episode-merge.d.ts +0 -2
- package/dist/domain/direct/stages/episode-merge.js +0 -11
- package/dist/domain/direct/stages/episode-merge.js.map +0 -1
- package/dist/domain/direct/stages/episode-plan.d.ts +0 -2
- package/dist/domain/direct/stages/episode-plan.js +0 -9
- package/dist/domain/direct/stages/episode-plan.js.map +0 -1
- package/dist/domain/direct/stages/episode-synopsis.d.ts +0 -2
- package/dist/domain/direct/stages/episode-synopsis.js +0 -12
- package/dist/domain/direct/stages/episode-synopsis.js.map +0 -1
- package/dist/domain/direct/stages/episode-titles.d.ts +0 -2
- package/dist/domain/direct/stages/episode-titles.js +0 -12
- package/dist/domain/direct/stages/episode-titles.js.map +0 -1
- package/dist/domain/direct/stages/index.d.ts +0 -19
- package/dist/domain/direct/stages/index.js +0 -41
- package/dist/domain/direct/stages/index.js.map +0 -1
- package/dist/domain/direct/stages/metadata.d.ts +0 -2
- package/dist/domain/direct/stages/metadata.js +0 -11
- package/dist/domain/direct/stages/metadata.js.map +0 -1
- package/dist/domain/direct/stages/script-merge.d.ts +0 -2
- package/dist/domain/direct/stages/script-merge.js +0 -15
- package/dist/domain/direct/stages/script-merge.js.map +0 -1
- package/dist/domain/direct/stages/script-synopsis.d.ts +0 -2
- package/dist/domain/direct/stages/script-synopsis.js +0 -12
- package/dist/domain/direct/stages/script-synopsis.js.map +0 -1
- package/dist/domain/direct/stages/source-prepare.d.ts +0 -2
- package/dist/domain/direct/stages/source-prepare.js +0 -9
- package/dist/domain/direct/stages/source-prepare.js.map +0 -1
- package/dist/domain/direct/stages/state-binding.d.ts +0 -2
- package/dist/domain/direct/stages/state-binding.js +0 -9
- package/dist/domain/direct/stages/state-binding.js.map +0 -1
- package/dist/domain/direct/stages/state-curation.d.ts +0 -2
- package/dist/domain/direct/stages/state-curation.js +0 -10
- package/dist/domain/direct/stages/state-curation.js.map +0 -1
- package/dist/domain/direct/stages/validate.d.ts +0 -2
- package/dist/domain/direct/stages/validate.js +0 -10
- package/dist/domain/direct/stages/validate.js.map +0 -1
- package/dist/domain/direct-core.d.ts +0 -313
- package/dist/domain/direct-core.js +0 -8514
- package/dist/domain/direct-core.js.map +0 -1
- package/dist/domain/script/validate.d.ts +0 -22
- package/dist/domain/script/validate.js +0 -997
- package/dist/domain/script/validate.js.map +0 -1
- package/dist/infra/providers.d.ts +0 -97
- package/dist/infra/providers.js +0 -1396
- package/dist/infra/providers.js.map +0 -1
- package/dist/usecases/direct.d.ts +0 -27
- package/dist/usecases/direct.js +0 -4172
- package/dist/usecases/direct.js.map +0 -1
- package/dist/usecases/parse.d.ts +0 -15
- package/dist/usecases/parse.js +0 -429
- package/dist/usecases/parse.js.map +0 -1
- package/dist/usecases/script/add-speaker.js +0 -22
- package/dist/usecases/script/add-speaker.js.map +0 -1
- package/dist/usecases/script/export.d.ts +0 -3
- package/dist/usecases/script/export.js +0 -111
- package/dist/usecases/script/export.js.map +0 -1
- package/dist/usecases/script/overlap.d.ts +0 -3
- package/dist/usecases/script/overlap.js +0 -21
- package/dist/usecases/script/overlap.js.map +0 -1
- package/dist/usecases/script/speakers.d.ts +0 -3
- package/dist/usecases/script/speakers.js +0 -28
- package/dist/usecases/script/speakers.js.map +0 -1
- package/dist/usecases/script/state-change.d.ts +0 -3
- package/dist/usecases/script/state-change.js +0 -24
- package/dist/usecases/script/state-change.js.map +0 -1
- package/skills/scriptctl/references/direct-workflow.md +0 -72
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
// Review HTML renderer (§4 view / §10 pass6). Pure: Script v3 doc → the verified
|
|
2
|
+
// cezhi-verify review UI (dual-tab 剧本审阅 / 资产总览, left video + right script
|
|
3
|
+
// rows, click a row to seek, per-actor state×episode matrix, scene chips). The
|
|
4
|
+
// HTML/CSS/JS is ported VERBATIM from the scaffold's 06_build_review_html; only
|
|
5
|
+
// the data it reads is transformed here from v3 into the shape that UI expects
|
|
6
|
+
// (asset_name / actor_names / actor_states / scene_num / location_name /
|
|
7
|
+
// action.extend.timestamp). Video is referenced relatively as src/ep_NNN.mp4
|
|
8
|
+
// (the view command symlinks the media into the workspace).
|
|
9
|
+
const isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
10
|
+
const isArr = (v) => Array.isArray(v);
|
|
11
|
+
const S = (v) => (typeof v === "string" ? v : "");
|
|
12
|
+
const SPACE_EN_TO_ZH = { interior: "内", exterior: "外" };
|
|
13
|
+
const TIME_EN_TO_ZH = { day: "日", night: "夜" };
|
|
14
|
+
const epNum = (episodeId) => { const m = episodeId.match(/(\d+)/); return m ? Number(m[1]) : 0; };
|
|
15
|
+
const scnNum = (sceneId) => { const m = sceneId.match(/(\d+)/); return m ? Number(m[1]) : 0; };
|
|
16
|
+
function nameMap(list, idField, nameField) {
|
|
17
|
+
const m = new Map();
|
|
18
|
+
if (isArr(list))
|
|
19
|
+
for (const a of list)
|
|
20
|
+
if (isObj(a) && typeof a[idField] === "string")
|
|
21
|
+
m.set(a[idField], S(a[nameField]));
|
|
22
|
+
return m;
|
|
23
|
+
}
|
|
24
|
+
function stateNameMap(list, idField) {
|
|
25
|
+
const m = new Map();
|
|
26
|
+
if (isArr(list))
|
|
27
|
+
for (const a of list) {
|
|
28
|
+
if (!isObj(a) || typeof a[idField] !== "string")
|
|
29
|
+
continue;
|
|
30
|
+
const sm = new Map();
|
|
31
|
+
for (const st of isArr(a.states) ? a.states : [])
|
|
32
|
+
if (isObj(st) && typeof st.state_id === "string")
|
|
33
|
+
sm.set(st.state_id, S(st.state_name));
|
|
34
|
+
m.set(a[idField], sm);
|
|
35
|
+
}
|
|
36
|
+
return m;
|
|
37
|
+
}
|
|
38
|
+
// v3 speaker → display string (actor name, or kind[:label]).
|
|
39
|
+
function speakerLabel(speaker, actorNames) {
|
|
40
|
+
if (!isObj(speaker))
|
|
41
|
+
return "";
|
|
42
|
+
if (typeof speaker.actor_id === "string")
|
|
43
|
+
return actorNames.get(speaker.actor_id) ?? speaker.actor_id;
|
|
44
|
+
if (typeof speaker.kind === "string")
|
|
45
|
+
return `${speaker.kind}${speaker.label ? `:${S(speaker.label)}` : ""}`;
|
|
46
|
+
return "";
|
|
47
|
+
}
|
|
48
|
+
// Transform a v3 script into the scaffold review-model consumed by the UI JS.
|
|
49
|
+
function toReviewModel(doc) {
|
|
50
|
+
const actorNames = nameMap(doc.actors, "actor_id", "actor_name");
|
|
51
|
+
const locNames = nameMap(doc.locations, "location_id", "location_name");
|
|
52
|
+
const propNames = nameMap(doc.props, "prop_id", "prop_name");
|
|
53
|
+
const actorStates = stateNameMap(doc.actors, "actor_id");
|
|
54
|
+
const episodes = (isArr(doc.episodes) ? doc.episodes : []).filter(isObj).map((ep) => ({
|
|
55
|
+
episode: epNum(S(ep.episode_id)),
|
|
56
|
+
synopsis: S(ep.synopsis),
|
|
57
|
+
scenes: (isArr(ep.scenes) ? ep.scenes : []).filter(isObj).map((sc) => {
|
|
58
|
+
const loc = isObj(sc.location) ? (locNames.get(S(sc.location.location_id)) ?? S(sc.location.location_id)) : "";
|
|
59
|
+
const anames = [];
|
|
60
|
+
const astates = {};
|
|
61
|
+
for (const r of isArr(sc.actors) ? sc.actors : []) {
|
|
62
|
+
if (!isObj(r) || typeof r.actor_id !== "string")
|
|
63
|
+
continue;
|
|
64
|
+
const nm = actorNames.get(r.actor_id) ?? r.actor_id;
|
|
65
|
+
if (!anames.includes(nm))
|
|
66
|
+
anames.push(nm);
|
|
67
|
+
if (typeof r.state_id === "string" && r.state_id !== "default")
|
|
68
|
+
astates[nm] = actorStates.get(r.actor_id)?.get(r.state_id) ?? r.state_id;
|
|
69
|
+
}
|
|
70
|
+
const pnames = [];
|
|
71
|
+
for (const r of isArr(sc.props) ? sc.props : [])
|
|
72
|
+
if (isObj(r) && typeof r.prop_id === "string") {
|
|
73
|
+
const nm = propNames.get(r.prop_id) ?? r.prop_id;
|
|
74
|
+
if (!pnames.includes(nm))
|
|
75
|
+
pnames.push(nm);
|
|
76
|
+
}
|
|
77
|
+
const env = isObj(sc.environment) ? sc.environment : {};
|
|
78
|
+
return {
|
|
79
|
+
scene_num: scnNum(S(sc.scene_id)),
|
|
80
|
+
location_name: loc,
|
|
81
|
+
environment: { space: SPACE_EN_TO_ZH[S(env.space)] ?? S(env.space), time: TIME_EN_TO_ZH[S(env.time)] ?? S(env.time) },
|
|
82
|
+
actor_names: anames,
|
|
83
|
+
actor_states: astates,
|
|
84
|
+
prop_names: pnames,
|
|
85
|
+
actions: (isArr(sc.actions) ? sc.actions : []).filter(isObj).map((a) => ({
|
|
86
|
+
type: S(a.type),
|
|
87
|
+
content: S(a.content),
|
|
88
|
+
speaker: speakerLabel(a.speaker, actorNames),
|
|
89
|
+
emotion: typeof a.emotion === "string" ? a.emotion : undefined,
|
|
90
|
+
extend: isObj(a.extend) && typeof a.extend.timestamp === "string" ? { timestamp: a.extend.timestamp } : undefined,
|
|
91
|
+
})),
|
|
92
|
+
};
|
|
93
|
+
}),
|
|
94
|
+
}));
|
|
95
|
+
const actors = (isArr(doc.actors) ? doc.actors : []).filter(isObj).map((a) => ({
|
|
96
|
+
asset_name: S(a.actor_name),
|
|
97
|
+
description: S(a.description),
|
|
98
|
+
extend: {},
|
|
99
|
+
}));
|
|
100
|
+
return { title: S(doc.title), actors, episodes };
|
|
101
|
+
}
|
|
102
|
+
export function renderReviewHtml(doc) {
|
|
103
|
+
if (!isObj(doc))
|
|
104
|
+
throw new Error("review renderer expects a script object");
|
|
105
|
+
const model = toReviewModel(doc);
|
|
106
|
+
const data = JSON.stringify(model);
|
|
107
|
+
const title = S(model.title);
|
|
108
|
+
return `<!DOCTYPE html>
|
|
109
|
+
<html lang="zh">
|
|
110
|
+
<head>
|
|
111
|
+
<meta charset="utf-8">
|
|
112
|
+
<title>剧本审阅器 · ${title}</title>
|
|
113
|
+
<style>
|
|
114
|
+
* { box-sizing: border-box; }
|
|
115
|
+
html, body { margin: 0; height: 100%; font: 14px/1.6 -apple-system, "PingFang SC", system-ui, sans-serif; color: #1a1a1a; }
|
|
116
|
+
body { display: flex; flex-direction: column; height: 100vh; background: #f5f5f7; }
|
|
117
|
+
header { display: flex; align-items: center; gap: 12px; padding: 8px 14px; background: #fff; border-bottom: 1px solid #ddd; flex: none; }
|
|
118
|
+
header h1 { font-size: 15px; margin: 0; font-weight: 600; }
|
|
119
|
+
header select, header .nav { font-size: 13px; padding: 4px 8px; border: 1px solid #ccc; border-radius: 6px; background: #fff; cursor: pointer; }
|
|
120
|
+
header .spacer { flex: 1; }
|
|
121
|
+
header .hint { color: #888; font-size: 12px; }
|
|
122
|
+
main { flex: 1; display: flex; min-height: 0; }
|
|
123
|
+
.left { width: 42%; min-width: 320px; max-width: 620px; background: #000; display: flex; flex-direction: column; align-items: center; justify-content: flex-start; position: relative; }
|
|
124
|
+
.left video { width: 100%; height: auto; max-height: 72vh; background: #000; }
|
|
125
|
+
.vbar { color: #ccc; font-size: 12px; padding: 6px 10px; width: 100%; display: flex; gap: 12px; align-items: center; background: #111; }
|
|
126
|
+
.vbar b { color: #fff; font-variant-numeric: tabular-nums; }
|
|
127
|
+
.right { flex: 1; overflow-y: auto; padding: 10px 4px 40vh 4px; background: #fafafa; }
|
|
128
|
+
.syn { background: #fff7e6; border: 1px solid #ffe0a3; border-radius: 8px; padding: 10px 12px; margin: 8px 10px 14px; color: #5b4a1f; font-size: 13px; }
|
|
129
|
+
.scene { margin: 0 10px 14px; border: 1px solid #e5e5e5; border-radius: 8px; overflow: hidden; background: #fff; }
|
|
130
|
+
.scene-head { background: #eef2f7; padding: 6px 12px; font-size: 12px; color: #445; display: flex; gap: 14px; flex-wrap: wrap; }
|
|
131
|
+
.scene-head .loc { font-weight: 600; color: #223; }
|
|
132
|
+
.rows { }
|
|
133
|
+
.row { display: flex; gap: 10px; padding: 5px 12px; border-top: 1px solid #f0f0f0; cursor: pointer; align-items: baseline; }
|
|
134
|
+
.row:hover { background: #eef6ff; }
|
|
135
|
+
.row.active { background: #d8ecff; }
|
|
136
|
+
.row .ts { flex: none; width: 46px; color: #3a7; font-variant-numeric: tabular-nums; font-size: 12px; }
|
|
137
|
+
.row .kind { flex: none; width: 58px; font-size: 12px; }
|
|
138
|
+
.row .kind.act { color: #999; }
|
|
139
|
+
.row .kind.dlg { color: #c60; font-weight: 600; }
|
|
140
|
+
.row .kind.think { color: #96c; font-style: italic; }
|
|
141
|
+
.row .body { flex: 1; }
|
|
142
|
+
.row .spk { color: #c60; font-weight: 600; margin-right: 4px; }
|
|
143
|
+
.row .emo { color: #a8a; font-size: 12px; margin-left: 4px; }
|
|
144
|
+
.row .body .subtitle { color: #b00; } /* 画面字幕引述,红色标出 */
|
|
145
|
+
/* ===== Tab ===== */
|
|
146
|
+
header .tabs { display: flex; gap: 4px; }
|
|
147
|
+
header .tab { font-size: 13px; padding: 5px 14px; border: 1px solid #ccc; border-radius: 6px; background: #fff; cursor: pointer; color: #555; }
|
|
148
|
+
header .tab.active { background: #1a6dff; color: #fff; border-color: #1a6dff; font-weight: 600; }
|
|
149
|
+
header .ep-ctl { display: flex; gap: 8px; align-items: center; }
|
|
150
|
+
/* ===== 资产总览 ===== */
|
|
151
|
+
.assets { flex: 1; overflow-y: auto; padding: 16px 18px 30vh; background: #f5f5f7; }
|
|
152
|
+
.asset-sec { margin: 0 0 26px; }
|
|
153
|
+
.asset-sec > h2 { font-size: 15px; margin: 4px 0 12px; color: #223; display: flex; align-items: center; gap: 8px; }
|
|
154
|
+
.asset-sec > h2 .cnt { font-size: 12px; font-weight: 400; color: #888; background: #ececf0; padding: 1px 8px; border-radius: 10px; }
|
|
155
|
+
.acard { background: #fff; border: 1px solid #e3e3e6; border-radius: 10px; padding: 14px 16px; margin-bottom: 12px; }
|
|
156
|
+
.acard .ah { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
|
|
157
|
+
.acard .ah .nm { font-size: 17px; font-weight: 700; color: #1a1a1a; }
|
|
158
|
+
.acard .ah .id { font-size: 12px; color: #aaa; font-variant-numeric: tabular-nums; }
|
|
159
|
+
.acard .ah .meta { margin-left: auto; font-size: 12px; color: #888; display: flex; gap: 6px; }
|
|
160
|
+
.acard .ah .meta .tag { background: #f0f0f3; border-radius: 6px; padding: 1px 8px; }
|
|
161
|
+
.acard .desc { font-size: 13px; color: #555; margin: 7px 0 4px; }
|
|
162
|
+
.acard .anchor { font-size: 12px; color: #999; margin-bottom: 8px; }
|
|
163
|
+
/* 状态×集 矩阵 */
|
|
164
|
+
.mtx { overflow-x: auto; margin-top: 8px; border: 1px solid #eee; border-radius: 8px; }
|
|
165
|
+
table.tl { border-collapse: collapse; font-size: 12px; width: 100%; }
|
|
166
|
+
table.tl th, table.tl td { border: 1px solid #eee; padding: 5px 7px; text-align: center; white-space: nowrap; }
|
|
167
|
+
table.tl thead th { background: #f7f7f9; color: #556; font-weight: 600; position: sticky; top: 0; }
|
|
168
|
+
table.tl .stname { text-align: left; font-weight: 600; color: #333; background: #fafafa; position: sticky; left: 0; min-width: 130px; max-width: 220px; white-space: normal; }
|
|
169
|
+
table.tl .stname.unmk { color: #999; font-weight: 400; }
|
|
170
|
+
table.tl .stname .dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 6px; vertical-align: middle; }
|
|
171
|
+
table.tl .stname .sc { color: #aaa; font-weight: 400; font-size: 11px; margin-left: 4px; }
|
|
172
|
+
table.tl td.cell { color: #ccc; }
|
|
173
|
+
table.tl td.hit { background: #fff0f0; }
|
|
174
|
+
table.tl td.hit.unmk { background: #f2f2f2; }
|
|
175
|
+
.scc { display: inline-block; min-width: 20px; margin: 1px; padding: 1px 6px; border-radius: 6px; font-variant-numeric: tabular-nums; cursor: pointer; }
|
|
176
|
+
.scc.on { background: #f4c9c9; color: #7a1f1f; border: 1px solid #e8a9a9; }
|
|
177
|
+
.scc.on.unmk { background: #dcdcdc; color: #555; border-color: #c9c9c9; }
|
|
178
|
+
.scc.on:hover { filter: brightness(0.94); }
|
|
179
|
+
</style>
|
|
180
|
+
</head>
|
|
181
|
+
<body>
|
|
182
|
+
<header>
|
|
183
|
+
<h1>${title || "剧本审阅器"}</h1>
|
|
184
|
+
<div class="tabs">
|
|
185
|
+
<button class="tab active" id="tab-script">剧本审阅</button>
|
|
186
|
+
<button class="tab" id="tab-assets">资产总览</button>
|
|
187
|
+
</div>
|
|
188
|
+
<span class="ep-ctl">
|
|
189
|
+
<select id="epsel"></select>
|
|
190
|
+
<button class="nav" id="prev">◀ 上一集</button>
|
|
191
|
+
<button class="nav" id="next">下一集 ▶</button>
|
|
192
|
+
</span>
|
|
193
|
+
<span class="spacer"></span>
|
|
194
|
+
<span class="hint" id="hint">点任意剧本行 → 视频跳到该时间戳</span>
|
|
195
|
+
</header>
|
|
196
|
+
<main id="view-script">
|
|
197
|
+
<div class="left">
|
|
198
|
+
<video id="vid" controls preload="metadata"></video>
|
|
199
|
+
<div class="vbar"><span>当前 <b id="cur">00:00</b></span><span id="epinfo"></span></div>
|
|
200
|
+
</div>
|
|
201
|
+
<div class="right" id="right"></div>
|
|
202
|
+
</main>
|
|
203
|
+
<div id="view-assets" class="assets" style="display:none"></div>
|
|
204
|
+
<script>
|
|
205
|
+
const SCRIPT = ${data};
|
|
206
|
+
const eps = SCRIPT.episodes.slice().sort((a,b)=>a.episode-b.episode);
|
|
207
|
+
const vid = document.getElementById('vid');
|
|
208
|
+
const right = document.getElementById('right');
|
|
209
|
+
const epsel = document.getElementById('epsel');
|
|
210
|
+
const cur = document.getElementById('cur');
|
|
211
|
+
const epinfo = document.getElementById('epinfo');
|
|
212
|
+
let curEp = eps.length?eps[0].episode:1;
|
|
213
|
+
|
|
214
|
+
function pad(n){return String(n).padStart(2,'0');}
|
|
215
|
+
function fmt(sec){sec=Math.max(0,sec|0);return pad(sec/60|0)+':'+pad(sec%60);}
|
|
216
|
+
function tsToSec(ts){if(!ts)return null;const m=String(ts).match(/(?:(\\d{1,2}):)?(\\d{1,2}):(\\d{2}(?:\\.\\d{1,3})?)/);return m?(+(m[1]||0))*3600+(+m[2])*60+(+m[3]):null;}
|
|
217
|
+
function epId(n){return 'ep_'+String(n).padStart(3,'0');}
|
|
218
|
+
function actKind(t){return t==='dialogue'?'dlg':t==='inner_thought'?'think':'act';}
|
|
219
|
+
function actTs(a){return a && a.extend ? a.extend.timestamp : undefined;}
|
|
220
|
+
|
|
221
|
+
function renderBody(content){
|
|
222
|
+
const esc = content.replace(/&/g,'&').replace(/</g,'<');
|
|
223
|
+
return esc.replace(/(画面(?:字幕|文字)[::][^。!?\\n]*)/g,'<span class="subtitle">$1</span>');
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
for(const e of eps){
|
|
227
|
+
const o=document.createElement('option');
|
|
228
|
+
o.value=e.episode; o.textContent='第 '+e.episode+' 集';
|
|
229
|
+
epsel.appendChild(o);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function loadEp(n, targetScene){
|
|
233
|
+
curEp=n;
|
|
234
|
+
epsel.value=n;
|
|
235
|
+
vid.src='src/'+epId(n)+'.mp4';
|
|
236
|
+
const ep=eps.find(e=>e.episode===n);
|
|
237
|
+
if(!ep)return;
|
|
238
|
+
epinfo.textContent='第'+n+'集 · '+ep.scenes.length+'场';
|
|
239
|
+
right.innerHTML='';
|
|
240
|
+
if(ep.synopsis){const s=document.createElement('div');s.className='syn';s.textContent='梗概:'+ep.synopsis;right.appendChild(s);}
|
|
241
|
+
for(const sc of ep.scenes){
|
|
242
|
+
const box=document.createElement('div');box.className='scene';
|
|
243
|
+
box.dataset.scene=sc.scene_num;
|
|
244
|
+
const head=document.createElement('div');head.className='scene-head';
|
|
245
|
+
const env=(sc.environment&&(sc.environment.space||'')+' '+(sc.environment.time||''))||'';
|
|
246
|
+
const chNames=sc.actor_names||[]; const chSt=sc.actor_states||{};
|
|
247
|
+
head.innerHTML='<span class="loc">场景'+sc.scene_num+' · '+(sc.location_name||'')+'</span>'+
|
|
248
|
+
'<span>'+env+'</span>'+
|
|
249
|
+
(chNames.length?'<span>角色: '+chNames.map(nm=>nm+(chSt[nm]?'('+chSt[nm]+')':'')).join('、')+'</span>':'');
|
|
250
|
+
box.appendChild(head);
|
|
251
|
+
const rows=document.createElement('div');rows.className='rows';
|
|
252
|
+
for(const a of sc.actions){
|
|
253
|
+
const ts=actTs(a); const kd=actKind(a.type); const sec=tsToSec(ts);
|
|
254
|
+
const row=document.createElement('div');row.className='row';row.dataset.sec=sec==null?'':sec;
|
|
255
|
+
const spk=a.speaker?'<span class="spk">'+a.speaker+'</span>':'';
|
|
256
|
+
const emo=a.emotion?'<span class="emo">['+a.emotion+']</span>':'';
|
|
257
|
+
row.innerHTML='<span class="ts">'+(ts||'')+'</span>'+
|
|
258
|
+
'<span class="kind '+kd+'">'+kd+'</span>'+
|
|
259
|
+
'<span class="body">'+spk+renderBody(a.content)+emo+'</span>';
|
|
260
|
+
if(sec!=null)row.addEventListener('click',()=>{vid.currentTime=sec;vid.play();});
|
|
261
|
+
rows.appendChild(row);
|
|
262
|
+
}
|
|
263
|
+
box.appendChild(rows);right.appendChild(box);
|
|
264
|
+
}
|
|
265
|
+
if(targetScene){
|
|
266
|
+
const tb=right.querySelector('.scene[data-scene="'+targetScene+'"]');
|
|
267
|
+
if(tb){tb.scrollIntoView({block:'start'}); tb.style.outline='2px solid #1a6dff'; setTimeout(()=>tb.style.outline='',1600);}
|
|
268
|
+
} else { right.scrollTop=0; }
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
vid.addEventListener('timeupdate',()=>{
|
|
272
|
+
cur.textContent=fmt(vid.currentTime);
|
|
273
|
+
const t=vid.currentTime;
|
|
274
|
+
const rows=[...right.querySelectorAll('.row')].filter(r=>r.dataset.sec!=='');
|
|
275
|
+
let active=null;
|
|
276
|
+
for(const r of rows){ if(+r.dataset.sec<=t+0.25) active=r; else break; }
|
|
277
|
+
right.querySelectorAll('.row.active').forEach(r=>r.classList.remove('active'));
|
|
278
|
+
if(active){active.classList.add('active');}
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
epsel.addEventListener('change',()=>loadEp(+epsel.value));
|
|
282
|
+
document.getElementById('prev').addEventListener('click',()=>{const i=eps.findIndex(e=>e.episode===curEp);if(i>0)loadEp(eps[i-1].episode);});
|
|
283
|
+
document.getElementById('next').addEventListener('click',()=>{const i=eps.findIndex(e=>e.episode===curEp);if(i<eps.length-1)loadEp(eps[i+1].episode);});
|
|
284
|
+
|
|
285
|
+
if(eps.length)loadEp(curEp);
|
|
286
|
+
|
|
287
|
+
// ===================== 资产总览 =====================
|
|
288
|
+
const allEps = eps.map(e=>e.episode).sort((a,b)=>a-b);
|
|
289
|
+
const actorMeta = {};
|
|
290
|
+
for(const a of (SCRIPT.actors||[])){
|
|
291
|
+
const ext=a.extend||{};
|
|
292
|
+
actorMeta[a.asset_name]={role:a.description||'',anchor:ext.anchor||''};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function aggAssets(){
|
|
296
|
+
const person={}, loc={}, prop={};
|
|
297
|
+
const addP=(nm,st,n,idx)=>{(person[nm]=person[nm]||{});(person[nm][st]=person[nm][st]||new Map()).set(n+':'+idx,{n,idx});};
|
|
298
|
+
const addX=(bag,nm,n,idx)=>{(bag[nm]=bag[nm]||new Map()).set(n+':'+idx,{n,idx});};
|
|
299
|
+
for(const ep of eps){ const n=ep.episode;
|
|
300
|
+
for(const sc of ep.scenes){ const idx=sc.scene_num;
|
|
301
|
+
const names=sc.actor_names||[]; const st=sc.actor_states||{};
|
|
302
|
+
for(const nm of names) addP(nm, st[nm]||'未标注', n, idx);
|
|
303
|
+
if(sc.location_name) addX(loc, sc.location_name, n, idx);
|
|
304
|
+
for(const p of (sc.prop_names||[])) addX(prop, p, n, idx);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return {person,loc,prop};
|
|
308
|
+
}
|
|
309
|
+
const AG = aggAssets();
|
|
310
|
+
|
|
311
|
+
function epsOf(occMaps){ const s=new Set(); for(const m of occMaps) for(const {n} of m.values()) s.add(n); return [...s].sort((a,b)=>a-b); }
|
|
312
|
+
function sceneChips(occMap, unmk){
|
|
313
|
+
const byEp={}; for(const {n,idx} of occMap.values()){(byEp[n]=byEp[n]||[]).push(idx);}
|
|
314
|
+
return byEp;
|
|
315
|
+
}
|
|
316
|
+
function chipEl(n, idx, unmk){
|
|
317
|
+
const c=document.createElement('span'); c.className='scc on'+(unmk?' unmk':''); c.textContent=idx;
|
|
318
|
+
c.title='第'+n+'集 · 场'+idx+' → 跳转';
|
|
319
|
+
c.addEventListener('click',(e)=>{e.stopPropagation();gotoScene(n,idx);});
|
|
320
|
+
return c;
|
|
321
|
+
}
|
|
322
|
+
function gotoScene(n, idx){ switchTab('script'); loadEp(n, idx); }
|
|
323
|
+
|
|
324
|
+
function personCard(name){
|
|
325
|
+
const states=AG.person[name]; const stKeys=Object.keys(states);
|
|
326
|
+
const involved=epsOf(Object.values(states));
|
|
327
|
+
const card=document.createElement('div'); card.className='acard';
|
|
328
|
+
const meta=actorMeta[name]||{};
|
|
329
|
+
const totalScenes=new Set(); for(const m of Object.values(states)) for(const k of m.keys()) totalScenes.add(k);
|
|
330
|
+
const head=document.createElement('div'); head.className='ah';
|
|
331
|
+
head.innerHTML='<span class="nm">'+esc(name)+'</span>'+
|
|
332
|
+
'<span class="meta"><span class="tag">'+totalScenes.size+' 场</span><span class="tag">'+involved.length+' 集</span><span class="tag">'+stKeys.length+' 状态</span></span>';
|
|
333
|
+
card.appendChild(head);
|
|
334
|
+
if(meta.role){const d=document.createElement('div');d.className='desc';d.textContent=meta.role;card.appendChild(d);}
|
|
335
|
+
if(meta.anchor){const d=document.createElement('div');d.className='anchor';d.textContent='锚点:'+meta.anchor;card.appendChild(d);}
|
|
336
|
+
const wrap=document.createElement('div'); wrap.className='mtx';
|
|
337
|
+
const tb=document.createElement('table'); tb.className='tl';
|
|
338
|
+
let thead='<thead><tr><th class="stname">状态\集</th>';
|
|
339
|
+
for(const n of involved) thead+='<th>'+n+'</th>';
|
|
340
|
+
thead+='</tr></thead>';
|
|
341
|
+
const ordered=stKeys.filter(s=>s!=='未标注').sort((a,b)=>states[b].size-states[a].size);
|
|
342
|
+
if(states['未标注']) ordered.push('未标注');
|
|
343
|
+
const tbody=document.createElement('tbody');
|
|
344
|
+
for(const st of ordered){
|
|
345
|
+
const unmk=st==='未标注';
|
|
346
|
+
const byEp=sceneChips(states[st], unmk);
|
|
347
|
+
const tr=document.createElement('tr');
|
|
348
|
+
const th=document.createElement('td'); th.className='stname'+(unmk?' unmk':'');
|
|
349
|
+
th.innerHTML='<span class="dot" style="background:'+(unmk?'#bbb':'#d9534f')+'"></span>'+esc(st)+'<span class="sc">'+states[st].size+'</span>';
|
|
350
|
+
tr.appendChild(th);
|
|
351
|
+
for(const n of involved){
|
|
352
|
+
const td=document.createElement('td'); td.className='cell'+(byEp[n]?' hit'+(unmk?' unmk':''):'');
|
|
353
|
+
if(byEp[n]){ for(const idx of byEp[n].sort((a,b)=>a-b)) td.appendChild(chipEl(n,idx,unmk)); }
|
|
354
|
+
tr.appendChild(td);
|
|
355
|
+
}
|
|
356
|
+
tbody.appendChild(tr);
|
|
357
|
+
}
|
|
358
|
+
tb.innerHTML=thead; tb.appendChild(tbody);
|
|
359
|
+
wrap.appendChild(tb); card.appendChild(wrap);
|
|
360
|
+
return card;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function occCard(name, occMap){
|
|
364
|
+
const involved=epsOf([occMap]);
|
|
365
|
+
const card=document.createElement('div'); card.className='acard';
|
|
366
|
+
const head=document.createElement('div'); head.className='ah';
|
|
367
|
+
head.innerHTML='<span class="nm">'+esc(name)+'</span>'+
|
|
368
|
+
'<span class="meta"><span class="tag">'+occMap.size+' 场</span><span class="tag">'+involved.length+' 集</span></span>';
|
|
369
|
+
card.appendChild(head);
|
|
370
|
+
const wrap=document.createElement('div'); wrap.className='mtx';
|
|
371
|
+
const tb=document.createElement('table'); tb.className='tl';
|
|
372
|
+
let thead='<thead><tr><th class="stname">出现\集</th>';
|
|
373
|
+
for(const n of involved) thead+='<th>'+n+'</th>'; thead+='</tr></thead>';
|
|
374
|
+
const byEp=sceneChips(occMap,false);
|
|
375
|
+
const tbody=document.createElement('tbody'); const tr=document.createElement('tr');
|
|
376
|
+
const th=document.createElement('td'); th.className='stname';
|
|
377
|
+
th.innerHTML='<span class="dot" style="background:#5b8def"></span>出现';
|
|
378
|
+
tr.appendChild(th);
|
|
379
|
+
for(const n of involved){ const td=document.createElement('td'); td.className='cell'+(byEp[n]?' hit':'');
|
|
380
|
+
if(byEp[n]){ for(const idx of byEp[n].sort((a,b)=>a-b)) td.appendChild(chipEl(n,idx,false)); } tr.appendChild(td);}
|
|
381
|
+
tbody.appendChild(tr); tb.innerHTML=thead; tb.appendChild(tbody);
|
|
382
|
+
wrap.appendChild(tb); card.appendChild(wrap);
|
|
383
|
+
return card;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function esc(s){return String(s).replace(/&/g,'&').replace(/</g,'<');}
|
|
387
|
+
|
|
388
|
+
let assetsRendered=false;
|
|
389
|
+
function renderAssets(){
|
|
390
|
+
if(assetsRendered) return; assetsRendered=true;
|
|
391
|
+
const root=document.getElementById('view-assets');
|
|
392
|
+
const persons=Object.keys(AG.person).sort((a,b)=>{
|
|
393
|
+
const ea=new Set(),eb=new Set();
|
|
394
|
+
for(const m of Object.values(AG.person[a])) for(const {n} of m.values()) ea.add(n);
|
|
395
|
+
for(const m of Object.values(AG.person[b])) for(const {n} of m.values()) eb.add(n);
|
|
396
|
+
return eb.size-ea.size;
|
|
397
|
+
});
|
|
398
|
+
const secP=document.createElement('div'); secP.className='asset-sec';
|
|
399
|
+
secP.innerHTML='<h2>人物 <span class="cnt">'+persons.length+'</span></h2>';
|
|
400
|
+
for(const nm of persons) secP.appendChild(personCard(nm));
|
|
401
|
+
root.appendChild(secP);
|
|
402
|
+
const locs=Object.keys(AG.loc).sort((a,b)=>AG.loc[b].size-AG.loc[a].size);
|
|
403
|
+
const secL=document.createElement('div'); secL.className='asset-sec';
|
|
404
|
+
secL.innerHTML='<h2>场景 <span class="cnt">'+locs.length+'</span></h2>';
|
|
405
|
+
for(const nm of locs) secL.appendChild(occCard(nm,AG.loc[nm]));
|
|
406
|
+
root.appendChild(secL);
|
|
407
|
+
const props=Object.keys(AG.prop).sort((a,b)=>AG.prop[b].size-AG.prop[a].size);
|
|
408
|
+
const secD=document.createElement('div'); secD.className='asset-sec';
|
|
409
|
+
secD.innerHTML='<h2>道具 <span class="cnt">'+props.length+'</span></h2>';
|
|
410
|
+
for(const nm of props) secD.appendChild(occCard(nm,AG.prop[nm]));
|
|
411
|
+
root.appendChild(secD);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const tabScript=document.getElementById('tab-script'), tabAssets=document.getElementById('tab-assets');
|
|
415
|
+
const viewScript=document.getElementById('view-script'), viewAssets=document.getElementById('view-assets');
|
|
416
|
+
const epCtl=document.querySelector('.ep-ctl'), hint=document.getElementById('hint');
|
|
417
|
+
function switchTab(which){
|
|
418
|
+
const asset=which==='assets';
|
|
419
|
+
tabScript.classList.toggle('active',!asset); tabAssets.classList.toggle('active',asset);
|
|
420
|
+
viewScript.style.display=asset?'none':'flex'; viewAssets.style.display=asset?'block':'none';
|
|
421
|
+
epCtl.style.display=asset?'none':'flex';
|
|
422
|
+
hint.textContent=asset?'点场号 → 跳回剧本审阅对应集/场':'点任意剧本行 → 视频跳到该时间戳';
|
|
423
|
+
if(asset) renderAssets();
|
|
424
|
+
}
|
|
425
|
+
tabScript.addEventListener('click',()=>switchTab('script'));
|
|
426
|
+
tabAssets.addEventListener('click',()=>switchTab('assets'));
|
|
427
|
+
</script>
|
|
428
|
+
</body>
|
|
429
|
+
</html>`;
|
|
430
|
+
}
|
|
431
|
+
//# sourceMappingURL=review-renderer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review-renderer.js","sourceRoot":"","sources":["../../../src/infra/ingest/review-renderer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export declare const TEXT_EXTENSIONS: readonly [".txt", ".md", ".docx"];
|
|
2
|
+
export declare const VIDEO_MIME: Readonly<Record<string, string>>;
|
|
3
|
+
export type SourceKind = "text" | "video-file" | "video-directory";
|
|
4
|
+
export declare class UnsupportedInputError extends Error {
|
|
5
|
+
readonly received: string;
|
|
6
|
+
constructor(received: string);
|
|
7
|
+
}
|
|
8
|
+
export type ResolvedSource = {
|
|
9
|
+
kind: "text";
|
|
10
|
+
path: string;
|
|
11
|
+
ext: string;
|
|
12
|
+
} | {
|
|
13
|
+
kind: "video-file";
|
|
14
|
+
path: string;
|
|
15
|
+
ext: string;
|
|
16
|
+
} | {
|
|
17
|
+
kind: "video-directory";
|
|
18
|
+
path: string;
|
|
19
|
+
files: string[];
|
|
20
|
+
};
|
|
21
|
+
export declare function resolveSource(sourcePath: string): ResolvedSource;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Ingest source resolver (§3.2/§10.1). Classifies a source path into text or
|
|
2
|
+
// video and enforces the strict input allowlist. PDF/JSON/XLSX are explicitly
|
|
3
|
+
// unsupported here (they belong to other commands), returning a clear error.
|
|
4
|
+
import * as fs from "node:fs";
|
|
5
|
+
import * as path from "node:path";
|
|
6
|
+
export const TEXT_EXTENSIONS = [".txt", ".md", ".docx"];
|
|
7
|
+
// Common Gemini-ingestible video containers (§10.1); extension → MIME.
|
|
8
|
+
export const VIDEO_MIME = {
|
|
9
|
+
".mp4": "video/mp4",
|
|
10
|
+
".mov": "video/mov",
|
|
11
|
+
".avi": "video/avi",
|
|
12
|
+
".mpeg": "video/mpeg",
|
|
13
|
+
".mpg": "video/mpg",
|
|
14
|
+
".webm": "video/webm",
|
|
15
|
+
".flv": "video/x-flv",
|
|
16
|
+
".wmv": "video/wmv",
|
|
17
|
+
};
|
|
18
|
+
export class UnsupportedInputError extends Error {
|
|
19
|
+
received;
|
|
20
|
+
constructor(received) {
|
|
21
|
+
super(`Unsupported input: ${received}`);
|
|
22
|
+
this.received = received;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function ext(p) {
|
|
26
|
+
return path.extname(p).toLowerCase();
|
|
27
|
+
}
|
|
28
|
+
function isVideoExt(e) {
|
|
29
|
+
return Object.hasOwn(VIDEO_MIME, e);
|
|
30
|
+
}
|
|
31
|
+
export function resolveSource(sourcePath) {
|
|
32
|
+
const stat = fs.statSync(sourcePath);
|
|
33
|
+
if (stat.isDirectory()) {
|
|
34
|
+
// Directory input is only for a set of video episodes (§10.1); scan one level.
|
|
35
|
+
const entries = fs.readdirSync(sourcePath)
|
|
36
|
+
.filter((name) => !name.startsWith("."))
|
|
37
|
+
.filter((name) => isVideoExt(ext(name)))
|
|
38
|
+
.sort()
|
|
39
|
+
.map((name) => path.join(sourcePath, name));
|
|
40
|
+
if (entries.length === 0)
|
|
41
|
+
throw new UnsupportedInputError(`directory has no supported video files: ${sourcePath}`);
|
|
42
|
+
return { kind: "video-directory", path: sourcePath, files: entries };
|
|
43
|
+
}
|
|
44
|
+
const e = ext(sourcePath);
|
|
45
|
+
if (TEXT_EXTENSIONS.includes(e))
|
|
46
|
+
return { kind: "text", path: sourcePath, ext: e };
|
|
47
|
+
if (isVideoExt(e))
|
|
48
|
+
return { kind: "video-file", path: sourcePath, ext: e };
|
|
49
|
+
throw new UnsupportedInputError(`file extension ${e || "(none)"} is not supported by ingest (txt/md/docx/video only)`);
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=source-resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source-resolver.js","sourceRoot":"","sources":["../../../src/infra/ingest/source-resolver.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,8EAA8E;AAC9E,6EAA6E;AAE7E,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,OAAO,CAAU,CAAC;AAEjE,uEAAuE;AACvE,MAAM,CAAC,MAAM,UAAU,GAAqC;IAC1D,MAAM,EAAE,WAAW;IACnB,MAAM,EAAE,WAAW;IACnB,MAAM,EAAE,WAAW;IACnB,OAAO,EAAE,YAAY;IACrB,MAAM,EAAE,WAAW;IACnB,OAAO,EAAE,YAAY;IACrB,MAAM,EAAE,aAAa;IACrB,MAAM,EAAE,WAAW;CACpB,CAAC;AAIF,MAAM,OAAO,qBAAsB,SAAQ,KAAK;IACzB;IAArB,YAAqB,QAAgB;QACnC,KAAK,CAAC,sBAAsB,QAAQ,EAAE,CAAC,CAAC;QADrB,aAAQ,GAAR,QAAQ,CAAQ;IAErC,CAAC;CACF;AAED,SAAS,GAAG,CAAC,CAAS;IACpB,OAAO,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;AACvC,CAAC;AAED,SAAS,UAAU,CAAC,CAAS;IAC3B,OAAO,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;AACtC,CAAC;AAOD,MAAM,UAAU,aAAa,CAAC,UAAkB;IAC9C,MAAM,IAAI,GAAG,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;IACrC,IAAI,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;QACvB,+EAA+E;QAC/E,MAAM,OAAO,GAAG,EAAE,CAAC,WAAW,CAAC,UAAU,CAAC;aACvC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;aACvC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;aACvC,IAAI,EAAE;aACN,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC,CAAC;QAC9C,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,MAAM,IAAI,qBAAqB,CAAC,2CAA2C,UAAU,EAAE,CAAC,CAAC;QACnH,OAAO,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;IACvE,CAAC;IACD,MAAM,CAAC,GAAG,GAAG,CAAC,UAAU,CAAC,CAAC;IAC1B,IAAK,eAAqC,CAAC,QAAQ,CAAC,CAAC,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC;IAC1G,IAAI,UAAU,CAAC,CAAC,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC;IAC3E,MAAM,IAAI,qBAAqB,CAAC,kBAAkB,CAAC,IAAI,QAAQ,sDAAsD,CAAC,CAAC;AACzH,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Text source reader (§pass1-prepare). Strict UTF-8 decode (rejects invalid
|
|
2
|
+
// bytes), BOM strip, CRLF/CR → LF. Markdown is preserved verbatim (order,
|
|
3
|
+
// headings, lists) — no screenplay interpretation here. DOCX reuses the existing
|
|
4
|
+
// OpenXML converter, then goes through the same normalization.
|
|
5
|
+
import * as fs from "node:fs";
|
|
6
|
+
import { convertDocx } from "../converters.js";
|
|
7
|
+
export class SourceDecodeError extends Error {
|
|
8
|
+
}
|
|
9
|
+
function normalize(text) {
|
|
10
|
+
return text.replace(/^/, "").replace(/\r\n?/g, "\n");
|
|
11
|
+
}
|
|
12
|
+
function decodeUtf8Strict(buf) {
|
|
13
|
+
try {
|
|
14
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(buf);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
throw new SourceDecodeError("source is not valid UTF-8");
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export async function readTextSource(sourcePath, ext) {
|
|
21
|
+
if (ext === ".docx") {
|
|
22
|
+
const [text] = await convertDocx(sourcePath);
|
|
23
|
+
return normalize(text);
|
|
24
|
+
}
|
|
25
|
+
return normalize(decodeUtf8Strict(fs.readFileSync(sourcePath)));
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=text-reader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text-reader.js","sourceRoot":"","sources":["../../../src/infra/ingest/text-reader.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,0EAA0E;AAC1E,iFAAiF;AACjF,+DAA+D;AAE/D,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAE9B,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE/C,MAAM,OAAO,iBAAkB,SAAQ,KAAK;CAAG;AAE/C,SAAS,SAAS,CAAC,IAAY;IAC7B,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;AACxD,CAAC;AAED,SAAS,gBAAgB,CAAC,GAAW;IACnC,IAAI,CAAC;QACH,OAAO,IAAI,WAAW,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAC/D,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,iBAAiB,CAAC,2BAA2B,CAAC,CAAC;IAC3D,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,UAAkB,EAAE,GAAW;IAClE,IAAI,GAAG,KAAK,OAAO,EAAE,CAAC;QACpB,MAAM,CAAC,IAAI,CAAC,GAAG,MAAM,WAAW,CAAC,UAAU,CAAC,CAAC;QAC7C,OAAO,SAAS,CAAC,IAAI,CAAC,CAAC;IACzB,CAAC;IACD,OAAO,SAAS,CAAC,gBAAgB,CAAC,EAAE,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;AAClE,CAAC"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// Video episode discovery (§10.1). Turns a resolved video source into a numbered
|
|
2
|
+
// episode list. A single file is episode 1. In a directory: if every file
|
|
3
|
+
// carries an episode number, reuse them (and reject duplicates); if none do,
|
|
4
|
+
// natural-sort and assign ep 1..N; mixing numbered and unnumbered files fails
|
|
5
|
+
// up front rather than guessing an order.
|
|
6
|
+
import * as path from "node:path";
|
|
7
|
+
import { CliError, EXIT_INPUT } from "../../common.js";
|
|
8
|
+
import { VIDEO_MIME } from "./source-resolver.js";
|
|
9
|
+
function ext(p) {
|
|
10
|
+
return path.extname(p).toLowerCase();
|
|
11
|
+
}
|
|
12
|
+
function mimeFor(p) {
|
|
13
|
+
return VIDEO_MIME[ext(p)] ?? "application/octet-stream";
|
|
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.
|
|
18
|
+
function episodeNumber(file) {
|
|
19
|
+
// Strip the extension first — `.mp4`/`.mpeg` carry digits that are not episode
|
|
20
|
+
// numbers.
|
|
21
|
+
const m = path.basename(file, path.extname(file)).match(/\d+/);
|
|
22
|
+
return m ? Number(m[0]) : null;
|
|
23
|
+
}
|
|
24
|
+
function discoveryError(message, received) {
|
|
25
|
+
return new CliError("INGEST VIDEO DISCOVERY", message, {
|
|
26
|
+
exitCode: EXIT_INPUT,
|
|
27
|
+
errorCode: "INGEST_VIDEO_DISCOVERY",
|
|
28
|
+
received,
|
|
29
|
+
nextSteps: ["Name every episode file with a number, or none of them, and rerun `scriptctl ingest`."],
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
function episode(file, n) {
|
|
33
|
+
return { episode: n, path: file, ext: ext(file), mime: mimeFor(file) };
|
|
34
|
+
}
|
|
35
|
+
export function discoverEpisodes(source) {
|
|
36
|
+
if (source.kind === "text") {
|
|
37
|
+
throw new CliError("INGEST WRONG PIPELINE", "discoverEpisodes received a text source.", { exitCode: EXIT_INPUT, errorCode: "INGEST_WRONG_PIPELINE" });
|
|
38
|
+
}
|
|
39
|
+
if (source.kind === "video-file")
|
|
40
|
+
return [episode(source.path, 1)];
|
|
41
|
+
const files = source.files; // already one-level, allowlisted, sorted (source-resolver)
|
|
42
|
+
const numbers = files.map(episodeNumber);
|
|
43
|
+
const numbered = numbers.filter((n) => n !== null);
|
|
44
|
+
if (numbered.length === 0) {
|
|
45
|
+
// None numbered: keep the resolver's natural sort, assign 1..N.
|
|
46
|
+
return files.map((file, i) => episode(file, i + 1));
|
|
47
|
+
}
|
|
48
|
+
if (numbered.length !== files.length) {
|
|
49
|
+
const bad = files.filter((_, i) => numbers[i] === null).map((f) => path.basename(f));
|
|
50
|
+
throw discoveryError("Some episode files carry a number and some do not.", [`unnumbered: ${bad.join(", ")}`]);
|
|
51
|
+
}
|
|
52
|
+
const seen = new Set();
|
|
53
|
+
for (const n of numbered) {
|
|
54
|
+
if (seen.has(n))
|
|
55
|
+
throw discoveryError("Two episode files resolve to the same number.", [`duplicate episode: ${n}`]);
|
|
56
|
+
seen.add(n);
|
|
57
|
+
}
|
|
58
|
+
return files.map((file, i) => episode(file, numbers[i])).sort((a, b) => a.episode - b.episode);
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=video-discovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"video-discovery.js","sourceRoot":"","sources":["../../../src/infra/ingest/video-discovery.ts"],"names":[],"mappings":"AAAA,iFAAiF;AACjF,0EAA0E;AAC1E,6EAA6E;AAC7E,8EAA8E;AAC9E,0CAA0C;AAE1C,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,EAAE,UAAU,EAAuB,MAAM,sBAAsB,CAAC;AASvE,SAAS,GAAG,CAAC,CAAS;IACpB,OAAO,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;AACvC,CAAC;AAED,SAAS,OAAO,CAAC,CAAS;IACxB,OAAO,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,0BAA0B,CAAC;AAC1D,CAAC;AAED,gFAAgF;AAChF,kFAAkF;AAClF,uEAAuE;AACvE,SAAS,aAAa,CAAC,IAAY;IACjC,+EAA+E;IAC/E,WAAW;IACX,MAAM,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC/D,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACjC,CAAC;AAED,SAAS,cAAc,CAAC,OAAe,EAAE,QAAkB;IACzD,OAAO,IAAI,QAAQ,CAAC,wBAAwB,EAAE,OAAO,EAAE;QACrD,QAAQ,EAAE,UAAU;QACpB,SAAS,EAAE,wBAAwB;QACnC,QAAQ;QACR,SAAS,EAAE,CAAC,uFAAuF,CAAC;KACrG,CAAC,CAAC;AACL,CAAC;AAED,SAAS,OAAO,CAAC,IAAY,EAAE,CAAS;IACtC,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;AACzE,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,MAAsB;IACrD,IAAI,MAAM,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QAC3B,MAAM,IAAI,QAAQ,CAAC,uBAAuB,EAAE,0CAA0C,EAAE,EAAE,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE,uBAAuB,EAAE,CAAC,CAAC;IACxJ,CAAC;IACD,IAAI,MAAM,CAAC,IAAI,KAAK,YAAY;QAAE,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IAEnE,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,2DAA2D;IACvF,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;IACzC,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC;IAEhE,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,gEAAgE;QAChE,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IACtD,CAAC;IACD,IAAI,QAAQ,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM,EAAE,CAAC;QACrC,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;QACrF,MAAM,cAAc,CAAC,oDAAoD,EAAE,CAAC,eAAe,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;IAChH,CAAC;IACD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;YAAE,MAAM,cAAc,CAAC,+CAA+C,EAAE,CAAC,sBAAsB,CAAC,EAAE,CAAC,CAAC,CAAC;QACpH,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IACd,CAAC;IACD,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC;AAClG,CAAC"}
|