From b4623d21cf7b001d3170236706bd051aed4afc1f Mon Sep 17 00:00:00 2001 From: grayhook Date: Tue, 8 Jul 2025 16:17:41 +0700 Subject: [PATCH] bump --- gemini/.core_actions.sh.swp | Bin 0 -> 16384 bytes gemini/.core_logic.sh.swp | Bin 0 -> 16384 bytes gemini/.core_ui.sh.swp | Bin 16384 -> 12288 bytes gemini/.universal_merger.sh.swp | Bin 16384 -> 16384 bytes gemini/core_actions.sh | 115 +++++------------------------- gemini/core_logic.sh | 152 ++++++++++++++++++++++------------------ gemini/core_ui.sh | 41 ++++++----- gemini/universal_merger.sh | 152 ++++++++++++++++++++++++---------------- 8 files changed, 213 insertions(+), 247 deletions(-) create mode 100644 gemini/.core_actions.sh.swp create mode 100644 gemini/.core_logic.sh.swp diff --git a/gemini/.core_actions.sh.swp b/gemini/.core_actions.sh.swp new file mode 100644 index 0000000000000000000000000000000000000000..72f990ff4d00b77095e7a7cf6518ef1c284d9f63 GIT binary patch literal 16384 zcmeI3Ym8K98OIN;#cS*RLu1sJ*&*x*vlq~`1y+h>Syo{o%hI4Mj>GKP-I3Xu?aUb} zrL-*cW%I< zHBECS`7?XYc`wiVf1dYwpXWKluBN-&H>o=snk+u=v8-LcN%jBohC8iGXFqK9W#fDM zGnw7;=yIR7&iLL9nF0H9ZRFVl#Q_`FWfJ)TJMH8ev-V&n*Ov!M`xDRXrP2fXd0l0LtWxWV~4yM7Qpc5$YQE>iUmbDT5>7ACf7km+PfW_e7@35?Y zf!~4=a29+HYz3bN8$cWQ+uNA~d;{DMc7Zmq9^3|Q0l$A6vVyZ<2pj;tpaCofFTd5Y zH24|#DfkKaF&F}Auo`>-+z9@;(6askUIO=meh>#=08#K>aOo!I1XG|3w1Q897O)WX zy~VO_0^g>9vIaDP2&f0I++bNRfLpcN*N`i`l>P~lMF%X*COgZfnE0$TOKJFinp{5lR9WFeKsH4a*t0$`DU&e%m zA=WbP&1^(19K?|M^d?KRWNjy(Ewc$lx&F+(u>m`mi}%@ZaFRt$Gv$m+$w_^Ykqxu zo>j`|=n1uiJtfBFcQrA`{*vQowdUc{mXLmy?ibJuaxo}+E*w^Rgd3x%?jRau84t^< zn1qSs>A98N>1u_$3rJ-W@sz5WhiRUmBZIm^n{k$@!lTSMt3+4)F%DQB%5!;=MH%~8 z65i+jP$=ePVuNV{5Fdvwh3P)~>BP?%cUw6&^y~31hG4 z)^w-4)2?`RY6aGEHJrXNjAF+Q6}=Db*3b}AJByp%m~BmimToR93O-17coy(fE)2!o0ZL{87`B+OC-~M^%1qts*~QZMPK$ZHd}az ztuUh}i;m(#nGijU->6}1!in_b1dA-38SQXKMFwNp9UjIQ{2!=Bcs^fUUnex5;36?-5gOD`W1!xd`bII2}b zDy^b9On6VS$Icj@#a(6c&S2hgFU`zQRX%;hnKKW3!yKLy!XGuh8<7iNta?hb(V{C9-zhv17Tt1F2+sw`UaL<#2jJPm4j6mV!Ce*BoF)1B+lv;b!SY2k(SK zhn#R?m2wcw7MqIEovE}k*Izkm1#u-) zh%S@L@hI0LY$4{lLK8g0Son1jy0nVvo%>208jL;aPoN9G#qTqg;#_R@@Vg3lkVX2v28m zjzkc8f=ETqesROH2Vig+%W$&s#O`Q1K48mLkKG&3ryTEYZz|qrB&@4lZ&VeIa?&5x zlXA#%@G`84j*!OudEPj)dOVakj&VI3QS><=jvBggbb6w8Y?mbe*lrLc8y35)9=l|x zGB&HjV$J%-jP*(^G$S!aX(s&xkyLDDh$rHGT@PxEW*Ty4=s1jft|eI-5_X!Uc{Gm+ zy(WGV0~*849&M=b9M9tEp46xD9pA?@Jg%P;lbF|sJdY1Kh7UQZk1INz=Hi4tDW0d= z!hDXWqSF&}IL6$jlh58Pnf*$f&lM!J8e*A%| z7lD&)xlWHj$>*ThIg$#QP0S&N&5FxACrcn5(xVNpyYyDwqQ+gxbmOi#KdqPizk@t= zJ-Mvp|Hb$JlK-Fa^8YyB9pF~*8oB;)@CbMiJOCo#E9Ce~!HeYhHnd5{JJAO!wS{{Cz5EATS-F1QD52d|O4{}Bv>7eEZOfO_x;a`gf@1lqt# za0mDZ_#jvae#H7Z!3JQ~dTsf2&?{gdU?5;1V8A4uZsM6p6~FCx)t~$898XB1}L!}=PTt# z)1tXa-PWX<%0W<+@wRW@-ktVkf&_ z%}q^iwA7PHm-F5F`b0jPrHmQma;3 z#XiMvqKjjudQ}w_ko=conrJbMa!aX%#fKz=&@&aIg|>cn)5i9W&xG8vPH_VH_5>^i zcYfJShlMCPTIMR6nRzSKCw6ASR&?a6YD>gQ<}Mj2ZvyyN zD*HoGNoLKhX;&U!#m)DptnRvt=nX8ssxV(swvtKA+k@)2B6Ch%EI^o78uQo8#53We zycZ#5QC9h18;AsN;#iUTSeZiOa8)qQ)W2YcK!5w*z9ySMUQmBO8Z1cbtLC X<-HvmpuFn7gLFOiL_|ia$p8NXu2P2c literal 0 HcmV?d00001 diff --git a/gemini/.core_logic.sh.swp b/gemini/.core_logic.sh.swp new file mode 100644 index 0000000000000000000000000000000000000000..cb43ebc3311f9e7bcde1a411bc691361ee16607f GIT binary patch literal 16384 zcmeHNTWlOx89sz-p-mA;RH!XF9xw8)ob@GuAdVN+wYSMad||z=XzVcBc*piC-q~d@ z&c$(X6GBrZZPF-Bg9(Aqszu@=$FbA(UGRd+)0vjHq9TN71rJCNBm~L>-+yLicV>4R zH^>iBnTfv4?#!HX{{K7wb-epq9*=bM9Zf9^$0r#Z{KNRz>suaS@BH*(Hkyqe9!sYW z(5>4YyQ1;KyVEJ{cF)kgWA=p2UFqRMO4IYX=B$=U=bDGp`h=FvH;-zmq@Kjrtfr2q zN0Y-%xv`CA_3nENcnoaFKp{U8{K|do3)@?ztJf=a{L`Nr*l?KlfX9HxfX9HxfX9Hx zfX9HxfX9Hx!25@Rd}a%GRerBc*7cUb2SSm!O)xzReuD(+ify_3g)$AHIx$AHIx z$AHIx$AHIx$AHIx$AHIx$G~04Ks>=%@uQ3#{TPJ9-~a9Y{_Xo2dkJ_ENC7_J&s!OL z1$Y@K0103an7EHI4fqx?1PlUOfq&i0*sp<$z&*f!KEc>c;2*#c@HOCJ;Ex|?>=(e# zfg`|n;IH>Eb{RMcd>MEf^1TWC23P}Dfn$J_`!3|w6Uk%1W58p;W58qJ|Cxa)#&Hyq z&a?9~-BWX8>4R!Y%jM#u8dt=RjA^kT=EXYBYsQme1^*v6rWLOEGFfdRsU1}F+M&F{ zC#H6E#v;8v3J-<2(jAWO3M+iyPM#mrbaT6FEbH@UvPnHZ z!j;drY)>fMCv70m#GTwm9_tVJ8iw)>JgdbMJecKD7A`g8CRMz>y&aJZ_6Jp*tVI1dTORWWNkN#2dYi((0itTdU#OS`D4PEh+cZz)*?35{p#xctGe z3aT@4{Cs#MIgZ~DCv*9nX+79>(v!FDmD`Z~Os1$?EyL1s@W8P22$65OE$@ z$AzLXjm-iDPys40aj3C?hoG5pf-y7BRZ-1N%cxEE72_0!Un3@XR2$U}A?gv$oU8)N zE>lD_&Tu^Rs+c2FFrK2XtB`I5U#7Y742Y(?YcxPUSl*#o7=ce3zG}xdvI=ok?v9MK zPa9{9XH8A6V^M4-w4&*+WBbIMaax)rn83xNah4_w_eJ`9yTWSEK=-5Js2UCL3V+kw zM2cvPG^NAZffgpe;XiG7EKMHOI}qy|h^dcu^oP~1NOY>%H)%SgO2K!^3iLF}aXEy6 zj<3x_la?4m7_ViLxpYEP^+F26X(Szv$E!#8hdcUvduXuv$ivI*KK~7130MRU06oAC z;IqIg`u!`w3&2I-i@<}xE%g1zfl1&9@C48Z`~`jgHsAr^Uf@mi{Cj~g&<5-P9s+)e z-hT?Xg8o!BF+Fh@HyZu`~boLeha(?tN=^EBJd^PGr%oy_$S~7umBLJ z6F`lFtdRm7Wj?n%BJ&;NH9d;&b0tFeFc^gG(4{FU1X`PKZoGR4BO#nO=y~`>t73)6 zQBBvf@w}!wEY%K^=aGjH&naRcFwDxZzHFS4@%${}4I;-X;{5k0M3i#Ebs01eq%Z(? zC?_CPnvrB1M6G$zFD(L4&kJ^E+9QSEHR}i3C8C|7= zN)B_YNH(kLFfUL-Q?v1d?y`dhA$46P_9R=g;1tPq9h6N|Re&?4kD^!$%3VX+K!&Eo ziTUQaDv{l;0)!r1o{BRhyk{)wi%4b9l;3 z=F(%}QdpM!sEGNN#jKl)I;wzEsk9C=&c;(YHGROUph5q0& zS*?IF^Ib-@gt#Yr=oWsI=hE3c4<4zk8r{NevYEY)v%?a3rtEFu6Yn82%u#5s$O3RD zzbi+Sd<*#kEt#1hOt+)v^N#ZaRVgS&gGZ{3rc`y3E8Qk_RJ^RAr>hdj4ARE);`{W9 z%`_l~Yr1XP?eI>UHp_#uO{e0A5?Urd##>AFU0p%Et9?VE+a*h9IJcudGgWJC;z)qA z#z~o*!MY+3 zTm>`OQn{ZM-L*n{?Y!aWQIYrU9)pxLwXa${pDmb8y{n`S`2$wpQ2h$V?iyYK2j#ZF zsy|V`a0NG3h#aw~$mAj4{lctKTHaw0OJv1= bM$|NsBR?lrH(ec8U)Q`psW+ov9Ap0lni*cY literal 0 HcmV?d00001 diff --git a/gemini/.core_ui.sh.swp b/gemini/.core_ui.sh.swp index ead34c1b5da186e9486fc4d5d0a17b5a88832944..e1c3de3eb13cf5e1dadc5d068953eca00837b728 100644 GIT binary patch delta 1392 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 Yi@z)vgn0ItSY2~iT&fupj!kd=3$dd`SpWb4 diff --git a/gemini/core_actions.sh b/gemini/core_actions.sh index 3282e33..8ce53dd 100644 --- a/gemini/core_actions.sh +++ b/gemini/core_actions.sh @@ -1,139 +1,56 @@ #!/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 -} - +# ... (action_merge_mkv и action_create_symlink без изменений) ... # Главная функция запуска обработки run_processing() { - # 1. Готовим списки файлов - if ! logic_prepare_file_lists; then - return 1 - fi - + 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" + # Больше не спрашиваем, а используем CURRENT_ACTION + local confirmation_text="Вы уверены, что хотите выполнить '${CURRENT_ACTION}' для ${#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 + if [ $? -ne 0 ]; then ui_show_message "Ошибка" "Не удалось создать каталог."; return; fi else - ui_show_message "Отмена" "Операция отменена, так как выходной каталог не существует." - return + 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 )) + 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" - # Обновляем текст в прогресс-баре - 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 + # Выполняем заранее выбранное действие + case "$CURRENT_ACTION" in "MERGE") - action_merge_mkv "$video" "$audio" "$output" - ;; + action_merge_mkv "$video" "$audio" "$output" ;; "SYMLINK") - action_create_symlink "$video" "$output" - ;; + # Для симлинка аудио-файл не нужен + 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 - + 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 index 2d9031e..012e366 100644 --- a/gemini/core_logic.sh +++ b/gemini/core_logic.sh @@ -4,104 +4,120 @@ # Модуль основной логики: поиск файлов, сопоставление, генерация имён #============================================================================== -# Глобальная переменная для хранения подготовленных данных -# Формат: "видео_файл\tаудио_файл\tвыходной_файл" для каждой строки declare -a FILE_TRIPLETS -# Основная функция, которая находит и сопоставляет файлы -# Возвращает 0 в случае успеха, 1 в случае ошибки +# Функция стала "умной" и проверяет CURRENT_ACTION 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 + local common_params_ok=true + if [[ -z "$VIDEO_SRC_DIR" || -z "$OUTPUT_BASE_DIR" || -z "$OUTPUT_SERIES_NAME" ]]; then + common_params_ok=false fi - # 1. Найти видеофайлы - # Использование `find ... -print0` и `mapfile` для безопасной обработки имён с пробелами + # 1. Находим видеофайлы (это нужно для обоих режимов) 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 + if $common_params_ok; then + mapfile -d '' video_list_raw < <(find "$VIDEO_SRC_DIR" -maxdepth 1 -name "$VIDEO_FILE_PATTERN" -print0 | sort -z) 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" + # РЕЖИМ: СКЛЕЙКА + 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 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 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 - local output_path="${OUTPUT_BASE_DIR}/${output_name}" + # Собираем триплеты + 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 - # Сохраняем триплет в массив - FILE_TRIPLETS+=("$video_file"$'\t'"$audio_file"$'\t'"$output_path") - done + # Собираем пары (аудио-поле оставляем пустым) + for video_file in "${video_list_raw[@]}"; do + local output_path + output_path=$(logic_generate_output_name "$video_file") || return 1 + FILE_TRIPLETS+=("$video_file"$'\t\t'"$output_path") + done + fi return 0 } +# Вспомогательная функция для генерации имени, чтобы не дублировать код +logic_generate_output_name() { + local video_file="$1" + local video_basename + video_basename=$(basename "$video_file") -# Функция для отображения предпросмотра -logic_show_preview() { - if ! logic_prepare_file_lists; then - # Сообщение об ошибке уже было показано внутри logic_prepare_file_lists + 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 "Предпросмотр" "Нет файлов для обработки." + ui_show_message "Предпросмотр" "Нет файлов для обработки по текущим настройкам." return fi - local preview_text="Будут обработаны следующие файлы (${#FILE_TRIPLETS[@]} шт.):\n\n" + local preview_text="РЕЖИМ: $CURRENT_ACTION\nБудут обработаны следующие файлы (${#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" + + if [ "$CURRENT_ACTION" == "MERGE" ]; then + preview_text+=" \ZbВИДЕО:\Zn $(basename "$video")\n" + preview_text+=" \ZbАУДИО:\Zn $(basename "$audio")\n" + preview_text+=" \Zb-> ВЫВОД:\Zn $(basename "$output")\n\n" + else # SYMLINK + preview_text+=" \ZbИСТОЧНИК:\Zn $(basename "$video")\n" + preview_text+=" \Zb-> ССЫЛКА:\Zn $(basename "$output")\n\n" + fi ((count++)) done diff --git a/gemini/core_ui.sh b/gemini/core_ui.sh index 0719d66..5ca2481 100644 --- a/gemini/core_ui.sh +++ b/gemini/core_ui.sh @@ -2,15 +2,14 @@ #============================================================================== # Модуль интерфейса (TUI) на основе 'dialog' -# ИСПРАВЛЕНА ПЕРЕДАЧА АРГУМЕНТОВ ЧЕРЕЗ МАССИВ +# ИСПРАВЛЕНА ВЫСОТА ДИНАМИЧЕСКОГО МЕНЮ #============================================================================== # Используем МАССИВ для опций, чтобы избежать проблем с кавычками и пробелами. DIALOG_OPTS=(--colors --backtitle "Универсальный обработчик медиа") # Функция-обертка для вызова dialog и корректного возврата результата. -# $1: Исходное значение (для возврата при отмене) -# $2, $3, ...: Команда dialog и ее аргументы +# ... (эта функция остается без изменений) _call_dialog() { local original_value="$1" shift @@ -26,15 +25,30 @@ _call_dialog() { fi } +# Показать начальное меню выбора действия +# ... (эта функция остается без изменений) +ui_select_initial_action() { + _call_dialog "" \ + dialog "${DIALOG_OPTS[@]}" --title "Выбор действия" \ + --cancel-label "Выход" \ + --menu "Выберите, что вы хотите сделать:" 15 70 2 \ + "MERGE" "Склеить видео и аудио (mkvmerge)" \ + "SYMLINK" "Создать симлинки с новыми именами" +} + # Показать главное меню ui_main_menu() { - # Передаем массив опций как "${DIALOG_OPTS[@]}" + # --- КЛЮЧЕВОЕ ИЗМЕНЕНИЕ ЗДЕСЬ --- + # Заменяем фиксированную высоту меню "15" на "0". + # "0" говорит dialog автоматически рассчитать высоту на основе количества пунктов. _call_dialog "" \ dialog "${DIALOG_OPTS[@]}" --title "Главное меню" \ - --menu "Выберите опцию для редактирования или действия:" 20 85 15 "${@}" + --cancel-label "Назад" \ + --menu "Выберите опцию для редактирования или действия:" 20 85 0 "${@}" } # Получить путь к каталогу +# ... (эта функция остается без изменений) ui_get_directory() { local title="$1" local current_path="$2" @@ -47,6 +61,7 @@ ui_get_directory() { } # Получить текстовый ввод от пользователя +# ... (остальные функции остаются без изменений) ui_get_input() { local title="$1" local current_value="$2" @@ -55,31 +70,15 @@ ui_get_input() { dialog "${DIALOG_OPTS[@]}" --title "$title" --inputbox "Введите новое значение:" 10 70 "$current_value" } -# Показать меню выбора действия перед запуском -ui_select_action() { - _call_dialog "" \ - dialog "${DIALOG_OPTS[@]}" --title "Выбор действия" \ - --menu "Какую операцию выполнить с найденными файлами?" 15 70 2 \ - "MERGE" "Склеить видео и аудио с помощью mkvmerge" \ - "SYMLINK" "Создать символические ссылки на видеофайлы" -} - - -# --- Функции без возврата значения --- - -# Показать информационное сообщение ui_show_message() { local title="$1" local text="$2" - # Здесь тоже исправляем на массив dialog "${DIALOG_OPTS[@]}" --title "$title" --msgbox "$text" 20 70 } -# Показать окно с выбором Да/Нет ui_confirm() { local title="$1" local text="$2" - # Здесь тоже исправляем на массив dialog "${DIALOG_OPTS[@]}" --title "$title" --yesno "$text" 10 70 return $? } diff --git a/gemini/universal_merger.sh b/gemini/universal_merger.sh index 91296dc..0942c6d 100755 --- a/gemini/universal_merger.sh +++ b/gemini/universal_merger.sh @@ -5,98 +5,132 @@ #============================================================================== # --- Подключение модулей --- -# Убедимся, что скрипты-модули находятся в том же каталоге 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" +CURRENT_ACTION="" + +# --- НОВАЯ ВСПОМОГАТЕЛЬНАЯ ФУНКЦИЯ --- +# Обрезает путь для красивого отображения в меню, оставляя конец строки. +# $1: Полный путь +# $2: Максимальная длина отображения +# $3: Текст, если путь не задан +truncate_path_for_display() { + local full_path="$1" + local max_len="$2" + local placeholder="$3" + + if [[ -z "$full_path" ]]; then + echo "$placeholder" + return + fi + + if [ ${#full_path} -gt "$max_len" ]; then + # Вычисляем, сколько символов с конца нужно оставить + # -3 нужно, чтобы вместить "..." в начале + local trim_len=$((max_len - 3)) + echo "...${full_path: -$trim_len}" + else + echo "$full_path" + fi +} # --- Главный цикл программы --- main() { + # 1. ВНЕШНИЙ ЦИКЛ: ВЫБОР ДЕЙСТВИЯ 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 + CURRENT_ACTION=$(ui_select_initial_action) + + if [[ -z "$CURRENT_ACTION" ]]; 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 + # 2. ВНУТРЕННИЙ ЦИКЛ: НАСТРОЙКА И ЗАПУСК + while true; do + # --- ИЗМЕНЕНИЯ ЗДЕСЬ --- + # Готовим пути для отображения с помощью новой функции + local max_display_len=45 # Можете поменять это значение под свой экран + local display_video_path=$(truncate_path_for_display "$VIDEO_SRC_DIR" "$max_display_len" "_не задан_") + local display_audio_path=$(truncate_path_for_display "$AUDIO_SRC_DIR" "$max_display_len" "_не задан_") + local display_output_path=$(truncate_path_for_display "$OUTPUT_BASE_DIR" "$max_display_len" "_не задан_") + + # Формируем пункты меню ДИНАМИЧЕСКИ + local menu_items=() + menu_items+=("V" "Видео каталог : $display_video_path") + + if [ "$CURRENT_ACTION" == "MERGE" ]; then + menu_items+=("A" "Аудио каталог : $display_audio_path") + fi + + menu_items+=( + "O" "Выходной каталог : $display_output_path" + "N" "Имя для Plex : ${OUTPUT_SERIES_NAME:-_не задано_}" + "S" "Номер сезона : $SEASON_NUMBER" + "" "--- Шаблоны и Regex ---" + "P" "Шаблон видеофайлов : $VIDEO_FILE_PATTERN" + ) + + if [ "$CURRENT_ACTION" == "MERGE" ]; then + menu_items+=("U" "Шаблон аудиофайлов : $AUDIO_FILE_PATTERN") + fi + + menu_items+=( + "R" "Regex номера серии : $EPISODE_NUMBER_REGEX" + "T" "Шаблон имени вывода : $OUTPUT_FILENAME_TEMPLATE" + "" "--- Действия ---" + "VIEW" "Предпросмотр сопоставления файлов" + "RUN" "ЗАПУСТИТЬ обработку" + "BACK" "<- Назад к выбору действия" + ) + + choice=$(ui_main_menu "${menu_items[@]}") + + # ... (остальная часть цикла main остается без изменений) ... + + if [[ "$choice" == "BACK" || -z "$choice" ]]; then + 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 done } # --- Точка входа --- -# Проверка наличия dialog if ! command -v dialog &> /dev/null; then echo "Команда 'dialog' не найдена. Пожалуйста, установите ее." - echo "sudo apt-get install dialog (Debian/Ubuntu)" exit 1 fi - main