capability-scan.sh 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. #!/usr/bin/env bash
  2. # What can THIS ffmpeg build actually do — encoders, hwaccels, key filters.
  3. #
  4. # Listing an encoder is not the same as it working: hardware encoders (NVENC/QSV/
  5. # AMF/VideoToolbox/VAAPI) routinely appear in `-encoders` yet fail at runtime on
  6. # driver/device mismatches. Default mode therefore PROOF-ENCODES 10 frames of
  7. # lavfi testsrc2 through every present hw encoder; --quick skips that (list-only).
  8. #
  9. # Usage: capability-scan.sh [--quick] [--json] [-q]
  10. # Input: none (inspects the ffmpeg on PATH)
  11. # Output: stdout = TSV records (kind, name, listed, verified), or --json envelope
  12. # (schema claude-mods.ffmpeg-ops.capability/v1)
  13. # Stderr: headers, progress, errors
  14. # Exit: 0 ok, 2 usage, 5 ffmpeg missing (jq missing for --json),
  15. # 10 at least one LISTED hw encoder FAILED its proof-encode
  16. #
  17. # Examples:
  18. # capability-scan.sh
  19. # capability-scan.sh --quick
  20. # capability-scan.sh --json | jq '.data.encoders[] | select(.hw and .listed)'
  21. # capability-scan.sh --json | jq -r '.data.recommended_hw // "none"'
  22. set -uo pipefail
  23. EXIT_OK=0; EXIT_USAGE=2; EXIT_MISSING_DEP=5; EXIT_FAILED_VERIFY=10
  24. SCHEMA="claude-mods.ffmpeg-ops.capability/v1"
  25. QUICK=0; JSON=0; QUIET=0
  26. while [[ $# -gt 0 ]]; do
  27. case "$1" in
  28. --quick) QUICK=1 ;;
  29. --json) JSON=1 ;;
  30. -q|--quiet) QUIET=1 ;;
  31. -h|--help) sed -n '2,23p' "$0" | sed 's/^# \{0,1\}//'; exit "$EXIT_OK" ;;
  32. *) echo "ERROR: unknown argument: $1 (try --help)" >&2; exit "$EXIT_USAGE" ;;
  33. esac
  34. shift
  35. done
  36. command -v ffmpeg >/dev/null 2>&1 || {
  37. [[ "$JSON" -eq 1 ]] && echo '{"error":{"code":"MISSING_DEPENDENCY","message":"ffmpeg not on PATH"}}'
  38. echo "ERROR: ffmpeg not found on PATH" >&2; exit "$EXIT_MISSING_DEP"; }
  39. HAS_JQ=0; command -v jq >/dev/null 2>&1 && HAS_JQ=1
  40. [[ "$JSON" -eq 1 && "$HAS_JQ" -eq 0 ]] && {
  41. echo '{"error":{"code":"MISSING_DEPENDENCY","message":"jq required for --json"}}'
  42. echo "ERROR: jq required for --json" >&2; exit "$EXIT_MISSING_DEP"; }
  43. emit() { [[ "$QUIET" -eq 1 ]] && return 0; printf '%s\n' "$1" >&2; }
  44. VERSION="$(ffmpeg -hide_banner -version 2>/dev/null | head -1)"
  45. ENCODERS_RAW="$(ffmpeg -hide_banner -encoders 2>/dev/null)"
  46. HWACCELS="$(ffmpeg -hide_banner -hwaccels 2>/dev/null | tail -n +2 | tr -d ' ' | grep -v '^$' || true)"
  47. FILTERS_RAW="$(ffmpeg -hide_banner -filters 2>/dev/null)"
  48. emit "== capability-scan: $VERSION"
  49. # Hardware encoders worth knowing about, in rough preference order per vendor.
  50. HW_ENCODERS=(h264_nvenc hevc_nvenc av1_nvenc
  51. h264_qsv hevc_qsv av1_qsv
  52. h264_amf hevc_amf av1_amf
  53. h264_videotoolbox hevc_videotoolbox
  54. h264_vaapi hevc_vaapi av1_vaapi)
  55. # Software encoders + filters the cookbook leans on.
  56. SW_ENCODERS=(libx264 libx265 libsvtav1 libaom-av1 libvpx-vp9 aac libopus libmp3lame ffv1)
  57. KEY_FILTERS=(scale crop pad overlay drawtext subtitles loudnorm silencedetect
  58. silenceremove lut3d curves eq zscale tonemap minterpolate vidstabdetect
  59. vidstabtransform bwdif hqdn3d nlmeans palettegen paletteuse libvmaf
  60. ssim psnr xstack showwaves showspectrum)
  61. # Flags-column width varies across ffmpeg majors (3 chars <=7.x, 2 in 8.x).
  62. listed_encoder() { grep -qE "^ [A-Z.]{6} +$1 " <<<"$ENCODERS_RAW"; }
  63. listed_filter() { grep -qE "^ +[A-Z.|]+ +$1 +" <<<"$FILTERS_RAW"; }
  64. proof_encode() { # $1 = encoder name; returns 0 verified, 1 failed
  65. local enc="$1" extra=()
  66. case "$enc" in
  67. *_vaapi) extra=(-vaapi_device /dev/dri/renderD128 -vf format=nv12,hwupload) ;;
  68. *_qsv) extra=(-vf format=nv12) ;;
  69. esac
  70. ffmpeg -v error -y -f lavfi -i testsrc2=duration=1:size=640x360:rate=30 \
  71. "${extra[@]+"${extra[@]}"}" -frames:v 10 -c:v "$enc" -f null - >/dev/null 2>&1
  72. }
  73. failed_verify=0
  74. ROWS=() # tsv rows for stdout
  75. JSON_ENC=() # jq-built objects
  76. RECOMMENDED=""
  77. for enc in "${HW_ENCODERS[@]}"; do
  78. listed=false verified=null
  79. if listed_encoder "$enc"; then
  80. listed=true
  81. if [[ "$QUICK" -eq 1 ]]; then
  82. verified=null
  83. emit " hw $enc listed (proof-encode skipped: --quick)"
  84. elif proof_encode "$enc"; then
  85. verified=true
  86. [[ -z "$RECOMMENDED" ]] && RECOMMENDED="$enc"
  87. emit " hw $enc VERIFIED"
  88. else
  89. verified=false; failed_verify=1
  90. emit " hw $enc LISTED BUT FAILED proof-encode (driver/device mismatch?)"
  91. fi
  92. fi
  93. ROWS+=("$(printf 'encoder\t%s\thw\t%s\t%s' "$enc" "$listed" "$verified")")
  94. [[ "$HAS_JQ" -eq 1 ]] && JSON_ENC+=("$(jq -cn --arg n "$enc" --argjson l "$listed" \
  95. --argjson v "$verified" '{name:$n, hw:true, listed:$l, verified:$v}')")
  96. done
  97. for enc in "${SW_ENCODERS[@]}"; do
  98. listed=false; listed_encoder "$enc" && listed=true
  99. ROWS+=("$(printf 'encoder\t%s\tsw\t%s\tnull' "$enc" "$listed")")
  100. [[ "$HAS_JQ" -eq 1 ]] && JSON_ENC+=("$(jq -cn --arg n "$enc" --argjson l "$listed" \
  101. '{name:$n, hw:false, listed:$l, verified:null}')")
  102. done
  103. JSON_FILT=()
  104. missing_filters=()
  105. for f in "${KEY_FILTERS[@]}"; do
  106. present=false; listed_filter "$f" && present=true
  107. [[ "$present" == false ]] && missing_filters+=("$f")
  108. ROWS+=("$(printf 'filter\t%s\t-\t%s\tnull' "$f" "$present")")
  109. [[ "$HAS_JQ" -eq 1 ]] && JSON_FILT+=("$(jq -cn --arg n "$f" --argjson p "$present" \
  110. '{name:$n, present:$p}')")
  111. done
  112. [[ ${#missing_filters[@]} -gt 0 ]] && \
  113. emit " note: filters not in this build: ${missing_filters[*]}"
  114. if [[ "$JSON" -eq 1 ]]; then
  115. printf '%s\n' "${JSON_ENC[@]}" | jq -s \
  116. --arg version "$VERSION" --arg schema "$SCHEMA" \
  117. --arg rec "$RECOMMENDED" --argjson quick "$([[ $QUICK -eq 1 ]] && echo true || echo false)" \
  118. --argjson hwaccels "$(printf '%s\n' $HWACCELS | jq -Rn '[inputs | select(length>0)]')" \
  119. --argjson filters "$(printf '%s\n' "${JSON_FILT[@]}" | jq -s '.')" \
  120. '{data:{version:$version, quick:$quick, hwaccels:$hwaccels, encoders:.,
  121. filters:$filters, recommended_hw:(if $rec=="" then null else $rec end)},
  122. meta:{schema:$schema}}'
  123. else
  124. printf '%s\n' "${ROWS[@]}"
  125. fi
  126. [[ "$failed_verify" -eq 1 ]] && exit "$EXIT_FAILED_VERIFY"
  127. exit "$EXIT_OK"