gts-x / src /lib /neon /parser.ts
JAMES HAN
GTS-X: Full HITL editor + QC + file converter for HuggingFace Spaces
c273232
Raw
History Blame Contribute Delete
4.49 kB
/**
* 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");
}