#!/bin/bash
#
# Copyright (C) 2023-2025 Eugene 'Vindex' Stulin
# Distributed under the Boost Software License 1.0.

set -eu -o pipefail

ENCLIB=libx265  # or hevc_nvenc
readonly DEFAULT_CRF=18  # constant rate factor


# The function prints help information.
Print_Help() {
cat <<EndOfHelp
This script re-encodes the specified video file into
a new file using the HEVC (H.265) codec.

Usage:
    ${0##*/} <input/path> [--crf <CRF>] [--nvidia] [-o <file>]

Default encoder: libx265.
Flag '--nvidia' enables hevc_nvenc instead of libx265.
Default CRF (constant rate factor) is 18.
The '-o' option specifies the path to the output file or directory.

Example:
    ${0##*/} video.mp4 --crf 20
Result will be saved to "video.x265.crf20.mp4".

Help and version:
    ${0##*/} --help|-h
    ${0##*/} --version|-v
EndOfHelp
}


# The function prints the script version.
Print_Version() {
    local VERSION_SCRIPT="$(dirname -- "${BASH_SOURCE[0]}")/bbsi-version"
    if [[ ! -x "$VERSION_SCRIPT" ]]; then
        echo "Unknown version."
    else
        "$VERSION_SCRIPT"
    fi
}


# The function finds and prints the index of the first video stream.
# It prints the index among video streams for use with -c:v:X.
# Parameters:
#     $1 - path to video file.
# The function may print an empty result.
Get_Main_Video_Stream_Index() {
    local VIDEO_TRACKS
    if ! VIDEO_TRACKS=$(ffprobe -v error -select_streams v \
        -show_entries stream=index,codec_name -of csv=p=0 "$1"); then
        echo "Failed to analyze file." >&2
        return 1
    fi
    local INDEX=$(grep -n -v mjpeg <<< "$VIDEO_TRACKS" | cut -d: -f1 | head -1)
    if [[ -n $INDEX ]]; then
        ((INDEX--))
        echo $INDEX
    fi
}


# The function finds and prints the index of the first MJPEG stream.
# It prints the index among video streams for use with -c:v:X.
# Parameters:
#     $1 - path to video file.
# The function may print an empty result.
Get_MJPEG_Stream_Index() {
    local VIDEO_TRACKS
    if ! VIDEO_TRACKS=$(ffprobe -v error -select_streams v \
        -show_entries stream=index,codec_name -of csv=p=0 "$1"); then
        echo "Failed to analyze file." >&2
        return 1
    fi
    local INDEX=$(grep -n mjpeg <<< "$VIDEO_TRACKS" | cut -d: -f1 | head -1)
    if [[ -n $INDEX ]]; then
        ((INDEX--))
        echo $INDEX
    fi
}


# The function prints information about wrong usage to stderr.
Wrong_Usage() {
    echo "Wrong usage. See: ${0##*/} --help" >&2
}


# parse command line arguments
CUSTOM_PATH=""
CRF="$DEFAULT_CRF"
TEMP=$(getopt -o 'o:hv' --long 'help,version,nvidia,crf:' -n "$0" -- "$@")
eval set -- "$TEMP"
while true; do
    case "$1" in
        '-h'|'--help')
            # getopt adds "--" as argument
            if [[ $# -ne 2 ]]; then
                Wrong_Usage
                exit 1
            fi
            Print_Help
            exit 0
            ;;
        '-v'|'--version')
            # getopt adds "--" as argument
            if [[ $# -ne 2 ]]; then
                Wrong_Usage
                exit 1
            fi
            Print_Version
            exit 0
            ;;
        '-o')
            CUSTOM_PATH="$2"
            shift 2
            continue
            ;;
        '--crf')
            CRF="$2"
            if [[ ! "$CRF" =~ ^[0-9]+$ ]]; then
                echo "CRF must be a numeric value." >&2
                exit 1
            fi
            shift 2
            ;;
        '--nvidia')
            shift
            ENCLIB=hevc_nvenc
            ;;
        '--')
            shift
            break
            ;;
        *)
            echo "Internal error" >&2
            exit 1
    esac
done

if [[ $# -eq 0 || $# -gt 2 ]]; then
    Wrong_Usage
    exit 1
fi
readonly MEDIAFILE="$1"

PROBE=$(ffmpeg -hide_banner -encoders | grep "$ENCLIB")
if [[ -z "$PROBE" ]]; then
    echo "Encoder $ENCLIB is not available." >&2
    exit 1
fi

if [[ -z "$CUSTOM_PATH" || -d "$CUSTOM_PATH" ]]; then
    PATH_WITHOUT_EXT="${MEDIAFILE%.*}"
    EXT="${MEDIAFILE##*.}"
    EXT="${EXT,,}"  # lower case
    if [[ "$EXT" == mp4 || "$EXT" == m4v ]]; then
        FINAL_EXT=mp4
    elif [[ "$EXT" == mov ]]; then
        FINAL_EXT=mov
    else
        FINAL_EXT=mkv
    fi
    [[ "$ENCLIB" == libx265 ]] && ENC_INDICATOR=x265 || ENC_INDICATOR=$ENCLIB
    [[ $CRF -ne $DEFAULT_CRF ]] && CRF_SUFF=crf${CRF}. || CRF_SUFF=""
    ENCODED_FILE="${PATH_WITHOUT_EXT}.${ENC_INDICATOR}.${CRF_SUFF}${FINAL_EXT}"
    if [[ -d "$CUSTOM_PATH" ]]; then
        ENCODED_FILE="${CUSTOM_PATH}/${ENCODED_FILE}"
    fi
else
    mkdir -p "$(dirname "$CUSTOM_PATH")"
    ENCODED_FILE="${CUSTOM_PATH}"
fi

VI=$(Get_Main_Video_Stream_Index "$MEDIAFILE")  # Video Index
if [[ -z $VI ]]; then
    echo "The file most likely does not contain video." >&2
    Wrong_Usage
    exit 1
fi
MI=$(Get_MJPEG_Stream_Index "$MEDIAFILE")  # MJPEG Index
if [[ -n "$MI" ]]; then
    OPTS_FOR_MJPEG="-map 0:v:$MI -c:v:0 copy -disposition:v:0 attached_pic"
    POS_FOR_VIDEO=1
else
    OPTS_FOR_MJPEG=""
    POS_FOR_VIDEO=0
fi
OPTS_FOR_VIDEO="-map 0:v:$VI -c:v:$POS_FOR_VIDEO $ENCLIB"
if [[ "$ENCLIB" == libx265 ]]; then
    OPTS_FOR_VIDEO+=" -crf $CRF"
elif [[ "$ENCLIB" == hevc_nvenc ]]; then
    OPTS_FOR_VIDEO+=" -cq $CRF -rc vbr"
fi
OPTS_FOR_OTHER="-map 0:a? -c:a copy -map 0:s? -c:s copy"

OPTIONS="$OPTS_FOR_MJPEG $OPTS_FOR_VIDEO $OPTS_FOR_OTHER"


Interrupt_Execution() {
    set +x
    echo "The script has been interrupted." 2>&1
    rm -f "$ENCODED_FILE"
    exit 1
}
trap Interrupt_Execution ABRT INT QUIT TERM


# re-encode
set -x
if ! ffmpeg -hide_banner -i "$MEDIAFILE" $OPTIONS "$ENCODED_FILE" -y; then
    set +x
    Interrupt_Execution
fi
set +x

echo "Saved as $ENCODED_FILE."
