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

set -eu -o pipefail

# The function prints help information.
Print_Help() {
cat <<EndOfHelp
The script saves some piece from mediafile (from time1 to time2).

Usage:
    ${0##*/} <input/video/path> <time1> [<time2>] [-o <output_path>] [--h264]
Time format: HH:MM:SS (hours, minutes, seconds). Hours are optional.
Output file name without extension by default: "output" (extension
depends on source video).
For positioning accuracy, recording is used.
The '--h264' flag enables libx264 codec for re-encoding (by default, libx265).

Example:
    ${0##*/} video.mp4 02:10 03:35
Result are saved to output.mp4 (extension depends on source video).

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 prints information about wrong usage to stderr.
Incorrect_Usage() {
    echo "Incorrect usage. See: ${0##*/} --help" >&2
}


# parse command line arguments
if [[ $# -eq 1 ]]; then
    if [[ "$1" == "-h" || "$1" == "--help" ]]; then
        Print_Help
        exit 0
    elif [[ "$1" == "-v" || "$1" == "--version" ]]; then
        Print_Version
        exit 0
    else
        Incorrect_Usage
        exit 1
    fi
fi
USE_H264=FALSE
CUSTOM_PATH=""
OPT_TEMP=$(getopt -o 'o:' --long 'h264' -n "$0" -- "$@")
if [[ $? -ne 0 ]]; then
    Incorrect_Usage
    exit 1
fi
eval set -- "$OPT_TEMP"
while true; do
    case "$1" in
        '-o')
            CUSTOM_PATH="$2"
            shift 2
            continue
            ;;
        '--h264')
            USE_H264=TRUE
            shift 1
            continue
            ;;
        '--')
            shift
            break
            ;;
        *)
            echo "Internal error" >&2
            exit 1
    esac
done

if [[ $# -eq 0 ]]; then
    Incorrect_Usage
    exit 1
fi

readonly FILE="$1"
readonly START="$2"
readonly FINISH="$3"


Check_Time_Format() {
    local TF='^([0-9]{1,2}:)?[0-5]?[0-9]:[0-5][0-9](\.[0-9]{1,5})?$'
    if ! [[ "$1" =~ $TF ]]; then
        Incorrect_Usage
        exit 1
    fi
}
Check_Time_Format "${START}"
[[ -n ${FINISH} ]] && Check_Time_Format "${FINISH}"


Show_Duration() {
    ffprobe -v error -show_entries format=duration \
        -of default=noprint_wrappers=1:nokey=1 "$1"
}


Calc_Seconds_From_Time_String() {
    local TS="$1"
    local SHORT_FORMAT='^[0-5]?[0-9]:[0-5][0-9](\.[0-9]{1,5})?$'  
    local LONG_FORMAT='^[0-9]{1,2}:[0-5]?[0-9]:[0-5][0-9](\.[0-9]{1,5})?$'
    local SECS=0
    local COUNTDOWN=0
    if [[ "$TS" =~ $SHORT_FORMAT ]]; then  # like 03:25
        COUNTDOWN=2
    elif [[ "$TS" =~ $LONG_FORMAT ]]; then  # like 01:03:25
        COUNTDOWN=3
    else
        Incorrect_Usage
        exit 1
    fi
    local ELEMS=$(echo $TS | sed 's/:/ /g')  # "MM:SS" -> "MM SS"
    for ELEM in $ELEMS; do
        if [[ $COUNTDOWN -eq 3 ]]; then
            ((SECS+=ELEM*60*60))
        elif [[ $COUNTDOWN -eq 2 ]]; then
            ((SECS+=ELEM*60))
        elif [[ $COUNTDOWN -eq 1 ]]; then
            SECS=$(echo "$SECS + $ELEM" | bc)
        fi
        ((COUNTDOWN--))
    done
    echo "$SECS"
}


DURATION=$(Show_Duration "$FILE")
T1=$(Calc_Seconds_From_Time_String "$START")
T2=-1
if [[ "$FINISH" != "" ]]; then
    T2=$(Calc_Seconds_From_Time_String "$FINISH")
fi
ERROR=FALSE
if [[ 1 == $(echo "$T1 > $DURATION" | bc) ]]; then
    echo "Too big timestamp: $START." >&2
    ERROR=TRUE
fi
if [[ $T2 -ne -1 && 1 == $(echo "$T2 > $DURATION" | bc) ]]; then
    echo "Too big timestamp: $FINISH." >&2
    ERROR=TRUE
fi
if [[ $ERROR == TRUE ]]; then
    echo "The playback duration is shorter than the specified timestamp." >&2
    Incorrect_Usage
    exit 1
fi


[[ -n "$CUSTOM_PATH" ]] && mkdir -p "$(dirname "$CUSTOM_PATH")"
EXT="${FILE##*.}"
[[ -z "$CUSTOM_PATH" ]] && OUTFILE="output.$EXT" || OUTFILE="$CUSTOM_PATH"


if [[ -f "$OUTFILE" ]]; then
    if [[ "$(realpath "$FILE")" == "$(realpath "$OUTFILE")" ]]; then
        Incorrect_Usage
        exit 1
    fi
    ANSWER=n
    echo "This file exists: $OUTFILE"
    echo -n "Delete it? Y/n >: "
    read ANSWER
    ANSWER=${ANSWER,,}  # to lower case
    if [[ $ANSWER == "y" || $ANSWER == "yes" || $ANSWER == "" ]]; then
        rm -f "$OUTFILE"
    else
        echo "Operation canceled."
        exit 0
    fi
fi


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

FFCMD="ffmpeg -hide_banner"
CODEC=libx265
if [[ $USE_H264 == TRUE ]]; then
    CODEC=libx264
fi
ENC_PARAMS="-map 0:v:0 -c:v:0 $CODEC -crf 18"
OTH_MAP='-map 0:a? -c:a copy -map 0:s? -c:s copy'

# do it
set -x
if [[ -n "$FINISH" ]]; then
    $FFCMD -i "$FILE" -ss "$START" -to "$FINISH" $ENC_PARAMS $OTH_MAP "$OUTFILE"
else
    $FFCMD -i "$FILE" -ss "$START" $ENC_PARAMS $OTH_MAP "$OUTFILE"
fi
CODE=$?
if [[ $CODE -ne 0 ]]; then
    rm -f "$OUTFILE"
    exit $CODE
fi
set +x

echo "Saved as $OUTFILE."
