makemkv-headless

Log | Files | Refs | README

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"