PR #23288 opened by add-uos-ffmpeg
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23288
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23288.patch

Add type-specific completion for codec options in ffmpeg, ffplay, and ffprobe.
Filter codec suggestions by media type (audio/video/subtitle/data).


>From a02d050a1380a48a175b6d4724082977a26f8e4e Mon Sep 17 00:00:00 2001
From: zhanghongyuan <[email protected]>
Date: Sun, 31 May 2026 23:56:53 +0800
Subject: [PATCH] fftools/bash-completion: add type-filtered codec completion

Add type-specific completion for codec options in ffmpeg, ffplay, and ffprobe.
Filter codec suggestions by media type (audio/video/subtitle/data).
---
 Makefile                        |  17 ++-
 configure                       |   5 +
 fftools/bash-completion/common  | 245 ++++++++++++++++++++++++++++++++
 fftools/bash-completion/ffmpeg  |  91 ++++++++++++
 fftools/bash-completion/ffplay  |  73 ++++++++++
 fftools/bash-completion/ffprobe |  71 +++++++++
 6 files changed, 500 insertions(+), 2 deletions(-)
 create mode 100644 fftools/bash-completion/common
 create mode 100644 fftools/bash-completion/ffmpeg
 create mode 100644 fftools/bash-completion/ffplay
 create mode 100644 fftools/bash-completion/ffprobe

diff --git a/Makefile b/Makefile
index f296e87ed4..2a800c1370 100644
--- a/Makefile
+++ b/Makefile
@@ -167,15 +167,28 @@ libavutil/ffversion.h .version:
 # force version.sh to run whenever version might have changed
 -include .version
 
-install: install-libs install-headers
+install: install-libs install-headers install-bash-completion
 
 install-libs: install-libs-yes
 
+install-bash-completion:
+       $(Q)mkdir -p "$(BASH_COMPLETION_DIR)"
+       $(INSTALL) -m 644 $(SRC_PATH)/fftools/bash-completion/ffmpeg 
"$(BASH_COMPLETION_DIR)/ffmpeg"
+       $(INSTALL) -m 644 $(SRC_PATH)/fftools/bash-completion/ffprobe 
"$(BASH_COMPLETION_DIR)/ffprobe"
+       $(INSTALL) -m 644 $(SRC_PATH)/fftools/bash-completion/ffplay 
"$(BASH_COMPLETION_DIR)/ffplay"
+       $(INSTALL) -m 644 $(SRC_PATH)/fftools/bash-completion/common 
"$(BASH_COMPLETION_DIR)/common"
+
 install-data: $(DATA_FILES)
        $(Q)mkdir -p "$(DATADIR)"
        $(INSTALL) -m 644 $(DATA_FILES) "$(DATADIR)"
 
-uninstall: uninstall-data uninstall-headers uninstall-libs uninstall-pkgconfig
+uninstall: uninstall-data uninstall-headers uninstall-libs uninstall-pkgconfig 
uninstall-bash-completion
+
+uninstall-bash-completion:
+       $(RM) "$(BASH_COMPLETION_DIR)/ffmpeg"
+       $(RM) "$(BASH_COMPLETION_DIR)/ffprobe"
+       $(RM) "$(BASH_COMPLETION_DIR)/common"
+       $(RM) "$(BASH_COMPLETION_DIR)/ffplay"
 
 uninstall-data:
        $(RM) -r "$(DATADIR)"
diff --git a/configure b/configure
index b0923e4789..677e6260f1 100755
--- a/configure
+++ b/configure
@@ -89,6 +89,7 @@ Standard options:
   --incdir=DIR             install includes in DIR [PREFIX/include]
   --mandir=DIR             install man page in DIR [PREFIX/share/man]
   --pkgconfigdir=DIR       install pkg-config files in DIR [LIBDIR/pkgconfig]
+  --bashcompletiondir=DIR  install bash completion files in DIR 
[PREFIX/share/bash-completion/completions]
   --enable-rpath           use rpath to allow installing libraries in paths
                            not part of the dynamic linker search path
                            use rpath when linking programs (USE WITH CARE)
@@ -2871,6 +2872,7 @@ CMDLINE_SELECT="
 "
 
 PATHS_LIST="
