Skip to content

Commit

Permalink
fixes and improvements to FFmpeg autocompletion (#2472)
Browse files Browse the repository at this point in the history
* ffmpeg fix format completion

* ffmpeg filter devices for the correct type

* ffmpeg filter codecs by type and add missing options

* ffmpeg: use opts pattern for actions

---------

Co-authored-by: rsteube <rsteube@users.noreply.github.com>
  • Loading branch information
jopejoe1 and rsteube authored Aug 14, 2024
1 parent e1d5cc9 commit 5b4c1a6
Show file tree
Hide file tree
Showing 12 changed files with 189 additions and 81 deletions.
43 changes: 29 additions & 14 deletions completers/ffmpeg_completer/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ func actionFlags() carapace.Action {
"-acodec", "force audio codec",
"-af", "set audio filters",
"-ar", "set audio sampling rate",
"-c", "codec name",
"-codec", "codec name",
"-f", "force format",
"-h", "show help",
"-i", "input file",
Expand All @@ -58,15 +60,14 @@ func actionFlags() carapace.Action {
"-help", "show help",
"-loglevel", "set logging level",
"--help", "show help",
"-scodec", "force subtitle codec",
"-sinks", "list sinks of the output device",
"-sources", "list sources of the input device",
"-vcodec", "force video codec",
"-vf", "set video filters",
).Style(style.Carapace.FlagArg),
carapace.ActionValuesDescribed(
"-b", "bitrate",
"-c", "codec name",
"-codec", "codec name",
).Style(style.Carapace.FlagOptArg),
carapace.ActionValuesDescribed(
"-L", "show license",
Expand Down Expand Up @@ -244,7 +245,6 @@ func actionFlags() carapace.Action {
"-apre", "set the audio options to the indicated preset",
"-s", "set frame size",
"-sn", "disable subtitle",
"-scodec", "force subtitle codec",
"-stag", "force subtitle tag/fourcc",
"-fix_sub_duration", "fix subtitles duration",
"-canvas_size", "set canvas size",
Expand All @@ -262,6 +262,7 @@ func actionFlags() carapace.Action {
return carapace.ActionValuesDescribed(
"a", "audio",
"v", "video",
"s", "subtitle",
)
default:
return carapace.ActionValues()
Expand All @@ -276,7 +277,10 @@ func actionFlagArguments(flag string) carapace.Action {
case "ab":
return carapace.ActionValues("16", "32", "64", "128", "192", "256", "320")
case "acodec":
return ffmpeg.ActionCodecs() // TODO only audio
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 {
Expand All @@ -301,15 +305,18 @@ func actionFlagArguments(flag string) carapace.Action {
}
return carapace.ActionValues() // TODO invalid flag (missing a/v))
case "c", "codec":
audio := true
subtitle := true
video := true
if len(splitted) > 1 {
switch splitted[1] {
case "a":
return ffmpeg.ActionCodecs() // TODO audio codecs
case "v":
return ffmpeg.ActionCodecs() // TODO video codecs
}
audio = splitted[1] == "a"
subtitle = splitted[1] == "s"
video = splitted[1] == "v"
}
return carapace.ActionValues("copy")
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":
Expand All @@ -320,11 +327,16 @@ func actionFlagArguments(flag string) carapace.Action {
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().NoSpace()
return ffmpeg.ActionDevices(ffmpeg.DeviceOpts{Demuxing: true}).NoSpace()
default:
return carapace.ActionValues()
}
Expand All @@ -333,13 +345,16 @@ func actionFlagArguments(flag string) carapace.Action {
return carapace.ActionMultiParts(",", func(c carapace.Context) carapace.Action {
switch len(c.Parts) {
case 0:
return ffmpeg.ActionDevices().NoSpace()
return ffmpeg.ActionDevices(ffmpeg.DeviceOpts{Muxing: true}).NoSpace()
default:
return carapace.ActionValues()
}
})
case "vcodec":
return ffmpeg.ActionCodecs() // TODO only video
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 {
Expand Down
2 changes: 1 addition & 1 deletion pkg/actions/tools/ffmpeg/bitstreamFilter.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,5 @@ func ActionBitstreamFilters() carapace.Action {
}
}
return carapace.ActionValues(vals...)
})
}).Tag("bitstream filters")
}
48 changes: 40 additions & 8 deletions pkg/actions/tools/ffmpeg/codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,56 @@ import (
"strings"

"github.com/carapace-sh/carapace"
"github.com/carapace-sh/carapace/pkg/style"
)

type CodecOpts struct {
Audio bool
Subtitle bool
Video bool
}

func (o CodecOpts) Default() CodecOpts {
o.Audio = true
o.Subtitle = true
o.Video = true
return o
}

// ActionCodecs completes codecs
//
// 4gv (4GV (Fourth Generation Vocoder))
// 4xm (4X Movie)
func ActionCodecs() carapace.Action {
func ActionCodecs(opts CodecOpts) carapace.Action {
return carapace.ActionExecCommand("ffmpeg", "-hide_banner", "-codecs")(func(output []byte) carapace.Action {
lines := strings.Split(string(output), "\n")
r := regexp.MustCompile(`^.{7} (?P<codec>[^ ]+) +(?P<description>.*)$`)
_, content, ok := strings.Cut(string(output), " -------")
if !ok {
return carapace.ActionMessage("failed to parse codecs")
}

lines := strings.Split(content, "\n")
r := regexp.MustCompile(`^ .{2}(?P<type>.).{3} (?P<codec>[^ ]+) +(?P<description>.*)$`)

vals := make([]string, 0)
for _, line := range lines[10 : len(lines)-1] {
if r.MatchString(line) {
matches := r.FindStringSubmatch(line)
vals = append(vals, matches[1], matches[2])
if matches := r.FindStringSubmatch(line); matches != nil {
switch matches[1] {
case "A":
if opts.Audio {
vals = append(vals, matches[2], matches[3], style.Yellow)
}
case "S":
if opts.Subtitle {
vals = append(vals, matches[2], matches[3], style.Magenta)
}
case "V":
if opts.Video {
vals = append(vals, matches[2], matches[3], style.Blue)
}
}
}
}
return carapace.ActionValuesDescribed(vals...)
})
vals = append(vals, "copy", "copy the codec of the input", style.Default)
return carapace.ActionStyledValuesDescribed(vals...)
}).Tag("codecs")
}
42 changes: 30 additions & 12 deletions pkg/actions/tools/ffmpeg/decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,31 +8,49 @@ import (
"github.com/carapace-sh/carapace/pkg/style"
)

type DecoderOpts struct {
Audio bool
Subtitle bool
Video bool
}

func (o DecoderOpts) Default() DecoderOpts {
o.Audio = true
o.Subtitle = true
o.Video = true
return o
}

// ActionDecoders completes decoders
//
// 4xm (4X Movie)
// 8bps (QuickTime 8BPS video)
func ActionDecoders() carapace.Action {
func ActionDecoders(opts DecoderOpts) carapace.Action {
return carapace.ActionExecCommand("ffmpeg", "-hide_banner", "-decoders")(func(output []byte) carapace.Action {
lines := strings.Split(string(output), "\n")
_, content, ok := strings.Cut(string(output), " ------")
if !ok {
return carapace.ActionMessage("failed to parse encoders")
}

lines := strings.Split(content, "\n")
r := regexp.MustCompile(`^ (?P<type>.).{5} (?P<name>[^ ]+) +(?P<description>.*)$`)

found := false
vals := make([]string, 0)
for _, line := range lines {
if !found {
found = line == " ------"
continue
}

if matches := r.FindStringSubmatch(line); matches != nil {
switch matches[1] {
case "V":
vals = append(vals, matches[2], matches[3], style.Blue)
case "A":
vals = append(vals, matches[2], matches[3], style.Yellow)
if opts.Audio {
vals = append(vals, matches[2], matches[3], style.Yellow)
}
case "S":
vals = append(vals, matches[2], matches[3], style.Magenta)
if opts.Subtitle {
vals = append(vals, matches[2], matches[3], style.Magenta)
}
case "V":
if opts.Video {
vals = append(vals, matches[2], matches[3], style.Blue)
}
}
}
}
Expand Down
17 changes: 8 additions & 9 deletions pkg/actions/tools/ffmpeg/demuxer.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,18 @@ import (
// ac3 (raw AC-3)
func ActionDemuxers() carapace.Action {
return carapace.ActionExecCommand("ffmpeg", "-hide_banner", "-demuxers")(func(output []byte) carapace.Action {
lines := strings.Split(string(output), "\n")
r := regexp.MustCompile(`^ (?P<type>.).{1} (?P<name>[^ ]+) +(?P<description>.*)$`)
_, content, ok := strings.Cut(string(output), " ---")
if !ok {
return carapace.ActionMessage("failed to parse demuxers")
}

lines := strings.Split(content, "\n")
r := regexp.MustCompile(`^.{5}(?P<name>[^ ]+) +(?P<description>.*)$`)

found := false
vals := make([]string, 0)
for _, line := range lines {
if !found {
found = line == " --"
continue
}

if matches := r.FindStringSubmatch(line); matches != nil {
vals = append(vals, matches[2], matches[3])
vals = append(vals, matches[1], matches[2])
}
}
return carapace.ActionValuesDescribed(vals...)
Expand Down
43 changes: 34 additions & 9 deletions pkg/actions/tools/ffmpeg/device.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,49 @@ import (
"strings"

"github.com/carapace-sh/carapace"
"github.com/carapace-sh/carapace/pkg/style"
)

type DeviceOpts struct {
Demuxing bool
Muxing bool
}

func (o DeviceOpts) Default() DeviceOpts {
o.Demuxing = true
o.Muxing = true
return o
}

// ActionDevices completes devices
//
// alsa (ALSA audio output)
// fbdev (Linux framebuffer)
func ActionDevices() carapace.Action {
func ActionDevices(opts DeviceOpts) carapace.Action {
return carapace.ActionExecCommand("ffmpeg", "-hide_banner", "-devices")(func(output []byte) carapace.Action {
lines := strings.Split(string(output), "\n")
r := regexp.MustCompile(`^.{3} (?P<devices>[^ ]+) +(?P<description>.*)$`)
_, content, ok := strings.Cut(string(output), " ---")
if !ok {
return carapace.ActionMessage("failed to parse devices")
}

lines := strings.Split(content, "\n")
r := regexp.MustCompile(`^ (?P<demuxer>.)(?P<muxer>.) (?P<devices>[^ ]+) +(?P<description>.*)$`)

vals := make([]string, 0)
for _, line := range lines[4 : len(lines)-1] {
if r.MatchString(line) {
matches := r.FindStringSubmatch(line)
vals = append(vals, matches[1], matches[2])
for _, line := range lines[:len(lines)-1] {
if matches := r.FindStringSubmatch(line); matches != nil {
demuxer := matches[1] == "D" && opts.Demuxing
muxer := matches[2] == "E" && opts.Muxing
switch {
case demuxer && muxer:
vals = append(vals, matches[3], matches[4], style.Magenta)
case demuxer:
vals = append(vals, matches[3], matches[4], style.Blue)
case muxer:
vals = append(vals, matches[3], matches[4], style.Yellow)
}
}
}
return carapace.ActionValuesDescribed(vals...)
})
return carapace.ActionStyledValuesDescribed(vals...)
}).Tag("devices")
}
42 changes: 30 additions & 12 deletions pkg/actions/tools/ffmpeg/encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,31 +8,49 @@ import (
"github.com/carapace-sh/carapace/pkg/style"
)

type EncoderOpts struct {
Audio bool
Subtitle bool
Video bool
}

func (o EncoderOpts) Default() EncoderOpts {
o.Audio = true
o.Subtitle = true
o.Video = true
return o
}

// ActionEncoders completes encoders
//
// ac3 (ATSC A/52A (AC-3))
// ac3_fixed (ATSC A/52A (AC-3) (codec ac3))
func ActionEncoders() carapace.Action {
func ActionEncoders(opts EncoderOpts) carapace.Action {
return carapace.ActionExecCommand("ffmpeg", "-hide_banner", "-encoders")(func(output []byte) carapace.Action {
lines := strings.Split(string(output), "\n")
_, content, ok := strings.Cut(string(output), " ------")
if !ok {
return carapace.ActionMessage("failed to parse encoders")
}

lines := strings.Split(content, "\n")
r := regexp.MustCompile(`^ (?P<type>.).{5} (?P<name>[^ ]+) +(?P<description>.*)$`)

found := false
vals := make([]string, 0)
for _, line := range lines {
if !found {
found = line == " ------"
continue
}

if matches := r.FindStringSubmatch(line); matches != nil {
switch matches[1] {
case "V":
vals = append(vals, matches[2], matches[3], style.Blue)
case "A":
vals = append(vals, matches[2], matches[3], style.Yellow)
if opts.Audio {
vals = append(vals, matches[2], matches[3], style.Yellow)
}
case "S":
vals = append(vals, matches[2], matches[3], style.Magenta)
if opts.Subtitle {
vals = append(vals, matches[2], matches[3], style.Magenta)
}
case "V":
if opts.Video {
vals = append(vals, matches[2], matches[3], style.Blue)
}
}
}
}
Expand Down
Loading

0 comments on commit 5b4c1a6

Please sign in to comment.