make_MP4.sh

Browser view only. Use the download link if you want to save the file.

Back to Toolbox
#!/bin/bash
## SKIP_PEAK_ANALYSIS=0

source ./launch_random_terminal.sh
source ./load_video_files.sh

# Ensure SKIP_MP4KILLER has a default value (set in make_MKV.sh, make_MPG.sh, etc.)
if [[ -z "${SKIP_MP4KILLER+x}" ]]; then
    SKIP_MP4KILLER=0
fi


# --- ensure audio normalized before any conversions ---
if [[ ! -f ".audio_check_done" ]]; then
    echo "Running initial audio check + normalization..."
    ## separate box, more readable  ./audio_check.sh -noterminal
    ./audio_check.sh
    echo
else
    echo "Audio already checked (flag found)."
fi


# Get the name of the script (without the path)
script_shortname=$(basename "$0" .sh)

## DEBUGGING ENABLE HERE ##
#debugfile="debug_${script_shortname}.txt"
debugfile="/dev/null"

# --- smart exit helpers ---

# True if script is sourced, not directly executed
is_sourced() { [[ "${BASH_SOURCE[0]}" != "$0" ]]; }

# True if running inside a mate-terminal or gnome-terminal session
from_terminal() {
    # Detect typical terminal envs (TERM, parent process, or $profile)
    [[ -n "$profile" ]] ||
    [[ "$TERM" == "xterm"* || "$TERM" == "linux" ]] ||
    ps -o comm= -p "$PPID" | grep -Eq 'mate-terminal|gnome-terminal|x-terminal-emulator'
}

safe_return() {
    local code=${1:-0}
    if is_sourced; then
        return "$code" 2>/dev/null || exit "$code"
    else
        exit "$code"
    fi
}


#### Hard quick overrides till picker can choose them ###
## Note, they MUST end in commas to make the list right, and yadif will end it if any are selected
#qparam="-q:v 0"
#qparam="-crf 18"


# === universal safe blind shot based on below + chatgpt suggestion ===
# === this worked on all 4 Tipton and Limon vids, noticed it could almost be run blindly on all jobs ===
    #brightfix="curves=all='0/0 0.25/0.33 0.50/0.62 0.80/0.86 1/0.98',eq=brightness=0:contrast=1.06:saturation=1.20" 

# === de-orange oversaturated witnesses + autobright attempt ===
    # now with a bit of desat, hue push
    #brightfix="curves=all='0/0 0.25/0.34 0.50/0.64 0.80/0.84 1/0.96',colortemperature=13000:pl=1,curves=r='0/0 0.5/0.42 1/0.88',eq=contrast=1.01:saturation=0.92:brightness=0.05,hue=h=345"

    # STRONG
    #brightfix="curves=all='0/0 0.25/0.34 0.50/0.64 0.80/0.84 1/0.96',colortemperature=13000:pl=1,curves=r='0/0 0.5/0.42 1/0.88',eq=contrast=1.03:saturation=1.12"
    # MEDIUM
    #brightfix="curves=all='0/0 0.25/0.34 0.50/0.63 0.80/0.85 1/0.97',colortemperature=12000:pl=1,curves=r='0/0 0.5/0.45 1/0.9',eq=contrast=1.04:saturation=1.10"

# === Earlier attempts that were onesy twosy
#brightfix="curves=all='0/0 0.25/0.33 0.50/0.62 0.80/0.86 1/0.98',eq=brightness=0.05:contrast=1.0:saturation=0.97"  #Arango dim, white shirt
#brightfix="rotate=-2*PI/180,unsharp"                                       #Insalaco pair for ALVS -- Rotate -2 degrees, sharpen
#brightfix="rotate=-2*PI/180,crop=in_w-80:in_h-80,unsharp=5:5:1.5:5:5:0.0"  #Same rotate as above, but 40 pixels off each edge + extra sharpening
#brightfix="eq=contrast=0.95:saturation=0.90:brightness=0.05,hue=h=25"      #Sgt Ruiz
#brightfix="curves='all=0/0 0.3/0.5 0.7/0.8 1/1',eq=brightness=0.015:contrast=1.1:saturation=1.2" #brighten due to white shirt
#brightfix="curves='all=0/0 0.3/0.5 0.7/0.8 1/1',eq=brightness=0.045:contrast=1.1:saturation=1.1" #chatGPT4 solution wow!
#brightfix="eq=contrast=1.1:saturation=0.65:brightness=0.05, hue=h=355"  #chatGPT4 (Charmara Dailey)
#brightfix="eq=contrast=0.9:saturation=1.05:brightness=0.08"  #(Glen Barnhill)
#brightfix="curves=all='0/0 0.15/0.30 0.40/0.65 0.70/0.88 0.92/0.97 1/1',eq=brightness=0.03:contrast=1.07:saturation=1.15" #Clifford Walker DC
#brightfix="curves=all='0/0 0.15/0.30 0.40/0.65 0.70/0.88 0.92/0.97 1/1',eq=brightness=0.20:contrast=1.07:saturation=1.15" #Jorge Perez
#brightext=.Brightfix
#scale="-s 1920x1080,"   #outside the -vf
#aspstring="-aspect 16:9" #outside the -vf
crop4x3param="crop=iw/4*3:ih,"  #inside the -vf
cropTBparam="crop=in_w:in_h*0.5773:0:in_h*0.2216,"

