ffmpeg position aware codec selection (#2533)

* ffmpeg: position aware codec selection

* ffmpeg: added `ActionDecodableCodecs` and `ActionEncodableCodecs`

---------

Co-authored-by: rsteube <rsteube@users.noreply.github.com>
This commit is contained in:
Johannes Jöns 2024-09-21 22:33:40 +00:00 committed by GitHub
parent f1f048fb04
commit 14541ada24
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 175 additions and 99 deletions

View File

@ -272,103 +272,136 @@ func actionFlags() carapace.Action {
}
func actionFlagArguments(flag string) carapace.Action {
splitted := strings.Split(strings.TrimLeft(flag, "-"), ":")
switch splitted[0] {
case "ab":
return carapace.ActionValues("16", "32", "64", "128", "192", "256", "320")
case "acodec":
return carapace.Batch(
ffmpeg.ActionCodecs(ffmpeg.CodecOpts{Audio: true}),
ffmpeg.ActionEncoders(ffmpeg.EncoderOpts{Audio: true}),
).ToA()
case "af":
return carapace.ActionMultiParts(",", func(c carapace.Context) carapace.Action {
return carapace.ActionMultiParts("=", func(c carapace.Context) carapace.Action {
switch len(c.Parts) {
case 0:
return ffmpeg.ActionFilters().NoSpace()
default:
return carapace.ActionValues()
}
})
})
case "ar":
return carapace.ActionValues("22050", "44100", "48000")
case "b":
if len(splitted) > 1 {
switch splitted[1] {
case "a":
return carapace.ActionValues("16", "32", "64", "128", "192", "256", "320")
case "v":
return carapace.ActionValues() // video bitrate
return carapace.ActionCallback(func(c carapace.Context) carapace.Action {
splitted := strings.Split(strings.TrimLeft(flag, "-"), ":")
beforeInput := true
for _, arg := range c.Args {
if arg == "-i" {
beforeInput = false
break
}
}
return carapace.ActionValues() // TODO invalid flag (missing a/v))
case "c", "codec":
audio := true
subtitle := true
video := true
if len(splitted) > 1 {
audio = splitted[1] == "a"
subtitle = splitted[1] == "s"
video = splitted[1] == "v"
}
return carapace.Batch(
ffmpeg.ActionCodecs(ffmpeg.CodecOpts{Audio: audio, Subtitle: subtitle, Video: video}),
ffmpeg.ActionEncoders(ffmpeg.EncoderOpts{Audio: audio, Subtitle: subtitle, Video: video}),
).ToA()
case "f":
return ffmpeg.ActionFormats()
case "h", "?", "help":
return ffmpeg.ActionHelpTopics()
case "hwaccel":
return ffmpeg.ActionHardwareAccelerations()
case "i":
return carapace.ActionFiles()
case "loglevel":
return ffmpeg.ActionLogLevels()
case "scodec":
return carapace.Batch(
ffmpeg.ActionCodecs(ffmpeg.CodecOpts{Subtitle: true}),
ffmpeg.ActionEncoders(ffmpeg.EncoderOpts{Subtitle: true}),
).ToA()
case "sinks":
return carapace.ActionMultiParts(",", func(c carapace.Context) carapace.Action {
switch len(c.Parts) {
case 0:
return ffmpeg.ActionDevices(ffmpeg.DeviceOpts{Demuxing: true}).NoSpace()
default:
return carapace.ActionValues()
}
})
case "sources":
return carapace.ActionMultiParts(",", func(c carapace.Context) carapace.Action {
switch len(c.Parts) {
case 0:
return ffmpeg.ActionDevices(ffmpeg.DeviceOpts{Muxing: true}).NoSpace()
default:
return carapace.ActionValues()
}
})
case "vcodec":
return carapace.Batch(
ffmpeg.ActionCodecs(ffmpeg.CodecOpts{Video: true}),
ffmpeg.ActionEncoders(ffmpeg.EncoderOpts{Video: true}),
).ToA()
case "vf":
return carapace.ActionMultiParts(",", func(c carapace.Context) carapace.Action {
return carapace.ActionMultiParts("=", func(c carapace.Context) carapace.Action {
switch splitted[0] {
case "ab":
return carapace.ActionValues("16", "32", "64", "128", "192", "256", "320")
case "acodec":
if beforeInput {
return carapace.Batch(
ffmpeg.ActionDecodableCodecs(ffmpeg.CodecOpts{Audio: true}),
ffmpeg.ActionDecoders(ffmpeg.DecoderOpts{Audio: true}),
).ToA()
}
return carapace.Batch(
ffmpeg.ActionEncodableCodecs(ffmpeg.CodecOpts{Audio: true}),
ffmpeg.ActionEncoders(ffmpeg.EncoderOpts{Audio: true}),
).ToA()
case "af":
return carapace.ActionMultiParts(",", func(c carapace.Context) carapace.Action {
return carapace.ActionMultiParts("=", func(c carapace.Context) carapace.Action {
switch len(c.Parts) {
case 0:
return ffmpeg.ActionFilters().NoSpace()
default:
return carapace.ActionValues()
}
})
})
case "ar":
return carapace.ActionValues("22050", "44100", "48000")
case "b":
if len(splitted) > 1 {
switch splitted[1] {
case "a":
return carapace.ActionValues("16", "32", "64", "128", "192", "256", "320")
case "v":
return carapace.ActionValues() // video bitrate
}
}
return carapace.ActionValues() // TODO invalid flag (missing a/v))
case "c", "codec":
audio := true
subtitle := true
video := true
if len(splitted) > 1 {
audio = splitted[1] == "a"
subtitle = splitted[1] == "s"
video = splitted[1] == "v"
}
if beforeInput {
return carapace.Batch(
ffmpeg.ActionDecodableCodecs(ffmpeg.CodecOpts{Audio: audio, Subtitle: subtitle, Video: video}),
ffmpeg.ActionDecoders(ffmpeg.DecoderOpts{Audio: audio, Subtitle: subtitle, Video: video}),
).ToA()
}
return carapace.Batch(
ffmpeg.ActionEncodableCodecs(ffmpeg.CodecOpts{Audio: audio, Subtitle: subtitle, Video: video}),
ffmpeg.ActionEncoders(ffmpeg.EncoderOpts{Audio: audio, Subtitle: subtitle, Video: video}),
).ToA()
case "f":
return ffmpeg.ActionFormats()
case "h", "?", "help":
return ffmpeg.ActionHelpTopics()
case "hwaccel":
return ffmpeg.ActionHardwareAccelerations()
case "i":
return carapace.ActionFiles()
case "loglevel":
return ffmpeg.ActionLogLevels()
case "scodec":
if beforeInput {
return carapace.Batch(
ffmpeg.ActionDecodableCodecs(ffmpeg.CodecOpts{Subtitle: true}),
ffmpeg.ActionDecoders(ffmpeg.DecoderOpts{Subtitle: true}),
).ToA()
}
return carapace.Batch(
ffmpeg.ActionEncodableCodecs(ffmpeg.CodecOpts{Subtitle: true}),
ffmpeg.ActionEncoders(ffmpeg.EncoderOpts{Subtitle: true}),
).ToA()
case "sinks":
return carapace.ActionMultiParts(",", func(c carapace.Context) carapace.Action {
switch len(c.Parts) {
case 0:
return ffmpeg.ActionFilters().NoSpace()
return ffmpeg.ActionDevices(ffmpeg.DeviceOpts{Demuxing: true}).NoSpace()
default:
return carapace.ActionValues()
}
})
})
default:
//return carapace.ActionValues() // TODO
return carapace.ActionFiles() // default file completion for now (positional)
}
case "sources":
return carapace.ActionMultiParts(",", func(c carapace.Context) carapace.Action {
switch len(c.Parts) {
case 0:
return ffmpeg.ActionDevices(ffmpeg.DeviceOpts{Muxing: true}).NoSpace()
default:
return carapace.ActionValues()
}
})
case "vcodec":
if beforeInput {
return carapace.Batch(
ffmpeg.ActionDecodableCodecs(ffmpeg.CodecOpts{Video: true}),
ffmpeg.ActionDecoders(ffmpeg.DecoderOpts{Video: true}),
).ToA()
}
return carapace.Batch(
ffmpeg.ActionEncodableCodecs(ffmpeg.CodecOpts{Video: true}),
ffmpeg.ActionEncoders(ffmpeg.EncoderOpts{Video: true}),
).ToA()
case "vf":
return carapace.ActionMultiParts(",", func(c carapace.Context) carapace.Action {
return carapace.ActionMultiParts("=", func(c carapace.Context) carapace.Action {
switch len(c.Parts) {
case 0:
return ffmpeg.ActionFilters().NoSpace()
default:
return carapace.ActionValues()
}
})
})
default:
//return carapace.ActionValues() // TODO
return carapace.ActionFiles() // default file completion for now (positional)
}
})
}

View File

@ -9,13 +9,17 @@ import (
)
type CodecOpts struct {
Audio bool
Subtitle bool
Video bool
Attachment bool
Audio bool
Data bool
Subtitle bool
Video bool
}
func (o CodecOpts) Default() CodecOpts {
o.Attachment = true
o.Audio = true
o.Data = true
o.Subtitle = true
o.Video = true
return o
@ -26,6 +30,30 @@ func (o CodecOpts) Default() CodecOpts {
// 4gv (4GV (Fourth Generation Vocoder))
// 4xm (4X Movie)
func ActionCodecs(opts CodecOpts) carapace.Action {
return actionCodecs(opts, nil)
}
// ActionEncodableCodecs completes codecs with encoding support
//
// amv (AMV Video)
// anull (Null audio codec)
func ActionEncodableCodecs(opts CodecOpts) carapace.Action {
return actionCodecs(opts, func(s string) bool {
return s[1] != 'E'
})
}
// ActionDecodableCodecs completes codecs with decoding support
//
// avrn (Avid AVI Codec)
// avrp (Avid 1:1 10-bit RGB Packer)
func ActionDecodableCodecs(opts CodecOpts) carapace.Action {
return actionCodecs(opts, func(s string) bool {
return s[0] != 'D'
})
}
func actionCodecs(opts CodecOpts, filter func(s string) bool) carapace.Action {
return carapace.ActionExecCommand("ffmpeg", "-hide_banner", "-codecs")(func(output []byte) carapace.Action {
_, content, ok := strings.Cut(string(output), " -------")
if !ok {
@ -33,28 +61,43 @@ func ActionCodecs(opts CodecOpts) carapace.Action {
}
lines := strings.Split(content, "\n")
r := regexp.MustCompile(`^ .{2}(?P<type>.).{3} (?P<codec>[^ ]+) +(?P<description>.*)$`)
r := regexp.MustCompile(`^ (?P<decoding>.)(?P<encoding>.)(?P<type>.).{3} (?P<codec>[^ ]+) +(?P<description>.*)$`)
vals := make([]string, 0)
for _, line := range lines[10 : len(lines)-1] {
if matches := r.FindStringSubmatch(line); matches != nil {
switch matches[1] {
if filter != nil && filter(line[1:7]) {
continue
}
switch matches[3] {
case "A":
if opts.Audio {
vals = append(vals, matches[2], matches[3], style.Yellow)
vals = append(vals, matches[4], matches[5], style.Yellow)
}
case "D":
if opts.Data {
vals = append(vals, matches[4], matches[5], style.Cyan)
}
case "S":
if opts.Subtitle {
vals = append(vals, matches[2], matches[3], style.Magenta)
vals = append(vals, matches[4], matches[5], style.Magenta)
}
case "T":
if opts.Attachment {
vals = append(vals, matches[4], matches[5], style.Green)
}
case "V":
if opts.Video {
vals = append(vals, matches[2], matches[3], style.Blue)
vals = append(vals, matches[4], matches[5], style.Blue)
}
}
}
}
vals = append(vals, "copy", "copy the codec of the input", style.Default)
if filter == nil || !filter("D ") {
vals = append(vals, "copy", "copy the codec of the input", style.Default)
}
return carapace.ActionStyledValuesDescribed(vals...)
}).Tag("codecs")
}