217 lines
6.1 KiB
Go
217 lines
6.1 KiB
Go
package normalize
|
|
|
|
import (
|
|
"fmt"
|
|
"path/filepath"
|
|
"sort"
|
|
"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/schema"
|
|
)
|
|
|
|
// BuildResult contains normalize output plus deterministic transformation diagnostics.
|
|
type BuildResult struct {
|
|
Output any
|
|
SortingChanged bool
|
|
IDsReassigned bool
|
|
SegmentsWithCategories int
|
|
}
|
|
|
|
// Build converts parsed normalize input into a selected seriatim output schema.
|
|
func Build(parsed ParsedTranscript, cfg config.NormalizeConfig) (BuildResult, error) {
|
|
ordered := sortedSegments(parsed.Segments)
|
|
sortingChanged := didSortingChangeOrder(ordered)
|
|
idsReassigned := didReassignIDs(ordered)
|
|
segmentsWithCategories := countSegmentsWithCategories(ordered)
|
|
|
|
switch cfg.OutputSchema {
|
|
case config.OutputSchemaMinimal:
|
|
output := buildMinimal(ordered)
|
|
if err := schema.ValidateMinimalTranscript(output); err != nil {
|
|
return BuildResult{}, fmt.Errorf("validate normalize output: %w", err)
|
|
}
|
|
return BuildResult{
|
|
Output: output,
|
|
SortingChanged: sortingChanged,
|
|
IDsReassigned: idsReassigned,
|
|
SegmentsWithCategories: segmentsWithCategories,
|
|
}, nil
|
|
case config.OutputSchemaIntermediate:
|
|
output := buildIntermediate(ordered)
|
|
if err := schema.ValidateIntermediateTranscript(output); err != nil {
|
|
return BuildResult{}, fmt.Errorf("validate normalize output: %w", err)
|
|
}
|
|
return BuildResult{
|
|
Output: output,
|
|
SortingChanged: sortingChanged,
|
|
IDsReassigned: idsReassigned,
|
|
SegmentsWithCategories: segmentsWithCategories,
|
|
}, nil
|
|
case config.OutputSchemaFull:
|
|
output := buildFull(ordered, cfg)
|
|
if err := schema.ValidateTranscript(output); err != nil {
|
|
return BuildResult{}, fmt.Errorf("validate normalize output: %w", err)
|
|
}
|
|
return BuildResult{
|
|
Output: output,
|
|
SortingChanged: sortingChanged,
|
|
IDsReassigned: idsReassigned,
|
|
SegmentsWithCategories: segmentsWithCategories,
|
|
}, nil
|
|
default:
|
|
return BuildResult{}, fmt.Errorf("unsupported output schema %q", cfg.OutputSchema)
|
|
}
|
|
}
|
|
|
|
func sortedSegments(input []InputSegment) []InputSegment {
|
|
ordered := make([]InputSegment, len(input))
|
|
copy(ordered, input)
|
|
sort.SliceStable(ordered, func(i, j int) bool {
|
|
left := ordered[i]
|
|
right := ordered[j]
|
|
if left.Start != right.Start {
|
|
return left.Start < right.Start
|
|
}
|
|
if left.End != right.End {
|
|
return left.End < right.End
|
|
}
|
|
if left.InputIndex != right.InputIndex {
|
|
return left.InputIndex < right.InputIndex
|
|
}
|
|
return left.Speaker < right.Speaker
|
|
})
|
|
return ordered
|
|
}
|
|
|
|
func buildMinimal(segments []InputSegment) schema.MinimalTranscript {
|
|
outputSegments := make([]schema.MinimalSegment, len(segments))
|
|
for index, segment := range segments {
|
|
outputSegments[index] = schema.MinimalSegment{
|
|
ID: index + 1,
|
|
Start: segment.Start,
|
|
End: segment.End,
|
|
Speaker: segment.Speaker,
|
|
Text: segment.Text,
|
|
}
|
|
}
|
|
|
|
return schema.MinimalTranscript{
|
|
Metadata: schema.MinimalMetadata{
|
|
Application: artifact.ApplicationName,
|
|
Version: buildinfo.Version,
|
|
OutputSchema: config.OutputSchemaMinimal,
|
|
},
|
|
Segments: outputSegments,
|
|
}
|
|
}
|
|
|
|
func buildIntermediate(segments []InputSegment) schema.IntermediateTranscript {
|
|
outputSegments := make([]schema.IntermediateSegment, len(segments))
|
|
for index, segment := range segments {
|
|
outputSegments[index] = schema.IntermediateSegment{
|
|
ID: index + 1,
|
|
Start: segment.Start,
|
|
End: segment.End,
|
|
Speaker: segment.Speaker,
|
|
Text: segment.Text,
|
|
Categories: append([]string(nil), segment.Categories...),
|
|
}
|
|
}
|
|
|
|
return schema.IntermediateTranscript{
|
|
Metadata: schema.IntermediateMetadata{
|
|
Application: artifact.ApplicationName,
|
|
Version: buildinfo.Version,
|
|
OutputSchema: config.OutputSchemaIntermediate,
|
|
},
|
|
Segments: outputSegments,
|
|
}
|
|
}
|
|
|
|
func buildFull(segments []InputSegment, cfg config.NormalizeConfig) schema.Transcript {
|
|
defaultSource := filepath.Base(cfg.InputFile)
|
|
outputSegments := make([]schema.Segment, len(segments))
|
|
for index, segment := range segments {
|
|
source := strings.TrimSpace(segment.Source)
|
|
if source == "" {
|
|
source = defaultSource
|
|
}
|
|
|
|
sourceSegmentIndex := copyIntPtr(segment.SourceSegmentIndex)
|
|
if sourceSegmentIndex == nil {
|
|
fallback := segment.InputIndex
|
|
sourceSegmentIndex = &fallback
|
|
}
|
|
|
|
outputSegments[index] = schema.Segment{
|
|
ID: index + 1,
|
|
Source: source,
|
|
SourceSegmentIndex: sourceSegmentIndex,
|
|
SourceRef: segment.SourceRef,
|
|
DerivedFrom: append([]string(nil), segment.DerivedFrom...),
|
|
Speaker: segment.Speaker,
|
|
Start: segment.Start,
|
|
End: segment.End,
|
|
Text: segment.Text,
|
|
Categories: append([]string(nil), segment.Categories...),
|
|
}
|
|
}
|
|
|
|
return schema.Transcript{
|
|
Metadata: schema.Metadata{
|
|
Application: artifact.ApplicationName,
|
|
Version: buildinfo.Version,
|
|
InputReader: "normalize-input",
|
|
InputFiles: []string{cfg.InputFile},
|
|
PreprocessingModules: []string{},
|
|
PostprocessingModules: []string{},
|
|
OutputModules: append([]string(nil), cfg.OutputModules...),
|
|
},
|
|
Segments: outputSegments,
|
|
OverlapGroups: []schema.OverlapGroup{},
|
|
}
|
|
}
|
|
|
|
func copyIntPtr(value *int) *int {
|
|
if value == nil {
|
|
return nil
|
|
}
|
|
copied := *value
|
|
return &copied
|
|
}
|
|
|
|
func didSortingChangeOrder(segments []InputSegment) bool {
|
|
for index, segment := range segments {
|
|
if segment.InputIndex != index {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func didReassignIDs(segments []InputSegment) bool {
|
|
if len(segments) == 0 {
|
|
return false
|
|
}
|
|
for index, segment := range segments {
|
|
newID := index + 1
|
|
if segment.OriginalID == nil || *segment.OriginalID != newID {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func countSegmentsWithCategories(segments []InputSegment) int {
|
|
count := 0
|
|
for _, segment := range segments {
|
|
if len(segment.Categories) > 0 {
|
|
count++
|
|
}
|
|
}
|
|
return count
|
|
}
|