From 074630a351effc2a87c344e47b2fb9245f94e596 Mon Sep 17 00:00:00 2001 From: grayhook Date: Tue, 8 Jul 2025 15:41:25 +0700 Subject: [PATCH] init --- .convert_demon_slayer-tv4.sh.swp | Bin 0 -> 12288 bytes convert_dandadadn.sh | 18 +++++ convert_demon_slayer-tv4.sh | 30 +++++++++ convert_demon_slayer1.sh | 8 +++ convert_hunter.sh | 19 ++++++ convert_mashle.sh | 19 ++++++ convert_revuvu.sh | 18 +++++ convert_rezero2.sh | 19 ++++++ convert_sheldon.sh | 18 +++++ convert_suba.sh | 19 ++++++ convert_tokyo_ghoul.sh | 12 ++++ convert_tokyo_ghoul_re.sh | 12 ++++ convert_tokyo_ghoul_re2.sh | 12 ++++ convert_tokyo_ghoul_root_a.sh | 12 ++++ gemini/core_actions.sh | 139 +++++++++++++++++++++++++++++++++++++++ gemini/core_logic.sh | 109 ++++++++++++++++++++++++++++++ gemini/core_ui.sh | 69 +++++++++++++++++++ gemini/universal_merger.sh | 102 ++++++++++++++++++++++++++++ 18 files changed, 635 insertions(+) create mode 100644 .convert_demon_slayer-tv4.sh.swp create mode 100755 convert_dandadadn.sh create mode 100755 convert_demon_slayer-tv4.sh create mode 100755 convert_demon_slayer1.sh create mode 100755 convert_hunter.sh create mode 100755 convert_mashle.sh create mode 100755 convert_revuvu.sh create mode 100755 convert_rezero2.sh create mode 100755 convert_sheldon.sh create mode 100755 convert_suba.sh create mode 100755 convert_tokyo_ghoul.sh create mode 100755 convert_tokyo_ghoul_re.sh create mode 100755 convert_tokyo_ghoul_re2.sh create mode 100644 convert_tokyo_ghoul_root_a.sh create mode 100644 gemini/core_actions.sh create mode 100644 gemini/core_logic.sh create mode 100644 gemini/core_ui.sh create mode 100755 gemini/universal_merger.sh diff --git a/.convert_demon_slayer-tv4.sh.swp b/.convert_demon_slayer-tv4.sh.swp new file mode 100644 index 0000000000000000000000000000000000000000..98f4bdfd4cd15b22e2cbe39d6da06b7759cd53a8 GIT binary patch literal 12288 zcmeI2&rcIU6vqcH#vgz`5>LjAV@)YZcUsT{f=w(bfhtf_K%*>ahVHaoX?L34Z7G53 z(UUj-1^xgJp7bA3qc{HmPwK%xLE_tGTPQzO;z50r{qA;W=Dm6Id2bJ!?o#YtdYX=C zF#_!@A@oDJ^rrh3*^FEwMO&!xu{xKW*MOv=IZCOzY$K$~lfB0VHrRftp*04)>6u!MOjZUu9S5h4b?V z&&LrYfCP{L5AY>-jr#qCLZvE;Ibj$%Q8P@HGc8x; z>ZTzT)d@Y0EiBX!cJWv)j%B>HzC)uH9r88<%?k)R3>yAb&wJ+Y=>Yf8c3>rb$IqR1 zm>rxAa20G|za8mTm{x7bUx9|%HHXpm$s1V@2zcQ*(x6dEnZvaKs`oF%q9cp?+MwRl zBUBsUp|#BX++8@5bCaCwaOgU3>d-%voE~pdCw4>Xajmjk=S3E2d%oFUFfD_!PLmH{5Oy@-~VNU*ox ziNLe13dfaxRd}Ryc+t&&BT9Fw(IM@hlSTj#-he zQ=^um4}`fS=%jE;rY-24ElkU_ij1_}w&23vwq5^PS{w)ZVPBP|{Y#_Pi7D=RD9k_|UsRyBhg!WF#z M1%AOSONTS^1HMJ^tN;K2 literal 0 HcmV?d00001 diff --git a/convert_dandadadn.sh b/convert_dandadadn.sh new file mode 100755 index 0000000..9d1f20a --- /dev/null +++ b/convert_dandadadn.sh @@ -0,0 +1,18 @@ +ROOTPATH="/var/www/nextcloud/data/grayhook/files/" +INDIR="Torrents" +OUTDIR="Archive/Anime" +ORIGNAME="Dandadan [WEB-DL CR 1080p AVC DDP]" +NAME="Dan Da Dan" +OUTPUTPATH="${ROOTPATH}/${OUTDIR}/${NAME}" +INPUTPATH="${ROOTPATH}/${INDIR}/${ORIGNAME}" + +mkdir -p "${ROOTPATH}/${OUTDIR}/${NAME}" || exit -1; +paste -d '\n' \ + <(find "$INPUTPATH" -maxdepth 1 -name '*.mkv' | sort) \ + <(find "$INPUTPATH" -name '*AniLibria*.mka' | sort) \ + <(find "$INPUTPATH" -maxdepth 1 -name '*.mkv' | sort | \ + sed -e "s/.* \([0-9]\{2\}\) .*/${OUTPUTPATH//\//\\\/}\/${NAME} s01e\1.mkv/g") | \ + xargs -d '\n' printf '"%s" "%s" "%s"\n' | \ + xargs -n 3 \ + ffmpeg_add_audio_into_video \ + ; diff --git a/convert_demon_slayer-tv4.sh b/convert_demon_slayer-tv4.sh new file mode 100755 index 0000000..4bf2836 --- /dev/null +++ b/convert_demon_slayer-tv4.sh @@ -0,0 +1,30 @@ +ROOTPATH="/var/www/nextcloud/data/grayhook/files/" +INDIR="Torrents" +OUTDIR="Archive/Anime" +ORIGNAME="Kimetsu.no.Yaiba.Hashira.Geiko.hen.WEB-DL.1080p" +NAME="Kimetsu no Yaiba Hashira Training" +OUTPUTPATH="${ROOTPATH}/${OUTDIR}/${NAME}" +INPUTPATH="${ROOTPATH}/${INDIR}/${ORIGNAME}" + +[ -e "${ROOTPATH}/${OUTDIR}/${NAME}" ] || exit -1; +paste -d '\n' \ + <(find "$INPUTPATH" -maxdepth 1 -name '*.mkv' | sort) \ + <(find "$INPUTPATH/RUS Sound/StudioBand" -name '*.mka' | sort) \ + <(find "$INPUTPATH" -maxdepth 1 -name '*.mkv' | sort | \ + sed -e "s/.* \([0-9]\{2\}\) .*/${OUTPUTPATH//\//\\\/}\/${NAME} s05e\1.mkv/g") | \ + xargs -d '\n' printf '"%s" "%s" "%s"\n' | \ + xargs -n 3 \ + ffmpeg_add_audio_into_video -d \ + ; + +read || exit 1; + +paste -d '\n' \ + <(find "$INPUTPATH" -maxdepth 1 -name '*.mkv' | sort) \ + <(find "$INPUTPATH/RUS Sound/StudioBand" -name '*.mka' | sort) \ + <(find "$INPUTPATH" -maxdepth 1 -name '*.mkv' | sort | \ + sed -e "s/.* \([0-9]\{2\}\) .*/${OUTPUTPATH//\//\\\/}\/${NAME} s05e\1.mkv/g") | \ + xargs -d '\n' printf '"%s" "%s" "%s"\n' | \ + xargs -n 3 \ + ffmpeg_add_audio_into_video \ + ; diff --git a/convert_demon_slayer1.sh b/convert_demon_slayer1.sh new file mode 100755 index 0000000..b524800 --- /dev/null +++ b/convert_demon_slayer1.sh @@ -0,0 +1,8 @@ +DIRPATH="/var/www/nextcloud/data/grayhook/files/Torrents/Kimetsu.no.Yaiba.Katanakaji.no.Sato.hen.WEB-DL.1080p" +PRENAME="[SubsPlease] Kimetsu no Yaiba - Katanakaji no Sato-hen - " +POSTNAME=" [1080p]" +SOUNDDIR="RUS Sound/StudioBand" +for x in {0..11}; do + ffmpeg_add_audio_into_video "$DIRPATH/$PRENAME`printf "%02d" $x`$POSTNAME.mkv" \ + "$DIRPATH/$SOUNDDIR/$PRENAME`printf "%02d" $x`$POSTNAME.mka"; +done diff --git a/convert_hunter.sh b/convert_hunter.sh new file mode 100755 index 0000000..7b91004 --- /dev/null +++ b/convert_hunter.sh @@ -0,0 +1,19 @@ +ROOTPATH="/var/www/nextcloud/data/grayhook/files/" +INDIR="Torrents" +OUTDIR="Archive/Anime" +ORIGNAME="Hunter x Hunter [6][BD 720] TV2" +NAME="Hunter x Hunter" +SED_NUMBER='s/-_\([0-9]\{3\}\)_/ \1 /g' +OUTPUTPATH="${ROOTPATH}/${OUTDIR}/${NAME}" +INPUTPATH="${ROOTPATH}/${INDIR}/${ORIGNAME}" + +mkdir -p "${ROOTPATH}/${OUTDIR}/${NAME}" || exit -1; +paste -d '\n' \ + <(find "$INPUTPATH" -name '*.mkv' | sort) \ + <(find "$INPUTPATH/Rus Audio/JAM" -name '*.ac3' | sort) \ + <(find "$INPUTPATH" -name '*.mkv' | sort | \ + sed -e "s/.*-_\([0-9]\{3\}\)_.*/${OUTPUTPATH//\//\\\/}\/${NAME} s01e\1.mkv/g") | \ + xargs -d '\n' printf '"%s" "%s" "%s"\n' | \ + xargs -n 3 \ + ffmpeg_add_audio_into_video \ + ; diff --git a/convert_mashle.sh b/convert_mashle.sh new file mode 100755 index 0000000..712a900 --- /dev/null +++ b/convert_mashle.sh @@ -0,0 +1,19 @@ +ROOTPATH="/var/www/nextcloud/data/grayhook/files/" +INDIR="Torrents" +OUTDIR="Archive/Anime" +ORIGNAME="MASHLE Season 2 [2024][WEB-DL][1080p]" +NAME="MASHLE: MAGIC AND MUSCLES" +SED_NUMBER='s/ \([0-9]\{2\}\) / 0\1 /g' +OUTPUTPATH="${ROOTPATH}/${OUTDIR}/${NAME}" +INPUTPATH="${ROOTPATH}/${INDIR}/${ORIGNAME}" + +mkdir -p "${ROOTPATH}/${OUTDIR}/${NAME}" || exit -1; +paste -d '\n' \ + <(find "$INPUTPATH" -name '*.mkv' | sort) \ + <(find "$INPUTPATH" -name '*Anilibria*.mka' | sort) \ + <(find "$INPUTPATH" -name '*.mkv' | sort | \ + sed -e "s/.* \([0-9]\{2\}\) .*/${OUTPUTPATH//\//\\\/}\/${NAME} s02e\1.mkv/g") | \ + xargs -d '\n' printf '"%s" "%s" "%s"\n' | \ + xargs -n 3 \ + ffmpeg_add_audio_into_video \ + ; diff --git a/convert_revuvu.sh b/convert_revuvu.sh new file mode 100755 index 0000000..fdfe266 --- /dev/null +++ b/convert_revuvu.sh @@ -0,0 +1,18 @@ +ROOTPATH="/var/www/nextcloud/data/grayhook/files/" +INDIR="Torrents" +OUTDIR="Archive/Anime" +ORIGNAME="[Kawaiika-Raws] (2020) Ishuzoku Reviewers [BDRip 1920x1080 HEVC FLAC]" +NAME="Interspecies Reviewers" +OUTPUTPATH="${ROOTPATH}/${OUTDIR}/${NAME}" +INPUTPATH="${ROOTPATH}/${INDIR}/${ORIGNAME}" + +mkdir -p "${ROOTPATH}/${OUTDIR}/${NAME}" || exit -1; +paste -d '\n' \ + <(find "$INPUTPATH" -maxdepth 1 -name '*.mkv' | sort) \ + <(find "$INPUTPATH" -name '*Wakanim*.mka' | sort) \ + <(find "$INPUTPATH" -maxdepth 1 -name '*.mkv' | sort | \ + sed -e "s/.* \([0-9]\{2\}\) .*/${OUTPUTPATH//\//\\\/}\/${NAME} s01e\1.mkv/g") | \ + xargs -d '\n' printf '"%s" "%s" "%s"\n' | \ + xargs -n 3 \ + ffmpeg_add_audio_into_video \ + ; diff --git a/convert_rezero2.sh b/convert_rezero2.sh new file mode 100755 index 0000000..4ec6399 --- /dev/null +++ b/convert_rezero2.sh @@ -0,0 +1,19 @@ +ROOTPATH="/var/www/nextcloud/data/grayhook/files/" +INDIR="Torrents" +OUTDIR="Archive/Anime" +#ORIGNAME="[Beatrice-Raws] Re.Zero - 2nd Season [BDRip 1080p HEVC TrueHD]" +ORIGNAME="[Beatrice-Raws] Re.Zero - 2nd Season - Part 2 [BDRip 1080p x265 Dolby TrueHD]" +NAME="Re ZERO Starting Life in Another World" +OUTPUTPATH="${ROOTPATH}/${OUTDIR}/${NAME}" +INPUTPATH="${ROOTPATH}/${INDIR}/${ORIGNAME}" + +[ -e "${ROOTPATH}/${OUTDIR}/${NAME}" ] || exit -1; +paste -d '\n' \ + <(find "$INPUTPATH" -maxdepth 1 -name '*.mkv' | sort) \ + <(find "$INPUTPATH/Sounds/Crunchyroll" -name '*.mka' | sort) \ + <(find "$INPUTPATH" -maxdepth 1 -name '*.mkv' | sort | \ + sed -e "s/.* \([0-9]\{2\}\) .*/${OUTPUTPATH//\//\\\/}\/${NAME} s02e\1.mkv/g") | \ + xargs -d '\n' printf '"%s" "%s" "%s"\n' | \ + xargs -n 3 \ + ffmpeg_add_audio_into_video \ + ; diff --git a/convert_sheldon.sh b/convert_sheldon.sh new file mode 100755 index 0000000..ca59b7b --- /dev/null +++ b/convert_sheldon.sh @@ -0,0 +1,18 @@ +ROOTPATH="/var/www/nextcloud/data/grayhook/files/" +INDIR="Torrents" +OUTDIR="Archive/Series" +ORIGNAME="Young Sheldon (Season 4) WEB-DL 1080p" +NAME="Young Sheldon S4" +OUTPUTPATH="${ROOTPATH}/${OUTDIR}/${NAME}" +INPUTPATH="${ROOTPATH}/${INDIR}/${ORIGNAME}" + +[ -e "${OUTPUTPATH}" ] || { echo "$OUTPUTPATH does not exist"; exit -1; } +paste -d '\n' \ + <(find "$INPUTPATH" -maxdepth 1 -name '*.mkv' | sort) \ + <(find "${ROOTPATH}/${INDIR}/Young.Sheldon.2021.S04.1080p.(Kurazh-Bambej)" -name '*.mkv' | sort) \ + <(find "$INPUTPATH" -maxdepth 1 -name '*.mkv' | sort | \ + sed -e "s/.*S04E\([0-9]\{2\}\).*/${OUTPUTPATH//\//\\\/}\/${NAME} s04e\1.mkv/g") | \ + xargs -d '\n' printf '"%s" "%s" "%s"\n' | \ + xargs -n 3 \ + ffmpeg_add_audio_into_video \ + ; diff --git a/convert_suba.sh b/convert_suba.sh new file mode 100755 index 0000000..ff1fffa --- /dev/null +++ b/convert_suba.sh @@ -0,0 +1,19 @@ +ROOTPATH="/var/www/nextcloud/data/grayhook/files/" +INDIR="Torrents" +OUTDIR="Archive/Anime" +#ORIGNAME="[Kawaiika-Raws] (2016) KonoSuba S1 [BDRip 1920x1080 HEVC FLAC]" +ORIGNAME="[Kawaiika-Raws] (2017) KonoSuba S2 [BDRip 1920x1080 HEVC FLAC]" +NAME="KonoSuba" +OUTPUTPATH="${ROOTPATH}/${OUTDIR}/${NAME}" +INPUTPATH="${ROOTPATH}/${INDIR}/${ORIGNAME}" + +[ -e "${ROOTPATH}/${OUTDIR}/${NAME}" ] || exit -1; +paste -d '\n' \ + <(find "$INPUTPATH" -maxdepth 1 -name '*.mkv' | sort) \ + <(find "$INPUTPATH/RUS Sound" -name '*crunchyroll*.mka' | sort) \ + <(find "$INPUTPATH" -maxdepth 1 -name '*.mkv' | sort | \ + sed -e "s/.*S2E\([0-9]\{2\}\) .*/${OUTPUTPATH//\//\\\/}\/${NAME} s02e\1.mkv/g") | \ + xargs -d '\n' printf '"%s" "%s" "%s"\n' | \ + xargs -n 3 \ + ffmpeg_add_audio_into_video \ + ; diff --git a/convert_tokyo_ghoul.sh b/convert_tokyo_ghoul.sh new file mode 100755 index 0000000..a615a09 --- /dev/null +++ b/convert_tokyo_ghoul.sh @@ -0,0 +1,12 @@ +DIRPATH="/var/www/nextcloud/data/grayhook/files/Torrents/" +VIDEODIR="Tokyo Ghoul/Tokyo Ghoul " +AUDIODIR="Tokyo Ghoul [BD] [720p]/" +OUTPUTPATH="/var/www/nextcloud/data/grayhook/files/Archive/Anime/Tokyo Ghoul TV-1 2014/Tokyo Ghoul S1E" +PRENAME="[Winter] Tokyo Ghoul " +POSTNAME=" [BDrip 1280x720 x264 Vorbis]" +SOUNDDIR="$AUDIODIR/RUS Sound/[JAM & Oriko]" +for x in {2..12}; do + ffmpeg_add_audio_into_video "$DIRPATH/$VIDEODIR`printf "%d" $x`.mkv" \ + "$DIRPATH/$SOUNDDIR/$PRENAME`printf "%02d" $x`$POSTNAME.mka" \ + "$OUTPUTPATH`printf "%02d" $x`.mkv"; +done diff --git a/convert_tokyo_ghoul_re.sh b/convert_tokyo_ghoul_re.sh new file mode 100755 index 0000000..d37c472 --- /dev/null +++ b/convert_tokyo_ghoul_re.sh @@ -0,0 +1,12 @@ +DIRPATH="/var/www/nextcloud/data/grayhook/files/Torrents/" +VIDEODIR="Tokyo Ghoul Re" +AUDIODIR="Tokyo Ghoul Re" +OUTPUTPATH="/var/www/nextcloud/data/grayhook/files/Archive/Anime/Tokyo Ghoul TV-3 2018/Tokyo Ghoul S3E" +PRENAME="[anti-raws]Tokyo Ghoul Re ep." +POSTNAME="[BDRemux]" +SOUNDDIR="$AUDIODIR/RUS Sound/[JAM Club]" +for x in {1..12}; do + ffmpeg_add_audio_into_video "$DIRPATH/$VIDEODIR/$PRENAME`printf "%02d" $x`$POSTNAME.mkv" \ + "$DIRPATH/$SOUNDDIR/$PRENAME`printf "%02d" $x`$POSTNAME.JAM Club.mka" \ + "$OUTPUTPATH`printf "%02d" $x`.mkv"; +done diff --git a/convert_tokyo_ghoul_re2.sh b/convert_tokyo_ghoul_re2.sh new file mode 100755 index 0000000..7355e68 --- /dev/null +++ b/convert_tokyo_ghoul_re2.sh @@ -0,0 +1,12 @@ +DIRPATH="/var/www/nextcloud/data/grayhook/files/Torrents/" +VIDEODIR="Tokyo Ghoul Re S2" +AUDIODIR="Tokyo Ghoul Re S2" +OUTPUTPATH="/var/www/nextcloud/data/grayhook/files/Archive/Anime/Tokyo Ghoul TV-4 2018/Tokyo Ghoul S4E" +PRENAME="[anti-raws]Tokyo Ghoul Re S2 ep." +POSTNAME="[BDRemux]" +SOUNDDIR="$AUDIODIR/RUS Sound/[JAM Club]" +for x in {1..12}; do + ffmpeg_add_audio_into_video "$DIRPATH/$VIDEODIR/$PRENAME`printf "%02d" $x`$POSTNAME.mkv" \ + "$DIRPATH/$SOUNDDIR/$PRENAME`printf "%02d" $x`$POSTNAME.JAM Club.mka" \ + "$OUTPUTPATH`printf "%02d" $x`.mkv"; +done diff --git a/convert_tokyo_ghoul_root_a.sh b/convert_tokyo_ghoul_root_a.sh new file mode 100644 index 0000000..31d5cb8 --- /dev/null +++ b/convert_tokyo_ghoul_root_a.sh @@ -0,0 +1,12 @@ +DIRPATH="/var/www/nextcloud/data/grayhook/files/Torrents/" +VIDEODIR="Tokyo Ghoul Root A" +AUDIODIR="Tokyo Ghoul Root A" +OUTPUTPATH="/var/www/nextcloud/data/grayhook/files/Archive/Anime/Tokyo Ghoul TV-2 2015/Tokyo Ghoul S2E" +PRENAME="[anti-raws]Tokyo Ghoul Root A ep." +POSTNAME="[BDRemux]" +SOUNDDIR="$AUDIODIR/RUS Sound/[JAM & Nika Lenina]" +for x in {11..12}; do + ffmpeg_add_audio_into_video "$DIRPATH/$VIDEODIR/$PRENAME`printf "%02d" $x`$POSTNAME.mkv" \ + "$DIRPATH/$SOUNDDIR/$PRENAME`printf "%02d" $x`$POSTNAME.JAM & Nika Lenina.mka" \ + "$OUTPUTPATH`printf "%02d" $x`.mkv"; +done diff --git a/gemini/core_actions.sh b/gemini/core_actions.sh new file mode 100644 index 0000000..3282e33 --- /dev/null +++ b/gemini/core_actions.sh @@ -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 +} diff --git a/gemini/core_logic.sh b/gemini/core_logic.sh new file mode 100644 index 0000000..2d9031e --- /dev/null +++ b/gemini/core_logic.sh @@ -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" +} diff --git a/gemini/core_ui.sh b/gemini/core_ui.sh new file mode 100644 index 0000000..bdd708b --- /dev/null +++ b/gemini/core_ui.sh @@ -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 +} diff --git a/gemini/universal_merger.sh b/gemini/universal_merger.sh new file mode 100755 index 0000000..91296dc --- /dev/null +++ b/gemini/universal_merger.sh @@ -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