#### brightfix options for reference ####
# [C] brightfix=-vf "curves=preset=lighter, yadif"
# [U] brightfix=-vf "eq=brightness=0.0:contrast=1.0:saturation=1.0, unsharp"
# [2] brightfix=-vf "eq=brightness=0.18:contrast=1.20:saturation=1.2, unsharp"
# [3] brightfix=-vf "eq=brightness=0.22:contrast=1.25:saturation=1.2, unsharp"
# [desat] brightfix=-vf "eq=brightness=0.05:contrast=1.0:saturation=0.75"
# [depink] brightfix=-vf "hue=h=020:s=0.98"
# [kh1] brightfix=-vf "hue=h=015:s=0.98, eq=brightness=0.05:contrast=1.0:saturation=0.65"
# [chatgpt help] curves=all='0/0 0.3/0.5 0.7/0.8 1/1',eq=brightness=0.043:contrast=1.1:saturation=1.1

echo Make_MP4 started.  Checking script name... > $debugfile

# Get the name of the script (without the path, but with the .sh)
script_name=$(basename "$0")

echo   ... script is $script_name >>$debugfile

if [[ -v preset_mode ]]; then
  echo "preset_mode is $preset_mode"
  MODE=$preset_mode
  targetEXT="${preset_mode,,}"
# Check if the script name matches make_MP4.sh or make_MKV.sh
elif [[ "$script_name" == "make_MP4.sh" ]]; then
    echo "This is the MP4 conversion script."
    MODE="MP4"
    targetEXT="mp4"
elif [[ "$script_name" == "make_MKV.sh" ]]; then
    echo "This is the MKV conversion script."
    MODE="MKV"
    targetEXT="mkv"
elif [[ "$script_name" == "make_MPEG1.sh" || "$script_name" == "make_Joseph.sh" || "$script_name" == "make_MPG.sh" ]]; then
    echo "This is the MPEG1 conversion script."
    MODE="MPG"
    targetEXT="mpg"
elif [[ "$script_name" == "make_MP3.sh" ]]; then
    echo "This is the MP3 conversion script."
    MODE="MP3"
    targetEXT="mp3"
else
    echo   ... unknown script, so starting clarification loop >> $debugfile
    while true; do
        clear
        echo "This is an unknown script name $script_name. "
        echo
        echo "Which type of files would you like to make?"
        echo
        echo "Operate as MP4 maker or MKV maker?"
        echo "[1] = MPEG-1 480p 1800k"
        echo "[4] = MP4  (h264/AVC) "
        echo "[K] = MKV  (h265/HEVC)"
        echo "[3] = MP3 audio 96k"
        echo
        echo ""

        # Read a single character without echoing
        read -rsn1 -p $'\e[1;97;44m Press 1, 4, K, 3  or  ESC / Q to quit \e[0m' input

        # Handle quitting options: q, Q, or ESC
        if [[ $input == 'q' || $input == 'Q' || $input == $'\033' ]]; then
            echo "Exiting the script."
            kill -- "$PPID"
            exit 0
        elif [[ $input == '4' ]]; then
            MODE="MP4"
            targetEXT="mp4"
            echo   ... setting MP4 mode >> $debugfile
            break  # Exit the loop after valid input
        elif [[ $input == 'k' || $input == 'K' ]]; then
            MODE="MKV"
            targetEXT="mkv"
            echo   ... setting MKV mode >> $debugfile
            break  # Exit the loop after valid input
        elif [[ $input == '1' ]]; then
            MODE="MPG"
            targetEXT="mpg"
            echo   ... setting MPG mode >> $debugfile
            break  # Exit the loop after valid input
        elif [[ $input == '3' ]]; then
            MODE="MP3"
            targetEXT="mp3"
            echo   ... setting MP3 mode >> $debugfile
            break  # Exit the loop after valid input
        fi
    done

