rip.sh.j2 (5317B)
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: 0, 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_NUM="${positional[1]:-0}" 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 regardless of --name 71 echo "Querying disc info..." 72 RAW=$(flatpak run --command=makemkvcon com.makemkv.MakeMKV \ 73 -r info "disc:$DISC" 2>/dev/null \ 74 | awk -F'"' '/^CINFO:2,/ { print $2; exit }' || echo "") 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 echo "Ripping title $TITLE_NUM from disc $DISC to $OUTPUT_DIR..." 119 MARKER=$(mktemp) 120 flatpak run --command=makemkvcon com.makemkv.MakeMKV \ 121 mkv "disc:$DISC" "$TITLE_NUM" "$OUTPUT_DIR" 122 123 RIPPED=$(find "$OUTPUT_DIR" -maxdepth 2 -name "*.mkv" -not -name "*_x265.mkv" \ 124 -newer "$MARKER" -type f | head -1) 125 rm -f "$MARKER" 126 127 if [[ -z "$RIPPED" ]]; then 128 echo "Error: could not find ripped MKV in $OUTPUT_DIR" 129 exit 1 130 fi 131 echo "Ripped: $RIPPED" 132 133 if [[ -n "$MOVIE_NAME" ]]; then 134 mkdir -p "$OUTPUT_DIR/$MOVIE_NAME" 135 chgrp {{ jellyfin_gid }} "$OUTPUT_DIR/$MOVIE_NAME" 2>/dev/null || true 136 chmod 2775 "$OUTPUT_DIR/$MOVIE_NAME" 137 DEST="$OUTPUT_DIR/$MOVIE_NAME/$MOVIE_NAME.mkv" 138 mv "$RIPPED" "$DEST" 139 RIPPED="$DEST" 140 echo "Moved to: $RIPPED" 141 fi 142 143 ENCODED="${RIPPED%.*}_x265.mkv" 144 echo "Encoding with English audio and subtitles only..." 145 MAKEMKV_RIP=1 encode "$RIPPED" "$ENCODED" "$CRF" $SOFTWARE 146 147 INSIZE=$(du -sh "$RIPPED" | cut -f1) 148 OUTSIZE=$(du -sh "$ENCODED" | cut -f1) 149 rm "$RIPPED" 150 151 # Record in DB 152 if [[ -n "$RAW" ]] && command -v sqlite3 &>/dev/null; then 153 OUTPUT_BYTES=$(stat -c%s "$ENCODED") 154 sqlite3 "$DB" "INSERT INTO rips (disc_id, title, output_file, output_bytes) \ 155 VALUES ('$(sql_str "$RAW")', '$(sql_str "$MOVIE_NAME")', '$(sql_str "$ENCODED")', $OUTPUT_BYTES);" 156 fi 157 158 echo "" 159 echo "Done. Kept $ENCODED ($OUTSIZE), removed source ($INSIZE)" 160 161 command -v notify &>/dev/null && notify "Rip complete" "$MOVIE_NAME — ${INSIZE} → ${OUTSIZE}" 162 163 echo "Triggering Jellyfin library scan..." 164 curl -s -X POST "{{ jellyfin_url }}/ScheduledTasks/Running/7738148ffcd07979c7ceb148e06b3aed" \ 165 -H "X-Emby-Token: {{ jellyfin_api_key }}" \ 166 -o /dev/null \ 167 && echo "Scan triggered." \ 168 || echo "Warning: could not reach Jellyfin at {{ jellyfin_url }}" 169 170 echo "Ejecting disc..." 171 eject "$DRIVE" 2>/dev/null || echo "Warning: could not eject $DRIVE"