package normalize import ( "context" "encoding/json" "fmt" "os" "strings" "gitea.maximumdirect.net/eric/seriatim/internal/artifact" "gitea.maximumdirect.net/eric/seriatim/internal/buildinfo" "gitea.maximumdirect.net/eric/seriatim/internal/config" "gitea.maximumdirect.net/eric/seriatim/internal/report" ) type normalizeAudit struct { Command string `json:"command"` InputFile string `json:"input_file"` OutputFile string `json:"output_file"` InputShape string `json:"input_shape"` InputSegmentCount int `json:"input_segment_count"` OutputSegmentCount int `json:"output_segment_count"` OutputSchema string `json:"output_schema"` OutputModules []string `json:"output_modules"` IDsReassigned bool `json:"ids_reassigned"` SortingChangedInput bool `json:"sorting_changed_input_order"` SegmentsWithCategories int `json:"segments_with_categories"` TimingFieldsRepaired int `json:"timing_fields_repaired"` TimingOrderSwapped int `json:"timing_order_swapped"` SpeakerFilled int `json:"speaker_filled"` SegmentsDroppedText int `json:"segments_dropped_text"` } // Run executes artifact-level normalization. func Run(ctx context.Context, cfg config.NormalizeConfig) error { if err := ctx.Err(); err != nil { return err } parsed, err := ParseFile(cfg.InputFile) if err != nil { return err } built, err := Build(parsed, cfg) if err != nil { return err } if err := writeOutputJSON(cfg.OutputFile, built.Output); err != nil { return err } if cfg.ReportFile != "" { audit := normalizeAudit{ Command: "normalize", InputFile: cfg.InputFile, OutputFile: cfg.OutputFile, InputShape: string(parsed.Shape), InputSegmentCount: parsed.InputSegmentCount, OutputSegmentCount: built.OutputSegmentCount, OutputSchema: cfg.OutputSchema, OutputModules: append([]string(nil), cfg.OutputModules...), IDsReassigned: built.IDsReassigned, SortingChangedInput: built.SortingChanged, SegmentsWithCategories: built.SegmentsWithCategories, TimingFieldsRepaired: parsed.Stats.TimingFieldsRepaired, TimingOrderSwapped: parsed.Stats.TimingOrderSwapped, SpeakerFilled: parsed.Stats.SpeakerFilled, SegmentsDroppedText: parsed.Stats.SegmentsDroppedText, } auditJSON, err := json.Marshal(audit) if err != nil { return fmt.Errorf("marshal normalize audit: %w", err) } events := []report.Event{ report.Info("normalize", "normalize", "started normalize command"), report.Info("normalize", "normalize", fmt.Sprintf("input file: %s", cfg.InputFile)), report.Info("normalize", "normalize", fmt.Sprintf("detected input shape: %s", parsed.Shape)), report.Info("normalize", "normalize", fmt.Sprintf("input segment count: %d", parsed.InputSegmentCount)), report.Info("normalize", "normalize", fmt.Sprintf("selected output schema: %s", cfg.OutputSchema)), report.Info("normalize", "normalize", fmt.Sprintf("selected output modules: %s", strings.Join(cfg.OutputModules, ","))), report.Info("normalize", "normalize", fmt.Sprintf("output file: %s", cfg.OutputFile)), report.Info("normalize", "normalize", fmt.Sprintf("ids reassigned: %t", built.IDsReassigned)), report.Info("normalize", "normalize", fmt.Sprintf("sorting changed input order: %t", built.SortingChanged)), report.Info("normalize", "normalize", fmt.Sprintf("segments with categories: %d", built.SegmentsWithCategories)), report.Info("normalize", "normalize", fmt.Sprintf("timing fields repaired: %d", parsed.Stats.TimingFieldsRepaired)), report.Info("normalize", "normalize", fmt.Sprintf("timing order swapped: %d", parsed.Stats.TimingOrderSwapped)), report.Info("normalize", "normalize", fmt.Sprintf("speaker placeholders added: %d", parsed.Stats.SpeakerFilled)), report.Info("normalize", "normalize", fmt.Sprintf("segments dropped for empty text: %d", parsed.Stats.SegmentsDroppedText)), report.Info("normalize", "normalize-audit", string(auditJSON)), } if parsed.InputSegmentCount == 0 { events = append(events, report.Warning("normalize", "normalize", "input transcript contains zero segments")) } events = append(events, report.Info("normalize", "validate-output", fmt.Sprintf("validated %d output segment(s)", built.OutputSegmentCount)), report.Info("output", "json", "wrote transcript JSON"), ) rpt := report.Report{ Metadata: report.Metadata{ Application: artifact.ApplicationName, Version: buildinfo.Version, InputReader: "normalize-input", InputFiles: []string{cfg.InputFile}, PreprocessingModules: []string{}, PostprocessingModules: []string{}, OutputModules: append([]string(nil), cfg.OutputModules...), }, Events: events, } if err := report.WriteJSON(cfg.ReportFile, rpt); err != nil { return fmt.Errorf("write --report-file %q: %w", cfg.ReportFile, err) } } return nil } func writeOutputJSON(path string, value any) error { file, err := os.Create(path) if err != nil { return err } defer file.Close() encoder := json.NewEncoder(file) encoder.SetIndent("", " ") if err := encoder.Encode(value); err != nil { return fmt.Errorf("encode normalize output JSON: %w", err) } return nil }