fi


echo Out of the loop, continuing >> "$debugfile"
# no point in a welcome here, pick_conversions wipes it out and has its own

source ./pick_conversions.sh

## we have $filenames[] populated now, safe to count
total_jobs=${#filenames[@]}
job_num=1

#I'm not sure I'll need this, but this is the location - before picking
if [[ "$MODE" == "MPG" || "$MODE" == "MP3" ]]; then ENABLE_QUALITY=0; else ENABLE_QUALITY=1; fi

if [[ "$ENABLE_QUALITY" == "1" ]]; then
    echo "DEBUG: Mode is $MODE, Enable Quality: $ENABLE_QUALITY" >> $debugfile
    # pick_rez_quality should watch for preset_rez now
    source ./pick_rez_quality.sh

    #crop4x3param="crop=iw/4*3:ih,"  #inside the -vf
    #cropTBparam="crop=in_w:in_h*0.5773:0:in_h*0.2216,"
    if [[ "$aspect" == "4x3" ]]; then
        echo Found aspect as 4x3 in $LINENO >> $debugfile
        cropstring="$crop4x3param"
    else
        echo Aspect isn't 4x3, it's $aspect in $LINENO >> $debugfile
    fi

    if [[ "$aspect" == "TopBotCrop" ]]; then
        cropstring="$cropTBparam"
        echo Found aspect as TopBotCrop in $LINENO >> $debugfile
    else
        echo Aspect isn't TopBotCrop, it's $aspect in $LINENO >> $debugfile
    fi
fi

speak() {
    local message="$1"
    if [[ $input == '1' ]]; then
        return 0
    fi
    if [ -e "DND" ]; then
        echo "DND is ON."
        mpg123 -n 50 "$HOME/bellding.mp3"
        return 0  # You can specify a status if needed
    fi
        
    #echo "(SayText \"$message\")" | festival --pipe
    pico2wave -w=output.wav "$message"
    aplay output.wav
    rm output.wav
}

update_title() {
    local message="$1"
    echo -ne "\033]0;$message\007"
}


generate_progress_bar() {
    local current=$1
    local total=$2
    local bar=""
    
    # Define block characters
    local filled_char="▆"  # Full Block
    local empty_char=" "   # Light Shade was ░
    
    # Calculate the number of filled and empty blocks
    local filled_length=$(( current * 2 ))  # 2 blocks per job
    local total_length=$(( total * 2 ))    # Total blocks
    
    # Ensure filled_length does not exceed total_length
    if [ "$filled_length" -gt "$total_length" ]; then
        filled_length=$total_length
    fi
    
    # Generate filled blocks
    for ((i=1; i<=filled_length; i++)); do
        bar+="$filled_char"
    done
    
    # Generate empty blocks
    for ((i=filled_length+1; i<=total_length; i++)); do
        bar+="$empty_char"
    done
    
    echo "[$bar]"
}



my-notify () {
    local message="$1"
    USE_SYSTEM_NOTIFY="0"  # Correct variable assignment
    statusfile="$HOME/status.log"  # Expand home directory properly
    this_script=$(basename "$script_name" .sh)
    
    # Generate the progress bar
    local progress_bar=$(generate_progress_bar "$job_num" "$total_jobs")
       
    if [ "$USE_SYSTEM_NOTIFY" == "1" ]; then
        notify-send -t 3000 "$message"  
    else 
        ## before counter added: echo -e "${color} $this_script: \033[0m $message" >> "$statusfile"
        if [[ "$total_jobs" -gt 1 ]]; then
            # Add "(job_num of total_jobs)" after the script name
            echo -e "${color} $this_script : \033[0m $progress_bar $message" >> "$statusfile"
        else
            echo -e "${color} $this_script: \033[0m $message" >> "$statusfile"
        fi        
    fi
}

## we're running, let's bring up ffdash if not already active
FFDASH_PATH="$HOME/bin/launch_ffdash.sh"
FFFLAG="$HOME/bin/ffdash_running.flag"

# --- Launch ffdash monitor if not active ---
if [[ -f "$FFFLAG" ]]; then
    # Check if the PID inside flag (if stored) or any ffdash process is still alive
    if pgrep -f "launch_ffdash.sh" >/dev/null 2>&1; then
        echo "ffdash already running."
    else
        echo "ffdash flag found but process not active — relaunching."
        rm -f "$FFFLAG"
    fi
fi

if [[ ! -f "$FFFLAG" ]]; then
    echo "Launching ffdash monitor..."
    if command -v mate-terminal >/dev/null 2>&1; then
        mate-terminal --title="FFdash" -- bash -c "'$FFDASH_PATH'"
    elif command -v gnome-terminal >/dev/null 2>&1; then
        gnome-terminal --title="FFdash" -- bash -c "'$FFDASH_PATH'" 2>/dev/null
    else
        xterm -T "FFdash" -e "$FFDASH_PATH"
    fi
    sleep 1
fi




# Function to ask user if they want to move source files to a folder
move_files() {
    # Extract the extension from the first input file (original extension) and convert it to uppercase
    ext=$(echo "${filenames[0]##*.}" | tr '[:lower:]' '[:upper:]')
    folder_name="orig ${ext} files"

    # Speak the question (if speak function is active)
    speak "Move the source files to a new folder?"

    # Prepare prompt appendage if folder already exists
    if [ -d "$folder_name" ]; then
        # count only regular files in the folder (not directories)
        fcount=$(find "$folder_name" -maxdepth 1 -type f | wc -l)
        appendage=" ($fcount files in \"$folder_name\" folder now)"
    else
        appendage=""
    fi

    while true; do
        # Print the prompt on the same line and wait for user input
        short_desc="(${ext} -> ${targetEXT})"
        # Add colored appendage
        if [ -n "$appendage" ]; then
            appendage_colored=$'\e[47m\e[30m'"$appendage"$'\e[0m\e[1;97;44m'
        else
            appendage_colored=""
        fi

        read -rsn1 -p $'\e[1;97;44m '"${short_desc}"' - Move files into "'"${folder_name}"'" folder'"${appendage_colored}"'? (Y/N) : ' input
        echo -e "\e[0m"  # Ensures prompt is reset correctly

        echo # Move to the next line after input is read
        case "$input" in
            [Yy])
                echo "Moving files to $folder_name..."
                mkdir -p "$folder_name" # Create the folder if it doesn't exist
                for inputfile in "${filenames[@]}"; do
                    mv "$inputfile" "$folder_name/"
                done
                echo "Files moved to $folder_name."
                break
                ;;
            [Nn])
                echo "Files will not be moved."
                break
                ;;
            *)
                # Beep for invalid input
                echo -ne '\007'  # Terminal beep sound
                echo "Invalid input. Please press Y or N."
                ;;
        esac
    done
}