+    bashcompletiondir
     bindir
     datadir
     docdir
@@ -4408,6 +4410,7 @@ docdir_default='${prefix}/share/doc/ffmpeg'
 incdir_default='${prefix}/include'
 libdir_default='${prefix}/lib'
 mandir_default='${prefix}/share/man'
+bashcompletiondir_default='${prefix}/share/bash-completion/completions'
 
 # toolchain
 ar_default="ar"
@@ -6524,6 +6527,7 @@ test_cpp_condition stdlib.h "defined(__PIC__) || 
defined(__pic__) || defined(PIC
 set_default libdir
 : ${shlibdir_default:="$libdir"}
 : ${pkgconfigdir_default:="$libdir/pkgconfig"}
+set_default bashcompletiondir
 
 set_default $PATHS_LIST
 set_default nm
@@ -8622,6 +8626,7 @@ DATADIR=\$(DESTDIR)$datadir
 DOCDIR=\$(DESTDIR)$docdir
 MANDIR=\$(DESTDIR)$mandir
 PKGCONFIGDIR=\$(DESTDIR)$pkgconfigdir
+BASH_COMPLETION_DIR=\$(DESTDIR)$bashcompletiondir
 INSTALL_NAME_DIR=$install_name_dir
 SRC_PATH=$source_path
 SRC_LINK=$source_link
diff --git a/fftools/bash-completion/common b/fftools/bash-completion/common
new file mode 100644
index 0000000000..c083d44eff
--- /dev/null
+++ b/fftools/bash-completion/common
@@ -0,0 +1,245 @@
+# Bash version check for declare -g (Bash 4.2+)
+# Fallback for older Bash versions (e.g., CentOS 6 uses Bash 4.1)
+_bash_version_ok=1
+if [[ ${BASH_VERSION%%.*} -lt 4 ]]; then
+    _bash_version_ok=0
+elif [[ ${BASH_VERSION%%.*} -eq 4 ]]; then
+    # Extract minor version number
+    _bash_minor="${BASH_VERSION#*.}"
+    _bash_minor="${_bash_minor%%.*}"
+    if [[ $_bash_minor -lt 2 ]]; then
+        _bash_version_ok=0
+    fi
+fi
+
+if [[ $_bash_version_ok -eq 0 ]]; then
+    declare -A _FF_CACHE
+    declare _FF_CACHE_TIME=0
+    declare _FF_CACHE_TTL=3600
+else
+    declare -gA _FF_CACHE
+    declare -g _FF_CACHE_TIME=0
+    declare -g _FF_CACHE_TTL=3600
+fi
+unset _bash_version_ok _bash_minor
+
+_ff_cached_get() {
+    local key="$1"
+    local cmd="$2"
+    local current_time
+    current_time=$(date +%s)
+
+    if (( current_time - _FF_CACHE_TIME > _FF_CACHE_TTL )); then
+        _FF_CACHE=()
+        _FF_CACHE_TIME=$current_time
+    fi
+
+    if [[ -z "${_FF_CACHE[$key]}" ]]; then
+        _FF_CACHE[$key]=$(eval "$cmd" 2>/dev/null)
+    fi
+    echo "${_FF_CACHE[$key]}"
+}
+
+# Generic encoder filtering with multi-type support
+_ff_get_encoders_by_type() {
+    local types="$1"
+    local cache_key
+    local awk_pattern
+    local valid_types=""
+
+    if [[ -z "$types" ]]; then
+        cache_key="encoders_all"
+        awk_pattern='^ [AVSD]'
+    else
+        local i
+        for ((i=0; i<${#types}; i++)); do
+            local char="${types:$i:1}"
+            if [[ "$char" =~ [AVSD] ]]; then
+                valid_types+="$char"
+            fi
+        done
+
+        if [[ -z "$valid_types" ]]; then
+            return
+        fi
+
+        cache_key="encoders_${valid_types}"
+        local type_list=""
+        local i
+        for ((i=0; i<${#valid_types}; i++)); do
+            type_list+="${valid_types:$i:1}|"
+        done
+        type_list="${type_list%|}"
+        awk_pattern="^ (${type_list})"
+    fi
+
+    _ff_cached_get "$cache_key" "ffmpeg -hide_banner -encoders 2>/dev/null | 
awk '/$awk_pattern/ && !/=/{print \$2}' | grep -v '^\$' | sort -u"
+}
+
+_ff_get_audio_encoders() {
+    _ff_get_encoders_by_type "A"
+}
+
+_ff_get_video_encoders() {
+    _ff_get_encoders_by_type "V"
+}
+
+_ff_get_subtitle_encoders() {
+    _ff_get_encoders_by_type "S"
+}
+
+_ff_get_data_encoders() {
+    _ff_get_encoders_by_type "D"
+}
+
+_ff_get_av_encoders() {
+    _ff_get_encoders_by_type "AV"
+}
+
+_ff_get_encoders() {
+    _ff_cached_get "encoders" "ffmpeg -hide_banner -encoders 2>/dev/null | awk 
'/^ [AVS]/ && !/=/{print \$2}' | grep -v '^\$' | sort -u"
+}
+
+# Generic decoder filtering with multi-type support
+_ff_get_decoders_by_type() {
+    local types="$1"
+    local cache_key
+    local awk_pattern
+    local valid_types=""
+
+    if [[ -z "$types" ]]; then
+        cache_key="decoders_all"
+        awk_pattern='^ [AVSD]'
+    else
+        local i
+        for ((i=0; i<${#types}; i++)); do
+            local char="${types:$i:1}"
+            if [[ "$char" =~ [AVSD] ]]; then
+                valid_types+="$char"
+            fi
+        done
+
+        if [[ -z "$valid_types" ]]; then
+            return
+        fi
+
+        cache_key="decoders_${valid_types}"
+        local type_list=""
+        local i
+        for ((i=0; i<${#valid_types}; i++)); do
+            type_list+="${valid_types:$i:1}|"
+        done
+        type_list="${type_list%|}"
+        awk_pattern="^ (${type_list})"
+    fi
+
+    _ff_cached_get "$cache_key" "ffmpeg -hide_banner -decoders 2>/dev/null | 
awk '/$awk_pattern/ && !/=/{print \$2}' | grep -v '^\$' | sort -u"
+}
+
+_ff_get_audio_decoders() {
+    _ff_get_decoders_by_type "A"
+}
+
+_ff_get_video_decoders() {
+    _ff_get_decoders_by_type "V"
+}
+
+_ff_get_subtitle_decoders() {
+    _ff_get_decoders_by_type "S"
+}
+
+_ff_get_data_decoders() {
+    _ff_get_decoders_by_type "D"
+}
+
+_ff_get_av_decoders() {
+    _ff_get_decoders_by_type "AV"
+}
+
+_ff_get_decoders() {
+    _ff_cached_get "decoders" "ffmpeg -hide_banner -decoders 2>/dev/null | awk 
'/^ [AVS]/ && !/=/{print \$2}' | grep -v '^\$' | sort -u"
+}
+
+_ff_get_filters() {
+    _ff_cached_get "filters" "ffmpeg -hide_banner -filters 2>/dev/null | awk 
'/^ [^. ]/{print \$2}' | sort -u"
+}
+
+_ff_get_muxers() {
+    _ff_cached_get "muxers" "ffmpeg -hide_banner -muxers 2>/dev/null | awk '/^ 
 E/{print \$2}' | sort -u"
+}
+
+_ff_get_demuxers() {
+    _ff_cached_get "demuxers" "ffmpeg -hide_banner -demuxers 2>/dev/null | awk 
'/^ D /{print \$2}' | sort -u"
+}
+
+_ff_get_pix_fmts() {
+    _ff_cached_get "pix_fmts" "ffmpeg -hide_banner -pix_fmts 2>/dev/null | awk 
'/^[A-Z.]{5} [a-z]/{print \$2}' | sort -u"
+}
+
+_ff_get_sample_fmts() {
+    _ff_cached_get "sample_fmts" "ffmpeg -hide_banner -sample_fmts 2>/dev/null 
| awk 'NR>1{print \$1}' | sort -u"
+}
+
+_ff_get_protocols() {
+    _ff_cached_get "protocols" "ffmpeg -hide_banner -protocols 2>/dev/null | 
awk '/^  [a-z]/{print \$1}' | sort -u"
+}
+
+_ff_get_bsfs() {
+    _ff_cached_get "bsfs" "ffmpeg -hide_banner -bsfs 2>/dev/null | awk 
'/^[a-z]/{print \$1}' | sort -u"
+}
+
+_ff_get_codecs() {
+    _ff_cached_get "codecs" "ffmpeg -hide_banner -codecs 2>/dev/null | awk '/^ 
[AVS]/{print \$2}' | grep -v '^\$' | sort -u"
+}
+
+_ff_get_formats() {
+    _ff_cached_get "formats" "ffmpeg -hide_banner -formats 2>/dev/null | awk 
'/^ [DE ] [^=]/{print \$2}' | sort -u"
+}
+
+_ff_get_hwaccels() {
+    _ff_cached_get "hwaccels" "ffmpeg -hide_banner -hwaccels 2>/dev/null | awk 
'/^[a-z]/{print \$1}' | sort -u"
+}
+
+_ff_help_get_values() {
+    local cur="$1"
+
+    case "${cur}" in
+        decoder=*)
+            compopt -o nospace
+            COMPREPLY=( $(compgen -W "$(_ff_get_decoders)" -- 
"${cur#decoder=}") )
+            ;;
+        encoder=*)
+            compopt -o nospace
+            COMPREPLY=( $(compgen -W "$(_ff_get_encoders)" -- 
"${cur#encoder=}") )
+            ;;
+        demuxer=*)
+            compopt -o nospace
+            COMPREPLY=( $(compgen -W "$(_ff_get_demuxers)" -- 
"${cur#demuxer=}") )
+            ;;
+        muxer=*)
+            compopt -o nospace
+            COMPREPLY=( $(compgen -W "$(_ff_get_muxers)" -- "${cur#muxer=}") )
+            ;;
+        filter=*)
+            compopt -o nospace
+            COMPREPLY=( $(compgen -W "$(_ff_get_filters)" -- "${cur#filter=}") 
)
+            ;;
+        bsf=*)
+            compopt -o nospace
+            COMPREPLY=( $(compgen -W "$(_ff_get_bsfs)" -- "${cur#bsf=}") )
+            ;;
+        protocol=*)
+            compopt -o nospace
+            COMPREPLY=( $(compgen -W "$(_ff_get_protocols)" -- 
"${cur#protocol=}") )
+            ;;
+        *)
+            compopt -o nospace
+            COMPREPLY=( $(compgen -W "long full decoder= encoder= demuxer= 
muxer= filter= bsf= protocol=" -- "$cur") )
+            ;;
+    esac
+}
+
+_ff_complete_loglevel() {
+    local cur="$1"
+    COMPREPLY=( $(compgen -W "quiet panic fatal error warning info verbose 
debug trace" -- "$cur") )
+}
diff --git a/fftools/bash-completion/ffmpeg b/fftools/bash-completion/ffmpeg
new file mode 100644
index 0000000000..c10a607d6a
--- /dev/null
+++ b/fftools/bash-completion/ffmpeg
@@ -0,0 +1,91 @@
+if [[ -f "${BASH_SOURCE[0]%%/ffmpeg}/common" ]]; then
+    source "${BASH_SOURCE[0]%%/ffmpeg}/common"
+fi
+
+_ffmpeg_get_options() {
+    ffmpeg -hide_banner -h full 2>/dev/null | awk '/^-/{print $1}' | sort -u
+}
+
+_ffmpeg_completion() {
+    local cur prev words cword
+    _init_completion -s || return
+
+    case "${prev}" in
+        -h|-\?|-help|--help)
+            _ff_help_get_values "$cur"
+            return 0
+            ;;
+        -c:v)
+            COMPREPLY=( $(compgen -W "copy $(_ff_get_video_encoders) 
$(_ff_get_video_decoders)" -- "$cur") )
+            return 0
+            ;;
+        -c:a)
+            COMPREPLY=( $(compgen -W "copy $(_ff_get_audio_encoders) 
$(_ff_get_audio_decoders)" -- "$cur") )
+            return 0
+            ;;
+        -c:s)
+            COMPREPLY=( $(compgen -W "copy $(_ff_get_subtitle_encoders) 
$(_ff_get_subtitle_decoders)" -- "$cur") )
+            return 0
+            ;;
+        -c|-codec|-vcodec|-acodec|-scodec)
+            COMPREPLY=( $(compgen -W "$(_ff_get_encoders)" -- "$cur") )
+            return 0
+            ;;
+        -c:v:*|-c:a:*|-c:s:*)
+            COMPREPLY=( $(compgen -W "copy $(_ff_get_encoders) 
$(_ff_get_decoders)" -- "$cur") )
+            return 0
+            ;;
+        -c:av)
+            COMPREPLY=( $(compgen -W "copy $(_ff_get_av_encoders) 
$(_ff_get_av_decoders)" -- "$cur") )
+            return 0
+            ;;
+        -c:avs)
+            COMPREPLY=( $(compgen -W "copy $(_ff_get_encoders_by_type 'AVS') 
$(_ff_get_decoders_by_type 'AVS')" -- "$cur") )
+            return 0
+            ;;
+        -f|-format)
+            COMPREPLY=( $(compgen -W "$(_ff_get_formats)" -- "$cur") )
+            return 0
+            ;;
+        -vf|-af|-filter|-filter_complex)
+            COMPREPLY=( $(compgen -W "$(_ff_get_filters)" -- "$cur") )
+            return 0
+            ;;
+        -pix_fmt)
+            COMPREPLY=( $(compgen -W "$(_ff_get_pix_fmts)" -- "$cur") )
+            return 0
+            ;;
+        -sample_fmt)
+            COMPREPLY=( $(compgen -W "$(_ff_get_sample_fmts)" -- "$cur") )
+            return 0
+            ;;
+        -bsf|-vbsf|-absf)
+            COMPREPLY=( $(compgen -W "$(_ff_get_bsfs)" -- "$cur") )
+            return 0
+            ;;
+        -hwaccel)
+            COMPREPLY=( $(compgen -W "auto none $(_ff_get_hwaccels)" -- 
"$cur") )
+            return 0
+            ;;
+        -i)
+            _filedir
+            return 0
+            ;;
+        
-ss|-to|-t|-timestamp|-frames|-vframes|-aframes|-sframes|-r|-s|-b|-b:v|-b:a|-ab|-ar|-ac|-crf|-preset|-tune|-profile|-level|-qscale|-qmin|-qmax|-map|-map_metadata|-map_chapters|-metadata|-x265-params|-x264-params)
+            return 0
+            ;;
+        -loglevel|-v)
+            _ff_complete_loglevel "$cur"
+            return 0
+            ;;
+    esac
+
+    if [[ "$cur" == -* ]]; then
+        COMPREPLY=( $(compgen -W "$(_ffmpeg_get_options)" -- "$cur") )
+        return 0
+    fi
+
+    _filedir
+}
+
+complete -F _ffmpeg_completion ffmpeg
diff --git a/fftools/bash-completion/ffplay b/fftools/bash-completion/ffplay
new file mode 100644
index 0000000000..c345bf6b07
--- /dev/null
+++ b/fftools/bash-completion/ffplay
@@ -0,0 +1,73 @@
+if [[ -f "${BASH_SOURCE[0]%%/ffplay}/common" ]]; then
+    source "${BASH_SOURCE[0]%%/ffplay}/common"
+fi
+
+_ffplay_get_options() {
+    ffplay -hide_banner -h 2>/dev/null | awk '/^-/{print $1}' | sort -u
+}
+
+_ff_complete_showmode() {
+    local cur="$1"
+    COMPREPLY=( $(compgen -W "video waves rdft" -- "$cur") )
+}
+
+_ff_complete_sync() {
+    local cur="$1"
+    COMPREPLY=( $(compgen -W "audio video ext" -- "$cur") )
+}
+
+_ffplay_completion() {
+    local cur prev words cword
+    _init_completion -s || return
+
+    case "${prev}" in
+        -h|-\?|-help|--help)
+            _ff_help_get_values "$cur"
+            return 0
+            ;;
+        -acodec)
+            COMPREPLY=( $(compgen -W "$(_ff_get_audio_decoders)" -- "$cur") )
+            return 0
+            ;;
+        -vcodec)
+            COMPREPLY=( $(compgen -W "$(_ff_get_video_decoders)" -- "$cur") )
+            return 0
+            ;;
+        -scodec)
+            COMPREPLY=( $(compgen -W "$(_ff_get_subtitle_decoders)" -- "$cur") 
)
+            return 0
+            ;;
+        -i)
+            _filedir
+            return 0
+            ;;
+        -vf|-af|-filter|-filter_complex)
+            COMPREPLY=( $(compgen -W "$(_ff_get_filters)" -- "$cur") )
+            return 0
+            ;;
+        
-ss|-to|-t|-timestamp|-frames|-vframes|-aframes|-window_title|-x|-y|-width|-height|-volume|-mute|-loop|-framedrop|-reorder_queue_size|-rdftspeed)
+            return 0
+            ;;
+        -sync)
+            _ff_complete_sync "$cur"
+            return 0
+            ;;
+        -showmode)
+            _ff_complete_showmode "$cur"
+            return 0
+            ;;
+        -loglevel|-v)
+            _ff_complete_loglevel "$cur"
+            return 0
+            ;;
+    esac
+
+    if [[ "$cur" == -* ]]; then
+        COMPREPLY=( $(compgen -W "$(_ffplay_get_options)" -- "$cur") )
+        return 0
+    fi
+
+    _filedir
+}
+
+complete -F _ffplay_completion ffplay
diff --git a/fftools/bash-completion/ffprobe b/fftools/bash-completion/ffprobe
new file mode 100644
index 0000000000..89c31baf98
--- /dev/null
+++ b/fftools/bash-completion/ffprobe
@@ -0,0 +1,71 @@
+if [[ -f "${BASH_SOURCE[0]%%/ffprobe}/common" ]]; then
+    source "${BASH_SOURCE[0]%%/ffprobe}/common"
+fi
+
+_ffprobe_get_options() {
+    ffprobe -hide_banner -h 2>/dev/null | awk '/^-/{print $1}' | sort -u
+}
+
+_ffprobe_completion() {
+    local cur prev words cword
+    _init_completion -s || return
+
+    case "${prev}" in
+        -h|-\?|-help|--help)
+            _ff_help_get_values "$cur"
+            return 0
+            ;;
+        -c:a)
+            COMPREPLY=( $(compgen -W "$(_ff_get_audio_decoders)" -- "$cur") )
+            return 0
+            ;;
+        -c:v)
+            COMPREPLY=( $(compgen -W "$(_ff_get_video_decoders)" -- "$cur") )
+            return 0
+            ;;
+        -c:s)
+            COMPREPLY=( $(compgen -W "$(_ff_get_subtitle_decoders)" -- "$cur") 
)
+            return 0
+            ;;
+        -c:d)
+            COMPREPLY=( $(compgen -W "$(_ff_get_data_decoders)" -- "$cur") )
+            return 0
+            ;;
+        -output_format|-print_format|-of)
+            COMPREPLY=( $(compgen -W "default compact csv flat ini json xml" 
-- "$cur") )
+            return 0
+            ;;
+        -select_streams)
+            COMPREPLY=( $(compgen -W "v a s d t" -- "$cur") )
+            return 0
+            ;;
+        -f)
+            COMPREPLY=( $(compgen -W "$(_ff_get_formats)" -- "$cur") )
+            return 0
+            ;;
+        -i|-o)
+            _filedir
+            return 0
+            ;;
+        -loglevel|-v)
+            _ff_complete_loglevel "$cur"
+            return 0
+            ;;
+        -show_entries)
+            COMPREPLY=( $(compgen -W "format stream packet frame program 
chapter streams packets frames programs chapters" -- "$cur") )
+            return 0
+            ;;
+        -read_intervals)
+            return 0
+            ;;
+    esac
+
+    if [[ "$cur" == -* ]]; then
+        COMPREPLY=( $(compgen -W "$(_ffprobe_get_options)" -- "$cur") )
+        return 0
+    fi
+
+    _filedir
+}
+
+complete -F _ffprobe_completion ffprobe
-- 
2.52.0

_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to