#!/usr/bin/env bash
set -euo pipefail

# License MIT
# Authors 2024
#   thunfisch
#   iiidefix
#   l3d

# This script is designed to help you automatically select the correct intro
# and outro files (prerendered with built-in fades) and the correct chunks
# of a chunked recording (e.g. OBS with automatic split every 5 minutes).
# It will open the first and last chunk in mpv if you want, so that you
# can find the start and end points (in seconds), you manually need to transfer
# them into the script for now (no idea how to convince mpv to output it).
# After that the script will render the introfile with the first chunk and the
# last chunk with the outrofile, and then in the last step assemble everything
# into the final recording with audio normalization.
# It will also assume, that the intros and outros are pictures...

# This script requires you to have ffmpeg and fzf installed.
which ffmpeg >/dev/null || (echo "Please install ffmpeg" ; exit 1)
which fzf >/dev/null || (echo "Please install fzf" ; exit 1)
which mpv >/dev/null || (echo "Please install mpv" ; exit 1)

INTROS_PATH="$HOME/Dokumente/syncthing/intros"
OUTROS_PATH="$HOME/Dokumente/syncthing/outros"
CHUNKS_PATH="$HOME/Dokumente/syncthing/"
OUTPUT_PATH="$HOME/Videos/WK25/rendered"

# temp dir
WORKDIR=$(mktemp -d)
function finish {
  rm -r "$WORKDIR"
}
trap finish EXIT

# Select the appropriate files
SELECTED_INTRO="$(find  "$INTROS_PATH" -type f | sort --reverse --human-numeric-sort | fzf --delimiter / --with-nth -1 --prompt "Intro File:")"
SELECTED_OUTRO="$(find  "$OUTROS_PATH" -type f | sort --reverse --human-numeric-sort | fzf --delimiter / --with-nth -1 --prompt "Outro File:")"
SELECTED_CHUNKS="$(find "$CHUNKS_PATH" -type f | sort --reverse | fzf --delimiter / --with-nth -1 -m --prompt "Video Chunks (use tab to select multiple):" | sort )"
readarray -t CHUNKS_ARRAY < <(echo "$SELECTED_CHUNKS")

FOOBARWTF="$(basename "$SELECTED_INTRO")"
DEFAULT_OUTPUT_NAME="${FOOBARWTF%.*}"
read -p "Please enter a name for the outputfile (path and extension will be added automatically): " -i "$DEFAULT_OUTPUT_NAME" -e OUTPUT_NAME

# Function to convert image to video with audio
convert_image_to_video() {
    local image_file="$1"
    local output_video="$2"
    local duration="$3"
    local fade_in="$4"
    local fade_out="$5"

    ffmpeg -loop 1 -t "$duration" -i "$image_file" -f lavfi -i anullsrc=r=48000:cl=stereo \
        -vf "fade=t=in:st=0:d=$fade_in,fade=t=out:st=$(($duration - $fade_out)):d=$fade_out,format=yuv420p" \
        -r 50 -c:v libx264 -c:a aac -b:a 192k -t "$duration" "$output_video"
}


# find the start-offset for the first chunk
read -p "Do you want to play the first chunk ${CHUNKS_ARRAY[0]} to find the start-offset? (y/n) [n]: " PLAY_FIRST_CHUNK
PLAY_FIRST_CHUNK="${PLAY_FIRST_CHUNK:-n}"

[[ "$PLAY_FIRST_CHUNK" == "y" ]] && mpv "${CHUNKS_ARRAY[0]}" --osd-level=3 --osd-status-msg='${=time-pos}' --really-quiet

read -p "Enter the start-offset in seconds for the first chunk ${CHUNKS_ARRAY[0]} [0]: " START_OFFSET
START_OFFSET="${START_OFFSET:-0}"


# find the end-offset for the last chunk
read -p "Do you want to play the last chunk ${CHUNKS_ARRAY[-1]} to find the end-offset? (y/n) [n]: " PLAY_LAST_CHUNK
PLAY_LAST_CHUNK="${PLAY_LAST_CHUNK:-n}"

[[ "$PLAY_LAST_CHUNK" == "y" ]] && mpv "${CHUNKS_ARRAY[-1]}" --osd-level=3 --osd-status-msg='${=time-pos}' --really-quiet

read -p "Enter the end-offset in seconds for the last chunk ${CHUNKS_ARRAY[0]} [1]: " END_OFFSET
END_OFFSET="${END_OFFSET:-1}"

# Check if intro is an image and convert if necessary
EXT_INTRO="${SELECTED_INTRO##*.}"
if [[ "$EXT_INTRO" == "png" || "$EXT_INTRO" == "jpg" || "$EXT_INTRO" == "jpeg" ]]; then
    INTRO_VIDEO="$WORKDIR/intro_converted.mkv"
    convert_image_to_video "$SELECTED_INTRO" "$INTRO_VIDEO" 4 1 1
    SELECTED_INTRO="$INTRO_VIDEO"
