Last active 16 hours ago

mxsr revised this gist 16 hours ago. Go to revision

1 file changed, 78 insertions, 22 deletions

encode_audio.hs renamed to encode_audio.sh

@@ -9,23 +9,33 @@ CODEC="opus"
9 9 ENCODER_TYPE="native"
10 10 STREAM_INDEX="0"
11 11 BITRATE_PER_CHANNEL=64 # kbps per channel for Opus
12 + TEMP_OPUS=""
13 + TEMP_FLAC=""
14 +
15 + # Ensure temporary files are cleaned up if the script exits or fails
16 + cleanup() {
17 + if [[ -n "$TEMP_OPUS" && -f "$TEMP_OPUS" ]]; then rm -f "$TEMP_OPUS"; fi
18 + if [[ -n "$TEMP_FLAC" && -f "$TEMP_FLAC" ]]; then rm -f "$TEMP_FLAC"; fi
19 + }
20 + trap cleanup EXIT
12 21
13 22 usage() {
14 23 cat <<EOF
15 24 Usage: $(basename "$0") -i <input_media> [OPTIONS]
16 25
17 - A script to extract and encode a specific audio track from a movie file to FLAC or Opus.
26 + A script to extract and encode a specific audio track from a movie file to FLAC, Opus, or both.
18 27
19 28 Options:
20 29 -i <file> Input media file (video or audio)
21 30 -o <file> Output file name (optional; auto-generated if omitted)
22 - -c <codec> Target codec: 'opus' or 'flac' (default: opus)
31 + -c <codec> Target codec: 'opus', 'flac', or 'dual' (default: opus)
32 + ('dual' encodes to both and muxes into an .mka file)
23 33 -e <type> Encoder type: 'native' (ffmpeg) or 'external' (flac/opusenc) (default: native)
24 34 -s <index> Audio stream index to select (default: 0, which is the first audio track)
25 35 -h Show this help message
26 36
27 37 Examples:
28 - $(basename "$0") -i movie.mkv -c opus -e native
38 + $(basename "$0") -i movie.mkv -c dual -e native
29 39 $(basename "$0") -i movie.mp4 -c flac -s 1 -o my_custom_audio.flac
30 40 EOF
31 41 exit 1
@@ -49,8 +59,8 @@ if [[ -z "$INPUT_FILE" || ! -f "$INPUT_FILE" ]]; then
49 59 usage
50 60 fi
51 61
52 - if [[ "$CODEC" != "opus" && "$CODEC" != "flac" ]]; then
53 - echo "Error: Codec must be 'opus' or 'flac'."
62 + if [[ "$CODEC" != "opus" && "$CODEC" != "flac" && "$CODEC" != "dual" ]]; then
63 + echo "Error: Codec must be 'opus', 'flac', or 'dual'."
54 64 exit 1
55 65 fi
56 66
@@ -70,12 +80,12 @@ command -v ffmpeg >/dev/null 2>&1 || { echo "Error: ffmpeg is required."; exit 1
70 80 command -v ffprobe >/dev/null 2>&1 || { echo "Error: ffprobe is required."; exit 1; }
71 81
72 82 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."
83 + if [[ "$CODEC" == "flac" || "$CODEC" == "dual" ]] && ! command -v flac >/dev/null 2>&1; then
84 + echo "Error: 'flac' tool is required for external flac/dual encoding."
75 85 exit 1
76 86 fi
77 - if [[ "$CODEC" == "opus" ]] && ! command -v opusenc >/dev/null 2>&1; then
78 - echo "Error: 'opusenc' tool is required for external opus encoding."
87 + if [[ "$CODEC" == "opus" || "$CODEC" == "dual" ]] && ! command -v opusenc >/dev/null 2>&1; then
88 + echo "Error: 'opusenc' tool is required for external opus/dual encoding."
79 89 exit 1
80 90 fi
81 91 fi
@@ -94,7 +104,17 @@ TOTAL_BITRATE=$(( CHANNELS * BITRATE_PER_CHANNEL ))
94 104
95 105 # Determine output filename if not provided by user
96 106 if [[ -z "$OUTPUT_FILE" ]]; then
97 - OUTPUT_FILE="${INPUT_FILE%.*}_track${STREAM_INDEX}.${CODEC}"
107 + if [[ "$CODEC" == "dual" ]]; then
108 + OUTPUT_FILE="${INPUT_FILE%.*}_track${STREAM_INDEX}.mka"
109 + else
110 + OUTPUT_FILE="${INPUT_FILE%.*}_track${STREAM_INDEX}.${CODEC}"
111 + fi
112 + fi
113 +
114 + # Check if output file already exists and abort if it does
115 + if [[ -e "$OUTPUT_FILE" ]]; then
116 + echo "Error: Output file '$OUTPUT_FILE' already exists. Aborting."
117 + exit 1
98 118 fi
99 119
100 120 # Handle channel mapping for native Opus (required for >2 channels)
@@ -104,10 +124,12 @@ if [[ "$CHANNELS" -gt 2 ]]; then
104 124 MAPPING_FAMILY=1
105 125 fi
106 126
127 + # FILTER FIX: Force libopus to see standard 7.1, 5.1, stereo, or mono layouts.
128 + # This silently fixes the "Invalid channel layout 5.1(side)" error.
129 + CHANNEL_FORMAT_FILTER="-filter:a aformat=channel_layouts=7.1|5.1|stereo|mono"
130 +
107 131 # Set FFmpeg logging profiles
108 - # Native: Hide banner, only show errors, but keep the progress bar (-stats)
109 132 FFMPEG_NATIVE_LOG="-hide_banner -loglevel error -stats"
110 - # External: Completely silent so it doesn't garble the external tool's output
111 133 FFMPEG_EXT_LOG="-hide_banner -loglevel quiet"
112 134
113 135 echo "Starting encode: Target=$CODEC | Encoder=$ENCODER_TYPE | Stream=a:${STREAM_INDEX} | Output=$OUTPUT_FILE"
@@ -115,30 +137,64 @@ echo "Starting encode: Target=$CODEC | Encoder=$ENCODER_TYPE | Stream=a:${STREAM
115 137 # Execute encoding based on user choices
116 138 if [[ "$CODEC" == "opus" ]]; then
117 139 echo "Target Bitrate: ${TOTAL_BITRATE}k"
118 -
140 +
119 141 if [[ "$ENCODER_TYPE" == "native" ]]; then
120 142 ffmpeg $FFMPEG_NATIVE_LOG -y -i "$INPUT_FILE" -map "0:a:${STREAM_INDEX}" \
143 + $CHANNEL_FORMAT_FILTER \
121 144 -c:a libopus -b:a "${TOTAL_BITRATE}k" -vbr on -mapping_family "$MAPPING_FAMILY" \
122 145 "$OUTPUT_FILE"
123 -
146 +
124 147 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 - | \
148 + ffmpeg $FFMPEG_EXT_LOG -y -i "$INPUT_FILE" -map "0:a:${STREAM_INDEX}" \
149 + $CHANNEL_FORMAT_FILTER -f wav -c:a pcm_s24le - | \
127 150 opusenc --bitrate "$TOTAL_BITRATE" - "$OUTPUT_FILE"
128 151 fi
129 152
130 153 elif [[ "$CODEC" == "flac" ]]; then
131 154 if [[ "$ENCODER_TYPE" == "native" ]]; then
132 - # Native FLAC encoding
133 155 ffmpeg $FFMPEG_NATIVE_LOG -y -i "$INPUT_FILE" -map "0:a:${STREAM_INDEX}" \
156 + $CHANNEL_FORMAT_FILTER \
134 157 -c:a flac -compression_level 8 \
135 158 "$OUTPUT_FILE"
136 -
159 +
137 160 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"
161 + ffmpeg $FFMPEG_EXT_LOG -y -i "$INPUT_FILE" -map "0:a:${STREAM_INDEX}" \
162 + $CHANNEL_FORMAT_FILTER -f wav -c:a pcm_s24le - | \
163 + flac --best --ignore-chunk-sizes - -o "$OUTPUT_FILE"
164 + fi
165 +
166 + elif [[ "$CODEC" == "dual" ]]; then
167 + echo "Target Opus Bitrate: ${TOTAL_BITRATE}k"
168 +
169 + if [[ "$ENCODER_TYPE" == "native" ]]; then
170 + # Map the stream twice. Note we use specific stream filters (-filter:a:0 and -filter:a:1) here
171 + # because applying a global -af filter breaks multiple mappings in FFmpeg.
172 + ffmpeg $FFMPEG_NATIVE_LOG -y -i "$INPUT_FILE" \
173 + -map "0:a:${STREAM_INDEX}" -filter:a:0 "aformat=channel_layouts=7.1|5.1|stereo|mono" -c:a:0 libopus -b:a:0 "${TOTAL_BITRATE}k" -vbr on -mapping_family "$MAPPING_FAMILY" -metadata:s:a:0 title="Opus (${TOTAL_BITRATE}k)" \
174 + -map "0:a:${STREAM_INDEX}" -filter:a:1 "aformat=channel_layouts=7.1|5.1|stereo|mono" -c:a:1 flac -compression_level 8 -metadata:s:a:1 title="FLAC (Lossless)" \
175 + "$OUTPUT_FILE"
176 +
177 + elif [[ "$ENCODER_TYPE" == "external" ]]; then
178 + TEMP_OPUS=$(mktemp --suffix=.opus)
179 + TEMP_FLAC=$(mktemp --suffix=.flac)
180 +
181 + echo "[1/3] Encoding Opus externally..."
182 + ffmpeg $FFMPEG_EXT_LOG -y -i "$INPUT_FILE" -map "0:a:${STREAM_INDEX}" \
183 + $CHANNEL_FORMAT_FILTER -f wav -c:a pcm_s24le - | \
184 + opusenc --quiet --bitrate "$TOTAL_BITRATE" - "$TEMP_OPUS"
185 +
186 + echo "[2/3] Encoding FLAC externally..."
187 + ffmpeg $FFMPEG_EXT_LOG -y -i "$INPUT_FILE" -map "0:a:${STREAM_INDEX}" \
188 + $CHANNEL_FORMAT_FILTER -f wav -c:a pcm_s24le - | \
189 + flac --silent --best --ignore-chunk-sizes - -o "$TEMP_FLAC"
190 +
191 + echo "[3/3] Multiplexing tracks into $OUTPUT_FILE..."
192 + ffmpeg $FFMPEG_NATIVE_LOG -y -i "$TEMP_OPUS" -i "$TEMP_FLAC" \
193 + -map 0:a -map 1:a -c copy \
194 + -metadata:s:a:0 title="Opus (${TOTAL_BITRATE}k)" \
195 + -metadata:s:a:1 title="FLAC (Lossless)" \
196 + "$OUTPUT_FILE"
141 197 fi
142 198 fi
143 199
144 - echo -e "\nEncoding complete: $OUTPUT_FILE"
200 + echo -e "\nEncoding complete: $OUTPUT_FILE"

mxsr revised this gist 16 hours ago. Go to revision

1 file changed

audio_encoder.sh renamed to encode_audio.hs

File renamed without changes

mxsr revised this gist 1 week ago. Go to revision

1 file changed, 144 insertions

audio_encoder.sh(file created)

@@ -0,0 +1,144 @@
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"
Newer Older