#!/bin/bash #============================================================================== # Модуль основной логики: поиск файлов, сопоставление, генерация имён #============================================================================== declare -a FILE_TRIPLETS # Функция стала "умной" и проверяет CURRENT_ACTION logic_prepare_file_lists() { FILE_TRIPLETS=() local common_params_ok=true if [[ -z "$VIDEO_SRC_DIR" || -z "$OUTPUT_BASE_DIR" || -z "$OUTPUT_SERIES_NAME" ]]; then common_params_ok=false fi local video_list_raw if $common_params_ok; then mapfile -d '' video_list_raw < <(find "$VIDEO_SRC_DIR" -maxdepth 1 -name "$VIDEO_FILE_PATTERN" -print0 | sort -z) fi # РЕЖИМ: СКЛЕЙКА if [ "$CURRENT_ACTION" == "MERGE" ]; then if ! $common_params_ok || [[ -z "$AUDIO_SRC_DIR" ]]; then ui_show_message "Ошибка" "Для склейки должны быть заданы все каталоги (видео, аудио, вывод) и имя сериала." return 1 fi if [ ${#video_list_raw[@]} -eq 0 ]; then ui_show_message "Ошибка" "Видеофайлы по шаблону '$VIDEO_FILE_PATTERN' не найдены." return 1 fi local audio_list_raw mapfile -d '' audio_list_raw < <(find "$AUDIO_SRC_DIR" -name "$AUDIO_FILE_PATTERN" -print0 | sort -z) if [ ${#audio_list_raw[@]} -eq 0 ]; then ui_show_message "Ошибка" "Аудиофайлы по шаблону '$AUDIO_FILE_PATTERN' не найдены." return 1 fi if [ ${#video_list_raw[@]} -ne ${#audio_list_raw[@]} ]; then ui_show_message "Ошибка" "Количество видео (${#video_list_raw[@]}) и аудио (${#audio_list_raw[@]}) не совпадает." return 1 fi for i in "${!video_list_raw[@]}"; do local video_file="${video_list_raw[$i]}" local audio_file="${audio_list_raw[$i]}" local output_path output_path=$(logic_generate_output_name "$video_file") || return 1 FILE_TRIPLETS+=("$video_file"$'\t'"$audio_file"$'\t'"$output_path") done # РЕЖИМ: СИМЛИНКИ elif [ "$CURRENT_ACTION" == "SYMLINK" ]; then if ! $common_params_ok; then ui_show_message "Ошибка" "Для симлинков должны быть заданы каталог видео, каталог вывода и имя сериала." return 1 fi if [ ${#video_list_raw[@]} -eq 0 ]; then ui_show_message "Ошибка" "Видеофайлы по шаблону '$VIDEO_FILE_PATTERN' не найдены." return 1 fi for video_file in "${video_list_raw[@]}"; do local output_path output_path=$(logic_generate_output_name "$video_file") || continue # --- ИСПРАВЛЕНИЕ ЗДЕСЬ: ИСПОЛЬЗУЕМ ЯВНЫЙ ЗАПОЛНИТЕЛЬ --- # Это гарантирует, что `read` всегда увидит три поля. local placeholder="__SYMLINK_AUDIO_PLACEHOLDER__" FILE_TRIPLETS+=("$video_file"$'\t'"$placeholder"$'\t'"$output_path") done fi logic_check_output_paths || return 1 return 0 } # Уже готовый результат: обычный файл или симлинк (в том числе битый). # Каталог сюда не входит: это не серия, а ошибка пути. output_file_exists() { local path="$1" [[ -f "$path" || -L "$path" ]] } # Одинаковые имена и путь-каталог ломают пропуск: вторая серия увидела бы # файл, созданный первой, и отчёт засчитал бы её как уже готовую. logic_check_output_paths() { local -A seen=() local triplet video audio output local -a dir_errors=() local -a dup_errors=() for triplet in "${FILE_TRIPLETS[@]}"; do IFS=$'\t' read -r video audio output <<< "$triplet" if [[ -d "$output" && ! -L "$output" ]]; then dir_errors+=("$(basename "$video") → $output") continue fi if [[ -n ${seen["$output"]+x} ]]; then dup_errors+=("$(basename "${seen[$output]}") и $(basename "$video") → $(basename "$output")") else seen["$output"]="$video" fi done if [ ${#dir_errors[@]} -gt 0 ]; then local text="Путь вывода указывает на каталог, а не на файл:\n\n" local line for line in "${dir_errors[@]}"; do text+="$line\n" done ui_show_message "Ошибка" "$text" return 1 fi if [ ${#dup_errors[@]} -gt 0 ]; then local text="Несколько серий получают одно и то же имя:\n\n" local line for line in "${dup_errors[@]}"; do text+="$line\n" done ui_show_message "Ошибка" "$text" return 1 fi return 0 } # Вспомогательная функция для генерации имени, чтобы не дублировать код logic_generate_output_name() { local video_file="$1" local video_basename video_basename=$(basename "$video_file") if [[ "$video_basename" =~ $EPISODE_NUMBER_REGEX ]]; then local episode_num episode_num=$(printf "%02d" "$((10#${BASH_REMATCH[1]}))") local output_name="$OUTPUT_FILENAME_TEMPLATE" output_name="${output_name/\{SERIES_NAME\}/$OUTPUT_SERIES_NAME}" output_name="${output_name/\{SEASON\}/$SEASON_NUMBER}" output_name="${output_name/\{EPISODE\}/$episode_num}" echo "${OUTPUT_BASE_DIR}/${output_name}" return 0 else ui_show_message "Ошибка Regex" "Не удалось извлечь номер эпизода из файла:\n$video_basename\n\nС помощью регулярного выражения:\n$EPISODE_NUMBER_REGEX" return 1 fi } # Предпросмотр теперь будет работать правильно logic_show_preview() { if ! logic_prepare_file_lists; then return 1; fi if [ ${#FILE_TRIPLETS[@]} -eq 0 ]; then ui_show_message "Предпросмотр" "Нет файлов для обработки по текущим настройкам." return fi local body="" local count=1 local skip_count=0 for triplet in "${FILE_TRIPLETS[@]}"; do IFS=$'\t' read -r video audio output <<< "$triplet" body+="$(printf "%02d" $count). \n" local exists=false if output_file_exists "$output"; then exists=true skip_count=$((skip_count + 1)) fi if [ "$CURRENT_ACTION" == "MERGE" ]; then body+=" \ZbВИДЕО:\Zn $(basename "$video")\n" body+=" \ZbАУДИО:\Zn $(basename "$audio")\n" body+=" \Zb-> ВЫВОД:\Zn $(basename "$output")\n" else # SYMLINK body+=" \ZbИСТОЧНИК:\Zn $(basename "$video")\n" body+=" \Zb-> ССЫЛКА:\Zn $(basename "$output")\n" fi if $exists; then body+=" \Z3ПРОПУСК:\Zn операция пропущена — файл уже есть\n\n" else body+="\n" fi count=$((count + 1)) done local pending=$((${#FILE_TRIPLETS[@]} - skip_count)) local preview_text="РЕЖИМ: $CURRENT_ACTION\n" preview_text+="К обработке: $pending, пропуск (уже есть): $skip_count\n\n" preview_text+="$body" ui_show_message "Предпросмотр" "$preview_text" }