fi

# Check if outro is an image and convert if necessary
EXT_OUTRO="${SELECTED_OUTRO##*.}"
if [[ "$EXT_OUTRO" == "png" || "$EXT_OUTRO" == "jpg" || "$EXT_OUTRO" == "jpeg" ]]; then
    OUTRO_VIDEO="$WORKDIR/outro_converted.mkv"
    convert_image_to_video "$SELECTED_OUTRO" "$OUTRO_VIDEO" 6 1 1
    SELECTED_OUTRO="$OUTRO_VIDEO"
fi

cat <<EOT



I will be rendering with the following configuration:
    + Selected Intro: ${SELECTED_INTRO}
    + Selected Outro: ${SELECTED_OUTRO}
    + Video Chunks:
        + First chunk: ${CHUNKS_ARRAY[0]}
          Starting at second ${START_OFFSET}
        + Last chunk: ${CHUNKS_ARRAY[-1]}
          Ending at second ${END_OFFSET}
        + All chunks:
EOT
for index in "${!CHUNKS_ARRAY[@]}"
do
    echo "            + $index: ${CHUNKS_ARRAY[index]}"
done
echo "Export: ${OUTPUT_PATH}/${OUTPUT_NAME}.mkv"
echo ; echo ; echo
read -p "Do you want to proceed with this configuration? (y/n) [y]" PROCEED
PROCEED="${PROCEED:-y}"

[[ "$PROCEED" == "y" ]] || (echo "aborting"; exit 1)
echo "doing ffmpeg things here"

# combine the videos...
ARRAY_LENGTH="${#CHUNKS_ARRAY[@]}"
if [[ ${ARRAY_LENGTH} -lt 2 ]]
then
    echo "Too few chunks, this script can't handle this yet. Please do that on your own."
    exit 1
fi

# STEP 1
# temp dir
WORKDIR=$(mktemp -d)
function finish {
  rm -r "$WORKDIR"
}
trap finish EXIT

# STEP 2
# introfile with first chunk and crossfade encode
ffmpeg -i "$SELECTED_INTRO" -ss "$START_OFFSET" -i "${CHUNKS_ARRAY[0]}" \
-filter_complex \
"[1:v:0]fade=t=in:st=0:d=0.2[x];
 [1:a:0]afade=t=in:st=0:d=0.2[a];
 [0:v:0][0:a:0]
 [x][a]
 concat=n=2:v=1:a=1
 [v0][a0]" \
-map '[v0]' -map '[a0]' \
-c:a aac -b:a 192k \
-c:v libx264 -threads 0 -pix_fmt yuv420p -crf 18 -profile:v high -level 4.1 -disposition default \
-metadata:s:a:0 language=native \
"${WORKDIR}/introcombined.mkv"

# STEP 3
# outrofile with last chunk and corssface encode
ffmpeg -i "$SELECTED_OUTRO" -t "$END_OFFSET" -i "${CHUNKS_ARRAY[-1]}" \
-filter_complex \
"[1:v:0]fade=t=out:st=$(($END_OFFSET - 1)):d=1.0[x];
 [1:a:0]afade=t=out:st=$(($END_OFFSET - 1)):d=1.0[a];
 [x][a]
 [0:v:0][0:a:0]
 concat=n=2:v=1:a=1
 [v0][a0]" \
-map '[v0]' -map '[a0]' \
-c:a aac -b:a 192k \
-c:v libx264 -threads 0 -pix_fmt yuv420p -crf 18 -profile:v high -level 4.1 -disposition default \
-metadata:s:a:0 language=native \
"${WORKDIR}/outrocombined.mkv"

# STEP 4
# encoded intro+outro and all chunks in between with c:v copy and audio dynnorm + encode

CHUNKLIST="${WORKDIR}/chunklist.txt"

echo "file '${WORKDIR}/introcombined.mkv'" > "$CHUNKLIST"

FFMPEG_CONCAT_CHUNKS=""
if [[ ${ARRAY_LENGTH} -gt 2 ]]
then
    for index in $(seq 1 $(( ${ARRAY_LENGTH} - 2 )) )
    do
        FFMPEG_CONCAT_CHUNKS="${FFMPEG_CONCAT_CHUNKS}|${CHUNKS_ARRAY[index]}"
        echo "file '${CHUNKS_ARRAY[index]}'" >> "$CHUNKLIST"
    done
fi

echo "file '${WORKDIR}/outrocombined.mkv'" >> "$CHUNKLIST"

ffmpeg \
    -f concat -safe 0 -i "$CHUNKLIST" \
    -af dynaudnorm \
    -c:v copy \
    -c:a aac -b:a 192k \
    -metadata:s:a:0 language=native \
    "${OUTPUT_PATH}/${OUTPUT_NAME}.mkv"

echo "Video Exported to ${OUTPUT_PATH}/${OUTPUT_NAME}.mkv"