Add trim CLI command
This commit is contained in:
@@ -17,5 +17,6 @@ func NewRootCommand() *cobra.Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cmd.AddCommand(newMergeCommand())
|
cmd.AddCommand(newMergeCommand())
|
||||||
|
cmd.AddCommand(newTrimCommand())
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|||||||
125
internal/cli/trim.go
Normal file
125
internal/cli/trim.go
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"gitea.maximumdirect.net/eric/seriatim/internal/config"
|
||||||
|
"gitea.maximumdirect.net/eric/seriatim/internal/report"
|
||||||
|
triminternal "gitea.maximumdirect.net/eric/seriatim/internal/trim"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newTrimCommand() *cobra.Command {
|
||||||
|
var opts config.TrimOptions
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "trim",
|
||||||
|
Short: "Trim an existing seriatim transcript artifact by segment ID",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
trimOpts := opts
|
||||||
|
if !cmd.Flags().Changed("output-schema") {
|
||||||
|
trimOpts.OutputSchema = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := config.NewTrimConfig(trimOpts)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
selector, err := triminternal.ParseSelector(cfg.Selector)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid selector %q: %w", cfg.Selector, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := os.ReadFile(cfg.InputFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("read --input-file %q: %w", cfg.InputFile, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
artifact, err := triminternal.ParseArtifactJSON(data)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("--input-file %q: %w", cfg.InputFile, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mode := triminternal.ModeKeep
|
||||||
|
if cfg.Mode == "remove" {
|
||||||
|
mode = triminternal.ModeRemove
|
||||||
|
}
|
||||||
|
|
||||||
|
trimmed, err := triminternal.ApplyArtifact(artifact, triminternal.Options{
|
||||||
|
Mode: mode,
|
||||||
|
Selector: selector,
|
||||||
|
AllowEmpty: cfg.AllowEmpty,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
outputSchema := artifact.Schema
|
||||||
|
if cfg.OutputSchema != "" {
|
||||||
|
outputSchema = cfg.OutputSchema
|
||||||
|
}
|
||||||
|
|
||||||
|
outputArtifact, err := triminternal.ConvertArtifact(trimmed.Artifact, outputSchema)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := triminternal.ValidateArtifact(outputArtifact); err != nil {
|
||||||
|
return fmt.Errorf("validate trimmed output: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := writeOutputJSON(cfg.OutputFile, outputArtifact.Value()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.ReportFile != "" {
|
||||||
|
rpt := report.Report{
|
||||||
|
Metadata: report.Metadata{
|
||||||
|
Application: outputArtifact.Application(),
|
||||||
|
Version: outputArtifact.Version(),
|
||||||
|
InputReader: "trim-artifact",
|
||||||
|
InputFiles: []string{cfg.InputFile},
|
||||||
|
OutputModules: []string{"json"},
|
||||||
|
},
|
||||||
|
Events: []report.Event{
|
||||||
|
report.Info("trim", "trim", fmt.Sprintf("mode=%s retained %d segment(s), removed %d segment(s)", cfg.Mode, len(trimmed.OldToNewID), len(trimmed.RemovedIDs))),
|
||||||
|
report.Info("trim", "validate-output", fmt.Sprintf("validated %d output segment(s)", outputArtifact.SegmentCount())),
|
||||||
|
report.Info("output", "json", "wrote transcript JSON"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := report.WriteJSON(cfg.ReportFile, rpt); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
flags := cmd.Flags()
|
||||||
|
flags.StringVar(&opts.InputFile, "input-file", "", "input seriatim transcript artifact JSON file")
|
||||||
|
flags.StringVar(&opts.OutputFile, "output-file", "", "output transcript JSON file")
|
||||||
|
flags.StringVar(&opts.ReportFile, "report-file", "", "optional report JSON file")
|
||||||
|
flags.StringVar(&opts.Keep, "keep", "", "segment ID selector to keep (for example: 1-10,15)")
|
||||||
|
flags.StringVar(&opts.Remove, "remove", "", "segment ID selector to remove (for example: 1-10,15)")
|
||||||
|
flags.StringVar(&opts.OutputSchema, "output-schema", "", "optional output JSON schema override: seriatim-minimal, seriatim-intermediate, or seriatim-full")
|
||||||
|
flags.BoolVar(&opts.AllowEmpty, "allow-empty", false, "allow trimming to an empty transcript")
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeOutputJSON(path string, value any) error {
|
||||||
|
file, err := os.Create(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
enc := json.NewEncoder(file)
|
||||||
|
enc.SetIndent("", " ")
|
||||||
|
return enc.Encode(value)
|
||||||
|
}
|
||||||
301
internal/cli/trim_test.go
Normal file
301
internal/cli/trim_test.go
Normal file
@@ -0,0 +1,301 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gitea.maximumdirect.net/eric/seriatim/internal/config"
|
||||||
|
"gitea.maximumdirect.net/eric/seriatim/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTrimKeepModeEndToEnd(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
input := writeTrimFullFixture(t, dir, "input.json")
|
||||||
|
output := filepath.Join(dir, "trimmed.json")
|
||||||
|
|
||||||
|
err := executeTrim(
|
||||||
|
"--input-file", input,
|
||||||
|
"--output-file", output,
|
||||||
|
"--keep", "2,4",
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("trim failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var transcript schema.Transcript
|
||||||
|
readJSON(t, output, &transcript)
|
||||||
|
if len(transcript.Segments) != 2 {
|
||||||
|
t.Fatalf("segment count = %d, want 2", len(transcript.Segments))
|
||||||
|
}
|
||||||
|
if transcript.Segments[0].Text != "two" || transcript.Segments[1].Text != "four" {
|
||||||
|
t.Fatalf("unexpected kept text order: %#v", transcript.Segments)
|
||||||
|
}
|
||||||
|
assertSequentialIDs(t, []int{transcript.Segments[0].ID, transcript.Segments[1].ID})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTrimRemoveModeEndToEnd(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
input := writeTrimFullFixture(t, dir, "input.json")
|
||||||
|
output := filepath.Join(dir, "trimmed.json")
|
||||||
|
|
||||||
|
err := executeTrim(
|
||||||
|
"--input-file", input,
|
||||||
|
"--output-file", output,
|
||||||
|
"--remove", "2,4",
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("trim failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var transcript schema.Transcript
|
||||||
|
readJSON(t, output, &transcript)
|
||||||
|
if len(transcript.Segments) != 2 {
|
||||||
|
t.Fatalf("segment count = %d, want 2", len(transcript.Segments))
|
||||||
|
}
|
||||||
|
if transcript.Segments[0].Text != "one" || transcript.Segments[1].Text != "three" {
|
||||||
|
t.Fatalf("unexpected remaining text order: %#v", transcript.Segments)
|
||||||
|
}
|
||||||
|
assertSequentialIDs(t, []int{transcript.Segments[0].ID, transcript.Segments[1].ID})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTrimMutualExclusionFailure(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
input := writeTrimFullFixture(t, dir, "input.json")
|
||||||
|
output := filepath.Join(dir, "trimmed.json")
|
||||||
|
|
||||||
|
err := executeTrim(
|
||||||
|
"--input-file", input,
|
||||||
|
"--output-file", output,
|
||||||
|
"--keep", "1",
|
||||||
|
"--remove", "2",
|
||||||
|
)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected mutual exclusion error")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "mutually exclusive") {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTrimMissingSelectionFailure(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
input := writeTrimFullFixture(t, dir, "input.json")
|
||||||
|
output := filepath.Join(dir, "trimmed.json")
|
||||||
|
|
||||||
|
err := executeTrim(
|
||||||
|
"--input-file", input,
|
||||||
|
"--output-file", output,
|
||||||
|
)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected selection flag error")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "exactly one of --keep or --remove is required") {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTrimInvalidSelectedIDFailure(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
input := writeTrimFullFixture(t, dir, "input.json")
|
||||||
|
output := filepath.Join(dir, "trimmed.json")
|
||||||
|
|
||||||
|
err := executeTrim(
|
||||||
|
"--input-file", input,
|
||||||
|
"--output-file", output,
|
||||||
|
"--keep", "99",
|
||||||
|
)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected missing selected ID error")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "does not exist") {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTrimOmittedOutputSchemaPreservesInputSchema(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
input := writeTrimMinimalFixture(t, dir, "input-minimal.json")
|
||||||
|
output := filepath.Join(dir, "trimmed.json")
|
||||||
|
|
||||||
|
err := executeTrim(
|
||||||
|
"--input-file", input,
|
||||||
|
"--output-file", output,
|
||||||
|
"--keep", "1",
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("trim failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var transcript schema.MinimalTranscript
|
||||||
|
readJSON(t, output, &transcript)
|
||||||
|
if transcript.Metadata.OutputSchema != config.OutputSchemaMinimal {
|
||||||
|
t.Fatalf("output_schema = %q, want %q", transcript.Metadata.OutputSchema, config.OutputSchemaMinimal)
|
||||||
|
}
|
||||||
|
if len(transcript.Segments) != 1 || transcript.Segments[0].ID != 1 {
|
||||||
|
t.Fatalf("unexpected minimal trim output: %#v", transcript.Segments)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTrimExplicitOutputSchemaChangesOutputSchema(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
input := writeTrimFullFixture(t, dir, "input.json")
|
||||||
|
output := filepath.Join(dir, "trimmed.json")
|
||||||
|
|
||||||
|
err := executeTrim(
|
||||||
|
"--input-file", input,
|
||||||
|
"--output-file", output,
|
||||||
|
"--keep", "1,3",
|
||||||
|
"--output-schema", config.OutputSchemaMinimal,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("trim failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var transcript schema.MinimalTranscript
|
||||||
|
readJSON(t, output, &transcript)
|
||||||
|
if transcript.Metadata.OutputSchema != config.OutputSchemaMinimal {
|
||||||
|
t.Fatalf("output_schema = %q, want %q", transcript.Metadata.OutputSchema, config.OutputSchemaMinimal)
|
||||||
|
}
|
||||||
|
if len(transcript.Segments) != 2 {
|
||||||
|
t.Fatalf("segment count = %d, want 2", len(transcript.Segments))
|
||||||
|
}
|
||||||
|
assertSequentialIDs(t, []int{transcript.Segments[0].ID, transcript.Segments[1].ID})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTrimAllowEmptyBehavior(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
input := writeTrimFullFixture(t, dir, "input.json")
|
||||||
|
output := filepath.Join(dir, "trimmed.json")
|
||||||
|
|
||||||
|
err := executeTrim(
|
||||||
|
"--input-file", input,
|
||||||
|
"--output-file", output,
|
||||||
|
"--remove", "1-4",
|
||||||
|
)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected empty-output error")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "empty transcript") {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = executeTrim(
|
||||||
|
"--input-file", input,
|
||||||
|
"--output-file", output,
|
||||||
|
"--remove", "1-4",
|
||||||
|
"--allow-empty",
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("trim with --allow-empty failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var transcript schema.Transcript
|
||||||
|
readJSON(t, output, &transcript)
|
||||||
|
if len(transcript.Segments) != 0 {
|
||||||
|
t.Fatalf("segment count = %d, want 0", len(transcript.Segments))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTrimRejectsNonSeriatimInputArtifacts(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
input := writeJSONFile(t, dir, "raw-whisperx.json", `{
|
||||||
|
"segments": [
|
||||||
|
{"start": 1, "end": 2, "text": "hello"}
|
||||||
|
]
|
||||||
|
}`)
|
||||||
|
output := filepath.Join(dir, "trimmed.json")
|
||||||
|
|
||||||
|
err := executeTrim(
|
||||||
|
"--input-file", input,
|
||||||
|
"--output-file", output,
|
||||||
|
"--keep", "1",
|
||||||
|
)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected invalid artifact error")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "not a valid seriatim output artifact") {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func executeTrim(args ...string) error {
|
||||||
|
cmd := NewRootCommand()
|
||||||
|
cmd.SetArgs(append([]string{"trim"}, args...))
|
||||||
|
return cmd.Execute()
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeTrimFullFixture(t *testing.T, dir string, name string) string {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
first := 10
|
||||||
|
second := 20
|
||||||
|
third := 30
|
||||||
|
fourth := 40
|
||||||
|
value := schema.Transcript{
|
||||||
|
Metadata: schema.Metadata{
|
||||||
|
Application: "seriatim",
|
||||||
|
Version: "v-test",
|
||||||
|
InputReader: "json-files",
|
||||||
|
InputFiles: []string{"a.json"},
|
||||||
|
PreprocessingModules: []string{"validate-raw"},
|
||||||
|
PostprocessingModules: []string{"assign-ids"},
|
||||||
|
OutputModules: []string{"json"},
|
||||||
|
},
|
||||||
|
Segments: []schema.Segment{
|
||||||
|
{ID: 1, Source: "a.json", SourceSegmentIndex: &first, SourceRef: "a.json#10", Speaker: "A", Start: 1, End: 2, Text: "one", OverlapGroupID: 9},
|
||||||
|
{ID: 2, Source: "a.json", SourceSegmentIndex: &second, SourceRef: "a.json#20", Speaker: "B", Start: 2, End: 3, Text: "two", OverlapGroupID: 9},
|
||||||
|
{ID: 3, Source: "a.json", SourceSegmentIndex: &third, SourceRef: "a.json#30", Speaker: "C", Start: 4, End: 5, Text: "three", OverlapGroupID: 10},
|
||||||
|
{ID: 4, Source: "a.json", SourceSegmentIndex: &fourth, SourceRef: "a.json#40", Speaker: "D", Start: 5, End: 6, Text: "four", OverlapGroupID: 10},
|
||||||
|
},
|
||||||
|
OverlapGroups: []schema.OverlapGroup{
|
||||||
|
{ID: 9, Start: 1, End: 3, Segments: []string{"a.json#10", "a.json#20"}, Speakers: []string{"A", "B"}, Class: "unknown", Resolution: "unresolved"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return writeTrimArtifactFile(t, dir, name, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeTrimMinimalFixture(t *testing.T, dir string, name string) string {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
value := schema.MinimalTranscript{
|
||||||
|
Metadata: schema.MinimalMetadata{
|
||||||
|
Application: "seriatim",
|
||||||
|
Version: "v-test",
|
||||||
|
OutputSchema: config.OutputSchemaMinimal,
|
||||||
|
},
|
||||||
|
Segments: []schema.MinimalSegment{
|
||||||
|
{ID: 1, Start: 1, End: 2, Speaker: "A", Text: "one"},
|
||||||
|
{ID: 2, Start: 2, End: 3, Speaker: "B", Text: "two"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return writeTrimArtifactFile(t, dir, name, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeTrimArtifactFile(t *testing.T, dir string, name string, value any) string {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
data, err := json.MarshalIndent(value, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("marshal fixture: %v", err)
|
||||||
|
}
|
||||||
|
path := filepath.Join(dir, name)
|
||||||
|
if err := os.WriteFile(path, append(data, '\n'), 0o600); err != nil {
|
||||||
|
t.Fatalf("write fixture: %v", err)
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertSequentialIDs(t *testing.T, ids []int) {
|
||||||
|
t.Helper()
|
||||||
|
for index, id := range ids {
|
||||||
|
want := index + 1
|
||||||
|
if id != want {
|
||||||
|
t.Fatalf("id at index %d = %d, want %d", index, id, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -47,6 +47,17 @@ type MergeOptions struct {
|
|||||||
CoalesceGap string
|
CoalesceGap string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TrimOptions captures raw CLI option values before validation.
|
||||||
|
type TrimOptions struct {
|
||||||
|
InputFile string
|
||||||
|
OutputFile string
|
||||||
|
ReportFile string
|
||||||
|
Keep string
|
||||||
|
Remove string
|
||||||
|
OutputSchema string
|
||||||
|
AllowEmpty bool
|
||||||
|
}
|
||||||
|
|
||||||
// Config is the validated runtime configuration for a merge invocation.
|
// Config is the validated runtime configuration for a merge invocation.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
InputFiles []string
|
InputFiles []string
|
||||||
@@ -66,6 +77,17 @@ type Config struct {
|
|||||||
FillerMaxDuration float64
|
FillerMaxDuration float64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TrimConfig is the validated runtime configuration for a trim invocation.
|
||||||
|
type TrimConfig struct {
|
||||||
|
InputFile string
|
||||||
|
OutputFile string
|
||||||
|
ReportFile string
|
||||||
|
Mode string
|
||||||
|
Selector string
|
||||||
|
OutputSchema string
|
||||||
|
AllowEmpty bool
|
||||||
|
}
|
||||||
|
|
||||||
// NewMergeConfig validates raw merge options and returns normalized config.
|
// NewMergeConfig validates raw merge options and returns normalized config.
|
||||||
func NewMergeConfig(opts MergeOptions) (Config, error) {
|
func NewMergeConfig(opts MergeOptions) (Config, error) {
|
||||||
cfg := Config{
|
cfg := Config{
|
||||||
@@ -168,6 +190,63 @@ func NewMergeConfig(opts MergeOptions) (Config, error) {
|
|||||||
return cfg, nil
|
return cfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewTrimConfig validates raw trim options and returns normalized config.
|
||||||
|
func NewTrimConfig(opts TrimOptions) (TrimConfig, error) {
|
||||||
|
inputFile := filepath.Clean(strings.TrimSpace(opts.InputFile))
|
||||||
|
if strings.TrimSpace(opts.InputFile) == "" {
|
||||||
|
return TrimConfig{}, errors.New("--input-file is required")
|
||||||
|
}
|
||||||
|
if err := requireFile(inputFile, "--input-file"); err != nil {
|
||||||
|
return TrimConfig{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
outputFile, err := normalizeOutputPath(opts.OutputFile, "--output-file")
|
||||||
|
if err != nil {
|
||||||
|
return TrimConfig{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
reportFile := ""
|
||||||
|
if strings.TrimSpace(opts.ReportFile) != "" {
|
||||||
|
reportFile, err = normalizeOutputPath(opts.ReportFile, "--report-file")
|
||||||
|
if err != nil {
|
||||||
|
return TrimConfig{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
keep := strings.TrimSpace(opts.Keep)
|
||||||
|
remove := strings.TrimSpace(opts.Remove)
|
||||||
|
if keep == "" && remove == "" {
|
||||||
|
return TrimConfig{}, errors.New("exactly one of --keep or --remove is required")
|
||||||
|
}
|
||||||
|
if keep != "" && remove != "" {
|
||||||
|
return TrimConfig{}, errors.New("--keep and --remove are mutually exclusive")
|
||||||
|
}
|
||||||
|
|
||||||
|
mode := "keep"
|
||||||
|
selector := keep
|
||||||
|
if remove != "" {
|
||||||
|
mode = "remove"
|
||||||
|
selector = remove
|
||||||
|
}
|
||||||
|
|
||||||
|
outputSchema := strings.TrimSpace(opts.OutputSchema)
|
||||||
|
if outputSchema != "" {
|
||||||
|
if err := validateOutputSchema(outputSchema); err != nil {
|
||||||
|
return TrimConfig{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return TrimConfig{
|
||||||
|
InputFile: inputFile,
|
||||||
|
OutputFile: outputFile,
|
||||||
|
ReportFile: reportFile,
|
||||||
|
Mode: mode,
|
||||||
|
Selector: selector,
|
||||||
|
OutputSchema: outputSchema,
|
||||||
|
AllowEmpty: opts.AllowEmpty,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func parseModuleList(value string) ([]string, error) {
|
func parseModuleList(value string) ([]string, error) {
|
||||||
value = strings.TrimSpace(value)
|
value = strings.TrimSpace(value)
|
||||||
if value == "" {
|
if value == "" {
|
||||||
|
|||||||
@@ -612,6 +612,105 @@ func TestCoalesceGapRejectsInvalidOverride(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNewTrimConfigRequiresInputAndOutput(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
input := writeTempFile(t, dir, "input.json")
|
||||||
|
output := filepath.Join(dir, "trimmed.json")
|
||||||
|
|
||||||
|
_, err := NewTrimConfig(TrimOptions{
|
||||||
|
OutputFile: output,
|
||||||
|
Keep: "1",
|
||||||
|
})
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "--input-file is required") {
|
||||||
|
t.Fatalf("expected input-file required error, got %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = NewTrimConfig(TrimOptions{
|
||||||
|
InputFile: input,
|
||||||
|
Keep: "1",
|
||||||
|
})
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "--output-file is required") {
|
||||||
|
t.Fatalf("expected output-file required error, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewTrimConfigRequiresExactlyOneSelectorFlag(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
input := writeTempFile(t, dir, "input.json")
|
||||||
|
output := filepath.Join(dir, "trimmed.json")
|
||||||
|
|
||||||
|
_, err := NewTrimConfig(TrimOptions{
|
||||||
|
InputFile: input,
|
||||||
|
OutputFile: output,
|
||||||
|
})
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "exactly one of --keep or --remove is required") {
|
||||||
|
t.Fatalf("expected missing selector error, got %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = NewTrimConfig(TrimOptions{
|
||||||
|
InputFile: input,
|
||||||
|
OutputFile: output,
|
||||||
|
Keep: "1",
|
||||||
|
Remove: "2",
|
||||||
|
})
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "mutually exclusive") {
|
||||||
|
t.Fatalf("expected mutually exclusive selector error, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewTrimConfigAcceptsOutputSchemaOverride(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
input := writeTempFile(t, dir, "input.json")
|
||||||
|
output := filepath.Join(dir, "trimmed.json")
|
||||||
|
reportPath := filepath.Join(dir, "report.json")
|
||||||
|
|
||||||
|
cfg, err := NewTrimConfig(TrimOptions{
|
||||||
|
InputFile: input,
|
||||||
|
OutputFile: output,
|
||||||
|
ReportFile: reportPath,
|
||||||
|
Remove: "3-5",
|
||||||
|
OutputSchema: OutputSchemaMinimal,
|
||||||
|
AllowEmpty: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("config failed: %v", err)
|
||||||
|
}
|
||||||
|
if cfg.Mode != "remove" {
|
||||||
|
t.Fatalf("mode = %q, want remove", cfg.Mode)
|
||||||
|
}
|
||||||
|
if cfg.Selector != "3-5" {
|
||||||
|
t.Fatalf("selector = %q, want 3-5", cfg.Selector)
|
||||||
|
}
|
||||||
|
if cfg.OutputSchema != OutputSchemaMinimal {
|
||||||
|
t.Fatalf("output schema = %q, want %q", cfg.OutputSchema, OutputSchemaMinimal)
|
||||||
|
}
|
||||||
|
if !cfg.AllowEmpty {
|
||||||
|
t.Fatal("allow empty should be true")
|
||||||
|
}
|
||||||
|
if cfg.ReportFile != reportPath {
|
||||||
|
t.Fatalf("report file = %q, want %q", cfg.ReportFile, reportPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewTrimConfigRejectsInvalidOutputSchemaOverride(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
input := writeTempFile(t, dir, "input.json")
|
||||||
|
output := filepath.Join(dir, "trimmed.json")
|
||||||
|
|
||||||
|
_, err := NewTrimConfig(TrimOptions{
|
||||||
|
InputFile: input,
|
||||||
|
OutputFile: output,
|
||||||
|
Keep: "1",
|
||||||
|
OutputSchema: "compact",
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected output schema validation error")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "--output-schema must be one of") {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func assertPositiveFloatEnvValidation(t *testing.T, envName string) {
|
func assertPositiveFloatEnvValidation(t *testing.T, envName string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
|
|||||||
@@ -95,6 +95,9 @@ func Apply(input schema.Transcript, opts Options) (Result, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
kept, groups := recomputeOverlapGroups(kept)
|
kept, groups := recomputeOverlapGroups(kept)
|
||||||
|
if groups == nil {
|
||||||
|
groups = make([]schema.OverlapGroup, 0)
|
||||||
|
}
|
||||||
|
|
||||||
out := copyTranscript(input)
|
out := copyTranscript(input)
|
||||||
out.Segments = kept
|
out.Segments = kept
|
||||||
@@ -278,7 +281,7 @@ func validateSelectedIDsExist(selected []int, idIndex map[int]int) error {
|
|||||||
|
|
||||||
func recomputeOverlapGroups(segments []schema.Segment) ([]schema.Segment, []schema.OverlapGroup) {
|
func recomputeOverlapGroups(segments []schema.Segment) ([]schema.Segment, []schema.OverlapGroup) {
|
||||||
if len(segments) == 0 {
|
if len(segments) == 0 {
|
||||||
return segments, nil
|
return segments, make([]schema.OverlapGroup, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
modelSegments := make([]model.Segment, len(segments))
|
modelSegments := make([]model.Segment, len(segments))
|
||||||
|
|||||||
387
internal/trim/artifact.go
Normal file
387
internal/trim/artifact.go
Normal file
@@ -0,0 +1,387 @@
|
|||||||
|
package trim
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"gitea.maximumdirect.net/eric/seriatim/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
SchemaMinimal = "seriatim-minimal"
|
||||||
|
SchemaIntermediate = "seriatim-intermediate"
|
||||||
|
SchemaFull = "seriatim-full"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Artifact stores a parsed seriatim output artifact of one supported schema.
|
||||||
|
type Artifact struct {
|
||||||
|
Schema string
|
||||||
|
Full *schema.Transcript
|
||||||
|
Intermediate *schema.IntermediateTranscript
|
||||||
|
Minimal *schema.MinimalTranscript
|
||||||
|
}
|
||||||
|
|
||||||
|
// ApplyArtifactResult contains trimmed artifact output and ID mapping metadata.
|
||||||
|
type ApplyArtifactResult struct {
|
||||||
|
Artifact Artifact
|
||||||
|
OldToNewID map[int]int
|
||||||
|
RemovedIDs []int
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseArtifactJSON parses and validates a serialized seriatim output artifact.
|
||||||
|
func ParseArtifactJSON(data []byte) (Artifact, error) {
|
||||||
|
var full schema.Transcript
|
||||||
|
if err := json.Unmarshal(data, &full); err == nil {
|
||||||
|
if err := schema.ValidateTranscript(full); err == nil {
|
||||||
|
return Artifact{
|
||||||
|
Schema: SchemaFull,
|
||||||
|
Full: &full,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var intermediate schema.IntermediateTranscript
|
||||||
|
if err := json.Unmarshal(data, &intermediate); err == nil {
|
||||||
|
if err := schema.ValidateIntermediateTranscript(intermediate); err == nil {
|
||||||
|
return Artifact{
|
||||||
|
Schema: SchemaIntermediate,
|
||||||
|
Intermediate: &intermediate,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var minimal schema.MinimalTranscript
|
||||||
|
if err := json.Unmarshal(data, &minimal); err == nil {
|
||||||
|
if err := schema.ValidateMinimalTranscript(minimal); err == nil {
|
||||||
|
return Artifact{
|
||||||
|
Schema: SchemaMinimal,
|
||||||
|
Minimal: &minimal,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Artifact{}, fmt.Errorf("input JSON is not a valid seriatim output artifact")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateArtifact validates an artifact against its declared schema.
|
||||||
|
func ValidateArtifact(artifact Artifact) error {
|
||||||
|
switch artifact.Schema {
|
||||||
|
case SchemaFull:
|
||||||
|
if artifact.Full == nil {
|
||||||
|
return fmt.Errorf("full artifact payload is missing")
|
||||||
|
}
|
||||||
|
return schema.ValidateTranscript(*artifact.Full)
|
||||||
|
case SchemaIntermediate:
|
||||||
|
if artifact.Intermediate == nil {
|
||||||
|
return fmt.Errorf("intermediate artifact payload is missing")
|
||||||
|
}
|
||||||
|
return schema.ValidateIntermediateTranscript(*artifact.Intermediate)
|
||||||
|
case SchemaMinimal:
|
||||||
|
if artifact.Minimal == nil {
|
||||||
|
return fmt.Errorf("minimal artifact payload is missing")
|
||||||
|
}
|
||||||
|
return schema.ValidateMinimalTranscript(*artifact.Minimal)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported artifact schema %q", artifact.Schema)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value returns the artifact value for JSON serialization.
|
||||||
|
func (artifact Artifact) Value() any {
|
||||||
|
switch artifact.Schema {
|
||||||
|
case SchemaFull:
|
||||||
|
if artifact.Full == nil {
|
||||||
|
return schema.Transcript{}
|
||||||
|
}
|
||||||
|
return *artifact.Full
|
||||||
|
case SchemaIntermediate:
|
||||||
|
if artifact.Intermediate == nil {
|
||||||
|
return schema.IntermediateTranscript{}
|
||||||
|
}
|
||||||
|
return *artifact.Intermediate
|
||||||
|
case SchemaMinimal:
|
||||||
|
if artifact.Minimal == nil {
|
||||||
|
return schema.MinimalTranscript{}
|
||||||
|
}
|
||||||
|
return *artifact.Minimal
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SegmentCount returns the number of segments in the artifact.
|
||||||
|
func (artifact Artifact) SegmentCount() int {
|
||||||
|
switch artifact.Schema {
|
||||||
|
case SchemaFull:
|
||||||
|
if artifact.Full == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return len(artifact.Full.Segments)
|
||||||
|
case SchemaIntermediate:
|
||||||
|
if artifact.Intermediate == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return len(artifact.Intermediate.Segments)
|
||||||
|
case SchemaMinimal:
|
||||||
|
if artifact.Minimal == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return len(artifact.Minimal.Segments)
|
||||||
|
default:
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Application returns artifact metadata application name.
|
||||||
|
func (artifact Artifact) Application() string {
|
||||||
|
switch artifact.Schema {
|
||||||
|
case SchemaFull:
|
||||||
|
if artifact.Full == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return artifact.Full.Metadata.Application
|
||||||
|
case SchemaIntermediate:
|
||||||
|
if artifact.Intermediate == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return artifact.Intermediate.Metadata.Application
|
||||||
|
case SchemaMinimal:
|
||||||
|
if artifact.Minimal == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return artifact.Minimal.Metadata.Application
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Version returns artifact metadata version.
|
||||||
|
func (artifact Artifact) Version() string {
|
||||||
|
switch artifact.Schema {
|
||||||
|
case SchemaFull:
|
||||||
|
if artifact.Full == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return artifact.Full.Metadata.Version
|
||||||
|
case SchemaIntermediate:
|
||||||
|
if artifact.Intermediate == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return artifact.Intermediate.Metadata.Version
|
||||||
|
case SchemaMinimal:
|
||||||
|
if artifact.Minimal == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return artifact.Minimal.Metadata.Version
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ApplyArtifact trims a parsed artifact while preserving its input schema.
|
||||||
|
func ApplyArtifact(input Artifact, opts Options) (ApplyArtifactResult, error) {
|
||||||
|
switch input.Schema {
|
||||||
|
case SchemaFull:
|
||||||
|
if input.Full == nil {
|
||||||
|
return ApplyArtifactResult{}, fmt.Errorf("full artifact payload is missing")
|
||||||
|
}
|
||||||
|
result, err := Apply(*input.Full, opts)
|
||||||
|
if err != nil {
|
||||||
|
return ApplyArtifactResult{}, err
|
||||||
|
}
|
||||||
|
out := result.Transcript
|
||||||
|
return ApplyArtifactResult{
|
||||||
|
Artifact: Artifact{
|
||||||
|
Schema: SchemaFull,
|
||||||
|
Full: &out,
|
||||||
|
},
|
||||||
|
OldToNewID: result.OldToNewID,
|
||||||
|
RemovedIDs: result.RemovedIDs,
|
||||||
|
}, nil
|
||||||
|
case SchemaIntermediate:
|
||||||
|
if input.Intermediate == nil {
|
||||||
|
return ApplyArtifactResult{}, fmt.Errorf("intermediate artifact payload is missing")
|
||||||
|
}
|
||||||
|
result, err := ApplyIntermediate(*input.Intermediate, opts)
|
||||||
|
if err != nil {
|
||||||
|
return ApplyArtifactResult{}, err
|
||||||
|
}
|
||||||
|
out := result.Transcript
|
||||||
|
return ApplyArtifactResult{
|
||||||
|
Artifact: Artifact{
|
||||||
|
Schema: SchemaIntermediate,
|
||||||
|
Intermediate: &out,
|
||||||
|
},
|
||||||
|
OldToNewID: result.OldToNewID,
|
||||||
|
RemovedIDs: result.RemovedIDs,
|
||||||
|
}, nil
|
||||||
|
case SchemaMinimal:
|
||||||
|
if input.Minimal == nil {
|
||||||
|
return ApplyArtifactResult{}, fmt.Errorf("minimal artifact payload is missing")
|
||||||
|
}
|
||||||
|
result, err := ApplyMinimal(*input.Minimal, opts)
|
||||||
|
if err != nil {
|
||||||
|
return ApplyArtifactResult{}, err
|
||||||
|
}
|
||||||
|
out := result.Transcript
|
||||||
|
return ApplyArtifactResult{
|
||||||
|
Artifact: Artifact{
|
||||||
|
Schema: SchemaMinimal,
|
||||||
|
Minimal: &out,
|
||||||
|
},
|
||||||
|
OldToNewID: result.OldToNewID,
|
||||||
|
RemovedIDs: result.RemovedIDs,
|
||||||
|
}, nil
|
||||||
|
default:
|
||||||
|
return ApplyArtifactResult{}, fmt.Errorf("unsupported artifact schema %q", input.Schema)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConvertArtifact converts a parsed artifact to another supported output schema.
|
||||||
|
func ConvertArtifact(input Artifact, outputSchema string) (Artifact, error) {
|
||||||
|
if outputSchema == "" || outputSchema == input.Schema {
|
||||||
|
return input, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch input.Schema {
|
||||||
|
case SchemaFull:
|
||||||
|
if input.Full == nil {
|
||||||
|
return Artifact{}, fmt.Errorf("full artifact payload is missing")
|
||||||
|
}
|
||||||
|
switch outputSchema {
|
||||||
|
case SchemaIntermediate:
|
||||||
|
out := intermediateFromFull(*input.Full)
|
||||||
|
return Artifact{
|
||||||
|
Schema: SchemaIntermediate,
|
||||||
|
Intermediate: &out,
|
||||||
|
}, nil
|
||||||
|
case SchemaMinimal:
|
||||||
|
out := minimalFromFull(*input.Full)
|
||||||
|
return Artifact{
|
||||||
|
Schema: SchemaMinimal,
|
||||||
|
Minimal: &out,
|
||||||
|
}, nil
|
||||||
|
default:
|
||||||
|
return Artifact{}, fmt.Errorf("unsupported output schema %q", outputSchema)
|
||||||
|
}
|
||||||
|
case SchemaIntermediate:
|
||||||
|
if input.Intermediate == nil {
|
||||||
|
return Artifact{}, fmt.Errorf("intermediate artifact payload is missing")
|
||||||
|
}
|
||||||
|
switch outputSchema {
|
||||||
|
case SchemaMinimal:
|
||||||
|
out := minimalFromIntermediate(*input.Intermediate)
|
||||||
|
return Artifact{
|
||||||
|
Schema: SchemaMinimal,
|
||||||
|
Minimal: &out,
|
||||||
|
}, nil
|
||||||
|
case SchemaFull:
|
||||||
|
return Artifact{}, fmt.Errorf("cannot emit %q from %q input artifact", SchemaFull, SchemaIntermediate)
|
||||||
|
default:
|
||||||
|
return Artifact{}, fmt.Errorf("unsupported output schema %q", outputSchema)
|
||||||
|
}
|
||||||
|
case SchemaMinimal:
|
||||||
|
if input.Minimal == nil {
|
||||||
|
return Artifact{}, fmt.Errorf("minimal artifact payload is missing")
|
||||||
|
}
|
||||||
|
switch outputSchema {
|
||||||
|
case SchemaIntermediate:
|
||||||
|
out := intermediateFromMinimal(*input.Minimal)
|
||||||
|
return Artifact{
|
||||||
|
Schema: SchemaIntermediate,
|
||||||
|
Intermediate: &out,
|
||||||
|
}, nil
|
||||||
|
case SchemaFull:
|
||||||
|
return Artifact{}, fmt.Errorf("cannot emit %q from %q input artifact", SchemaFull, SchemaMinimal)
|
||||||
|
default:
|
||||||
|
return Artifact{}, fmt.Errorf("unsupported output schema %q", outputSchema)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return Artifact{}, fmt.Errorf("unsupported input schema %q", input.Schema)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func intermediateFromFull(input schema.Transcript) schema.IntermediateTranscript {
|
||||||
|
segments := make([]schema.IntermediateSegment, len(input.Segments))
|
||||||
|
for index, segment := range input.Segments {
|
||||||
|
segments[index] = schema.IntermediateSegment{
|
||||||
|
ID: segment.ID,
|
||||||
|
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: input.Metadata.Application,
|
||||||
|
Version: input.Metadata.Version,
|
||||||
|
OutputSchema: SchemaIntermediate,
|
||||||
|
},
|
||||||
|
Segments: segments,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func minimalFromFull(input schema.Transcript) schema.MinimalTranscript {
|
||||||
|
segments := make([]schema.MinimalSegment, len(input.Segments))
|
||||||
|
for index, segment := range input.Segments {
|
||||||
|
segments[index] = schema.MinimalSegment{
|
||||||
|
ID: segment.ID,
|
||||||
|
Start: segment.Start,
|
||||||
|
End: segment.End,
|
||||||
|
Speaker: segment.Speaker,
|
||||||
|
Text: segment.Text,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return schema.MinimalTranscript{
|
||||||
|
Metadata: schema.MinimalMetadata{
|
||||||
|
Application: input.Metadata.Application,
|
||||||
|
Version: input.Metadata.Version,
|
||||||
|
OutputSchema: SchemaMinimal,
|
||||||
|
},
|
||||||
|
Segments: segments,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func minimalFromIntermediate(input schema.IntermediateTranscript) schema.MinimalTranscript {
|
||||||
|
segments := make([]schema.MinimalSegment, len(input.Segments))
|
||||||
|
for index, segment := range input.Segments {
|
||||||
|
segments[index] = schema.MinimalSegment{
|
||||||
|
ID: segment.ID,
|
||||||
|
Start: segment.Start,
|
||||||
|
End: segment.End,
|
||||||
|
Speaker: segment.Speaker,
|
||||||
|
Text: segment.Text,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return schema.MinimalTranscript{
|
||||||
|
Metadata: schema.MinimalMetadata{
|
||||||
|
Application: input.Metadata.Application,
|
||||||
|
Version: input.Metadata.Version,
|
||||||
|
OutputSchema: SchemaMinimal,
|
||||||
|
},
|
||||||
|
Segments: segments,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func intermediateFromMinimal(input schema.MinimalTranscript) schema.IntermediateTranscript {
|
||||||
|
segments := make([]schema.IntermediateSegment, len(input.Segments))
|
||||||
|
for index, segment := range input.Segments {
|
||||||
|
segments[index] = schema.IntermediateSegment{
|
||||||
|
ID: segment.ID,
|
||||||
|
Start: segment.Start,
|
||||||
|
End: segment.End,
|
||||||
|
Speaker: segment.Speaker,
|
||||||
|
Text: segment.Text,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return schema.IntermediateTranscript{
|
||||||
|
Metadata: schema.IntermediateMetadata{
|
||||||
|
Application: input.Metadata.Application,
|
||||||
|
Version: input.Metadata.Version,
|
||||||
|
OutputSchema: SchemaIntermediate,
|
||||||
|
},
|
||||||
|
Segments: segments,
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user