commit 2a9406b3705204bd424737c9eb8cc06bc67f2389 Author: Thorsten Date: Mon Dec 16 20:34:13 2024 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b45a6bb --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +template.json +.vscode +settings.ini \ No newline at end of file diff --git a/auto_mkvmerge.desktop b/auto_mkvmerge.desktop new file mode 100644 index 0000000..b9e84ab --- /dev/null +++ b/auto_mkvmerge.desktop @@ -0,0 +1,7 @@ +#!/usr/bin/env xdg-open +[Desktop Entry] +Name=auto_mkvmerge +Exec=sh -e -c "exec \\"\\$(dirname \\"\\$0\\")/bin/auto_mkvmerge.sh\\"" %k +Icon=mkvmerge +Terminal=true +Type=Application \ No newline at end of file diff --git a/bin/auto_mkvmerge.sh b/bin/auto_mkvmerge.sh new file mode 100644 index 0000000..27f5079 --- /dev/null +++ b/bin/auto_mkvmerge.sh @@ -0,0 +1,472 @@ +#!/bin/bash + +#─────────────────────────────────── user settings ───────────────────────────────────# +#─────────────────────────────────────────────────────────────────────────────────────# +# pathes +input_folder="" +output_folder="" +jsonfile_path="./template.json" +#─────────────────────────────────────────────────────────────────────────────────────# +# determine what to do if the video files have different audio and subtitle tracks +# possible values: +# normal | if credibility check returns with no errors start merging +# check | stops after credibility check +# nocheck | merge without a credibility check +mode=normal +#─────────────────────────────────────────────────────────────────────────────────────# +# choose which mkvmerge version is used +# possible values: +# binary | use "/usr/bin/mkvmerge" +# flatpak | use "flatpak run org.bunkus.mkvtoolnix-gui mkvmerge" +# path | use your own path to mkvmerge +mkvmerge=binary +#─────────────────────────────────────────────────────────────────────────────────────# +# get text notifications of errors and when the script is done (via notify-send) +# possible values: +# on | notifications on +# off | notifications off +notification_text=on +#─────────────────────────────────────────────────────────────────────────────────────# +# get audio notifications of errors and when the script is done +# possible values: +# off | no sound +# beep | beep sounds via speaker-test +# voice | tts via spd-say +# file | play wav files from $(pwd)./bin folder namend done.wav & error.wav +nofification_audio=beep +#─────────────────────────────────────────────────────────────────────────────────────# +# overwrite the above settings with the values from a ini file +# possible values: +# on | ignore above values and use $settings_file +# off | use the values from script +overwrite_with_settings_ini=on +settings_file="./settings.ini" +#─────────────────────────────────────────────────────────────────────────────────────# +#─────────────────────────────────── script start ────────────────────────────────────# + + + +# ffprobe +ffprobe_path="/usr/bin/ffprobe" + + +# global variables +declare -a json_array +declare -a video_file_list +outputpath_index=-1 +inputpath_index=-1 + + +#colors +RED='\033[0;31m' +GREEN='\033[0;92m' +BLUE='\033[0;94m' +WHITE_ON_GRAY='\033[0;37;100m' +BLACK_ON_WHITE='\033[0;30;47m' +WHITE_ON_RED='\033[0;37;41m' +NC='\033[0m' # No Color + + + + +# Main Function +main() { + + # get the user variables of the ini file + if [ "$overwrite_with_settings_ini" = "on" ]; then + get_user_variables_from_ini_file + fi + + # convert relativ to absolut pathes + jsonfile_path=$(relativ_to_fullpath "$jsonfile_path") + + + # apply user varible $mkvmerge to $mkvmerge_path + if [ "$mkvmerge" = "binary" ]; then + mkvmerge_path="/usr/bin/mkvmerge" + elif [ "$mkvmerge" = "flatpak" ]; then + mkvmerge_path="flatpak run org.bunkus.mkvtoolnix-gui mkvmerge" + else + mkvmerge_path=$mkvmerge + fi + + + + echo "────────────────────────────────────────────────────────────────" + echo -e "${BLACK_ON_WHITE} Auto MKVMerge start ${NC}" + echo "────────────────────────────────────────────────────────────────" + echo " " + echo -e "${WHITE_ON_GRAY} Input Folder ${NC} ""$input_folder""" + echo -e "${WHITE_ON_GRAY} Output Folder ${NC} ""$output_folder""" + echo -e "${WHITE_ON_GRAY} JSON File ${NC} ""$jsonfile_path""" + echo -e "${WHITE_ON_GRAY} MKVMerge ${NC} ""$mkvmerge_path""" + echo -e "${WHITE_ON_GRAY} Mode ${NC} ""$mode""" + echo " " + echo "────────────────────────────────────────────────────────────────" + + # map json array to bash array + check_dependecies + init + + + #credibility check + if [ ! "$mode" = "nocheck" ]; then + check_credibility "${video_file_list[@]}" + fi + + #start merging + if [ ! "$mode" = "check" ]; then + echo -e "${BLACK_ON_WHITE} Starting MKVMerge ${NC}" + local filecount=1 #count variable for the echo output + local filecountdecimal="" #same count variable in 3 decimal + local filetotaldecimal=$(printf "%03d" ${#video_file_list[@]}) #converting filescount to a 3 decimal output + + for file_path in "${video_file_list[@]}"; do + + # highlight_episode + highlighted_episode=$(highlight_episode "$file_path") + + filecountdecimal=$(printf "%03d" $filecount) + echo "────────────────────────────────────────────────────────────────" + echo -e "${WHITE_ON_GRAY} ""$filecountdecimal""|""$filetotaldecimal"" ${NC} Merging: $highlighted_episode${NC}" + echo "────────────────────────────────────────────────────────────────" + ((filecount++)) + output_path="$output_folder/$(basename "$file_path")" + apply_mkvmerge_on_file "$file_path" "$output_path" + done + fi + + _exit 0 +} + + + +check_dependecies(){ + + # Error check: Input path is invalid + if [ ! -e "$input_folder" ]; then + _exit 1 "Input path is invalid!" + fi + + # Error check: Output path is invalid and cannot be created + if [ ! -e "$output_folder" ]; then + # try to create the path + mkdir -p "$output_folder" > /dev/null 2>&1 + if [ $? -ne 0 ]; then + #echo "Output path is invalid or cannot be created!" + _exit 1 "Output path is invalid or cannot be created!" + fi + fi + + # Error check: Input and Output paths must not be identical + if [[ "$input_folder" == "$output_folder" ]]; then + _exit 1 "Input and Output paths must not be identical!" + fi + + if ! test -x "$ffprobe_path"; then + _exit 1 "ffprobe is not installed. Please install it before running this script." + fi + + # Error check: Videos.json file not found + if [ ! -e "$jsonfile_path" ]; then + _exit 1 "Videos.json file not found!" + fi + + # Error check: mkvmerge not found + if [ "$mkvmerge" = "binary" ]; then + if ! test -x "$mkvmerge_path"; then + _exit 1 "mkvmerge not found!" + fi + elif [ "$mkvmerge" = "flatpak" ]; then + if ! test -x "/var/lib/flatpak/app/org.bunkus.mkvtoolnix-gui"; then + _exit 1 "mkvmerge not found!" + fi + else + if ! test -x "$mkvmerge_path"; then + _exit 1 "mkvmerge not found!" + fi + fi +} + +init() { + + mapfile -t json_array < <(jq -r '.[]' "$jsonfile_path") + + #loop over indices of bash array + for index in "${!json_array[@]}";do + #get item on current position + item="${json_array[$index]}" + + #if current position has value --output the next index is the output path + if [[ "$item" == "--output" ]]; then + outputpath_index=$(($index + 1)) + fi + + #if current position has value ( the next index is the input path (hopefully) + if [[ "$item" == "(" ]]; then + inputpath_index=$(($index + 1)) + fi + done + + #if one path is not found: invalid json + if [[ $outputpath_index -eq -1 || $inputpath_index -eq -1 ]]; then + _exit 1 "there is an error with the json config file: input or output path not found" + fi + + #save all mp4 and mkv file paths in video_file_list array + mapfile -t video_file_list < <(find "$input_folder" -type f \( -name "*.mp4" -o -name "*.mkv" -o -name "*.flv" -o -name "*.avi" \) | sort) + + if [ "${#json_array[@]}" -eq 0 ]; then + _exit 1 "no files found in $input_folder" + fi + +} + + +get_user_variables_from_ini_file() { + + # get absolut path of the ini file + settings_file=$(relativ_to_fullpath "$settings_file") + + # abort script if settings file can't be found + if [ ! -e "$settings_file" ]; then + _exit 1 "Settingsfile not found at: "$settings_file + fi + + # read ini values + input_folder=$(sed -nr "/^\[pathes\]/ { :l /^input_folder[ ]*=/ { s/[^=]*=[ ]*//; p; q;}; n; b l;}" "$settings_file") + output_folder=$(sed -nr "/^\[pathes\]/ { :l /^output_folder[ ]*=/ { s/[^=]*=[ ]*//; p; q;}; n; b l;}" "$settings_file") + jsonfile_path=$(sed -nr "/^\[pathes\]/ { :l /^jsonfile_path[ ]*=/ { s/[^=]*=[ ]*//; p; q;}; n; b l;}" "$settings_file") + mode=$(sed -nr "/^\[mode\]/ { :l /^mode[ ]*=/ { s/[^=]*=[ ]*//; p; q;}; n; b l;}" "$settings_file") + mkvmerge=$(sed -nr "/^\[mkvmerge\]/ { :l /^mkvmerge[ ]*=/ { s/[^=]*=[ ]*//; p; q;}; n; b l;}" "$settings_file") + notification_text=$(sed -nr "/^\[notification_text\]/ { :l /^notification_text[ ]*=/ { s/[^=]*=[ ]*//; p; q;}; n; b l;}" "$settings_file") + nofification_audio=$(sed -nr "/^\[nofification_audio\]/ { :l /^nofification_audio[ ]*=/ { s/[^=]*=[ ]*//; p; q;}; n; b l;}" "$settings_file") + + + +} + +check_credibility(){ + + local global_error=false + local file_error=false + local first_iteration_audio_language_array=() + local first_iteration_subtitle_language_array=() + local files=("$@") + local filetotaldecimal=$(printf "%03d" ${#files[@]}) #converting filescount to a 3 decimal output + local filecount=1 #count variable for the echo output + local filecountdecimal="" #same count variable in 3 decimal + local audio_language_array_sep="" + local subtitle_language_array_sep="" + local seperator="|" + + + echo -e "${BLACK_ON_WHITE} Checking credibility ${NC}" + echo "────────────────────────────────────────────────────────────────" + + for file in "${files[@]}"; do + + ffprobe_json=$("$ffprobe_path" -v error -show_streams -of json "$file") + + # Run ffprobe and extract language information using jq, store it in a bash array + readarray -t audio_language_array < <(echo "$ffprobe_json" | jq '.streams[] | select(.codec_type == "audio") | .tags.language') + readarray -t subtitle_language_array < <(echo "$ffprobe_json" | jq '.streams[] | select(.codec_type == "subtitle") | .tags.language') + + # Debug Print the contents of the array + #printf '%s\n' "${language_array[@]}" + + if [[ $filecount = 1 ]]; then + first_iteration_audio_language_array=("${audio_language_array[@]}") + first_iteration_subtitle_language_array=("${subtitle_language_array[@]}") + elif [[ "${first_iteration_audio_language_array[*]}" != "${audio_language_array[*]}" || "${first_iteration_subtitle_language_array[*]}" != "${subtitle_language_array[*]}" ]]; then + global_error=true + file_error=true + fi + + #converting filecount to a 3 decimal output + filecountdecimal=$(printf "%03d" $filecount) + + + #converadding seperator to audio_language_array ($seperator) + audio_language_array_sep=$(printf "%s$seperator" "${audio_language_array[@]}") + #cutting off last seperator + audio_language_array_sep=${audio_language_array_sep%$seperator} + + #adding seperator to subtitle_language_array ($seperator) + subtitle_language_array_sep=$(printf "%s$seperator" "${subtitle_language_array[@]}") + #cutting last off seperator + subtitle_language_array_sep=${subtitle_language_array_sep%$seperator} + + # highlight_episode + highlighted_episode=$(highlight_episode "$file") + + if [ $file_error = true ]; then + echo -e "${WHITE_ON_GRAY} ""$filecountdecimal""|""$filetotaldecimal"" ${NC} ${RED}Audio: ${#audio_language_array[@]} [${audio_language_array_sep[*]}]${NC} ${WHITE_ON_GRAY}|${NC} ${RED}Subtitle: ${#subtitle_language_array[@]} [${subtitle_language_array_sep[*]}]${NC} $highlighted_episode${NC}" + else + echo -e "${WHITE_ON_GRAY} ""$filecountdecimal""|""$filetotaldecimal"" ${NC} ${GREEN}Audio: ${#audio_language_array[@]} [${audio_language_array_sep[*]}]${NC} ${WHITE_ON_GRAY}|${NC} ${GREEN}Subtitle: ${#subtitle_language_array[@]} [${subtitle_language_array_sep[*]}]${NC} $highlighted_episode${NC}" + fi + + ((filecount++)) + + file_error=false + + done + + if [ $global_error = true ]; then + #echo "────────────────────────────────────────────────────────────────" + #echo -e "${WHITE_ON_RED} The credibility check returned with errors! Aborting... ${NC}" + #echo "────────────────────────────────────────────────────────────────" + _exit 1 "The credibility check returned with errors! Aborting" + else + echo "────────────────────────────────────────────────────────────────" + echo -e "${BLACK_ON_WHITE} The credibility test went smoothly ${NC}" + echo "────────────────────────────────────────────────────────────────" + fi +} + + +# MKV Merge Batch Function +apply_mkvmerge_on_file() { + #parameter is path of a video file to be merged + local input_file=$1 + local output_file=$2 + local command="$mkvmerge_path" + + #update input path in video json array + json_array[inputpath_index]="$input_file" + + #update output path in video json array + json_array[outputpath_index]="$output_file" + + for argument in "${json_array[@]}"; do + command+=" \"$argument\"" + done + + eval "$command" + +} + +#beep sounds via speaker-test +playsound_speaker_sound() { + ( + \speaker-test --frequency $1 --test sine > /dev/null 2>&1 & + pid=$! + \sleep 0.${2}s + \kill -9 $pid > /dev/null 2>&1 + ) > /dev/null 2>&1 +} + + +_exit(){ + + #get the exit message + local error_state="$1" + local error_msg="$2" + local scriptpath="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" + + #script stopted with errors + if [ "$error_state" = 1 ]; then + + #echo output + echo "────────────────────────────────────────────────────────────────" + echo -e "${WHITE_ON_RED} Error ${NC}" + echo + echo -e "${RED}$error_msg${NC}" + echo "────────────────────────────────────────────────────────────────" + + #notification_text + if [ "$notification_text" = "on" ]; then + notify-send --urgency=critical "Auto MKVMerge Warning" "$error_msg" + fi + + #nofification_audio + if [ "$nofification_audio" = "beep" ]; then + playsound_speaker_sound 1500 150 + playsound_speaker_sound 1500 150 + playsound_speaker_sound 1500 150 + elif [ "$nofification_audio" = "voice" ]; then + spd-say "Auto MKVMerge Error" + elif [ "$nofification_audio" = "file" ]; then + aplay "$scriptpath/error.wav" > /dev/null 2>&1 + fi + + #exit the script + echo + read -n 1 -s -r -p "Press any key to exit" + exit + + fi + + #normal exit + if [ "$error_state" = 0 ]; then + + #echo output + echo "────────────────────────────────────────────────────────────────" + echo -e "${BLACK_ON_WHITE} Finished! ${NC}" + echo "────────────────────────────────────────────────────────────────" + + #notification_text + if [ "$notification_text" = "on" ]; then + notify-send --urgency=normal "Auto MKVMerge" "Finished!" + fi + + #nofification_audio + if [ "$nofification_audio" = "beep" ]; then + playsound_speaker_sound 200 200 + playsound_speaker_sound 400 200 + elif [ "$nofification_audio" = "voice" ]; then + spd-say "Auto MKVMerge ist fertig" + elif [ "$nofification_audio" = "file" ]; then + aplay "$scriptpath/done.wav" > /dev/null 2>&1 + fi + + #exit the script + echo + read -n 1 -s -r -p "Press any key to exit" + exit + + fi + + #exit the script + echo + read -n 1 -s -r -p "Press any key to exit" + exit + +} + + +# coloring SxxExx in string +highlight_episode() { + local input="$1" + local normal_color=${NC} + local episode_color=${BLUE} + + local highlighted_episode=$(echo "$input" | awk -v normal_color="$normal_color" -v episode_color="$episode_color" 'BEGIN { IGNORECASE=1 } { gsub(/s[0-9]{2}e[0-9]{2}([e][0-9]{2})?/, episode_color"&\033[0m", $0); print normal_color$0"\033[0m" }') + + echo "$highlighted_episode" +} + + +relativ_to_fullpath() { + + local path="$1" + scriptpath="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" + + if [ ! -e "$path" ]; then + pathfull=$(realpath "$(dirname "$scriptpath")/$path") + else + pathfull=$path + fi + + echo "$pathfull" + +} + + + + +# Main +main + + +$SHELL diff --git a/bin/auto_sort.sh b/bin/auto_sort.sh new file mode 100644 index 0000000..bfe4bef --- /dev/null +++ b/bin/auto_sort.sh @@ -0,0 +1,165 @@ +#!/bin/bash + +# global variables +declare -a video_file_list +settings_file="./settings.ini" + +get_user_variables_from_ini_file + + +#save all mp4 and mkv file paths in video_file_list array +mapfile -t video_file_list < <(find "$input_folder" -type f \( -name "*.mp4" -o -name "*.mkv" -o -name "*.avi" \) | sort) + + +# Funktion, um den kleinsten gemeinsamen übergeordneten Ordner zu finden +find_common_parent() { + local common_parent + local first_file=true + + # Durchlaufe alle übergebenen Dateien + for file in "$@"; do + # Überprüfe, ob die Datei existiert und eine Datei ist + if [ -f "$file" ]; then + # Bestimme den Ordner der Datei + file_dir=$(dirname "$file") + + # Wenn dies die erste Datei ist, setze den gemeinsamen übergeordneten Ordner auf den Ordner dieser Datei + if [ "$first_file" = true ]; then + common_parent="$file_dir" + first_file=false + else + # Andernfalls bestimme den gemeinsamen übergeordneten Ordner zwischen dem aktuellen und dem bisherigen + common_parent=$(realpath --relative-to="$common_parent" "$file_dir") + fi + else + echo "Datei existiert nicht: $file" + return 1 + fi + done + + # Gib den gemeinsamen übergeordneten Ordner zurück + echo "$common_parent" +} + + + + + +get_user_variables_from_ini_file() { + + # get absolut path of the ini file + settings_file=$(relativ_to_fullpath "$settings_file") + + # abort script if settings file can't be found + if [ ! -e "$settings_file" ]; then + _exit 1 "Settingsfile not found at: "$settings_file + fi + + # read ini values + input_folder=$(sed -nr "/^\[pathes\]/ { :l /^input_folder[ ]*=/ { s/[^=]*=[ ]*//; p; q;}; n; b l;}" "$settings_file") + output_folder=$(sed -nr "/^\[pathes\]/ { :l /^output_folder[ ]*=/ { s/[^=]*=[ ]*//; p; q;}; n; b l;}" "$settings_file") + jsonfile_path=$(sed -nr "/^\[pathes\]/ { :l /^jsonfile_path[ ]*=/ { s/[^=]*=[ ]*//; p; q;}; n; b l;}" "$settings_file") + mode=$(sed -nr "/^\[mode\]/ { :l /^mode[ ]*=/ { s/[^=]*=[ ]*//; p; q;}; n; b l;}" "$settings_file") + mkvmerge=$(sed -nr "/^\[mkvmerge\]/ { :l /^mkvmerge[ ]*=/ { s/[^=]*=[ ]*//; p; q;}; n; b l;}" "$settings_file") + notification_text=$(sed -nr "/^\[notification_text\]/ { :l /^notification_text[ ]*=/ { s/[^=]*=[ ]*//; p; q;}; n; b l;}" "$settings_file") + nofification_audio=$(sed -nr "/^\[nofification_audio\]/ { :l /^nofification_audio[ ]*=/ { s/[^=]*=[ ]*//; p; q;}; n; b l;}" "$settings_file") + + + +} + +relativ_to_fullpath() { + + local path="$1" + scriptpath="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" + + if [ ! -e "$path" ]; then + pathfull=$(realpath "$(dirname "$scriptpath")/$path") + else + pathfull=$path + fi + +} + + + + + + + +_exit(){ + + #get the exit message + local error_state="$1" + local error_msg="$2" + local scriptpath="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" + + #script stopted with errors + if [ "$error_state" = 1 ]; then + + #echo output + echo "────────────────────────────────────────────────────────────────" + echo -e "${WHITE_ON_RED} Error ${NC}" + echo + echo -e "${RED}$error_msg${NC}" + echo "────────────────────────────────────────────────────────────────" + + #notification_text + if [ "$notification_text" = "on" ]; then + notify-send --urgency=critical "Auto MKVMerge Warning" "$error_msg" + fi + + #nofification_audio + if [ "$nofification_audio" = "beep" ]; then + playsound_speaker_sound 1500 150 + playsound_speaker_sound 1500 150 + playsound_speaker_sound 1500 150 + elif [ "$nofification_audio" = "voice" ]; then + spd-say "Auto MKVMerge Error" + elif [ "$nofification_audio" = "file" ]; then + aplay "$scriptpath/error.wav" > /dev/null 2>&1 + fi + + #exit the script + echo + read -n 1 -s -r -p "Press any key to exit" + exit + + fi + + #normal exit + if [ "$error_state" = 0 ]; then + + #echo output + echo "────────────────────────────────────────────────────────────────" + echo -e "${BLACK_ON_WHITE} Finished! ${NC}" + echo "────────────────────────────────────────────────────────────────" + + #notification_text + if [ "$notification_text" = "on" ]; then + notify-send --urgency=normal "Auto MKVMerge" "Finished!" + fi + + #nofification_audio + if [ "$nofification_audio" = "beep" ]; then + playsound_speaker_sound 200 200 + playsound_speaker_sound 400 200 + elif [ "$nofification_audio" = "voice" ]; then + spd-say "Auto MKVMerge ist fertig" + elif [ "$nofification_audio" = "file" ]; then + aplay "$scriptpath/done.wav" > /dev/null 2>&1 + fi + + #exit the script + echo + read -n 1 -s -r -p "Press any key to exit" + exit + + fi + + #exit the script + echo + read -n 1 -s -r -p "Press any key to exit" + exit + +} \ No newline at end of file diff --git a/bin/done.wav b/bin/done.wav new file mode 100644 index 0000000..ea5c577 Binary files /dev/null and b/bin/done.wav differ diff --git a/bin/error.wav b/bin/error.wav new file mode 100644 index 0000000..aac4bb0 Binary files /dev/null and b/bin/error.wav differ