# ========== PARAMS ========================
MPGbitrate="1800k"
##params1mpg="-f mpeg -r 29.97 -write_tmcd 0 -pix_fmt yuv420p"
##params3mpg="-b:v $MPGbitrate -maxrate $MPGbitrate -minrate $MPGbitrate -bufsize 32768k -sws_flags lanczos -s 720:480 -aspect 16:9 -g 15 -ar 48000 -ab 128k"
params1mpg="-f mpeg -r 29.97 -pix_fmt yuv420p"
params3mpg="-b:v 1800k -maxrate 1800k -minrate 1800k -bufsize 32768k -sws_flags lanczos+accurate_rnd+full_chroma_int -s 720x480 -aspect 16:9 -g 12 -bf 2 -qmin 2 -qmax 8 -ar 48000 -ac 2 -ab 160k"


##params1mp4="-vcodec libx264 -preset faster -pix_fmt yuv420p"
params1mp4="-fflags +genpts -avoid_negative_ts make_zero -ignore_editlist 1 -vcodec libx264 -preset faster -pix_fmt yuv420p"

##params3mp4="$scale $aspstring -max_muxing_queue_size 9999 -r 29.97 $qparam -tune film -ar 48000 -c:a aac -b:a 256k"
##params3mp4="$scale $aspstring -max_muxing_queue_size 9999 $qparam -tune film -c:a aac -b:a 256k -ar 48000 -ac 2"
params3mp4="$scale $aspstring -max_muxing_queue_size 9999 $qparam -tune film -c:a aac -b:a 256k -ar 48000 -ac 2 -af asetpts=PTS-STARTPTS,aresample=48000"


params1mkv="-c:v libx265 -preset superfast $qparam"
params3mkv="$scale $aspstring -max_muxing_queue_size 9999 -c:a aac -b:a 256k -ar 48000 -ac 2 -af asetpts=PTS-STARTPTS,aresample=48000"

