rip.sh.j2 (6110B)
1 #!/bin/bash 2 set -euo pipefail 3 4 OUTPUT_DIR="/mnt/Movies" 5 DB="/mnt/Movies/.rip.db" 6 SOFTWARE="" 7 NAME_OVERRIDE="" 8 9 usage() { 10 echo "Usage: rip [disc] [title] [crf] [--software] [--name \"Movie Title\"]" 11 echo "" 12 echo " disc disc number (default: 0)" 13 echo " title title number (default: auto-detected as the longest title — usually the main feature)" 14 echo " crf encode quality 18-28 (default: 22, lower = better quality)" 15 echo " --software use software x265 encoder instead of hardware" 16 echo " --name output filename base (default: auto-detected from disc)" 17 echo "" 18 echo "Examples:" 19 echo " rip" 20 echo " rip 0 1" 21 echo " rip 0 0 18" 22 echo " rip 0 0 18 --software" 23 echo " rip --name \"Raiders of the Lost Ark (1981)\"" 24 echo " rip 0 0 22 --name \"Raiders of the Lost Ark (1981)\"" 25 exit 1 26 } 27 28 [[ "${1:-}" == "-h" ]] && usage 29 30 positional=() 31 while [[ $# -gt 0 ]]; do 32 case "$1" in 33 --software) SOFTWARE="--software"; shift ;; 34 --name) NAME_OVERRIDE="$2"; shift 2 ;; 35 *) positional+=("$1"); shift ;; 36 esac 37 done 38 39 DISC="${positional[0]:-0}" 40 TITLE_ARG="${positional[1]:-}" 41 CRF="${positional[2]:-22}" 42 43 DRIVE="/dev/sr0" 44 45 if [[ ! -b "$DRIVE" ]]; then 46 echo "Error: no optical drive found at $DRIVE" 47 exit 1 48 fi 49 50 if ! dd if="$DRIVE" of=/dev/null bs=2048 count=1 status=none 2>/dev/null; then 51 echo "Error: no disc detected in $DRIVE" 52 exit 1 53 fi 54 55 sql_str() { printf '%s' "${1//\'/\'\'}"; } 56 57 init_db() { 58 sqlite3 "$DB" <<'SQL' 59 CREATE TABLE IF NOT EXISTS rips ( 60 id INTEGER PRIMARY KEY AUTOINCREMENT, 61 disc_id TEXT NOT NULL, 62 title TEXT, 63 output_file TEXT, 64 output_bytes INTEGER, 65 ripped_at TEXT NOT NULL DEFAULT (datetime('now')) 66 ); 67 SQL 68 } 69 70 # Always query disc info — needed for duplicate detection and title auto-detection regardless of --name 71 echo "Querying disc info..." 72 INFO_RAW=$(flatpak run --command=makemkvcon com.makemkv.MakeMKV \ 73 -r info "disc:$DISC" 2>/dev/null || echo "") 74 RAW=$(printf '%s\n' "$INFO_RAW" | awk -F'"' '/^CINFO:2,/ { print $2; exit }') 75 76 # Determine output movie name 77 if [[ -n "$NAME_OVERRIDE" ]]; then 78 MOVIE_NAME="${NAME_OVERRIDE//\//-}" 79 else 80 if [[ -n "$RAW" ]]; then 81 MOVIE_NAME=$(python3 -c " 82 import sys, re 83 JUNK = {'ws', 'fs', 'bd', 'uhd', 'hd', '3d', 'disc1', 'disc2', 'disc3', 'disc4'} 84 parts = sys.argv[1].replace('_', ' ').split() 85 clean = [p for p in parts if p and p.lower() not in JUNK and not re.match(r'^\d+x\d+$', p.lower())] 86 print(' '.join(p.capitalize() for p in clean)) 87 " "$RAW") 88 else 89 MOVIE_NAME="" 90 fi 91 echo "" 92 if ! read -r -t 60 -p "Movie name [${MOVIE_NAME:-enter name}] (60s timeout): " input; then 93 echo "" 94 echo "No input received in 60s, using default: ${MOVIE_NAME:-<none>}" 95 fi 96 [[ -n "${input:-}" ]] && MOVIE_NAME="$input" 97 if [[ -z "$MOVIE_NAME" ]]; then 98 echo "Error: movie name is required" 99 exit 1 100 fi 101 fi 102 103 # Duplicate check 104 if [[ -n "$RAW" ]] && command -v sqlite3 &>/dev/null; then 105 init_db 106 EXISTING=$(sqlite3 "$DB" \ 107 "SELECT title, output_file, ripped_at FROM rips WHERE disc_id='$(sql_str "$RAW")' LIMIT 1;") 108 if [[ -n "$EXISTING" ]]; then 109 echo "" 110 echo "Warning: this disc has already been ripped:" 111 echo " $EXISTING" 112 echo "" 113 read -r -p "Rip again anyway? [y/N] " confirm 114 [[ "${confirm,,}" != "y" ]] && { echo "Aborted."; exit 0; } 115 fi 116 fi 117 118 # MakeMKV numbers titles by discovery order, not duration, so title 0 is not 119 # reliably the main feature. Default to the longest title unless one is given. 120 if [[ -n "$TITLE_ARG" ]]; then 121 TITLE_NUM="$TITLE_ARG" 122 else 123 TITLE_NUM=$(printf '%s\n' "$INFO_RAW" \ 124 | grep '^TINFO:[0-9]*,9,0,' \ 125 | sed -E 's/^TINFO:([0-9]+),9,0,"([0-9]+):([0-9]+):([0-9]+)"/\1 \2 \3 \4/' \ 126 | awk '{secs=$2*3600+$3*60+$4; if (secs>max) {max=secs; best=$1}} END{print best}') 127 if [[ -z "$TITLE_NUM" ]]; then 128 echo "Warning: could not auto-detect main feature title, defaulting to title 0" 129 TITLE_NUM=0 130 else 131 echo "Auto-detected main feature: title $TITLE_NUM" 132 fi 133 fi 134 135 echo "Ripping title $TITLE_NUM from disc $DISC to $OUTPUT_DIR..." 136 MARKER=$(mktemp) 137 flatpak run --command=makemkvcon com.makemkv.MakeMKV \ 138 mkv "disc:$DISC" "$TITLE_NUM" "$OUTPUT_DIR" 139 140 RIPPED=$(find "$OUTPUT_DIR" -maxdepth 2 -name "*.mkv" -not -name "*_x265.mkv" \ 141 -newer "$MARKER" -type f | head -1) 142 rm -f "$MARKER" 143 144 if [[ -z "$RIPPED" ]]; then 145 echo "Error: could not find ripped MKV in $OUTPUT_DIR" 146 exit 1 147 fi 148 echo "Ripped: $RIPPED" 149 150 if [[ -n "$MOVIE_NAME" ]]; then 151 mkdir -p "$OUTPUT_DIR/$MOVIE_NAME" 152 chgrp {{ jellyfin_gid }} "$OUTPUT_DIR/$MOVIE_NAME" 2>/dev/null || true 153 chmod 2775 "$OUTPUT_DIR/$MOVIE_NAME" 154 DEST="$OUTPUT_DIR/$MOVIE_NAME/$MOVIE_NAME.mkv" 155 mv "$RIPPED" "$DEST" 156 RIPPED="$DEST" 157 echo "Moved to: $RIPPED" 158 fi 159 160 ENCODED="${RIPPED%.*}_x265.mkv" 161 echo "Encoding with English audio and subtitles only..." 162 MAKEMKV_RIP=1 encode "$RIPPED" "$ENCODED" "$CRF" $SOFTWARE 163 164 INSIZE=$(du -sh "$RIPPED" | cut -f1) 165 OUTSIZE=$(du -sh "$ENCODED" | cut -f1) 166 rm "$RIPPED" 167 168 # Record in DB 169 if [[ -n "$RAW" ]] && command -v sqlite3 &>/dev/null; then 170 OUTPUT_BYTES=$(stat -c%s "$ENCODED") 171 sqlite3 "$DB" "INSERT INTO rips (disc_id, title, output_file, output_bytes) \ 172 VALUES ('$(sql_str "$RAW")', '$(sql_str "$MOVIE_NAME")', '$(sql_str "$ENCODED")', $OUTPUT_BYTES);" 173 fi 174 175 echo "" 176 echo "Done. Kept $ENCODED ($OUTSIZE), removed source ($INSIZE)" 177 178 command -v notify &>/dev/null && notify "Rip complete" "$MOVIE_NAME — ${INSIZE} → ${OUTSIZE}" 179 180 echo "Triggering Jellyfin library scan..." 181 curl -s -X POST "{{ jellyfin_url }}/ScheduledTasks/Running/7738148ffcd07979c7ceb148e06b3aed" \ 182 -H "X-Emby-Token: {{ jellyfin_api_key }}" \ 183 -o /dev/null \ 184 && echo "Scan triggered." \ 185 || echo "Warning: could not reach Jellyfin at {{ jellyfin_url }}" 186 187 echo "Ejecting disc..." 188 eject "$DRIVE" 2>/dev/null || echo "Warning: could not eject $DRIVE"