export interface TranscriptWord { text: string; beginMs: number; endMs: number; punctuation: string; } export interface TranscriptSentence { id: number; beginMs: number; endMs: number; text: string; words: TranscriptWord[]; } export interface WordTranscript { text: string; sentences: TranscriptSentence[]; } export type PauseClassification = "unsafe" | "review" | "safe"; export interface PauseCandidate { id: string; startMs: number; endMs: number; durationMs: number; classification: PauseClassification; beforeText: string; afterText: string; boundary: "within-sentence" | "between-sentences"; /** ASR segmentation alone does not establish a semantic sentence boundary. */ semanticBoundary?: "punctuation" | "unknown"; context: { before: string; after: string; }; adjacentFillerIds: string[]; recommendation: EditorialRecommendation; reasons: string[]; } export type EditorialRecommendation = "cut" | "review" | "keep"; export type DeliveryCue = "neutral" | "hesitation" | "emphasis" | "question"; export interface SentenceAnalysis { sentenceIndex: number; sentenceId: number; beginMs: number; endMs: number; text: string; wordStartIndex: number; wordEndIndex: number; deliveryCues: DeliveryCue[]; confidence: "low"; evidence: string[]; } export interface FillerCandidate { id: string; wordIndex: number; sentenceIndex: number; text: string; startMs: number; endMs: number; kind: "hesitation" | "discourse"; matchConfidence: "exact" | "contextual"; recommendation: "review"; contextText: string; reasons: string[]; } export interface RepetitionCandidate { id: string; sentenceIndex: number; text: string; firstWordIndex: number; secondWordIndex: number; startMs: number; endMs: number; recommendation: "review"; contextText: string; reasons: string[]; } export interface ArollSegment { id: string; sourceStartMs: number; sourceEndMs: number; } export interface BrollPlacement { id: string; assetPath: string; assetBytes: number; assetSha256: string; outputStartMs: number; outputEndMs: number; assetStartMs?: number; selectionReceipt?: BrollSelectionReceipt; fit: "cover" | "contain"; audio: "keep-primary"; query?: string; reason?: string; } export interface BrollMatchReceipt { schemaVersion: 1; projectId: string; revision: number; continuityPlanSha256: string; needId: string; needSha256: string; assetDirectory: string; candidateOffset: number; candidateAssetPaths: string[]; matchSha256: string; } export interface BrollSelectionReceipt { assetBytes: number; assetSha256: string; manifestPath: string; manifestSha256: string; selectedEndMs: number; evidenceTimestampsMs: number[]; matchReceipt?: BrollMatchReceipt; visualIdentity?: BrollVisualIdentity; selectionSha256?: string; } export type BrollShotScale = "wide" | "medium" | "close-up" | "macro" | "screen" | "document" | "mixed" | "other"; export type BrollShotAngle = "front" | "side" | "top-down" | "over-shoulder" | "screen-capture" | "mixed" | "other"; export interface BrollVisualIdentity { subject: string; action: string; shotScale: BrollShotScale; angle: BrollShotAngle; } export type BrollPlacementInput = Omit< BrollPlacement, "assetBytes" | "assetSha256" | "assetStartMs" | "selectionReceipt" > & { assetStartMs: number; selectionReceipt: BrollSelectionReceipt; }; export type BrollPurpose = "demonstrate" | "explain" | "evidence" | "establish" | "transition" | "mask-cut"; export interface BrollVisualReview { decision: "mask-with-broll"; reviewedJumpCutOutputMs: number; artifactPath: string; artifactSha256?: string; } interface BrollNeedBase { id: string; outputStartMs: number; outputEndMs: number; speechText: string; searchTerms: string[]; reason: string; } export type BrollNeed = | BrollNeedBase & { purpose: Exclude; /** Essential means the spoken claim cannot be understood or verified as well without the picture. */ necessity: "essential" | "supporting"; /** Exact words whose visual information is being illustrated; speechText remains the full context. */ visualCueText: string; visualReview?: never; } | BrollNeedBase & { purpose: "mask-cut"; visualReview: BrollVisualReview; }; export interface BrollAssetCandidate { assetPath: string; fileName: string; matchedTerms: string[]; score: number; confidence: "high" | "medium" | "low"; } interface BrollAssetMatchBase { requiredDurationMs: number; totalCandidates: number; candidateOffset: number; nextCandidateOffset: number | null; shortlist: BrollAssetCandidate[]; } export type BrollAssetMatch = | BrollAssetMatchBase & { selectionMode: "filename-direct"; nextStep: { action: "inspect-selected-asset"; reason: string }; } | BrollAssetMatchBase & { selectionMode: "filename-shortlist"; nextStep: { action: "inspect-shortlist"; reason: string }; } | BrollAssetMatchBase & { selectionMode: "visual-fallback"; nextStep: { action: "visual-fallback"; reason: string }; }; export interface BrollWindowSelection { placement: BrollPlacementInput; } export interface BgmMixPolicy { method: "ebu-r128-dialogue-relative"; dialogueAnalysisPath: string; musicAnalysisPath: string; dialogueIntegratedLufs: number; musicIntegratedLufs: number; targetMusicBelowDialogueLu: number; maxTruePeakDbtp: number; } export interface BgmPlanReceipt { schemaVersion: 1; projectId: string; revision: number; arollSha256: string; outputDurationMs: number; musicDurationMs: number; durationRelation: "longer" | "equal" | "shorter"; requiredPlayback: "trim" | "loop"; voiceAnalysis: FileRef; musicAnalysis: FileRef; musicAsset: FileRef; dialogueIntegratedLufs: number; fullMusicIntegratedLufs: number; targetMusicBelowDialogueLu: number; maxTruePeakDbtp: number; planSha256: string; } export interface BgmSelectionReceipt { planReceipt: BgmPlanReceipt; selectedMusicAnalysis: FileRef; mix: BgmMixPolicy; evidenceTimestampsMs: number[]; selectionSha256: string; } export interface BgmTrack { assetPath: string; assetBytes: number; assetSha256: string; sourceStartMs: number; sourceEndMs: number; outputStartMs: number; outputEndMs: number; playback: "trim" | "loop"; fadeInMs: number; fadeOutMs: number; ducking: "dialogue-sidechain"; mix: BgmMixPolicy; selectionReceipt?: BgmSelectionReceipt; } export interface BrollContinuityBridge { fromNeedId: string; toNeedId: string; gapStartMs: number; gapEndMs: number; durationMs: number; strategy: "extend-previous-broll"; } export interface ArollJumpCut { fromSegmentId: string; toSegmentId: string; outputTimeMs: number; sourceDeltaMs: number; suggestedOutputStartMs: number; suggestedOutputEndMs: number; coveredByNeedIds: string[]; status: "covered" | "review" | "needs-broll"; } export interface BrollCoverageSummary { brollDurationMs: number; arollDurationMs: number; brollCoverageRatio: number; longestContinuousBrollMs: number; warnings?: Array<{ code: "long-continuous-run"; message: string; }>; } export type BrollNeedOmissionReason = "a-roll-preferred" | "continuous-run" | "ending-a-roll"; export interface OmittedBrollNeed { id: string; reason: BrollNeedOmissionReason; } export interface BrollPlanSelection { inputNeedCount: number; selectedNeedCount: number; omittedNeeds: OmittedBrollNeed[]; maxInitialContinuousBrollMs: number; minMeaningfulArollIslandMs: number; minEndingArollMs: number; } export interface BrollContinuityPlan { outputDurationMs: number; shortGapMs: number; minMeaningfulArollIslandMs: number; minEndingArollMs: number; cutCoverBeforeMs: number; cutCoverAfterMs: number; needs: BrollNeed[]; bridges: BrollContinuityBridge[]; jumpCuts: ArollJumpCut[]; coverage: BrollCoverageSummary; selection: BrollPlanSelection; } interface BrollContinuityPlanReceiptBase { projectId: string; revision: number; arollSha256: string; shortGapMs: number; cutCoverBeforeMs: number; cutCoverAfterMs: number; jumpCuts: ArollJumpCut[]; planSha256: string; } export interface BrollContinuityPlanReceiptV1 extends BrollContinuityPlanReceiptBase { schemaVersion: 1; needs: Array<{ id: string; outputStartMs: number; outputEndMs: number }>; } export interface BrollContinuityPlanReceiptV2 extends BrollContinuityPlanReceiptBase { schemaVersion: 2; needs: Array<({ id: string; outputStartMs: number; outputEndMs: number; } & ( | { purpose: Exclude; visualReview?: never } | { purpose: "mask-cut"; visualReview: BrollVisualReview & { artifactSha256: string } } ))>; } export interface BrollContinuityPlanReceiptV3 extends BrollContinuityPlanReceiptBase { schemaVersion: 3; needs: Array<( | BrollNeedBase & { purpose: Exclude; necessity: "essential" | "supporting"; visualCueText: string; visualReview?: never; } | BrollNeedBase & { purpose: "mask-cut"; visualReview: BrollVisualReview & { artifactSha256: string }; } )>; } export interface BrollContinuityPlanReceiptV4 extends BrollContinuityPlanReceiptBase { schemaVersion: 4; minMeaningfulArollIslandMs: number; minEndingArollMs: number; needs: BrollContinuityPlanReceiptV3["needs"]; } export type BrollContinuityPlanReceipt = | BrollContinuityPlanReceiptV1 | BrollContinuityPlanReceiptV2 | BrollContinuityPlanReceiptV3 | BrollContinuityPlanReceiptV4; export interface TranscriptAnalysis { schemaVersion: 3; text: string; words: TranscriptWord[]; sentences: SentenceAnalysis[]; fillers: FillerCandidate[]; repetitions: RepetitionCandidate[]; contentCandidates: ContentCandidate[]; diagnostics: TranscriptDiagnostics; candidates: PauseCandidate[]; segments: ArollSegment[]; outputDurationMs: number; } export interface WordRange { startWordIndex: number; endWordIndex: number; } export interface ContentCandidate { id: string; kind: "repeated-passage" | "similar-passage" | "restart"; ranges: WordRange[]; contextTexts: string[]; recommendation: "review"; confidence: "low"; reason: string; } export interface TranscriptDiagnostics { adjacentWordPairs: number; zeroGapPairs: number; positiveGapPairs: number; contentCandidatesTruncated: boolean; warnings: string[]; } export interface EditorialDecision { id: string; kind: "retake" | "false-start" | "filler" | "pause" | "other"; action: "edit" | "keep" | "review"; candidateIds: string[]; ranges: Array; reason: string; } export interface EditorialPlan { scope: "full" | "pauses-only"; /** Required for pauses-only: quote the user's explicit scope restriction. */ scopeReason?: string; reviewedSentenceIndexes: number[]; sourceReview: { method: "text-and-visual" | "listened"; notes: string }; decisions: EditorialDecision[]; uniqueInformation: Array; segmentReasons: Array<{ segmentId: string; reason: string }>; unresolvedIssues: string[]; } export interface EditorialPlanReceipt { schemaVersion: 1; projectId: string; revision: number; sourceSha256: string; transcriptSha256: string; arollSha256: string; plan: EditorialPlan; planSha256: string; } export type EditorialStatus = "draft" | "needs-review" | "content-reviewed" | "pauses-only-reviewed"; export interface TalkingHeadPolicy { cutThresholdMs: number; headPaddingMs: number; tailPaddingMs: number; } export interface FileRef { path: string; bytes: number; sha256: string; } export interface TalkingHeadProject { schemaVersion: 1; projectId: string; currentRevision: number; source: FileRef; sourceDurationMs?: number; transcript: FileRef; analysisPath: string; createdAt: string; updatedAt: string; } export interface TalkingHeadSnapshot { schemaVersion: 1 | 2 | 3; projectId: string; revision: number; parentRevision: number | null; createdAt: string; policy: TalkingHeadPolicy; aroll: ArollSegment[]; broll: BrollPlacement[]; bgm?: BgmTrack; continuityPlanReceipt?: BrollContinuityPlanReceipt; jumpCuts?: ArollJumpCut[]; brollCoverage?: BrollCoverageSummary; outputDurationMs: number; editorialStatus?: EditorialStatus; editorialPlanReceipt?: EditorialPlanReceipt; } export interface RenderArtifact extends FileRef { projectId: string; revision: number; durationMs: number; receiptPath: string; createdAt: string; }