#!/usr/bin/env bash # mtp (media tail parts) - play .part files while they are still downloading # # Usage: # mtp - auto-detect newest .part or media file in current directory # mtp - play a specific file # === COLORS === G='\033[1;32m' Y='\033[1;33m' C='\033[1;36m' M='\033[1;35m' R='\033[0m' # === FILE SELECTION === if [ -n "$1" ]; then PART_FILE="$1" if [ ! -f "$PART_FILE" ]; then echo -e "${Y}❌ File not found: $PART_FILE${R}" exit 1 fi echo -e "${C}📂 Using file from argument${R}" else PART_FILE=$(ls -t *.part 2>/dev/null | head -1) if [ -z "$PART_FILE" ]; then PART_FILE=$(ls -t *.{mp4,mkv,webm,avi,mp3,opus,ogg,m4a,flac,wav} 2>/dev/null | head -1) fi if [ -z "$PART_FILE" ]; then echo -e "${Y}❌ No media files found in current directory${R}" exit 1 fi echo -e "${C}📂 Auto-selected newest file${R}" fi # === WATCH LATER SETUP === WATCH_LATER_BASE="$HOME/.config/mpv/watch_later" mkdir -p "$WATCH_LATER_BASE" if [[ "$PART_FILE" == *.part ]]; then SESSION_DIR="$WATCH_LATER_BASE/downloading" echo -e "${Y}📥 Mode: Downloading (.part file)${R}" echo -e "${C} Position saved temporarily (may shift as file grows)${R}" else MTIME=$(stat -f%m "$PART_FILE" 2>/dev/null || stat -c%Y "$PART_FILE" 2>/dev/null) PART_FILE_SAFE=$(echo "$PART_FILE" | tr '/' '_' | tr ' ' '_') UNIQUE_SESSION="${PART_FILE_SAFE}_${MTIME}" SESSION_DIR="$WATCH_LATER_BASE/completed/$UNIQUE_SESSION" echo -e "${G}✅ Mode: Completed file${R}" fi mkdir -p "$SESSION_DIR" # Permanent input config saved in session dir INPUT_CONF="$SESSION_DIR/input.conf" cat > "$INPUT_CONF" <<'EOF' SPACE cycle pause 9 add volume -5 0 add volume +5 , seek -10 . seek +10 u quit 42 q quit EOF # === SHOW SAVED POSITION === _show_saved_pos() { local wl_file=$(find "$SESSION_DIR" -maxdepth 1 -type f ! -name "input.conf" 2>/dev/null | head -1) if [ -n "$wl_file" ]; then local saved_pos=$(grep "^start=" "$wl_file" 2>/dev/null | cut -d'=' -f2 | cut -d'.' -f1) if [ -n "$saved_pos" ]; then local saved_min=$((saved_pos / 60)) local saved_sec=$((saved_pos % 60)) echo -e "${G}💾 Resuming from:${R} ${saved_min}m ${saved_sec}s" fi fi } # === BUFFER WAIT === _wait_buffer() { local size=$(stat -f%z "$PART_FILE" 2>/dev/null || stat -c%s "$PART_FILE" 2>/dev/null) local size_mb=$(echo "scale=2; $size / 1048576" | bc) if [[ "$PART_FILE" == *.part ]]; then echo -e "${Y}⏳ Waiting for minimum buffer: 1 MB${R}" while [ "$size" -lt 1048576 ]; do sleep 1 size=$(stat -f%z "$PART_FILE" 2>/dev/null || stat -c%s "$PART_FILE" 2>/dev/null) size_mb=$(echo "scale=2; $size / 1048576" | bc) echo -ne "\r${M}📊 Size:${R} ${size_mb} MB " done echo -e "\n${G}✅ Minimum buffer reached: ${size_mb} MB${R}" fi } # === SHOW HEADER === _show_header() { local size=$(stat -f%z "$PART_FILE" 2>/dev/null || stat -c%s "$PART_FILE" 2>/dev/null) local size_mb=$(echo "scale=2; $size / 1048576" | bc) clear echo -e "${C}____________________________________________________________${R}" echo -e "${C} MTP v1.1 | MEDIA TAIL PLAYER ${R}" echo -e "${C}____________________________________________________________${R}" echo -e "${Y} 9 / 0 : Volume down / up ${R}" echo -e "${Y} , / . : Seek -10 / +10 sec ${R}" echo -e "${Y} SPACE : Pause / Resume ${R}" echo -e "${Y} u : Refresh (keeps pos) ${R}" echo -e "${Y} q : Quit ${R}" echo -e "${C}____________________________________________________________${R}" echo -e "${G}🎵 File:${R} $(basename "$PART_FILE")" echo -e "${M}📊 Size:${R} ${size_mb} MB" _show_saved_pos echo -e "${C}____________________________________________________________${R}" } # === MAIN PLAYBACK LOOP === _play_loop() { local size_session_start=$(stat -f%z "$PART_FILE" 2>/dev/null || stat -c%s "$PART_FILE" 2>/dev/null) while true; do _show_header mpv \ --demuxer-lavf-o=fflags=+nobuffer+fastseek \ --demuxer-readahead-secs=1 \ --cache=yes \ --cache-secs=10 \ --demuxer-max-bytes=50M \ --demuxer-max-back-bytes=20M \ --stream-buffer-size=2M \ --hr-seek=yes \ --hr-seek-demuxer-offset=0 \ --save-position-on-quit \ --resume-playback \ --watch-later-directory="$SESSION_DIR" \ --input-conf="$INPUT_CONF" \ --title="mtp: $(basename "$PART_FILE")" \ "$PART_FILE" local exit_code=$? if [ $exit_code -eq 42 ]; then local size_new=$(stat -f%z "$PART_FILE" 2>/dev/null || stat -c%s "$PART_FILE" 2>/dev/null) local size_new_mb=$(echo "scale=2; $size_new / 1048576" | bc) local size_before=$(stat -f%z "$PART_FILE" 2>/dev/null || stat -c%s "$PART_FILE" 2>/dev/null) local gained=$(echo "scale=2; ($size_new - $size_before) / 1048576" | bc) echo -e "\n${G}🔄 Refreshed — size: ${size_new_mb} MB${R}" echo -e "${C} Resuming from saved position...${R}" sleep 1 continue else echo "" if [ $exit_code -eq 0 ]; then echo -e "${G}✅ Playback finished${R}" else echo -e "${Y}⚠️ Interrupted (code: $exit_code)${R}" fi break fi done # Final summary local size_end=$(stat -f%z "$PART_FILE" 2>/dev/null || stat -c%s "$PART_FILE" 2>/dev/null) local size_end_mb=$(echo "scale=2; $size_end / 1048576" | bc) local total_diff_mb=$(echo "scale=2; ($size_end - $size_session_start) / 1048576" | bc) echo -e "${C}____________________________________________________________${R}" echo -e "${M}📊 Size at end:${R} ${size_end_mb} MB" if [ "$total_diff_mb" != "0.00" ]; then echo -e "${G}📈 Downloaded during session:${R} +${total_diff_mb} MB" fi echo -e "${C}____________________________________________________________${R}" } # === RUN === _wait_buffer _play_loop