init
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
#!/bin/bash
|
||||
|
||||
#==============================================================================
|
||||
# Модуль действий: выполнение mkvmerge, создание симлинков и т.д.
|
||||
#==============================================================================
|
||||
|
||||
# Функция для склейки видео и аудио
|
||||
# $1: Исходный видеофайл
|
||||
# $2: Исходный аудиофайл
|
||||
# $3: Выходной файл
|
||||
action_merge_mkv() {
|
||||
local video_file="$1"
|
||||
local audio_file="$2"
|
||||
local output_file="$3"
|
||||
|
||||
echo "---"
|
||||
echo "ИСХОДНОЕ ВИДЕО : $video_file"
|
||||
echo "ИСХОДНОЕ АУДИО : $audio_file"
|
||||
echo "РЕЗУЛЬТАТ : $output_file"
|
||||
|
||||
# Это основная команда. Вы можете настроить ее под себя.
|
||||
# --language 0:und - язык первой дорожки (видео) - неопределенный
|
||||
# --language 1:rus - язык второй дорожки (аудио) - русский
|
||||
# Вы можете добавить больше опций, например, --track-name, --default-track-flag
|
||||
mkvmerge \
|
||||
-o "$output_file" \
|
||||
--language 0:und "$video_file" \
|
||||
--language 1:rus "$audio_file"
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "УСПЕХ: Файл создан."
|
||||
else
|
||||
echo "ОШИБКА: mkvmerge завершился с ошибкой."
|
||||
fi
|
||||
echo
|
||||
}
|
||||
|
||||
# Функция для создания символической ссылки
|
||||
# $1: Исходный видеофайл
|
||||
# $2: Имя ссылки (выходной файл)
|
||||
action_create_symlink() {
|
||||
local video_file="$1"
|
||||
local output_file="$2"
|
||||
|
||||
echo "---"
|
||||
echo "ИСТОЧНИК: $video_file"
|
||||
echo "ССЫЛКА : $output_file"
|
||||
|
||||
# Создаем символическую ссылку
|
||||
ln -s "$video_file" "$output_file"
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "УСПЕХ: Ссылка создана."
|
||||
else
|
||||
echo "ОШИБКА: Не удалось создать ссылку."
|
||||
fi
|
||||
echo
|
||||
}
|
||||
|
||||
|
||||
# Главная функция запуска обработки
|
||||
run_processing() {
|
||||
# 1. Готовим списки файлов
|
||||
if ! logic_prepare_file_lists; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ ${#FILE_TRIPLETS[@]} -eq 0 ]; then
|
||||
ui_show_message "Запуск" "Нет файлов для обработки."
|
||||
return
|
||||
fi
|
||||
|
||||
# 2. Спрашиваем, что делать
|
||||
local action_to_perform
|
||||
action_to_perform=$(ui_select_action)
|
||||
|
||||
if [[ -z "$action_to_perform" ]]; then
|
||||
ui_show_message "Отмена" "Операция отменена пользователем."
|
||||
return
|
||||
fi
|
||||
|
||||
# 3. Финальное подтверждение
|
||||
local confirmation_text="Вы уверены, что хотите выполнить '${action_to_perform}' для ${#FILE_TRIPLETS[@]} файлов?\n\n"
|
||||
confirmation_text+="Результаты будут сохранены в:\n$OUTPUT_BASE_DIR"
|
||||
if ! ui_confirm "Финальное подтверждение" "$confirmation_text"; then
|
||||
ui_show_message "Отмена" "Операция отменена пользователем."
|
||||
return
|
||||
fi
|
||||
|
||||
# 4. Создаем выходной каталог, если его нет
|
||||
if [ ! -d "$OUTPUT_BASE_DIR" ]; then
|
||||
if ui_confirm "Создание каталога" "Каталог '$OUTPUT_BASE_DIR' не существует. Создать его?"; then
|
||||
mkdir -p "$OUTPUT_BASE_DIR"
|
||||
if [ $? -ne 0 ]; then
|
||||
ui_show_message "Ошибка" "Не удалось создать каталог '$OUTPUT_BASE_DIR'."
|
||||
return
|
||||
fi
|
||||
else
|
||||
ui_show_message "Отмена" "Операция отменена, так как выходной каталог не существует."
|
||||
return
|
||||
fi
|
||||
fi
|
||||
|
||||
# 5. Выполнение операции с прогресс-баром
|
||||
local total_files=${#FILE_TRIPLETS[@]}
|
||||
local current_file=0
|
||||
|
||||
(
|
||||
for triplet in "${FILE_TRIPLETS[@]}"; do
|
||||
# Рассчитываем прогресс
|
||||
local progress=$(( 100 * current_file / total_files ))
|
||||
echo "$progress"
|
||||
|
||||
# Обновляем текст в прогресс-баре
|
||||
echo -e "XXX\n$((current_file + 1)) / $total_files\nОбработка: $(basename "$triplet" | cut -f1 -d$'\t')\nXXX"
|
||||
|
||||
# Разбираем триплет
|
||||
IFS=$'\t' read -r video audio output <<< "$triplet"
|
||||
|
||||
# Выполняем выбранное действие
|
||||
case "$action_to_perform" in
|
||||
"MERGE")
|
||||
action_merge_mkv "$video" "$audio" "$output"
|
||||
;;
|
||||
"SYMLINK")
|
||||
action_create_symlink "$video" "$output"
|
||||
;;
|
||||
esac
|
||||
|
||||
((current_file++))
|
||||
done
|
||||
# Для завершения прогресс-бара
|
||||
echo "100"
|
||||
echo -e "XXX\nГотово!\nНажмите Enter для выхода\nXXX"
|
||||
sleep 2
|
||||
) | dialog $DIALOG_OPTS --title "Выполнение..." --gauge "Подготовка..." 10 70 0
|
||||
|
||||
clear
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
#!/bin/bash
|
||||
|
||||
#==============================================================================
|
||||
# Модуль основной логики: поиск файлов, сопоставление, генерация имён
|
||||
#==============================================================================
|
||||
|
||||
# Глобальная переменная для хранения подготовленных данных
|
||||
# Формат: "видео_файл\tаудио_файл\tвыходной_файл" для каждой строки
|
||||
declare -a FILE_TRIPLETS
|
||||
|
||||
# Основная функция, которая находит и сопоставляет файлы
|
||||
# Возвращает 0 в случае успеха, 1 в случае ошибки
|
||||
logic_prepare_file_lists() {
|
||||
# Очищаем предыдущие результаты
|
||||
FILE_TRIPLETS=()
|
||||
|
||||
# Проверка, заданы ли все необходимые пути
|
||||
if [[ -z "$VIDEO_SRC_DIR" || -z "$AUDIO_SRC_DIR" || -z "$OUTPUT_BASE_DIR" || -z "$OUTPUT_SERIES_NAME" ]]; then
|
||||
ui_show_message "Ошибка" "Не все обязательные параметры заданы (каталоги видео, аудио, вывода и имя сериала)."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 1. Найти видеофайлы
|
||||
# Использование `find ... -print0` и `mapfile` для безопасной обработки имён с пробелами
|
||||
local video_list_raw
|
||||
mapfile -d '' video_list_raw < <(find "$VIDEO_SRC_DIR" -maxdepth 1 -name "$VIDEO_FILE_PATTERN" -print0 | sort -z)
|
||||
if [ ${#video_list_raw[@]} -eq 0 ]; then
|
||||
ui_show_message "Ошибка" "Видеофайлы по шаблону '$VIDEO_FILE_PATTERN' в каталоге '$VIDEO_SRC_DIR' не найдены."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 2. Найти аудиофайлы
|
||||
# maxdepth не используется, чтобы искать в подкаталогах, как в примерах
|
||||
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' в каталоге '$AUDIO_SRC_DIR' не найдены."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 3. Проверить совпадение количества файлов
|
||||
if [ ${#video_list_raw[@]} -ne ${#audio_list_raw[@]} ]; then
|
||||
local msg="Количество найденных файлов не совпадает!\n\n"
|
||||
msg+="Видео: ${#video_list_raw[@]}\n"
|
||||
msg+="Аудио: ${#audio_list_raw[@]}\n\n"
|
||||
msg+="Проверьте каталоги и шаблоны поиска."
|
||||
ui_show_message "Ошибка сопоставления" "$msg"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 4. Сгенерировать выходные имена и собрать триплеты
|
||||
local i
|
||||
for i in "${!video_list_raw[@]}"; do
|
||||
local video_file="${video_list_raw[$i]}"
|
||||
local audio_file="${audio_list_raw[$i]}"
|
||||
local video_basename
|
||||
video_basename=$(basename "$video_file")
|
||||
|
||||
# Извлекаем номер эпизода с помощью regex
|
||||
if [[ "$video_basename" =~ $EPISODE_NUMBER_REGEX ]]; then
|
||||
local episode_num="${BASH_REMATCH[1]}"
|
||||
# Приводим к формату с ведущим нулём, если нужно (например, 1 -> 01)
|
||||
episode_num=$(printf "%02d" "$((10#$episode_num))")
|
||||
else
|
||||
ui_show_message "Ошибка Regex" "Не удалось извлечь номер эпизода из файла:\n$video_basename\n\nС помощью регулярного выражения:\n$EPISODE_NUMBER_REGEX"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Собираем имя выходного файла из шаблона
|
||||
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}"
|
||||
|
||||
local output_path="${OUTPUT_BASE_DIR}/${output_name}"
|
||||
|
||||
# Сохраняем триплет в массив
|
||||
FILE_TRIPLETS+=("$video_file"$'\t'"$audio_file"$'\t'"$output_path")
|
||||
done
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
# Функция для отображения предпросмотра
|
||||
logic_show_preview() {
|
||||
if ! logic_prepare_file_lists; then
|
||||
# Сообщение об ошибке уже было показано внутри logic_prepare_file_lists
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ ${#FILE_TRIPLETS[@]} -eq 0 ]; then
|
||||
ui_show_message "Предпросмотр" "Нет файлов для обработки."
|
||||
return
|
||||
fi
|
||||
|
||||
local preview_text="Будут обработаны следующие файлы (${#FILE_TRIPLETS[@]} шт.):\n\n"
|
||||
local count=1
|
||||
for triplet in "${FILE_TRIPLETS[@]}"; do
|
||||
IFS=$'\t' read -r video audio output <<< "$triplet"
|
||||
preview_text+="$(printf "%02d" $count). \n"
|
||||
preview_text+=" \ZbВИДЕО:\Zn $(basename "$video")\n"
|
||||
preview_text+=" \ZbАУДИО:\Zn $(basename "$audio")\n"
|
||||
preview_text+=" \Zb-> ВЫВОД:\Zn $(basename "$output")\n\n"
|
||||
((count++))
|
||||
done
|
||||
|
||||
ui_show_message "Предпросмотр" "$preview_text"
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
#!/bin/bash
|
||||
|
||||
#==============================================================================
|
||||
# Модуль интерфейса (TUI) на основе 'dialog'
|
||||
#==============================================================================
|
||||
|
||||
DIALOG_OPTS="--colors --backtitle 'Универсальный обработчик медиа'"
|
||||
|
||||
# Показать главное меню
|
||||
# Принимает массив пунктов меню
|
||||
ui_main_menu() {
|
||||
dialog $DIALOG_OPTS --title "Главное меню" \
|
||||
--menu "Выберите опцию для редактирования или действия:" 20 85 15 "${@}" 2>&1 >/dev/tty
|
||||
}
|
||||
|
||||
# Получить путь к каталогу
|
||||
# $1: Заголовок окна
|
||||
# $2: Текущее значение (для старта)
|
||||
# $3: Путь по умолчанию (если текущее значение пусто)
|
||||
ui_get_directory() {
|
||||
local title="$1"
|
||||
local current_path="$2"
|
||||
local default_path="$3"
|
||||
local start_path="${current_path:-$default_path}"
|
||||
|
||||
# Убедимся, что путь заканчивается на / для dselect
|
||||
[[ "$start_path" != */ ]] && start_path="$start_path/"
|
||||
|
||||
dialog $DIALOG_OPTS --title "$title" \
|
||||
--dselect "$start_path" 10 70 2>&1 >/dev/tty
|
||||
}
|
||||
|
||||
# Получить текстовый ввод от пользователя
|
||||
# $1: Заголовок окна
|
||||
# $2: Текущее значение
|
||||
ui_get_input() {
|
||||
local title="$1"
|
||||
local current_value="$2"
|
||||
|
||||
dialog $DIALOG_OPTS --title "$title" \
|
||||
--inputbox "Введите новое значение:" 10 70 "$current_value" 2>&1 >/dev/tty
|
||||
}
|
||||
|
||||
# Показать информационное сообщение
|
||||
# $1: Заголовок
|
||||
# $2: Текст сообщения
|
||||
ui_show_message() {
|
||||
local title="$1"
|
||||
local text="$2"
|
||||
dialog $DIALOG_OPTS --title "$title" --msgbox "$text" 20 70
|
||||
}
|
||||
|
||||
# Показать окно с выбором Да/Нет
|
||||
# $1: Заголовок
|
||||
# $2: Текст вопроса
|
||||
ui_confirm() {
|
||||
local title="$1"
|
||||
local text="$2"
|
||||
dialog $DIALOG_OPTS --title "$title" --yesno "$text" 10 70
|
||||
return $?
|
||||
}
|
||||
|
||||
# Показать меню выбора действия перед запуском
|
||||
ui_select_action() {
|
||||
dialog $DIALOG_OPTS --title "Выбор действия" \
|
||||
--menu "Какую операцию выполнить с найденными файлами?" 15 70 2 \
|
||||
"MERGE" "Склеить видео и аудио с помощью mkvmerge" \
|
||||
"SYMLINK" "Создать символические ссылки на видеофайлы" 2>&1 >/dev/tty
|
||||
}
|
||||
Executable
+102
@@ -0,0 +1,102 @@
|
||||
#!/bin/bash
|
||||
|
||||
#==============================================================================
|
||||
# Универсальный скрипт для обработки медиафайлов с TUI-интерфейсом
|
||||
#==============================================================================
|
||||
|
||||
# --- Подключение модулей ---
|
||||
# Убедимся, что скрипты-модули находятся в том же каталоге
|
||||
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd)
|
||||
source "$SCRIPT_DIR/core_ui.sh"
|
||||
source "$SCRIPT_DIR/core_logic.sh"
|
||||
source "$SCRIPT_DIR/core_actions.sh"
|
||||
|
||||
# --- Конфигурация по умолчанию (можно менять) ---
|
||||
# Эти пути будут предлагаться по умолчанию в диалогах выбора
|
||||
DEFAULT_ROOT_PATH="/var/www/nextcloud/data/grayhook/files/"
|
||||
DEFAULT_TORRENTS_DIR="${DEFAULT_ROOT_PATH}/Torrents"
|
||||
DEFAULT_ARCHIVE_DIR="${DEFAULT_ROOT_PATH}/Archive/Anime"
|
||||
|
||||
# --- Переменные состояния (будут меняться через интерфейс) ---
|
||||
VIDEO_SRC_DIR=""
|
||||
AUDIO_SRC_DIR=""
|
||||
OUTPUT_BASE_DIR=""
|
||||
OUTPUT_SERIES_NAME=""
|
||||
SEASON_NUMBER="01"
|
||||
|
||||
# Паттерны для поиска файлов (можно использовать find-совместимые wildcards)
|
||||
VIDEO_FILE_PATTERN="*.mkv"
|
||||
AUDIO_FILE_PATTERN="*.mka"
|
||||
|
||||
# Регулярное выражение для извлечения номера серии из ИМЕНИ ВИДЕОФАЙЛА
|
||||
# Использует синтаксис ERE (sed -E). Группа захвата (в скобках) должна поймать номер.
|
||||
EPISODE_NUMBER_REGEX='.* ([0-9]{2}) .*'
|
||||
|
||||
# Шаблон для имени выходного файла. Заполнители будут заменены.
|
||||
# {SERIES_NAME} - Имя сериала
|
||||
# {SEASON} - Номер сезона (с ведущим нулём)
|
||||
# {EPISODE} - Номер эпизода (с ведущим нулём, извлечённый regex'ом)
|
||||
OUTPUT_FILENAME_TEMPLATE="{SERIES_NAME} s{SEASON}e{EPISODE}.mkv"
|
||||
|
||||
# --- Главный цикл программы ---
|
||||
main() {
|
||||
while true; do
|
||||
# Формируем пункты меню с текущими значениями
|
||||
menu_items=(
|
||||
"V" "Видео каталог : ${VIDEO_SRC_DIR:-_не задан_}"
|
||||
"A" "Аудио каталог : ${AUDIO_SRC_DIR:-_не задан_}"
|
||||
"O" "Выходной каталог : ${OUTPUT_BASE_DIR:-_не задан_}"
|
||||
"N" "Имя для Plex : ${OUTPUT_SERIES_NAME:-_не задано_}"
|
||||
"S" "Номер сезона : $SEASON_NUMBER"
|
||||
"" "--- Шаблоны и Regex ---"
|
||||
"P" "Шаблон видеофайлов : $VIDEO_FILE_PATTERN"
|
||||
"U" "Шаблон аудиофайлов : $AUDIO_FILE_PATTERN"
|
||||
"R" "Regex номера серии : $EPISODE_NUMBER_REGEX"
|
||||
"T" "Шаблон имени вывода : $OUTPUT_FILENAME_TEMPLATE"
|
||||
"" "--- Действия ---"
|
||||
"VIEW" "Предпросмотр сопоставления файлов"
|
||||
"RUN" "ЗАПУСТИТЬ обработку"
|
||||
)
|
||||
|
||||
choice=$(ui_main_menu "${menu_items[@]}")
|
||||
|
||||
# Выход из скрипта по кнопке Cancel или Esc
|
||||
if [[ -z "$choice" ]]; then
|
||||
clear
|
||||
echo "Выход."
|
||||
break
|
||||
fi
|
||||
|
||||
case "$choice" in
|
||||
V) VIDEO_SRC_DIR=$(ui_get_directory "Выберите каталог с видеофайлами" "$VIDEO_SRC_DIR" "$DEFAULT_TORRENTS_DIR") ;;
|
||||
A) AUDIO_SRC_DIR=$(ui_get_directory "Выберите каталог с аудиофайлами" "$AUDIO_SRC_DIR" "$VIDEO_SRC_DIR") ;;
|
||||
O) OUTPUT_BASE_DIR=$(ui_get_directory "Выберите БАЗОВЫЙ каталог для результата" "$OUTPUT_BASE_DIR" "$DEFAULT_ARCHIVE_DIR") ;;
|
||||
N) OUTPUT_SERIES_NAME=$(ui_get_input "Введите имя сериала для Plex" "$OUTPUT_SERIES_NAME") ;;
|
||||
S) SEASON_NUMBER=$(ui_get_input "Введите номер сезона (например, 01, 02)" "$SEASON_NUMBER") ;;
|
||||
P) VIDEO_FILE_PATTERN=$(ui_get_input "Введите шаблон для поиска видео (*.mkv, *ep*.mkv)" "$VIDEO_FILE_PATTERN") ;;
|
||||
U) AUDIO_FILE_PATTERN=$(ui_get_input "Введите шаблон для поиска аудио (*.mka, *.ac3)" "$AUDIO_FILE_PATTERN") ;;
|
||||
R) EPISODE_NUMBER_REGEX=$(ui_get_input "Введите ERE-regex для номера серии" "$EPISODE_NUMBER_REGEX") ;;
|
||||
T) OUTPUT_FILENAME_TEMPLATE=$(ui_get_input "Введите шаблон имени выходного файла" "$OUTPUT_FILENAME_TEMPLATE") ;;
|
||||
|
||||
VIEW)
|
||||
# Вызываем предпросмотр
|
||||
logic_show_preview
|
||||
;;
|
||||
|
||||
RUN)
|
||||
# Вызываем меню выбора действия и запускаем обработку
|
||||
run_processing
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
# --- Точка входа ---
|
||||
# Проверка наличия dialog
|
||||
if ! command -v dialog &> /dev/null; then
|
||||
echo "Команда 'dialog' не найдена. Пожалуйста, установите ее."
|
||||
echo "sudo apt-get install dialog (Debian/Ubuntu)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
main
|
||||
Reference in New Issue
Block a user