Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fixes and improvements to FFmpeg autocompletion #2472

Merged
merged 4 commits into from
Aug 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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