Files
notarius/internal/modules/input/seriatim/model.go

149 lines
3.9 KiB
Go

package seriatim
import (
"bytes"
"encoding/json"
"fmt"
"io"
)
type transcript struct {
Metadata map[string]any `json:"metadata"`
Segments []segment `json:"segments"`
}
type segment struct {
ID string `json:"id"`
Start json.Number `json:"start"`
End json.Number `json:"end"`
Speaker string `json:"speaker"`
Text string `json:"text"`
}
func decodeTranscript(raw []byte) (transcript, error) {
var fields map[string]json.RawMessage
if err := decodeJSON(raw, &fields); err != nil {
return transcript{}, err
}
if fields == nil {
return transcript{}, fmt.Errorf("top-level value must be an object")
}
metadataRaw, ok := fields["metadata"]
if !ok {
return transcript{}, fmt.Errorf("metadata is required")
}
var metadata map[string]any
if err := decodeJSON(metadataRaw, &metadata); err != nil {
return transcript{}, fmt.Errorf("metadata must be an object: %w", err)
}
if metadata == nil {
return transcript{}, fmt.Errorf("metadata must be an object")
}
segmentsRaw, ok := fields["segments"]
if !ok {
return transcript{}, fmt.Errorf("segments are required")
}
var segmentValues []json.RawMessage
if err := decodeJSON(segmentsRaw, &segmentValues); err != nil {
return transcript{}, fmt.Errorf("segments must be an array: %w", err)
}
if segmentValues == nil {
return transcript{}, fmt.Errorf("segments must be an array")
}
segments := make([]segment, 0, len(segmentValues))
for i, rawSegment := range segmentValues {
segment, err := decodeSegment(rawSegment, i)
if err != nil {
return transcript{}, err
}
segments = append(segments, segment)
}
return transcript{
Metadata: metadata,
Segments: segments,
}, nil
}
func decodeSegment(raw []byte, index int) (segment, error) {
var fields map[string]json.RawMessage
if err := decodeJSON(raw, &fields); err != nil {
return segment{}, fmt.Errorf("segment[%d] must be an object: %w", index, err)
}
if fields == nil {
return segment{}, fmt.Errorf("segment[%d] must be an object", index)
}
var decoded segment
if err := decodeOptionalSegmentID(fields, "id", &decoded.ID); err != nil {
return segment{}, fmt.Errorf("segment[%d] id must be a string or number: %w", index, err)
}
if err := decodeOptionalNumber(fields, "start", &decoded.Start); err != nil {
return segment{}, fmt.Errorf("segment[%d] start must be a number: %w", index, err)
}
if err := decodeOptionalNumber(fields, "end", &decoded.End); err != nil {
return segment{}, fmt.Errorf("segment[%d] end must be a number: %w", index, err)
}
if err := decodeOptionalString(fields, "speaker", &decoded.Speaker); err != nil {
return segment{}, fmt.Errorf("segment[%d] speaker must be a string: %w", index, err)
}
if err := decodeOptionalString(fields, "text", &decoded.Text); err != nil {
return segment{}, fmt.Errorf("segment[%d] text must be a string: %w", index, err)
}
return decoded, nil
}
func decodeOptionalString(fields map[string]json.RawMessage, key string, out *string) error {
raw, ok := fields[key]
if !ok {
return nil
}
return decodeJSON(raw, out)
}
func decodeOptionalSegmentID(fields map[string]json.RawMessage, key string, out *string) error {
raw, ok := fields[key]
if !ok {
return nil
}
var text string
if err := decodeJSON(raw, &text); err == nil {
*out = text
return nil
}
var number json.Number
if err := decodeJSON(raw, &number); err == nil {
*out = number.String()
return nil
}
return fmt.Errorf("must be a string or number")
}
func decodeOptionalNumber(fields map[string]json.RawMessage, key string, out *json.Number) error {
raw, ok := fields[key]
if !ok {
return nil
}
return decodeJSON(raw, out)
}
func decodeJSON(raw []byte, out any) error {
decoder := json.NewDecoder(bytes.NewReader(raw))
decoder.UseNumber()
if err := decoder.Decode(out); err != nil {
return err
}
if err := decoder.Decode(&struct{}{}); err != io.EOF {
if err == nil {
return fmt.Errorf("unexpected trailing JSON value")
}
return err
}
return nil
}