|
| 1 | +#!/usr/bin/env bash |
| 2 | +# |
| 3 | +# Usage: ./rip-to-audio <SOURCE_DIR> <TARGET_DIR> |
| 4 | +# |
| 5 | +# - Finds all *.mkv in SOURCE_DIR (recursive). |
| 6 | +# - Strips patterns like 'HDTV-720p' from the output filename. |
| 7 | +# - Tries to copy audio directly (no re-encode). |
| 8 | +# - If that fails, re-encode to AAC (VBR). |
| 9 | +# - Uses a .partial file to avoid corrupt final output on interruption. |
| 10 | +# - Skips files that already have a corresponding .m4a. |
| 11 | +# - Prevents ffmpeg from reading stdin (so it doesn't break the find-loop). |
| 12 | + |
| 13 | +set -euo pipefail |
| 14 | + |
| 15 | +if (( $# < 2 )); then |
| 16 | + echo "Usage: $0 <SOURCE_DIR> <TARGET_DIR>" |
| 17 | + exit 1 |
| 18 | +fi |
| 19 | + |
| 20 | +SOURCE_DIR="$1" |
| 21 | +TARGET_DIR="$2" |
| 22 | + |
| 23 | +# Make sure ffmpeg is available |
| 24 | +if ! command -v ffmpeg &>/dev/null; then |
| 25 | + echo "Error: ffmpeg not found." |
| 26 | + exit 1 |
| 27 | +fi |
| 28 | + |
| 29 | +# Remove video-related tags on the file |
| 30 | +STRIP_PATTERN='s/\s*HDTV.*//Ig' |
| 31 | + |
| 32 | +# Find all *.mkv files (case-insensitive) and process them one by one. |
| 33 | +# We use process substitution (`< <(...)`) so ffmpeg won't consume our loop's stdin. |
| 34 | +while IFS= read -r -d '' mkv_file; do |
| 35 | + # 1) Compute the relative path within SOURCE_DIR |
| 36 | + # e.g.: /path/to/source/sub/Video.HDTV-720p.mkv |
| 37 | + # => sub/Video.HDTV-720p.mkv |
| 38 | + rel_path="${mkv_file#"$SOURCE_DIR"/}" |
| 39 | + |
| 40 | + # 2) Strip off .mkv to get the "raw" name; we also want to remove the directory part |
| 41 | + # so we can apply the sed-based pattern on the filename itself. |
| 42 | + dir_part="$(dirname "$rel_path")" |
| 43 | + base_no_ext="$(basename "${rel_path%.mkv}")" |
| 44 | + |
| 45 | + # 3) Use sed to remove the 'HDTV-720p' pattern (and minor punctuation around it). |
| 46 | + # Adjust this as needed for other patterns like "WEB-1080p" or "BluRay.1080p". |
| 47 | + cleaned_name="$(echo "$base_no_ext" | sed -E "$STRIP_PATTERN")" |
| 48 | + |
| 49 | + # 4) Construct final out_file path, ensuring .m4a extension |
| 50 | + # e.g.: $TARGET_DIR/sub/Video.m4a |
| 51 | + out_file="$TARGET_DIR/$dir_part/$cleaned_name.m4a" |
| 52 | + partial_file="$out_file.partial" |
| 53 | + |
| 54 | + # 5) Skip if final output already exists |
| 55 | + if [[ -f "$out_file" ]]; then |
| 56 | + echo "Skipping (already exists): $out_file" |
| 57 | + continue |
| 58 | + fi |
| 59 | + |
| 60 | + # 6) Remove stale partial file if present |
| 61 | + if [[ -f "$partial_file" ]]; then |
| 62 | + echo "Removing stale partial: $partial_file" |
| 63 | + rm -f "$partial_file" |
| 64 | + fi |
| 65 | + |
| 66 | + # 7) Create output directory if necessary |
| 67 | + mkdir -p "$(dirname "$out_file")" |
| 68 | + |
| 69 | + echo "Processing: $mkv_file" |
| 70 | + echo " to -> $out_file" |
| 71 | + |
| 72 | + # 8) First, try copying the existing audio track (no re-encode) |
| 73 | + # -nostdin => ffmpeg won't read from stdin |
| 74 | + # -vn => drop video |
| 75 | + # -c:a copy => copy audio bitstream |
| 76 | + # -f mp4 => produce an MP4 container (m4a is audio-only MP4) |
| 77 | + # -y => overwrite partial file if it exists |
| 78 | + set +e # We'll handle ffmpeg failure manually |
| 79 | + ffmpeg -nostdin \ |
| 80 | + -i "$mkv_file" \ |
| 81 | + -vn -c:a copy \ |
| 82 | + -f mp4 -y \ |
| 83 | + "$partial_file" |
| 84 | + ffmpeg_status=$? |
| 85 | + set -e |
| 86 | + |
| 87 | + if (( ffmpeg_status != 0 )); then |
| 88 | + echo "Audio copy failed (unsupported codec?), re-encoding to AAC VBR..." |
| 89 | + |
| 90 | + # 9) Fallback: re-encode to AAC VBR |
| 91 | + # -q:a 2 => a typical VBR quality level (range ~0=best to ~5=lower) |
| 92 | + ffmpeg -nostdin \ |
| 93 | + -i "$mkv_file" \ |
| 94 | + -vn -c:a aac -q:a 2 \ |
| 95 | + -f mp4 -y \ |
| 96 | + "$partial_file" |
| 97 | + fi |
| 98 | + |
| 99 | + # 10) Move partial to final output if successful |
| 100 | + mv "$partial_file" "$out_file" |
| 101 | + |
| 102 | + echo "Done: $out_file" |
| 103 | + echo |
| 104 | + |
| 105 | +done < <(find "$SOURCE_DIR" -type f \( -iname "*.mkv" -o -iname "*.mp4" -o -iname "*.avi" -o -iname "*.mov" \) -print0) |
0 commit comments