audio_encoder.sh
· 5.0 KiB · Bash
Raw
#!/usr/bin/env bash
# Strict mode for robust error handling
set -euo pipefail
# Default variables
INPUT_FILE=""
OUTPUT_FILE=""
CODEC="opus"
ENCODER_TYPE="native"
STREAM_INDEX="0"
BITRATE_PER_CHANNEL=64 # kbps per channel for Opus
usage() {
cat <<EOF
Usage: $(basename "$0") -i <input_media> [OPTIONS]
A script to extract and encode a specific audio track from a movie file to FLAC or Opus.
Options:
-i <file> Input media file (video or audio)
-o <file> Output file name (optional; auto-generated if omitted)
-c <codec> Target codec: 'opus' or 'flac' (default: opus)
-e <type> Encoder type: 'native' (ffmpeg) or 'external' (flac/opusenc) (default: native)
-s <index> Audio stream index to select (default: 0, which is the first audio track)
-h Show this help message
Examples:
$(basename "$0") -i movie.mkv -c opus -e native
$(basename "$0") -i movie.mp4 -c flac -s 1 -o my_custom_audio.flac
EOF
exit 1
}
# Parse command line arguments
while getopts "i:o:c:e:s:h" opt; do
case "$opt" in
i) INPUT_FILE="$OPTARG" ;;
o) OUTPUT_FILE="$OPTARG" ;;
c) CODEC=$(echo "$OPTARG" | tr '[:upper:]' '[:lower:]') ;;
e) ENCODER_TYPE=$(echo "$OPTARG" | tr '[:upper:]' '[:lower:]') ;;
s) STREAM_INDEX="$OPTARG" ;;
h|*) usage ;;
esac
done
# Validate inputs
if [[ -z "$INPUT_FILE" || ! -f "$INPUT_FILE" ]]; then
echo "Error: Input file not found or not specified."
usage
fi
if [[ "$CODEC" != "opus" && "$CODEC" != "flac" ]]; then
echo "Error: Codec must be 'opus' or 'flac'."
exit 1
fi
if [[ "$ENCODER_TYPE" != "native" && "$ENCODER_TYPE" != "external" ]]; then
echo "Error: Encoder type must be 'native' or 'external'."
exit 1
fi
# Validate stream index is a number
if ! [[ "$STREAM_INDEX" =~ ^[0-9]+$ ]]; then
echo "Error: Stream index must be a non-negative integer."
exit 1
fi
# Check dependencies
command -v ffmpeg >/dev/null 2>&1 || { echo "Error: ffmpeg is required."; exit 1; }
command -v ffprobe >/dev/null 2>&1 || { echo "Error: ffprobe is required."; exit 1; }
if [[ "$ENCODER_TYPE" == "external" ]]; then
if [[ "$CODEC" == "flac" ]] && ! command -v flac >/dev/null 2>&1; then
echo "Error: 'flac' tool is required for external flac encoding."
exit 1
fi
if [[ "$CODEC" == "opus" ]] && ! command -v opusenc >/dev/null 2>&1; then
echo "Error: 'opusenc' tool is required for external opus encoding."
exit 1
fi
fi
# Determine number of audio channels of the selected audio stream
CHANNELS=$(ffprobe -v error -select_streams "a:${STREAM_INDEX}" -show_entries stream=channels -of default=noprint_wrappers=1:nokey=1 "$INPUT_FILE")
if [[ -z "$CHANNELS" ]]; then
echo "Error: Could not detect audio channels for stream index a:${STREAM_INDEX} in $INPUT_FILE. (Does that track exist?)"
exit 1
fi
echo "Detected $CHANNELS channel(s) in audio stream ${STREAM_INDEX}."
# Calculate Opus bitrate (ignored for lossless FLAC)
TOTAL_BITRATE=$(( CHANNELS * BITRATE_PER_CHANNEL ))
# Determine output filename if not provided by user
if [[ -z "$OUTPUT_FILE" ]]; then
OUTPUT_FILE="${INPUT_FILE%.*}_track${STREAM_INDEX}.${CODEC}"
fi
# Handle channel mapping for native Opus (required for >2 channels)
# Family 0 is for mono/stereo. Family 1 is for surround (3 to 8 channels).
MAPPING_FAMILY=0
if [[ "$CHANNELS" -gt 2 ]]; then
MAPPING_FAMILY=1
fi
# Set FFmpeg logging profiles
# Native: Hide banner, only show errors, but keep the progress bar (-stats)
FFMPEG_NATIVE_LOG="-hide_banner -loglevel error -stats"
# External: Completely silent so it doesn't garble the external tool's output
FFMPEG_EXT_LOG="-hide_banner -loglevel quiet"
echo "Starting encode: Target=$CODEC | Encoder=$ENCODER_TYPE | Stream=a:${STREAM_INDEX} | Output=$OUTPUT_FILE"
# Execute encoding based on user choices
if [[ "$CODEC" == "opus" ]]; then
echo "Target Bitrate: ${TOTAL_BITRATE}k"
if [[ "$ENCODER_TYPE" == "native" ]]; then
ffmpeg $FFMPEG_NATIVE_LOG -y -i "$INPUT_FILE" -map "0:a:${STREAM_INDEX}" \
-c:a libopus -b:a "${TOTAL_BITRATE}k" -vbr on -mapping_family "$MAPPING_FAMILY" \
"$OUTPUT_FILE"
elif [[ "$ENCODER_TYPE" == "external" ]]; then
# Pipe 24-bit PCM WAV to opusenc.
ffmpeg $FFMPEG_EXT_LOG -y -i "$INPUT_FILE" -map "0:a:${STREAM_INDEX}" -f wav -c:a pcm_s24le - | \
opusenc --bitrate "$TOTAL_BITRATE" - "$OUTPUT_FILE"
fi
elif [[ "$CODEC" == "flac" ]]; then
if [[ "$ENCODER_TYPE" == "native" ]]; then
# Native FLAC encoding
ffmpeg $FFMPEG_NATIVE_LOG -y -i "$INPUT_FILE" -map "0:a:${STREAM_INDEX}" \
-c:a flac -compression_level 8 \
"$OUTPUT_FILE"
elif [[ "$ENCODER_TYPE" == "external" ]]; then
# Pipe 24-bit PCM WAV to external FLAC encoder.
ffmpeg $FFMPEG_EXT_LOG -y -i "$INPUT_FILE" -map "0:a:${STREAM_INDEX}" -f wav -c:a pcm_s24le - | \
flac -8 - -o "$OUTPUT_FILE"
fi
fi
echo -e "\nEncoding complete: $OUTPUT_FILE"
| 1 | #!/usr/bin/env bash |
| 2 | # Strict mode for robust error handling |
| 3 | set -euo pipefail |
| 4 | |
| 5 | # Default variables |
| 6 | INPUT_FILE="" |
| 7 | OUTPUT_FILE="" |
| 8 | CODEC="opus" |
| 9 | ENCODER_TYPE="native" |
| 10 | STREAM_INDEX="0" |
| 11 | BITRATE_PER_CHANNEL=64 # kbps per channel for Opus |
| 12 | |
| 13 | usage() { |
| 14 | cat <<EOF |
| 15 | Usage: $(basename "$0") -i <input_media> [OPTIONS] |
| 16 | |
| 17 | A script to extract and encode a specific audio track from a movie file to FLAC or Opus. |
| 18 | |
| 19 | Options: |
| 20 | -i <file> Input media file (video or audio) |
| 21 | -o <file> Output file name (optional; auto-generated if omitted) |
| 22 | -c <codec> Target codec: 'opus' or 'flac' (default: opus) |
| 23 | -e <type> Encoder type: 'native' (ffmpeg) or 'external' (flac/opusenc) (default: native) |
| 24 | -s <index> Audio stream index to select (default: 0, which is the first audio track) |
| 25 | -h Show this help message |
| 26 | |
| 27 | Examples: |
| 28 | $(basename "$0") -i movie.mkv -c opus -e native |
| 29 | $(basename "$0") -i movie.mp4 -c flac -s 1 -o my_custom_audio.flac |
| 30 | EOF |
| 31 | exit 1 |
| 32 | } |
| 33 | |
| 34 | # Parse command line arguments |
| 35 | while getopts "i:o:c:e:s:h" opt; do |
| 36 | case "$opt" in |
| 37 | i) INPUT_FILE="$OPTARG" ;; |
| 38 | o) OUTPUT_FILE="$OPTARG" ;; |
| 39 | c) CODEC=$(echo "$OPTARG" | tr '[:upper:]' '[:lower:]') ;; |
| 40 | e) ENCODER_TYPE=$(echo "$OPTARG" | tr '[:upper:]' '[:lower:]') ;; |
| 41 | s) STREAM_INDEX="$OPTARG" ;; |
| 42 | h|*) usage ;; |
| 43 | esac |
| 44 | done |
| 45 | |
| 46 | # Validate inputs |
| 47 | if [[ -z "$INPUT_FILE" || ! -f "$INPUT_FILE" ]]; then |
| 48 | echo "Error: Input file not found or not specified." |
| 49 | usage |
| 50 | fi |
| 51 | |
| 52 | if [[ "$CODEC" != "opus" && "$CODEC" != "flac" ]]; then |
| 53 | echo "Error: Codec must be 'opus' or 'flac'." |
| 54 | exit 1 |
| 55 | fi |
| 56 | |
| 57 | if [[ "$ENCODER_TYPE" != "native" && "$ENCODER_TYPE" != "external" ]]; then |
| 58 | echo "Error: Encoder type must be 'native' or 'external'." |
| 59 | exit 1 |
| 60 | fi |
| 61 | |
| 62 | # Validate stream index is a number |
| 63 | if ! [[ "$STREAM_INDEX" =~ ^[0-9]+$ ]]; then |
| 64 | echo "Error: Stream index must be a non-negative integer." |
| 65 | exit 1 |
| 66 | fi |
| 67 | |
| 68 | # Check dependencies |
| 69 | command -v ffmpeg >/dev/null 2>&1 || { echo "Error: ffmpeg is required."; exit 1; } |
| 70 | command -v ffprobe >/dev/null 2>&1 || { echo "Error: ffprobe is required."; exit 1; } |
| 71 | |
| 72 | if [[ "$ENCODER_TYPE" == "external" ]]; then |
| 73 | if [[ "$CODEC" == "flac" ]] && ! command -v flac >/dev/null 2>&1; then |
| 74 | echo "Error: 'flac' tool is required for external flac encoding." |
| 75 | exit 1 |
| 76 | fi |
| 77 | if [[ "$CODEC" == "opus" ]] && ! command -v opusenc >/dev/null 2>&1; then |
| 78 | echo "Error: 'opusenc' tool is required for external opus encoding." |
| 79 | exit 1 |
| 80 | fi |
| 81 | fi |
| 82 | |
| 83 | # Determine number of audio channels of the selected audio stream |
| 84 | CHANNELS=$(ffprobe -v error -select_streams "a:${STREAM_INDEX}" -show_entries stream=channels -of default=noprint_wrappers=1:nokey=1 "$INPUT_FILE") |
| 85 | if [[ -z "$CHANNELS" ]]; then |
| 86 | echo "Error: Could not detect audio channels for stream index a:${STREAM_INDEX} in $INPUT_FILE. (Does that track exist?)" |
| 87 | exit 1 |
| 88 | fi |
| 89 | |
| 90 | echo "Detected $CHANNELS channel(s) in audio stream ${STREAM_INDEX}." |
| 91 | |
| 92 | # Calculate Opus bitrate (ignored for lossless FLAC) |
| 93 | TOTAL_BITRATE=$(( CHANNELS * BITRATE_PER_CHANNEL )) |
| 94 | |
| 95 | # Determine output filename if not provided by user |
| 96 | if [[ -z "$OUTPUT_FILE" ]]; then |
| 97 | OUTPUT_FILE="${INPUT_FILE%.*}_track${STREAM_INDEX}.${CODEC}" |
| 98 | fi |
| 99 | |
| 100 | # Handle channel mapping for native Opus (required for >2 channels) |
| 101 | # Family 0 is for mono/stereo. Family 1 is for surround (3 to 8 channels). |
| 102 | MAPPING_FAMILY=0 |
| 103 | if [[ "$CHANNELS" -gt 2 ]]; then |
| 104 | MAPPING_FAMILY=1 |
| 105 | fi |
| 106 | |
| 107 | # Set FFmpeg logging profiles |
| 108 | # Native: Hide banner, only show errors, but keep the progress bar (-stats) |
| 109 | FFMPEG_NATIVE_LOG="-hide_banner -loglevel error -stats" |
| 110 | # External: Completely silent so it doesn't garble the external tool's output |
| 111 | FFMPEG_EXT_LOG="-hide_banner -loglevel quiet" |
| 112 | |
| 113 | echo "Starting encode: Target=$CODEC | Encoder=$ENCODER_TYPE | Stream=a:${STREAM_INDEX} | Output=$OUTPUT_FILE" |
| 114 | |
| 115 | # Execute encoding based on user choices |
| 116 | if [[ "$CODEC" == "opus" ]]; then |
| 117 | echo "Target Bitrate: ${TOTAL_BITRATE}k" |
| 118 | |
| 119 | if [[ "$ENCODER_TYPE" == "native" ]]; then |
| 120 | ffmpeg $FFMPEG_NATIVE_LOG -y -i "$INPUT_FILE" -map "0:a:${STREAM_INDEX}" \ |
| 121 | -c:a libopus -b:a "${TOTAL_BITRATE}k" -vbr on -mapping_family "$MAPPING_FAMILY" \ |
| 122 | "$OUTPUT_FILE" |
| 123 | |
| 124 | elif [[ "$ENCODER_TYPE" == "external" ]]; then |
| 125 | # Pipe 24-bit PCM WAV to opusenc. |
| 126 | ffmpeg $FFMPEG_EXT_LOG -y -i "$INPUT_FILE" -map "0:a:${STREAM_INDEX}" -f wav -c:a pcm_s24le - | \ |
| 127 | opusenc --bitrate "$TOTAL_BITRATE" - "$OUTPUT_FILE" |
| 128 | fi |
| 129 | |
| 130 | elif [[ "$CODEC" == "flac" ]]; then |
| 131 | if [[ "$ENCODER_TYPE" == "native" ]]; then |
| 132 | # Native FLAC encoding |
| 133 | ffmpeg $FFMPEG_NATIVE_LOG -y -i "$INPUT_FILE" -map "0:a:${STREAM_INDEX}" \ |
| 134 | -c:a flac -compression_level 8 \ |
| 135 | "$OUTPUT_FILE" |
| 136 | |
| 137 | elif [[ "$ENCODER_TYPE" == "external" ]]; then |
| 138 | # Pipe 24-bit PCM WAV to external FLAC encoder. |
| 139 | ffmpeg $FFMPEG_EXT_LOG -y -i "$INPUT_FILE" -map "0:a:${STREAM_INDEX}" -f wav -c:a pcm_s24le - | \ |
| 140 | flac -8 - -o "$OUTPUT_FILE" |
| 141 | fi |
| 142 | fi |
| 143 | |
| 144 | echo -e "\nEncoding complete: $OUTPUT_FILE" |