MP3bitrate="96k"
params1mp3="-f mp3 -vn -ar 48000 -ac 2 -ab $MP3bitrate"
params3mp3=""   # no more needed

## copy params over to the generic params1/3 variables based on the MODE we're in
if [ "$MODE" == "MP4" ]; then
    params1="$params1mp4"
    params3="$params3mp4"
elif [ "$MODE" == "MKV" ]; then
    params1="$params1mkv"
    params3="$params3mkv"
elif [ "$MODE" == "MPG" ]; then
    params1="$params1mpg"
    params3="$params3mpg"
elif [ "$MODE" == "MP3" ]; then
    params1="$params1mp3"
    params3="$params3mp3"
else
    echo "Unknown mode: $MODE in $LINENO"
    kill -- "$PPID"
    exit 1
fi


#echo "params1=$params1" >> "$debugfile"
#echo "params3=$params3" >> "$debugfile"
# ==========================================

# Extract the filenames without extension
for i in "${!filenames[@]}"; do
    inputfile="${filenames[$i]}"
    
    basename=$(basename "$inputfile" | sed 's/\.[^.]*$//')
    echo basename is $basename. >> $debugfile
     # Conditional output filename based on the mode
        if [[ "$MODE" == "MPG" || "$MODE" == "MP3" ]]; then
            # For MPG, only include brightness adjustments or crops, no resolution or quality
            outfile="${basename}${brightext}.${targetEXT}"
        else
            # For other modes (MP4, MKV), include resolution and quality
            outfile="${basename}.${resolution}.${quality}.${targetEXT}"
        fi
    echo "Processing file: $inputfile" >> "$debugfile"

    # Change the title of the terminal window
    # Extract the duration of the input video using ffmpeg (trim the fractional second)
    video_duration=$(ffmpeg -i "$inputfile" 2>&1 | grep "Duration" | cut -d ' ' -f 4 | sed 's/\.[0-9]*,//')

    # Change the title of the terminal window to include the runtime
    update_title "${basename} → $MODE (${video_duration}) $dBtitle"


    # Run ffmpeg with the same parameters
    echo Calling speak function.... >> $debugfile
    if [ "$MODE" == "MPG" ]; then
        speak "Mpeg one conversion started on $basename"
    elif [[ $MODE == "MP3" ]]; then
        speak "Emm pee three conversion started on $basename"
    else
        speak "$MODE conversion started on $basename"
    fi
    echo
    my-notify "${inputfile##*/} -> $outfile ..."

    echo Brightfix=$brightfix. and scale=$scale. and aspstring=$aspstring. >> "$debugfile"

    # Concatenate the non-empty filter strings
    filter_chain=""
    ## don't put scale in filterchain   [[ -n "$scale" ]] && filter_chain+="$scale"
    [[ -n "$cropstring" ]] && filter_chain+="$cropstring"       ## crop goes in -vf
    [[ -n "$brightfix" ]] && filter_chain+="$brightfix, yadif"  ## last thing in -vf to close the comma list

    ##if crop or brightening is used, cram ext additions into $brightext
    if [[ -n "$cropstring" ]]; then
        brightext=".crop"
        echo Cropstring populated, adding .crop to ext in $LINENO >> $debugfile
    else
        echo Cropstring NOT populated, no .crop added in $LINENO >> $debugfile
    fi

    if [[ -n "$brightfix" ]]; then
        brightext+=".B1"
        echo Brightfix populated, adding .B1 to ext in $LINENO >> $debugfile
    else
        echo Brightfix NOT populated, no .B1 added in $LINENO >> $debugfile
    fi

    # Debugging the filter_chain before adding yadif
    echo "Filter chain: '$filter_chain'" >> "$debugfile"

    # --- unified ffmpeg launcher with /tmp/ffdash params/progress tracking ---

    # rename outfile name if filters are active
    if [[ -n "$filter_chain" ]]; then
        echo "#with vf"
        if [ "$MODE" == "MPG" ]; then
            outfile="${basename}$brightext.$targetEXT"
        else
            outfile="${basename}.${resolution}.${quality}$brightext.$targetEXT"
        fi
        echo "Filters enabled, changed name to $outfile" >> "$debugfile"
    else
        echo "#without vf"
        echo "No filters enabled, name stays $outfile" >> "$debugfile"
    fi

    # --- setup progress + params in /tmp/ffdash ---
    mkdir -p /tmp/ffdash
    jobbase="${outfile##*/}"
    paramfile="/tmp/ffdash/${jobbase}.params"
    progressfile="/tmp/ffdash/${jobbase}.progress"

    # get runtime string the same way as your titlebar
    video_duration=$(ffmpeg -i "$inputfile" 2>&1 | grep "Duration" | cut -d ' ' -f 4 | sed 's/,//' | sed 's/\.[0-9]*$//')
    if [[ -z "$video_duration" ]]; then
        duration_seconds=0
    else
        IFS=: read -r h m s <<<"$video_duration"
        duration_seconds=$(awk "BEGIN {print ($h * 3600) + ($m * 60) + $s}")
    fi

    # write params file
