diff --git a/actions.go b/actions.go new file mode 100644 index 0000000..f91b6c4 --- /dev/null +++ b/actions.go @@ -0,0 +1,171 @@ +package main + +import ( + "context" + "errors" + "fmt" + "os" + "strings" + + "github.com/urfave/cli/v2" +) + +// errorFromSlice converts the slice of strings into a single multi-line string +// and returns it, or returns nil if the error list is empty. +func errorFromSlice(errmsgs []string) error { + if len(errmsgs) != 0 { + return errors.New(strings.Join(errmsgs, "\n")) + } + return nil +} + +// checkTwoArgs shows the help message for the current context and return an +// error if we don't have exactly two arguments. +func checkTwoArgs(c *cli.Context) error { + if c.Args().Len() != 2 { + cli.ShowCommandHelp(c, c.Command.Name) + return errors.New("need input and output files") + } + return nil +} + +// checkMultiArgs shows the help message for the current context and return an +// error if we don't have at least one argument. +func checkMultiArgs(c *cli.Context) error { + if c.Args().Len() < 1 { + cli.ShowCommandHelp(c, c.Command.Name) + return errors.New("no files to process") + } + return nil +} + +func runnerFromContext(ctx context.Context) *runner { + ret, ok := ctx.Value(runnerKey).(*runner) + if !ok { + panic("internal error: Unable to retrieve runner from context.") + } + return ret +} + +func actionMerge(c *cli.Context) error { + return remux(c.Args().Slice(), c.String("output"), *runnerFromContext(c.Context), c.Bool("subs")) +} + +func actionOnly(c *cli.Context) error { + if err := checkTwoArgs(c); err != nil { + return err + } + + infile := c.Args().Get(0) + outfile := c.Args().Get(1) + + run := *runnerFromContext(c.Context) + + mkv := mustParseFile(infile) + tfi, err := extract(mkv, c.Int("track"), run) + defer os.Remove(tfi.fname) + if err != nil { + return fmt.Errorf("%s: %v", infile, err) + } + return submux(infile, outfile, true, run) +} + +func actionPrint(c *cli.Context) error { + if err := checkMultiArgs(c); err != nil { + return err + } + + var errmsgs []string + + for _, fname := range c.Args().Slice() { + output, err := format(c.String("format"), fname) + if err != nil { + errmsgs = append(errmsgs, fmt.Sprintf("%s: %v", fname, err)) + continue + } + fmt.Println(output) + } + return errorFromSlice(errmsgs) +} + +func actionRemux(c *cli.Context) error { + if err := checkTwoArgs(c); err != nil { + return err + } + + infile := c.Args().Get(0) + outfile := c.Args().Get(1) + run := *runnerFromContext(c.Context) + + return remux([]string{infile}, outfile, run, true) +} + +func actionRename(c *cli.Context) error { + if err := checkMultiArgs(c); err != nil { + return err + } + + var errmsgs []string + + for _, fname := range readable(c.Args().Slice()) { + err := rename(c.String("format"), fname, c.Bool("dry-run")) + if err != nil { + errmsgs = append(errmsgs, fmt.Sprintf("%s: %v", fname, err)) + } + } + return errorFromSlice(errmsgs) +} + +func actionSetDefault(c *cli.Context) error { + if err := checkMultiArgs(c); err != nil { + return err + } + + run := *runnerFromContext(c.Context) + + var errmsgs []string + + for _, fname := range readable(c.Args().Slice()) { + mkv := mustParseFile(fname) + err := setdefault(mkv, c.Int("track"), run) + if err != nil { + errmsgs = append(errmsgs, fmt.Sprintf("%s: %s", fname, err)) + } + } + return errorFromSlice(errmsgs) +} + +func actionSetDefaultByLang(c *cli.Context) error { + if err := checkMultiArgs(c); err != nil { + return err + } + + run := *runnerFromContext(c.Context) + + var errmsgs []string + + for _, fname := range readable(c.Args().Slice()) { + mkv := mustParseFile(fname) + track, err := trackByLanguage(mkv, c.StringSlice("lang"), c.StringSlice("ignore")) + if err != nil { + errmsgs = append(errmsgs, fmt.Sprintf("%s: %v", fname, err)) + continue + } + err = setdefault(mkv, track, run) + if err != nil { + errmsgs = append(errmsgs, fmt.Sprintf("%s: %v", fname, err)) + } + } + return errorFromSlice(errmsgs) +} + +func actionShow(c *cli.Context) error { + if err := checkMultiArgs(c); err != nil { + return err + } + for _, fname := range readable(c.Args().Slice()) { + mkv := mustParseFile(fname) + show(mkv, c.Bool("uid")) + } + return nil +} diff --git a/go.mod b/go.mod index e6e5781..ef57892 100644 --- a/go.mod +++ b/go.mod @@ -3,15 +3,13 @@ module github.com/marcopaganini/mkvtool go 1.16 require ( - github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect - github.com/alecthomas/units v0.0.0-20210927113745-59d0afb8317a // indirect - github.com/fatih/structs v1.1.0 // indirect + github.com/fatih/structs v1.1.0 github.com/go-openapi/strfmt v0.21.0 // indirect github.com/jedib0t/go-pretty v4.3.0+incompatible github.com/mattn/go-runewidth v0.0.13 // indirect github.com/middelink/go-parse-torrent-name v0.0.0-20190301154245-3ff4efacd4c4 github.com/stretchr/testify v1.7.0 // indirect + github.com/urfave/cli/v2 v2.8.1 golang.org/x/sys v0.0.0-20211106132015-ebca88c72f68 // indirect - golang.org/x/text v0.3.7 // indirect - gopkg.in/alecthomas/kingpin.v2 v2.2.6 + golang.org/x/text v0.3.7 ) diff --git a/go.sum b/go.sum index cf3f954..73337ef 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,9 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20210927113745-59d0afb8317a h1:E/8AP5dFtMhl5KPJz66Kt9G0n+7Sn41Fy1wv9/jHOrc= -github.com/alecthomas/units v0.0.0-20210927113745-59d0afb8317a/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= +github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef h1:46PFijGLmAjMPwCCCo7Jf0W6f9slllCkkv7vyc1yOSg= github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU= +github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -82,6 +81,8 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= @@ -91,15 +92,18 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/urfave/cli/v2 v2.8.1 h1:CGuYNZF9IKZY/rfBe3lJpccSoIY1ytfvmgQT90cNOl4= +github.com/urfave/cli/v2 v2.8.1/go.mod h1:Z41J9TPoffeoqP0Iza0YbAhGvymRdZAd2uPmZ5JxRdY= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= go.mongodb.org/mongo-driver v1.7.3 h1:G4l/eYY9VrQAK/AUgkV0koQKzQnyddnWxrd/Etf0jIs= go.mongodb.org/mongo-driver v1.7.3/go.mod h1:NqaYOwnXWr5Pm7AOpO5QFxKJ503nbMse/R79oO62zWg= @@ -132,14 +136,12 @@ golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c h1:grhR+C34yXImVGp7EzNk+DTIk+323eIUWOmEevy6bDo= gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index 4e62b8f..0e32d43 100644 --- a/main.go +++ b/main.go @@ -1,13 +1,19 @@ package main import ( + "context" "fmt" "log" "os" - "gopkg.in/alecthomas/kingpin.v2" + "github.com/urfave/cli/v2" ) +// Custom key for *runner in WithValue context. +type key int + +const runnerKey = key(iota) + // readable returns a slice of readable files in the input slice. func readable(fnames []string) []string { var ret []string @@ -24,62 +30,16 @@ func readable(fnames []string) []string { func main() { var ( - app = kingpin.New("mkvtool", "Easy operations on matroska containers.") - dryrun = app.Flag("dry-run", "Dry-run mode (only show commands).").Short('n').Bool() - - // merge - mergeCmd = app.Command("merge", "Merge input tracks and files (subtitle/video/audio) into an output file.") - mergeOutput = mergeCmd.Flag("output", "Output file.").Required().Short('o').String() - mergeSubs = mergeCmd.Flag("subs", "Copy subs from video file.").Default("true").Bool() - mergeInputs = mergeCmd.Arg("input-files", "Input files.").Required().Strings() - - // only - onlyCmd = app.Command("only", "Remove all subtitle tracks, except one.") - setOnlyTrack = onlyCmd.Arg("track", "Track number to keep.").Required().Int() - setOnlyInput = onlyCmd.Arg("input", "Matroska Input file.").Required().String() - setOnlyOutput = onlyCmd.Arg("output", "Matroska Output file.").Required().String() - - // print - printCmd = app.Command("print", "Parse input filename and print scene information using a printf style mask.") - printFormat = printCmd.Flag("format", "Formatting mask").Short('f').Default("%{title}.mkv").String() - printFiles = printCmd.Arg("input-files", "Matroska file(s).").Required().Strings() - - // remux - remuxCmd = app.Command("remux", "Remux input file into an output file.") - remuxCmdInput = remuxCmd.Arg("input-file", "Matroska Input file.").Required().String() - remuxCmdOutput = remuxCmd.Arg("output-file", "Matroska Output file.").Required().String() - - // rename - renameCmd = app.Command("rename", "Rename file based on scene information in filename.") - renameFormat = renameCmd.Flag("format", "Formatting mask").Short('f').Default("%{title}.%{container}").String() - renameFiles = renameCmd.Arg("input-files", "Matroska file(s).").Required().Strings() - - // setdefault - setDefaultCmd = app.Command("setdefault", "Set default subtitle tag on a track.") - setDefaultTrack = setDefaultCmd.Arg("track", "Track number to set as default.").Required().Int() - setDefaultFiles = setDefaultCmd.Arg("mkvfile", "Matroska file.").Required().Strings() - - // setdefaultbylanguage - setDefaultByLangCmd = app.Command("setdefaultbylang", "Set default subtitle track by language.") - setDefaultByLangList = setDefaultByLangCmd.Flag("lang", "Preferred languages (Use multiple times. Use 'default' for tracks with no language set.)").Required().Strings() - setDefaultByLangIgnore = setDefaultByLangCmd.Flag("ignore", "Ignore tracks with this string in the name (can be used multiple times.)").Strings() - setDefaultByLangFiles = setDefaultByLangCmd.Arg("mkvfiles", "Matroska file(s).").Required().Strings() - - // show - showCmd = app.Command("show", "Show Information about file(s).") - showUID = showCmd.Flag("uid", "Include track UIDs in the output.").Short('u').Bool() - showFiles = showCmd.Arg("input-files", "Matroska Input files.").Required().Strings() - - // version - versionCmd = app.Command("version", "Show version information.") - // Command runner. runCmd runCommand // Dry-run command runner (only print commands). fakeRunCmd fakeRunCommand - run runner + // This is overriden to fakeRunCmd when using dry-run. + run runner = runCmd + + dryrun bool ) if err := requirements(); err != nil { @@ -89,96 +49,184 @@ func main() { // Plain logs. log.SetFlags(0) - k := kingpin.MustParse(app.Parse(os.Args[1:])) - - // Run will resolve to a print-only version when dry-run is chosen. - run = runCmd - if *dryrun { - fmt.Println("Dry-run mode: Will not modify any files.") - run = fakeRunCmd + app := &cli.App{ + Name: "mkvtool", + Authors: []*cli.Author{ + { + Name: "Marco Paganini", + Email: "paganini@paganini.net", + }, + }, + Usage: "Easy operations on Matroska containers.", + Version: BuildVersion, + + // Global Flags + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "dry-run", + Aliases: []string{"n"}, + Value: false, + Usage: "Dry-run mode (only show commands)", + Destination: &dryrun, + }, + }, + Action: func(c *cli.Context) error { + cli.ShowCommandHelp(c, "") + return nil + }, + Before: func(c *cli.Context) error { + // Run will resolve to a print-only version when dry-run is chosen. + if dryrun { + fmt.Println("Dry-run mode: Will not modify any files.") + run = fakeRunCmd + c.Context = context.WithValue(c.Context, runnerKey, &run) + } + return nil + }, } - var errors []error - - switch k { - // Just print version number and exit. - case versionCmd.FullCommand(): - fmt.Printf("Build Version: %s\n", BuildVersion) + // Commands. + app.Commands = []*cli.Command{ + // merge + { + Name: "merge", + Usage: "Merge input tracks and files (A/V/S) into an output file", + ArgsUsage: "FILE(s)...", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "output", + Aliases: []string{"o"}, + Usage: "Output file", + Required: true, + }, + &cli.BoolFlag{ + Name: "subs", + Usage: "Copy subtitles from original video file", + Value: true, + }, + }, + Action: actionMerge, + }, - case mergeCmd.FullCommand(): - errors = append(errors, remux(*mergeInputs, *mergeOutput, run, *mergeSubs)) + // only + { + Name: "only", + Usage: "Remove all subtitle tracks, except one", + ArgsUsage: "input_file output_file", + Flags: []cli.Flag{ + &cli.IntFlag{ + Name: "track", + Aliases: []string{"t"}, + Usage: "Track number to keep", + Required: true, + }, + &cli.BoolFlag{ + Name: "subs", + Usage: "Copy subtitles from original video file", + Value: true, + }, + }, + Action: actionOnly, + }, - case onlyCmd.FullCommand(): - mkv := mustParseFile(*setOnlyInput) - tfi, err := extract(mkv, *setOnlyTrack, run) - if err != nil { - errors = append(errors, fmt.Errorf("%s: %v", *setOnlyInput, err)) - break - } - errors = append(errors, submux(*setOnlyInput, *setOnlyOutput, true, run, tfi)) - // Attempt to remove even on error. - _ = os.Remove(tfi.fname) - - case printCmd.FullCommand(): - for _, fname := range *printFiles { - output, err := format(*printFormat, fname) - if err != nil { - errors = append(errors, fmt.Errorf("%s: %v", fname, err)) - continue - } - fmt.Println(output) - } + // print + { + Name: "print", + Usage: "Parse input filename and print scene information using a printf style mask.", + ArgsUsage: "FILE(s)...", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "format", + Aliases: []string{"f"}, + Value: "%{title}.mkv", + Usage: "Formating mask", + }, + }, + Action: actionPrint, + }, - case remuxCmd.FullCommand(): - errors = append(errors, remux([]string{*remuxCmdInput}, *remuxCmdOutput, run, true)) + // remux + { + Name: "remux", + Usage: "Remux input file into an output file", + ArgsUsage: "input_file output_file", + Action: actionRemux, + }, - case renameCmd.FullCommand(): - for _, fname := range readable(*renameFiles) { - err := rename(*renameFormat, fname, *dryrun) - if err != nil { - errors = append(errors, fmt.Errorf("%s: %v", fname, err)) - } - } + // rename + { + Name: "rename", + Usage: "Rename file based on scene information in filename.", + ArgsUsage: "FILE(s)...", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "format", + Aliases: []string{"f"}, + Value: "%{title}.%{container}", + Usage: "Formating mask", + }, + }, + Action: actionRename, + }, - case setDefaultCmd.FullCommand(): - for _, fname := range readable(*setDefaultFiles) { - mkv := mustParseFile(fname) - err := setdefault(mkv, *setDefaultTrack, run) - if err != nil { - errors = append(errors, fmt.Errorf("%s: %s", fname, err)) - } - } + // setdefault + { + Name: "setdefault", + Usage: "Set the default subtitle tag on a track.", + ArgsUsage: "FILE(s)...", + Flags: []cli.Flag{ + &cli.IntFlag{ + Name: "track", + Aliases: []string{"t"}, + Usage: "Track Number", + Required: true, + }, + }, + Action: actionSetDefault, + }, - case setDefaultByLangCmd.FullCommand(): - for _, fname := range readable(*setDefaultByLangFiles) { - mkv := mustParseFile(fname) - track, err := trackByLanguage(mkv, *setDefaultByLangList, *setDefaultByLangIgnore) - if err != nil { - errors = append(errors, fmt.Errorf("%s: %s", fname, err)) - break - } - err = setdefault(mkv, track, run) - if err != nil { - errors = append(errors, fmt.Errorf("%s: %s", fname, err)) - } - } + // setdefaultbylanguage + { + Name: "setdefaultbylanguage", + Usage: "Set default subtitle track by language.", + ArgsUsage: "FILE(s)...", + Flags: []cli.Flag{ + &cli.StringSliceFlag{ + Name: "lang", + Aliases: []string{"l"}, + Usage: "Preferred languages (Use multiple times. Use 'default' for tracks with no language set.)", + Required: true, + }, + &cli.StringSliceFlag{ + Name: "ignore", + Aliases: []string{"i"}, + Usage: "Ignore tracks with this string in the name (can be used multiple times.)", + }, + }, + Action: actionSetDefaultByLang, + }, - case showCmd.FullCommand(): - for _, f := range readable(*showFiles) { - mkv := mustParseFile(f) - show(mkv, *showUID) - } + // show + { + Name: "show", + Usage: "Show information about files", + ArgsUsage: "FILE(s)...", + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "uid", + Aliases: []string{"u"}, + Usage: "Include track UIDs in the output", + }, + }, + Action: actionShow, + }, } - // Print any errors found during processing. Exit accordindly. - var failed bool - for _, err := range errors { - if err != nil { - log.Println(err) - failed = true - } - } - if failed { - log.Fatalln("Execution failed") + ctx := context.Background() + ctx = context.WithValue(ctx, runnerKey, &run) + err := app.RunContext(ctx, os.Args) + + if err != nil { + log.Fatalln("Execution failed:", err) } }