added long filename handling for autosort
This commit is contained in:
243
bin/auto_sort.sh
243
bin/auto_sort.sh
@@ -1,12 +1,15 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
# Auto Sort — sortiert Videodateien nach Audio-/Untertitel-Spuren
|
||||||
|
# mit Erkennung zu langer Ordnernamen → generische Gruppennamen
|
||||||
|
|
||||||
# global variables
|
set -euo pipefail
|
||||||
declare -a video_file_list
|
|
||||||
|
# --------------------------------------------------------------------
|
||||||
|
# CONFIG / COLORS
|
||||||
|
# --------------------------------------------------------------------
|
||||||
settings_file="./settings.ini"
|
settings_file="./settings.ini"
|
||||||
ffprobe_path="/usr/bin/ffprobe"
|
ffprobe_path="/usr/bin/ffprobe"
|
||||||
|
|
||||||
|
|
||||||
#colors
|
|
||||||
RED='\033[0;31m'
|
RED='\033[0;31m'
|
||||||
GREEN='\033[0;92m'
|
GREEN='\033[0;92m'
|
||||||
BLUE='\033[0;94m'
|
BLUE='\033[0;94m'
|
||||||
@@ -15,179 +18,187 @@ WHITE_ON_GRAY='\033[0;37;100m'
|
|||||||
BLACK_ON_WHITE='\033[0;30;47m'
|
BLACK_ON_WHITE='\033[0;30;47m'
|
||||||
WHITE_ON_RED='\033[0;37;41m'
|
WHITE_ON_RED='\033[0;37;41m'
|
||||||
YELLOW_ON_WHITE='\033[0;37;43m'
|
YELLOW_ON_WHITE='\033[0;37;43m'
|
||||||
NC='\033[0m' # No Color
|
NC='\033[0m'
|
||||||
|
|
||||||
|
declare -A group_map # Map von Kombination → Ordnername
|
||||||
|
declare -A files_by_group # Map von Kombination → Liste von Dateien
|
||||||
|
declare -a combinations # Liste aller eindeutigen Kombinationen
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------
|
||||||
|
# FUNKTIONEN
|
||||||
# Funktion zum Extrahieren der Sprachen und Audio-Codec für alle Audio-Spuren
|
# --------------------------------------------------------------------
|
||||||
extract_audio_languages_and_codecs() {
|
extract_audio_languages_and_codecs() {
|
||||||
local file=$1
|
local file=$1
|
||||||
# Führe ffprobe aus und extrahiere die Streams
|
local ffprobe_json
|
||||||
ffprobe_json=$("$ffprobe_path" -v error -show_streams -of json "$file")
|
ffprobe_json=$("$ffprobe_path" -v error -show_streams -of json "$file")
|
||||||
|
local audio_streams=()
|
||||||
# Durchlaufe alle Audio-Streams und extrahiere Sprache und Codec
|
while IFS= read -r s; do
|
||||||
audio_streams=()
|
codec_name=$(echo "$s" | jq -r '.codec_name')
|
||||||
while IFS= read -r audio_stream; do
|
language=$(echo "$s" | jq -r '.tags.language // "unknown"')
|
||||||
stream_index=$(echo "$audio_stream" | jq -r '.index')
|
|
||||||
codec_name=$(echo "$audio_stream" | jq -r '.codec_name')
|
|
||||||
language=$(echo "$audio_stream" | jq -r '.tags.language')
|
|
||||||
|
|
||||||
# Format für jede Audio-Spur: [index-codec_language]
|
|
||||||
audio_streams+=("[$codec_name"_"$language]")
|
audio_streams+=("[$codec_name"_"$language]")
|
||||||
done < <(echo "$ffprobe_json" | jq -c '.streams[] | select(.codec_type == "audio")')
|
done < <(echo "$ffprobe_json" | jq -c '.streams[] | select(.codec_type=="audio")')
|
||||||
|
|
||||||
# Rückgabe des Arrays der Audio-Streams
|
|
||||||
echo "${audio_streams[@]}"
|
echo "${audio_streams[@]}"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Funktion zum Extrahieren der Sprachen und "forced"-Tags für alle Untertitel-Spuren
|
|
||||||
extract_subtitle_languages_and_forced_tags() {
|
extract_subtitle_languages_and_forced_tags() {
|
||||||
local file=$1
|
local file=$1
|
||||||
# Führe ffprobe aus und extrahiere die Streams
|
local ffprobe_json
|
||||||
ffprobe_json=$("$ffprobe_path" -v error -show_streams -of json "$file")
|
ffprobe_json=$("$ffprobe_path" -v error -show_streams -of json "$file")
|
||||||
|
local subtitle_streams=()
|
||||||
# Durchlaufe alle Untertitel-Streams und extrahiere Sprache und Forced-Tag
|
while IFS= read -r s; do
|
||||||
subtitle_streams=()
|
language=$(echo "$s" | jq -r '.tags.language // "unknown"')
|
||||||
while IFS= read -r subtitle_stream; do
|
forced_flag=$(echo "$s" | jq -r '.disposition.forced // 0')
|
||||||
stream_index=$(echo "$subtitle_stream" | jq -r '.index')
|
if [[ "$forced_flag" == "1" ]]; then
|
||||||
language=$(echo "$subtitle_stream" | jq -r '.tags.language')
|
|
||||||
# Überprüfe sowohl das Forced-Tag in "tags" als auch das Forced-Flag in "disposition"
|
|
||||||
forced=$(echo "$subtitle_stream" | jq -r '.tags.forced // empty')
|
|
||||||
forced_flag=$(echo "$subtitle_stream" | jq -r '.disposition.forced')
|
|
||||||
|
|
||||||
# Wenn das "forced"-Tag existiert, berücksichtige es
|
|
||||||
if [[ "$forced" == "1" || "$forced_flag" == "1" ]]; then
|
|
||||||
subtitle_streams+=("[forced_$language]")
|
subtitle_streams+=("[forced_$language]")
|
||||||
fi
|
fi
|
||||||
subtitle_streams+=("[$language]")
|
subtitle_streams+=("[$language]")
|
||||||
done < <(echo "$ffprobe_json" | jq -c '.streams[] | select(.codec_type == "subtitle")')
|
done < <(echo "$ffprobe_json" | jq -c '.streams[] | select(.codec_type=="subtitle")')
|
||||||
|
|
||||||
# Rückgabe des Arrays der Untertitel-Streams
|
|
||||||
echo "${subtitle_streams[@]}"
|
echo "${subtitle_streams[@]}"
|
||||||
}
|
}
|
||||||
|
|
||||||
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() {
|
relativ_to_fullpath() {
|
||||||
local path="$1"
|
local path="$1"
|
||||||
|
local scriptpath
|
||||||
scriptpath="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"
|
scriptpath="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"
|
||||||
|
|
||||||
# Entferne doppelte Slashes, bevor der Pfad weiterverarbeitet wird
|
|
||||||
path=$(echo "$path" | sed 's|/+|/|g')
|
path=$(echo "$path" | sed 's|/+|/|g')
|
||||||
|
|
||||||
if [ ! -e "$path" ]; then
|
if [ ! -e "$path" ]; then
|
||||||
pathfull=$(realpath "$(dirname "$scriptpath")/$path" 2>/dev/null)
|
realpath "$(dirname "$scriptpath")/$path" 2>/dev/null || echo "$path"
|
||||||
else
|
else
|
||||||
pathfull=$(realpath "$path" 2>/dev/null)
|
realpath "$path" 2>/dev/null || echo "$path"
|
||||||
fi
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
echo "$pathfull"
|
get_user_variables_from_ini_file() {
|
||||||
|
settings_file=$(relativ_to_fullpath "$settings_file")
|
||||||
|
if [ ! -e "$settings_file" ]; then
|
||||||
|
echo -e "${WHITE_ON_RED} Settingsfile nicht gefunden: $settings_file ${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
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")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------
|
||||||
main(){
|
# MAIN
|
||||||
|
# --------------------------------------------------------------------
|
||||||
|
main() {
|
||||||
get_user_variables_from_ini_file
|
get_user_variables_from_ini_file
|
||||||
|
|
||||||
output_dir="$input_folder/[0] Sort"
|
output_dir="$input_folder/[0] Sort"
|
||||||
# Entferne doppelte Slashes
|
|
||||||
output_dir=$(echo "$output_dir" | sed 's|//|/|g')
|
output_dir=$(echo "$output_dir" | sed 's|//|/|g')
|
||||||
|
|
||||||
|
|
||||||
echo "────────────────────────────────────────────────────────────────"
|
|
||||||
echo -e "${BLACK_ON_WHITE} Auto Sort ${NC}"
|
|
||||||
echo "────────────────────────────────────────────────────────────────"
|
|
||||||
echo " "
|
|
||||||
echo -e "${WHITE_ON_GRAY} Input Folder ${NC} ""$input_folder"""
|
|
||||||
echo -e "${WHITE_ON_GRAY} Output Folder ${NC} ""$output_dir"""
|
|
||||||
echo " "
|
|
||||||
echo "────────────────────────────────────────────────────────────────"
|
|
||||||
|
|
||||||
|
|
||||||
mkdir -p "$output_dir"
|
mkdir -p "$output_dir"
|
||||||
|
|
||||||
# Durchlaufe alle Videodateien im Eingabeverzeichnis rekursiv
|
echo "──────────────────────────────────────────────────────────────"
|
||||||
find "$input_folder" -type f \( -iname "*.mp4" -o -iname "*.mkv" -o -iname "*.avi" \) | while IFS= read -r file; do
|
echo -e "${BLACK_ON_WHITE} Auto Sort — Analysephase ${NC}"
|
||||||
|
echo "──────────────────────────────────────────────────────────────"
|
||||||
|
|
||||||
echo "────────────────────────────────────────────────────────────────"
|
# 1️⃣ Alle Dateien analysieren und Kombinationen erfassen
|
||||||
|
found_any=false
|
||||||
|
|
||||||
# Datei relativ zu input_folder
|
# Sortierordner-Pfad in ein sauberes Pattern für find umwandeln
|
||||||
|
escaped_output_dir=$(printf '%s\n' "$output_dir" | sed 's/[][\.*^$(){}?+|/]/\\&/g')
|
||||||
|
|
||||||
|
while IFS= read -r file; do
|
||||||
|
found_any=true
|
||||||
relative_path=$(realpath --relative-to="$input_folder" "$file")
|
relative_path=$(realpath --relative-to="$input_folder" "$file")
|
||||||
|
|
||||||
# Überspringe Dateien, die "-sample" im Dateinamen haben
|
# Sample-Dateien erkennen (case-insensitive, am Ende des Dateinamens)
|
||||||
if [[ "$file" == *"sample."* ]]; then
|
basename_lower=$(basename "$file" | tr '[:upper:]' '[:lower:]')
|
||||||
|
if [[ "$basename_lower" =~ \.sample\.(mkv|mp4|avi|vob|ts|mpeg|mov)$ ]]; then
|
||||||
echo -e "${YELLOW_ON_WHITE} Überspringe ${NC} ${YELLOW}$relative_path${NC}"
|
echo -e "${YELLOW_ON_WHITE} Überspringe Sample ${NC} $relative_path"
|
||||||
echo -e "${YELLOW_ON_WHITE} Grund ${NC} ${YELLOW}'sample.' im Dateinamen${NC}"
|
echo -e "${YELLOW_ON_WHITE} Grund ${NC} Enthält '.sample' vor Dateiendung${NC}"
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Extrahiere die Audio-Spuren, Sprache und Codec
|
|
||||||
audio_streams=$(extract_audio_languages_and_codecs "$file")
|
audio_streams=$(extract_audio_languages_and_codecs "$file")
|
||||||
|
|
||||||
# Extrahiere die Untertitel-Spuren, Sprache und "forced"-Tag
|
|
||||||
subtitle_streams=$(extract_subtitle_languages_and_forced_tags "$file")
|
subtitle_streams=$(extract_subtitle_languages_and_forced_tags "$file")
|
||||||
|
|
||||||
# Wenn keine Audio- oder Untertitel-Spuren vorhanden sind, überspringe die Datei
|
|
||||||
if [ -z "$audio_streams" ] && [ -z "$subtitle_streams" ]; then
|
if [ -z "$audio_streams" ] && [ -z "$subtitle_streams" ]; then
|
||||||
echo -e "${WHITE_ON_RED} Überspringe ${NC} ${RED}$relative_path${NC}"
|
echo -e "${WHITE_ON_RED} Keine Audio-/Subtitle-Spuren: ${NC} $relative_path"
|
||||||
echo -e "${WHITE_ON_RED} Grund ${NC} ${RED}Keine Audio- oder Untertitel-Spuren gefunden${NC}"
|
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Kombiniere die Audio- und Untertitel-Streams mit einem | nur, wenn subtitle_streams nicht leer ist
|
# Ausgabe der gefundenen Spuren
|
||||||
if [ -z "$subtitle_streams" ]; then
|
echo -e "${WHITE_ON_GRAY} Datei ${NC} $relative_path"
|
||||||
combined_streams="$audio_streams"
|
if [ -n "$audio_streams" ]; then
|
||||||
|
echo -e " 🎧 ${GREEN}Audio:${NC} $audio_streams"
|
||||||
else
|
else
|
||||||
combined_streams="${audio_streams} | ${subtitle_streams}"
|
echo -e " 🎧 ${RED}Audio:${NC} (keine)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Generiere den Ordnernamen basierend auf den kombinierten Streams
|
if [ -n "$subtitle_streams" ]; then
|
||||||
folder_name=$(echo "$combined_streams" | tr ' ' ' ')
|
echo -e " 💬 ${YELLOW}Subtitles:${NC} $subtitle_streams"
|
||||||
|
else
|
||||||
|
echo -e " 💬 ${RED}Subtitles:${NC} (keine)"
|
||||||
|
fi
|
||||||
|
|
||||||
# Zielordner für diese Kombination erstellen
|
combined_streams="$audio_streams | $subtitle_streams"
|
||||||
|
files_by_group["$combined_streams"]+="$file"$'\n'
|
||||||
|
|
||||||
|
done < <(
|
||||||
|
find "$input_folder" \
|
||||||
|
-type d -regex "^$escaped_output_dir$" -prune -false -o \
|
||||||
|
-type d -regex "^$escaped_output_dir/.*" -prune -false -o \
|
||||||
|
-type f \( \
|
||||||
|
-iname "*.mkv" -o -iname "*.mp4" -o -iname "*.avi" -o \
|
||||||
|
-iname "*.ts" -o -iname "*.vob" -o -iname "*.mpeg" -o -iname "*.mov" \
|
||||||
|
\) -print
|
||||||
|
)
|
||||||
|
|
||||||
|
if [ "$found_any" = false ]; then
|
||||||
|
echo "──────────────────────────────────────────────────────────────"
|
||||||
|
echo -e "${YELLOW_ON_WHITE} Keine neuen Videodateien gefunden. ${NC}"
|
||||||
|
echo "──────────────────────────────────────────────────────────────"
|
||||||
|
read -rp "Zum Schließen bitte [ENTER] drücken ..."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# 2️⃣ Ordnernamen prüfen
|
||||||
|
group_counter=1
|
||||||
|
for combo in "${!files_by_group[@]}"; do
|
||||||
|
folder_name=$(echo "$combo" | tr -s ' ' '_')
|
||||||
|
target_folder="$output_dir/$folder_name"
|
||||||
|
if [ ${#target_folder} -gt 240 ]; then
|
||||||
|
folder_name="Gruppe_${group_counter}"
|
||||||
|
((group_counter++))
|
||||||
|
fi
|
||||||
|
group_map["$combo"]="$folder_name"
|
||||||
|
combinations+=("$combo")
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "──────────────────────────────────────────────────────────────"
|
||||||
|
echo -e "${BLACK_ON_WHITE} Verschiebe Dateien ${NC}"
|
||||||
|
echo "──────────────────────────────────────────────────────────────"
|
||||||
|
|
||||||
|
# 3️⃣ Dateien verschieben
|
||||||
|
for combo in "${combinations[@]}"; do
|
||||||
|
folder_name="${group_map[$combo]}"
|
||||||
target_folder="$output_dir/$folder_name"
|
target_folder="$output_dir/$folder_name"
|
||||||
mkdir -p "$target_folder"
|
mkdir -p "$target_folder"
|
||||||
|
|
||||||
|
while IFS= read -r file; do
|
||||||
echo -e "${WHITE_ON_GRAY} Verschiebe ${NC} $relative_path"
|
[ -z "$file" ] && continue
|
||||||
echo -e "${WHITE_ON_GRAY} Nach ${NC}${GREEN}$folder_name${NC}"
|
relative_path=$(realpath --relative-to="$input_folder" "$file")
|
||||||
|
echo -e "${WHITE_ON_GRAY} → ${NC} ${relative_path}"
|
||||||
# Datei in den entsprechenden Ordner verschieben
|
echo -e " nach ${GREEN}$folder_name${NC}"
|
||||||
mv "$file" "$target_folder/"
|
mv "$file" "$target_folder/"
|
||||||
|
done <<< "${files_by_group[$combo]}"
|
||||||
done
|
done
|
||||||
|
|
||||||
|
echo "──────────────────────────────────────────────────────────────"
|
||||||
|
echo -e "${BLACK_ON_WHITE} Fertig! ${NC}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Main
|
# Main
|
||||||
main
|
main
|
||||||
|
|
||||||
|
echo
|
||||||
|
read -rp "Zum Schließen bitte [ENTER] drücken ..."
|
||||||
|
exit 0
|
||||||
$SHELL
|
$SHELL
|
||||||
Reference in New Issue
Block a user