Spaces:
Sleeping
Sleeping
| /** | |
| * Neon Step3 JSON parser. | |
| * Extracts transcription, shot changes, FN events, and SDH events | |
| * into a normalized format for the GTS-X source panel + AI QC context. | |
| */ | |
| export interface NeonTranscriptionEntry { | |
| id: number; | |
| index: number; | |
| content: string; | |
| cleanText: string; | |
| startTimeMs: number; | |
| endTimeMs: number; | |
| speakerLabel: string; | |
| metadataType: string; // "Dialogue" | "On-Screen Text" | etc. | |
| subPosition: string; // "bottom" | "top" | |
| faceDetection: string; | |
| lipMovement: string; | |
| ostType: string; | |
| noiseType: string; | |
| emtInfo?: { | |
| label: string; | |
| gender: string; | |
| emotion: string; | |
| listener: string; | |
| }; | |
| } | |
| export interface NeonShotChange { | |
| id: number; | |
| startMs: number; | |
| endMs: number; | |
| startFrame: number; | |
| endFrame: number; | |
| } | |
| export interface NeonFNEvent { | |
| text: string; | |
| startTimeMs: number; | |
| endTimeMs: number; | |
| source: string; | |
| ocrType: string; | |
| ostType: string; | |
| significance: string; | |
| } | |
| export interface NeonSourceData { | |
| transcription: NeonTranscriptionEntry[]; | |
| shotChanges: NeonShotChange[]; | |
| fnEvents: NeonFNEvent[]; | |
| sdhEvents: any[]; | |
| summary: { scriptSummary: string; speakerSummary: any[] }; | |
| } | |
| /** Parse SRT-style timestamp "HH:MM:SS,mmm" to milliseconds */ | |
| function parseSrtTime(time: string): number { | |
| if (!time) return 0; | |
| const match = time.match(/(\d{2}):(\d{2}):(\d{2})[,.](\d{3})/); | |
| if (!match) return 0; | |
| return ( | |
| parseInt(match[1]) * 3600000 + | |
| parseInt(match[2]) * 60000 + | |
| parseInt(match[3]) * 1000 + | |
| parseInt(match[4]) | |
| ); | |
| } | |
| export function parseNeonStep3(json: any): NeonSourceData { | |
| const transcription: NeonTranscriptionEntry[] = (json.transcription || []).map( | |
| (t: any) => ({ | |
| id: t.id, | |
| index: t.index ?? t.id - 1, | |
| content: t.content || "", | |
| cleanText: t.clean_text || t.content || "", | |
| startTimeMs: parseSrtTime(t.start_time), | |
| endTimeMs: parseSrtTime(t.end_time), | |
| speakerLabel: t.speaker_label || "", | |
| metadataType: t.metadata_type || "Dialogue", | |
| subPosition: t.sub_position || "bottom", | |
| faceDetection: t.face_detection || "", | |
| lipMovement: t.lip_movement || "", | |
| ostType: t.ost_type || "none", | |
| noiseType: t.noise_type || "none", | |
| emtInfo: t.emt_additional_info | |
| ? { | |
| label: t.emt_additional_info.label || "", | |
| gender: t.emt_additional_info.gender || "", | |
| emotion: t.emt_additional_info.emotion || "", | |
| listener: t.emt_additional_info.listener || "", | |
| } | |
| : undefined, | |
| }), | |
| ); | |
| const shotChanges: NeonShotChange[] = (json.shot_changes || []).map( | |
| (sc: any) => ({ | |
| id: sc.id, | |
| startMs: sc.start_timestamp_millis, | |
| endMs: sc.end_timestamp_millis, | |
| startFrame: sc.start_frame, | |
| endFrame: sc.end_frame, | |
| }), | |
| ); | |
| const fnEvents: NeonFNEvent[] = (json.fn_events || []).map((fn: any) => ({ | |
| text: fn.text || fn.content || "", | |
| startTimeMs: (fn.start_time || 0) * 1000, | |
| endTimeMs: (fn.end_time || 0) * 1000, | |
| source: fn.source || "", | |
| ocrType: fn.ocr_type || "", | |
| ostType: fn.ost_type || "", | |
| significance: fn.narrative_significance || "", | |
| })); | |
| return { | |
| transcription, | |
| shotChanges, | |
| fnEvents, | |
| sdhEvents: json.sdh_events || [], | |
| summary: { | |
| scriptSummary: json.summary?.script_summary || "", | |
| speakerSummary: json.summary?.speaker_summary || [], | |
| }, | |
| }; | |
| } | |
| /** | |
| * Find Neon source entries that overlap with a given time range. | |
| * Used for time-aligned source panel — shows which Neon entries | |
| * correspond to a given output subtitle. | |
| */ | |
| export function findOverlappingEntries( | |
| transcription: NeonTranscriptionEntry[], | |
| startMs: number, | |
| endMs: number, | |
| ): NeonTranscriptionEntry[] { | |
| return transcription.filter( | |
| (t) => t.startTimeMs < endMs && t.endTimeMs > startMs, | |
| ); | |
| } | |
| /** | |
| * Build a formatted source text block for a given time range. | |
| * Shows all overlapping Neon entries with speaker labels. | |
| */ | |
| export function getSourceTextForTimeRange( | |
| transcription: NeonTranscriptionEntry[], | |
| startMs: number, | |
| endMs: number, | |
| ): string { | |
| const entries = findOverlappingEntries(transcription, startMs, endMs); | |
| if (entries.length === 0) return ""; | |
| return entries | |
| .map((e) => { | |
| const speaker = e.speakerLabel ? `[${e.speakerLabel}] ` : ""; | |
| const type = e.metadataType !== "Dialogue" ? ` (${e.metadataType})` : ""; | |
| return `${speaker}${e.content}${type}`; | |
| }) | |
| .join("\n"); | |
| } | |