cat >"$paramfile" <<EOF
src=$inputfile
out=$outfile
duration=$duration_seconds
job=$job_num
total=$total_jobs
EOF

    echo "Progress file: $progressfile" >> "$debugfile"
    echo "Param file: $paramfile (duration=${duration_seconds}s)" >> "$debugfile"

    # --- build ffmpeg command dynamically ---
    ff_cmd=( ffmpeg -i "$inputfile" )

    # add first param group
    [[ -n "$params1" ]] && ff_cmd+=( $params1 )

    # add filters if needed
    # Always force CFR 29.97 in the video filtergraph (reliable)
    if [[ -n "$filter_chain" ]]; then
        ff_cmd+=( -vf "${filter_chain},fps=30000/1001" )
    else
        ff_cmd+=( -vf "fps=30000/1001" )
    fi

    # add audio / rest of params
    [[ -n "$params3" ]] && ff_cmd+=( $params3 )

    # add progress + output
    ff_cmd+=( -progress "$progressfile" "$outfile" )

    echo "EXECUTING : ${ff_cmd[*]}" >> "$debugfile"

    # --- run ffmpeg ---
    "${ff_cmd[@]}"
    ffmpeg_status=$?

    # --- cleanup ---
    rm -f "$progressfile" "$paramfile"

    # notify on result
    if [ $ffmpeg_status -ne 0 ]; then
        echo "FFmpeg failed with exit code $ffmpeg_status" >> "$debugfile"
        my-notify " \033[41;97m FFmpeg FAILED!!! \033[0m exit code $ffmpeg_status for $outfile"

        # Ask user if they want to continue or abort the batch
        echo
        echo -e "\e[1;41;97m ⚠️  FFmpeg error for $outfile (exit $ffmpeg_status) \e[0m"
        echo "FFmpeg encountered an error."
        echo
        echo "Press C to continue with the next file, or Q to abort the batch."
        echo

        while true; do
            read -rsn1 -p $'\e[1;97;44m [C]ontinue  /  [Q]uit \e[0m: ' choice
            echo
            case "$choice" in
                [Cc])
                    echo "Continuing to next file..." >> "$debugfile"
                    break
                    ;;
                [Qq])
                    echo "User chose to abort batch after FFmpeg error." >> "$debugfile"
                    echo "Aborting batch..."
                    kill -- "$PPID"
                    exit "$ffmpeg_status"
                    ;;
                *)
                    echo "Invalid choice. Press C or Q."
                    ;;
            esac
        done
    fi

    # --- only runs if FFmpeg succeeded ---
    echo "FFmpeg succeeded" >> "$debugfile"
    my-notify "$outfile \033[42;97m finished \033[0m"

    # --- end unified ffmpeg launcher ---

    
    # Increment the job counter
    job_num=$((job_num + 1))
    
done

# --- Only reach here if all FFmpeg jobs succeeded ---
echo "All jobs finished successfully" >> "$debugfile"
my-notify "Batch complete"
if [ "$MODE" != "MP3" ]; then
    update_title "*** DONE *** ${basename}"
    # Avoid double ding with move_files prompt
    if [[ ! -e "DND" ]]; then
        speak "All jobs finished"
    fi
    move_files
else
    # MP3 mode has no move_files; safe to ding once here
    speak "All jobs finished"
fi

# If we launched from double-click, exit the terminal cleanly unless there's post minilauncher biz waiting (joseph cleanup)
if from_terminal; then
    echo "All conversions completed. Closing window..."
    sleep 1

    if [[ "${SKIP_MP4KILLER:-0}" -eq 1 ]]; then
        # Debug or chained mode — skip killing terminal
        safe_return 0
    else
        # Normal mode — close the terminal completely
        kill -- "$PPID"
        exit 0
    fi
else
    safe_return 0
fi