mirror of
https://github.com/rsteube/carapace-bin.git
synced 2025-05-05 15:32:53 +00:00
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:
parent
f1f048fb04
commit
14541ada24
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user