#!/bin/bash
# Make every folder 0775 and files 0664
umask 0002

# ------------------------------------------------------------------------
# --- SETUP AND USAGE ----------------------------------------------------
# Run it manually for testing:
# bash /data/scripts/video-converter.sh
# ------------------------------------------------------------------------

# ------------------------------------------------------------------------
# --- CONFIGURATION ------------------------------------------------------
# ------------------------------------------------------------------------
SOURCE_DIR="/data/downloads"
OUTPUT_DIR="/data/converted"
HISTORY_FILE="/data/scripts/processed_video_files.log"

VA_DRIVER="/dev/dri/renderD128"
VA_INIT="-vaapi_device $VA_DRIVER"
ENC_HEVC="hevc_vaapi"
ENC_H264="h264_vaapi"

# ------------------------------------------------------------------------
# --- START SCANNING -----------------------------------------------------
# Find all video files (by extension) in SOURCE_DIR and all subfolders
# ------------------------------------------------------------------------
find "$SOURCE_DIR" -maxdepth 5 \( -path "$SOURCE_DIR/incomplete" -o -path "$SOURCE_DIR/logs" \) -prune -o -type f -regex ".*\.\(mkv\|mp4\|m4v\|avi\|mov\|wmv\|webm\|mpg\|mpeg\|ts\|mts\|m2ts\|ogv\)" -print0 | while read -r -d '' file; do

	echo "-- START -- $(date +'%Y-%m-%d %H:%M:%S') ------------------------------------------------"

	filename=$(basename "$file")

	echo "File $filename"

	# --------------------------------------------------------------------
	# --- CHECK HISTORY --------------------------------------------------
	# --------------------------------------------------------------------
	if grep -Fxq "$file" "$HISTORY_FILE"; then
		echo "-> $filename has already been processed"
		continue
	fi

	# --------------------------------------------------------------------
	# --- GET VIDEO ------------------------------------------------------
	# --------------------------------------------------------------------
	video_track=$(ffprobe -v error -select_streams v:0 \
		-show_entries stream=codec_name,height,pix_fmt,bits_per_raw_sample,codec_tag_string:format=bit_rate,duration \
		-of compact=p=0:item_sep='|' "$file" | tr '\n' '|')
	
	IFS='|' read -ra fields <<< "$video_track"

	# Loop through each "key=value" pair in the line
	for field in "${fields[@]}"; do
		case "$field" in
			codec_name=*)
				video_codec="${field#*=}" 
				video_codec=${video_codec:-""}
				;;
			height=*)
				video_height="${field#*=}" 
				video_height=${video_height:-0}
				;;
			pix_fmt=*)
				video_pix_fmt="${field#*=}" 
				video_pix_fmt=${video_pix_fmt:-""}
				;;
			bits_per_raw_sample=*)
				video_bit_depth="${field#*=}" 
				video_bit_depth=${video_bit_depth:-""}
				;;
			codec_tag_string=*)
				video_codec_tag="${field#*=}" 
				video_codec_tag=${video_codec_tag:-""}
				;;
			bit_rate=*)
				video_orig_bitrate="${field#*=}" 
				video_orig_bitrate=${video_orig_bitrate%.*}
				video_orig_bitrate=${video_orig_bitrate:-0}
				;;
			duration=*)
				video_duration="${field#*=}" 
				video_duration=${video_duration%.*}
				video_duration=${video_duration:-0}
				;;
		esac
	done

	# Fix the bit depth
	if [[ -z "$video_bit_depth" || "$video_bit_depth" == "N/A" ]]; then
		# 10-bit patterns (p10, 10le, 10be)
		if [[ "$video_pix_fmt" == *"10p"* || "$video_pix_fmt" == *"10le"* || "$video_pix_fmt" == *"10be"* ]]; then
			video_bit_depth=10
		# 12-bit patterns (p12, 12le, 12be)
		elif [[ "$video_pix_fmt" == *"12p"* || "$video_pix_fmt" == *"12le"* || "$video_pix_fmt" == *"12be"* ]]; then
			video_bit_depth=12
		else
			# Everything else (nv12, yuv420p etc.)
			video_bit_depth=8
		fi
	fi

	echo "Video: $video_codec"
	echo "-> Metadata: height $video_height / pixel format $video_pix_fmt ($video_bit_depth-bit) / bitrate $video_orig_bitrate / duration $video_duration"

	# --- SET UP VIDEO TRACK
	video_cmd=""
	bsf_cmd=""
	hw_accel=""
	video_filter=""

	# Common flags
	video_common="-color_range tv -colorspace bt709 -color_trc bt709 -color_primaries bt709"
		
	# --- CHANGE THE CODEC?
	if [[ "$video_codec" == "hevc" && "$video_bit_depth" -le 10 ]]; then
		# --- KEEP HEVC 8/10-bit

		# If the tag is 'hev1' do some trickery to fix headers
		if [[ "$video_codec_tag" == "hev1" || "$video_codec_tag" == "[0][0][0][0]" ]]; then
			bsf_cmd="-bsf:v hevc_mp4toannexb"
		fi

		video_cmd="$bsf_cmd -c:v copy -tag:v hvc1 -flags +global_header"

		echo "-> Copy hevc $video_bit_depth-bit track"
	elif [[ "$video_codec" == "h264" && "$video_bit_depth" -eq 8 ]]; then
		# --- KEEP H264 8-bit

		# If the tag is 'avc1' do some trickery to fix headers
		if [[ "$video_codec_tag" == "avc1" || "$video_codec_tag" == "[0][0][0][0]" ]]; then
			bsf_cmd="-bsf:v h264_mp4toannexb"
		fi

		video_cmd="-c:v copy -tag:v avc1 -flags +global_header"

		echo "-> Copy h264 8-bit track"
	elif [[ "$video_codec" == "hevc" && "$video_bit_depth" -gt 10 ]]; then
		# --- CONVERT HEVC 12-bit to 10-bit

		# Video conversion with HW decoding -> HW encoding
		video_cmd="$VA_INIT -c:v $ENC_HEVC -global_quality 23 -preset fast -vf format=p010,hwupload $video_common"
		hw_accel="-hwaccel vaapi -hwaccel_device $VA_DRIVER -hwaccel_output_format vaapi"

		echo "-> Converting from hevc 12-bit to 10-bit"
	elif [[ "$video_codec" == "h264" && "$video_bit_depth" -gt 8 ]]; then
		# --- CONVERT H264 10/12-bit to 8-bit

		# Video conversion with HW decoding -> HW encoding
		video_cmd="$VA_INIT -c:v $ENC_H264 -global_quality 20 -preset fast -vf format=nv12,hwupload $video_common"
		hw_accel="-hwaccel vaapi -hwaccel_device $VA_DRIVER -hwaccel_output_format vaapi"

		echo "-> Converting from h264 $video_bit_depth-bit to 8-bit"
	elif [[ "$video_codec" == "av1" || "$video_codec" == "vp9" ]]; then
		# --- CONVERT AV1/VP9 INTO HEVC 8/10-bit

		# Cap the bit depth
		if [[ "$video_bit_depth" -ge 10 ]]; then
			video_filter="-vf format=p010,hwupload"
			target_depth=10
		else
			video_filter="-vf format=nv12,hwupload"
			target_depth=8
		fi

		# Video conversion with SW decoding -> HW encoding
		video_cmd="$VA_INIT -c:v $ENC_HEVC -global_quality 23 -preset slow $video_filter $video_common"

		echo "-> Converting from $video_codec to hevc $target_depth-bit"
	elif [[ "$video_codec" =~ ^(mpeg4|wmv1|wmv2|wmv3|vc1|mpeg2video|mpeg1video|flv1)$ ]]; then
		# --- HANDLE LEGACY CODECS

		# Figure out bitrate if it's not in the file
		if [[ -z "$video_orig_bitrate" || "$video_orig_bitrate" == 0 ]]; then
			if [ "$video_duration" -gt 0 ]; then
				video_size=$(stat -c%s "$file")
				video_orig_bitrate=$(( ($video_size * 8) / $video_duration ))
			else
				video_orig_bitrate=8000000
			fi
	
			echo "-> Bitrate unknown - Calculated $video_orig_bitrate"
		fi
	
		# Limit bitrate based on video size/quality
		video_buffer="15"
		if [[ "$video_height" -ge 2160 && $video_orig_bitrate > 15000000 ]]; then
			video_avg_bitrate="15"
			video_max_bitrate="18"
			video_buffer="23"
		elif [[ "$video_height" -ge 1080 && $video_orig_bitrate > 8000000 ]]; then
			video_avg_bitrate="8"
			video_max_bitrate="10"
		elif [[ "$video_height" -ge 720 && $video_orig_bitrate > 4000000 ]]; then
			video_avg_bitrate="4"
			video_max_bitrate="5"
		elif [[ "$video_height" -ge 480 && $video_orig_bitrate > 2500000 ]]; then
			video_avg_bitrate="2.5"
			video_max_bitrate="3"
		else
			video_avg_bitrate=$(echo "$video_orig_bitrate / 10^6" | bc)
			video_max_bitrate=$(echo "$video_avg_bitrate * 12 / 10" | bc)
			video_buffer="5"
		fi
	
		# Video conversion with SW decoding -> HW encoding
		video_cmd="$VA_INIT -c:v $ENC_H264 -global_quality 20 -preset medium -vf format=nv12,hwupload $video_common -b:v ${video_avg_bitrate}M -maxrate ${video_max_bitrate}M -bufsize ${video_buffer}M"

		echo "-> Converting from legacy $video_codec to h264 8-bit"
	else
		# --- VIDEO IS SOME KIND OF WEIRDO FORMAT (FALLBACK)
		# Convert to h264 8-bit
		video_cmd="$VA_INIT -c:v $ENC_H264 -global_quality 20 -preset medium -vf format=nv12,hwupload $video_common"

		echo "-> Converting $video_codec to HEVC $video_bit_depth-bit"
	fi

	unset video_track video_codec video_height video_pix_fmt video_bit_depth video_codec_tag video_duration video_orig_bitrate
	unset video_common bsf_cmd video_bit_depth target_depth video_size video_filter video_orig_bitrate video_avg_bitrate video_max_bitrate video_buffer

	# --------------------------------------------------------------------
	# --- GET AUDIO ------------------------------------------------------
	# Get all audio streams
	# Example output: 0:ac3:2:jpn 1:aac:6:eng
	# --------------------------------------------------------------------
	audio_list=$(ffprobe -v error -select_streams a \
		-show_entries stream=index,codec_name,channels,bit_rate:stream_tags=language \
		-of compact=p=0:item_sep='|' "$file")
			
	# --- SET UP AUDIO TRACKS
	audio_map=""
	audio_cmd=""
	audio_count=0
	
	if [ -n "$audio_list" ]; then
		while IFS='|' read -ra fields; do
			# Reset variables for every track
			audio_idx=""
			audio_codec=""
			audio_channels=""
			audio_bitrate=""
			audio_language=""

			# Loop through each "key=value" pair in the line
			for field in "${fields[@]}"; do
				case "$field" in
					index=*)
						audio_idx="${field#*=}" 
						audio_idx=${audio_idx:-""}
						;;
					codec_name=*)
						audio_codec="${field#*=}" 
						audio_codec=${audio_codec:-""}
						;;
					channels=*)
						audio_channels="${field#*=}" 
						audio_channels=${audio_channels:-""}
						;;
					bit_rate=*)
						audio_bitrate="${field#*=}" 
						audio_bitrate=${audio_bitrate:-""}
						;;
					"tag:language="*)
						audio_language="${field#*=}" 
						audio_language=${audio_language:-"und"}
						;;
				esac
			done

			# Skip the "stream" prefix field and empty indices
			[[ -z "$audio_idx" ]] && continue
	
			echo "Audio: found $audio_codec $audio_channels-ch track"
			echo "-> Metadata: track $audio_idx / language $audio_language / bitrate $audio_bitrate"

			# If no bitrate is reported, try to calculate it
			if [ "$audio_bitrate" = "N/A" ] || [ -z "$audio_bitrate" ]; then
			    # Read a portion of the stream packets to calculate the actual average bitrate
			    audio_bitrate=$(ffprobe -v error -select_streams "a:$audio_idx" -show_entries stream=bit_rate -read_intervals "%10" -of default=noprint_wrappers=1:nokey=1 "$file")
			fi
			
			# If still no bitrate is reported, assume 0
			if ! [[ "$audio_bitrate" =~ ^[0-9]+$ ]]; then
			    audio_bitrate=0
			fi

			# Try to assert the stream is actually what it says
			echo "-> Check: bitstream for stream $audio_idx"

			if ! ffmpeg -v error -select_streams "a:$audio_count" -i "$file" -t 3 -f null - 2>/dev/null; then
				audio_codec="forced_transcode"

				echo "-> Bitstream: Failed to decode as $audio_codec. Forcing re-encode."
			else
				echo "-> Bitstream: Validation passed."
			fi

			# If source is lower than max, use source bitrate. If higher, cap it at max.
			if [ "$audio_bitrate" -lt "192000" ] && [ "$audio_bitrate" -gt 0 ]; then
				audio_max_bitrate="$((audio_bitrate / 1000))k"
			else
				audio_max_bitrate="192k"
			fi

			# Convert non-AAC, convert anything other than 2 or 6 channels.
			if [[ "$audio_codec" != "aac" ]] || [[ "$audio_channels" -gt 6 ]]; then
				if [ "$audio_channels" -ge 6 ]; then
					audio_channels=6
	
					echo "-> Convert: $audio_codec $audio_channels-ch to AAC 6-ch"
				else
					echo "-> Convert: $audio_codec to AAC $audio_channels-ch"
				fi

				audio_map="$audio_map -map 0:$audio_idx"
				audio_cmd="$audio_cmd -c:a:$audio_count aac -ac:a:$audio_count $audio_channels -b:a:$audio_count $audio_max_bitrate"
			else
				audio_map="$audio_map -map 0:$audio_idx"
				audio_cmd="$audio_cmd -c:a:$audio_idx copy"
	
				echo "-> Copy: AAC $audio_channels-ch"
			fi

			((audio_count++))

		done <<< "$audio_list"
	else
		audio_cmd="-an"

		echo "Audio: No tracks found."
	fi

	unset audio_list audio_idx audio_codec audio_channels audio_bitrate audio_language
	unset audio_max_bitrate audio_count

	# --------------------------------------------------------------------
	# --- GET SUBTITLES --------------------------------------------------
	# Keep only English and Unknown languages
	# --------------------------------------------------------------------
	sub_list=$(ffprobe -v error -select_streams s \
		-show_entries stream=index,codec_name:stream_tags=language,title:disposition=commentary,forced \
		-of compact=p=0:item_sep='|' "$file")

	# --- SET UP SUBTITLE TRACKs
	subtitle_source_file="${filename%.*}"
	subtitle_inputs=""
	
	subtitle_map="" 
	subtitle_cmd="" 
	subtitle_count=0
	
	# Defaults
	sub_file_language=""

	if [ -n "$sub_list" ]; then
		# --- GET INTERNAL SUBTITLES (TRACKS)
		# Check if subtitles are in the video file
		# Example output: 0|srt|eng|title|bool|bool 1|srt|und|title|bool|bool

		while IFS='|' read -ra fields; do
			# Reset variables for every track
			sub_idx=""
			sub_t_codec=""
			sub_t_language="und"
			sub_t_title=""
			sub_t_commentary=0
			sub_t_forced=0
			sub_codec=""
			sub_forced=""
	
			# Loop through each "key=value" pair in the line
			for field in "${fields[@]}"; do
				case "$field" in
					index=*)
						sub_idx="${field#*=}" 
						sub_idx=${sub_idx:-""}
						;;
					codec_name=*)
						sub_t_codec="${field#*=}" 
						sub_t_codec=${sub_t_codec:-""}
						;;
					"tag:language="*)
						sub_t_language="${field#*=}" 
						sub_t_language=${sub_t_language:-"und"}
						;;
					"tag:title="*)
						sub_t_title="${field#*=}" 
						sub_t_title=${sub_t_title:-""}
						;;
					"disposition:commentary="*)
						sub_t_commentary="${field#*=}" 
						sub_t_commentary=${sub_t_commentary:-0}
						;;
					"disposition:forced="*)
						sub_t_forced="${field#*=}" 
						sub_t_forced=${sub_t_forced:-0}
						;;
				esac
			done
	
			# Skip the "stream" prefix field and empty indices
			[[ -z "$sub_idx" ]] && continue
	
			echo "Subtitle: found $sub_t_codec track"
			echo "-> Metadata: track $sub_idx / language $sub_t_language / title $sub_t_title / commentary $sub_t_commentary / forced $sub_t_forced"

			# We only want mov_text/SRT/ASS/VTT
			if [[ ! "$sub_t_codec" =~ ^(mov_text|srt|subrip|ass|ssa|webvtt|vtt)$ ]]; then
				echo "-> Ignore track $sub_idx $sub_t_codec"

				continue
			fi

			# We don't want these either
			if [[ "${sub_t_title,,}" =~ commentary || "$sub_t_commentary" -eq 1 ]]; then
				echo "-> Ignore track $sub_idx $sub_t_title"

				continue
			fi

			# We only want certain languages
			if [[ "$sub_t_language" =~ ^(en|eng|und)$ ]]; then
				case "$sub_t_codec" in
					ass|ssa|srt|subrip)
						sub_codec="-c:s:$subtitle_count copy"
						;;
					*)
						# Fallback for everything else (like webvtt, vtt and mov_text) to SRT 
						sub_codec="-c:s:$subtitle_count srt"
						;;
				esac

				# Check if it's a forced/foreign track
				if [[ "$sub_t_forced" -eq 1 ]]; then
					sub_forced="-disposition:s:$subtitle_count forced"
					
					echo "-> Set as forced"
				fi	

				subtitle_map="$subtitle_map -map 0:s:$subtitle_count"
				subtitle_cmd="$subtitle_cmd $sub_codec $sub_forced"

				echo "-> Adding track $subtitle_count $sub_t_codec $sub_t_language from track $sub_idx"

				((subtitle_count++))
			else 
				echo "-> Ignore track $sub_idx $sub_t_language"
			fi
		done <<< "$sub_list"
	fi

	# --- GET EXTERNAL SUBTITLES (FILES)
	# Check if an external subtitle file is available

	# Add a file counter (indexing external files)
	sub_file_count=1
	
	find "$(dirname "$file")" -maxdepth 2 -type f -regex ".*\.\(srt\|ass\|vtt\)" -print0 | while read -r -d '' subtitle_src; do
		sub_file=$(basename "$subtitle_src")
		sub_file_ext="${subtitle_src##*.}"
		
		# Reset variables for every file
		sub_f_language="und"
		sub_codec=""
		sub_forced=""

		if [[ "$sub_file" == "$subtitle_source_file"* ]]; then
			# Try to find specific language code
			if [[ "$sub_file" =~ \.(en|eng)\. ]]; then
				sub_f_language="${BASH_REMATCH[1]}"
			elif [[ "$sub_file" == "$filename.${subtitle_src##*.}" ]]; then
				# If the language code is missing/undefined
				sub_f_language="und"
			else
				# False positive match treated as unknown/undefined
				sub_f_language="und"
			fi
			
			echo "Subtitle: found file $sub_file / language $sub_f_language"

			# We don't want these tracks
			if [[ "${sub_file,,}" =~ [._[:space:]-]commentary[._[:space:]-] ]]; then
				echo "-> Ignore $sub_file"

				continue
			fi
			
			# We only want certain languages
			if [[ "$sub_f_language" =~ ^(en|eng|und)$ ]]; then
				# Convert VTT to SRT for better compatibility
				case "$sub_file_ext" in
					ass|ssa|srt)
						sub_codec="-c:s:$subtitle_count copy"
						;;
					*)
						# Fallback for anything else (like webvtt, vtt and mov_text) to SRT 
						sub_codec="-c:s:$subtitle_count srt"
					;;
				esac

				# Check if it's a forced/foreign track
				if [[ "${sub_file,,}" =~ [._[:space:]-](forced|foreign)[._[:space:]-] ]]; then
					sub_forced="-disposition:s:$subtitle_count forced"

					echo "-> Set as forced"
				fi
			
				subtitle_inputs="$subtitle_inputs -i \"$subtitle_src\""
				subtitle_map="$subtitle_map -map $sub_file_count:0"
				subtitle_cmd="$subtitle_cmd $sub_codec -metadata:s:s:$subtitle_count language=$sub_f_language $sub_forced"

				echo "-> Adding track $subtitle_count $sub_file_ext $sub_t_language from file $sub_file"

				((sub_file_count++))
				((subtitle_count++))
			else 
				echo "-> Ignore: $sub_f_language"
			fi
		fi
	done

	unset sub_idx sub_t_codec sub_t_language sub_t_title sub_t_commentary sub_t_forced sub_codec sub_forced
	unset subtitle_count sub_file_count sub_file sub_file_ext sub_f_language sub_codec sub_forced

	# --------------------------------------------------------------------
	# --- CHOOSE FILE LOCATION -------------------------------------------
	# --------------------------------------------------------------------
	filepath=$(dirname "$file")

	if [[ $filepath == $SOURCE_DIR ]]; then
		output_path="$OUTPUT_DIR"
	else
		# Maintain subfolders
		output_path="$OUTPUT_DIR${filepath#$SOURCE_DIR}"

		# Trim everything except letters, numbers, spaces, dots, slashes, dashes, and underscores
		output_path=$(echo "$output_path" | sed 's/[^a-zA-Z0-9 .\/_-]//g')
		# Trim leading/trailing spaces
		output_path=$(echo "$output_path" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')

		# Create the output directory
		mkdir -p "$output_path" && chmod 2775 "$output_path"
	fi

	# --------------------------------------------------------------------
	# --- CHOOSE FILENAME ------------------------------------------------
	# --------------------------------------------------------------------
	output_filename="${filename%.*}.mkv"

	# Clean up file names
	output_filename=$(echo "$output_filename" | sed 's/[^a-zA-Z0-9 .\/_-]//g')
	# Trim leading/trailing spaces
	output_filename=$(echo "$output_filename" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')

	if [ -f "$output_path/$output_filename" ]; then
		output_filename="${filename%.*}-$(date +%H%M%S).mkv"

		echo "Processing: File exists - Renaming to $output_filename"
	fi

	# --------------------------------------------------------------------
	# --- PROCESS THE FILE -----------------------------------------------
	# --------------------------------------------------------------------
	echo "Processing $filename with FFmpeg"

	# --- Do the conversion/remuxing
	ffmpeg -hide_banner -loglevel error -nostdin -y \
		$hw_accel -threads 4 \
		-i "$file" \
		$subtitle_inputs \
		-filter_threads 2 \
		-map 0:v:0 $audio_map $subtitle_map \
		-map 0:t? -c:t copy -map -0:d -map_metadata:g -1 -map_chapters -1 \
		-metadata:s:v title="" -metadata:s:a title="" \
		$video_cmd $audio_cmd $subtitle_cmd \
		"$output_path/$output_filename"

	# --------------------------------------------------------------------
	# --- ADD FILENAME TO HISTORY LOG ------------------------------------
	# --------------------------------------------------------------------
	if [ $? -eq 0 ]; then
		# Keep the last 10000 items
		tail -n 10000 "$HISTORY_FILE" > "${HISTORY_FILE}.tmp"
		mv "${HISTORY_FILE}.tmp" "$HISTORY_FILE"
		
		# Add current file
		echo "$file" >> "$HISTORY_FILE"
		echo "Finished: Added filename to history"
	else
		rm "$output_path/$output_filename"

		echo "Error: Processing failed for $file"
	fi
done

echo "-- FINISHED -- $(date +'%Y-%m-%d %H:%M:%S') ---------------------------------------------"
