192 lines
6.1 KiB
Go
192 lines
6.1 KiB
Go
package cli
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"sort"
|
|
|
|
"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"
|
|
)
|
|
|
|
type trimAuditReport struct {
|
|
Operation string `json:"operation"`
|
|
InputFile string `json:"input_file"`
|
|
OutputFile string `json:"output_file"`
|
|
InputSchema string `json:"input_schema"`
|
|
OutputSchema string `json:"output_schema"`
|
|
Mode string `json:"mode"`
|
|
Selector string `json:"selector"`
|
|
SelectedIDs []int `json:"selected_ids"`
|
|
AllowEmpty bool `json:"allow_empty"`
|
|
InputSegmentCount int `json:"input_segment_count"`
|
|
RetainedSegmentCount int `json:"retained_segment_count"`
|
|
RemovedSegmentCount int `json:"removed_segment_count"`
|
|
RemovedInputIDs []int `json:"removed_input_ids"`
|
|
OldToNewIDMapping []trimIDMapping `json:"old_to_new_id_mapping"`
|
|
OverlapGroupsRecomputed bool `json:"overlap_groups_recomputed"`
|
|
}
|
|
|
|
type trimIDMapping struct {
|
|
OldID int `json:"old_id"`
|
|
NewID int `json:"new_id"`
|
|
}
|
|
|
|
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)
|
|
}
|
|
inputSegmentCount := artifact.SegmentCount()
|
|
inputSchema := artifact.Schema
|
|
|
|
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 != "" {
|
|
audit := trimAuditReport{
|
|
Operation: "trim",
|
|
InputFile: cfg.InputFile,
|
|
OutputFile: cfg.OutputFile,
|
|
InputSchema: inputSchema,
|
|
OutputSchema: outputArtifact.Schema,
|
|
Mode: cfg.Mode,
|
|
Selector: cfg.Selector,
|
|
SelectedIDs: selector.IDs(),
|
|
AllowEmpty: cfg.AllowEmpty,
|
|
InputSegmentCount: inputSegmentCount,
|
|
RetainedSegmentCount: len(trimmed.OldToNewID),
|
|
RemovedSegmentCount: len(trimmed.RemovedIDs),
|
|
RemovedInputIDs: append([]int(nil), trimmed.RemovedIDs...),
|
|
OldToNewIDMapping: orderedIDMapping(trimmed.OldToNewID),
|
|
OverlapGroupsRecomputed: trimmed.OverlapGroupsRecomputed,
|
|
}
|
|
auditJSON, err := json.Marshal(audit)
|
|
if err != nil {
|
|
return fmt.Errorf("marshal trim audit report: %w", err)
|
|
}
|
|
|
|
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("trimmed %d input segment(s) into %d output segment(s) with mode=%s", inputSegmentCount, outputArtifact.SegmentCount(), cfg.Mode)),
|
|
report.Info("trim", "trim-audit", string(auditJSON)),
|
|
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)
|
|
}
|
|
|
|
func orderedIDMapping(mapping map[int]int) []trimIDMapping {
|
|
keys := make([]int, 0, len(mapping))
|
|
for oldID := range mapping {
|
|
keys = append(keys, oldID)
|
|
}
|
|
sort.Ints(keys)
|
|
|
|
pairs := make([]trimIDMapping, 0, len(keys))
|
|
for _, oldID := range keys {
|
|
pairs = append(pairs, trimIDMapping{
|
|
OldID: oldID,
|
|
NewID: mapping[oldID],
|
|
})
|
|
}
|
|
return pairs
|
|
}
|