Загрузить файлы в «/»
This commit is contained in:
420
torrent_sort.sh
Normal file
420
torrent_sort.sh
Normal file
@ -0,0 +1,420 @@
|
||||
#!/bin/bash
|
||||
|
||||
# --- НАСТРОЙКИ ---
|
||||
BASE_LOG_DIR="/data/torrents"
|
||||
mkdir -p "$BASE_LOG_DIR"
|
||||
|
||||
LOG_FILE="$BASE_LOG_DIR/process.log"
|
||||
ERROR_LOG="$BASE_LOG_DIR/error.log"
|
||||
COLLISION_LOG="$BASE_LOG_DIR/collision.log"
|
||||
|
||||
# Куда складывать готовое (в подпапки)
|
||||
ANIME_DEST="/data/media/anime"
|
||||
# Куда складывать, если скрипт не смог распарсить имя
|
||||
ANIME_UNSORTED="/data/media/anime/000_anime_unsorted"
|
||||
|
||||
|
||||
# --- ФУНКЦИИ ЛОГИРОВАНИЯ ---
|
||||
timestamp() { date '+%Y-%m-%d %H:%M:%S'; }
|
||||
|
||||
log_info() {
|
||||
echo "[$(timestamp)] [INFO] $1" >> "$LOG_FILE"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo "[$(timestamp)] [ERROR] $1" >> "$ERROR_LOG"
|
||||
# Дублируем ошибку в основной лог для контекста
|
||||
echo "[$(timestamp)] [ERROR] $1" >> "$LOG_FILE"
|
||||
}
|
||||
|
||||
log_collision() {
|
||||
echo "[$(timestamp)] [SKIP] Target exists: '$1' | Source was: '$2'" >> "$COLLISION_LOG"
|
||||
echo "[$(timestamp)] [SKIP] Collision: $1" >> "$LOG_FILE"
|
||||
}
|
||||
|
||||
|
||||
# Логируем новую задачу
|
||||
log_info "----------------------------------------------"
|
||||
log_info "Torrent: $1"
|
||||
log_info "Category: $2"
|
||||
|
||||
REAL_PATH=$3
|
||||
|
||||
# Проверка на существование пути
|
||||
if [ ! -e "$REAL_PATH" ]; then
|
||||
log_error "Path does not exist: $REAL_PATH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ==========================================
|
||||
# БЛОК ОБРАБОТКИ АНИМЕ
|
||||
# ==========================================
|
||||
if [[ $2 == 'Anime' ]]; then
|
||||
log_info "Processing Anime category..."
|
||||
|
||||
# Создаем папки назначения, если их нет
|
||||
mkdir -p "$ANIME_DEST"
|
||||
mkdir -p "$ANIME_UNSORTED"
|
||||
|
||||
# Ищем видеофайлы (mkv, mp4, avi и т.д.)
|
||||
# Используем while read, чтобы корректно работать с пробелами в путях
|
||||
|
||||
# Ищем все видеофайлы
|
||||
find "$REAL_PATH" -type f | grep -iE "\.(mkv|mp4|avi|webm)$" | while read -r FILE_PATH; do
|
||||
|
||||
FILENAME=$(basename "$FILE_PATH")
|
||||
EXT="${FILENAME##*.}"
|
||||
|
||||
# 1. НОРМАЛИЗАЦИЯ: Заменяем _ на пробелы
|
||||
NORMALIZED_NAME="${FILENAME//_/ }"
|
||||
|
||||
# 2. УДАЛЕНИЕ РАСШИРЕНИЯ (чтобы не мешало парсить хвост)
|
||||
BASE_NAME="${NORMALIZED_NAME%.*}"
|
||||
|
||||
# 3. Trim (Убираем пробелы/точки в начале и конце сразу, чтобы case работал четко)
|
||||
BASE_NAME=$(echo "$BASE_NAME" | sed 's/^[. ,]*//;s/[. ,]*$//')
|
||||
|
||||
# Переменные по умолчанию
|
||||
MODE="Unsorted" # По умолчанию считаем фильмом, если не доказано обратное (сериал)
|
||||
TITLE=""
|
||||
EPISODE=""
|
||||
|
||||
# =======================================================
|
||||
# ГЛАВНЫЙ БЛОК ПАРСИНГА ПО ГРУППАМ (CASE)
|
||||
# =======================================================
|
||||
|
||||
case "$BASE_NAME" in
|
||||
|
||||
# === ГРУППА 1: Стандартные [Group] ===
|
||||
*"[SubsPlease]"*|*"[Erai-raws]"*|*"[HorribleSubs]"*|*"[Anime Time]"*)
|
||||
|
||||
# 1. Удаляем только тег группы в начале
|
||||
TEMP_NAME="${BASE_NAME#*]}"
|
||||
TEMP_NAME=$(echo "$TEMP_NAME" | sed 's/^[ ]*//')
|
||||
|
||||
# 2. РАЗБИВАЕМ ПО ПОСЛЕДНЕМУ ТИРЕ
|
||||
# ^(.+) -> Группа 1: Название (включая годы, скобки и т.д.)
|
||||
# [[:space:]]-[[:space:]] -> Разделитель
|
||||
# (.+) -> Группа 2: "Грязный" номер эпизода + тех. инфа
|
||||
if [[ "$TEMP_NAME" =~ ^(.+)[[:space:]]-[[:space:]](.+)$ ]]; then
|
||||
|
||||
RAW_TITLE="${BASH_REMATCH[1]}" # Spice and Wolf (2024)
|
||||
RAW_TAIL="${BASH_REMATCH[2]}" # 01 (1080p) [Hash]
|
||||
|
||||
# 3. ЧИСТИМ ТОЛЬКО ХВОСТ
|
||||
# Здесь мы можем быть агрессивными, так как год остался в RAW_TITLE
|
||||
# Удаляем всё, что начинается с ( или [
|
||||
CLEAN_TAIL=$(echo "$RAW_TAIL" | sed -E 's/[ ]*(\(|\[).+$//')
|
||||
# Trim пробелов
|
||||
CLEAN_TAIL=$(echo "$CLEAN_TAIL" | sed 's/^[. ,]*//;s/[. ,]*$//')
|
||||
|
||||
# 4. АНАЛИЗИРУЕМ ЧИСТЫЙ ХВОСТ
|
||||
|
||||
# Вариант А: Это цифры (01, 01.5, 01v2)
|
||||
if [[ "$CLEAN_TAIL" =~ ^[0-9]+(\.[0-9]+)?[vV]?[0-9]*$ ]]; then
|
||||
MODE="Episode"
|
||||
TITLE="$RAW_TITLE"
|
||||
EPISODE="$CLEAN_TAIL"
|
||||
|
||||
# Вариант Б: Это Спешл (OVA, SP, Special)
|
||||
elif [[ "$CLEAN_TAIL" =~ ^([sS]pecial|[sS][pP]|[oO][vV][aA]|[oO][aA][dD]) ]]; then
|
||||
MODE="Episode"
|
||||
TITLE="$RAW_TITLE"
|
||||
EPISODE="$CLEAN_TAIL"
|
||||
|
||||
# Вариант В: Это просто текст (значит это Фильм с подзаголовком)
|
||||
# Пример: Blue Lock - Episode Nagi
|
||||
else
|
||||
MODE="Movie"
|
||||
# Для фильма собираем название обратно: Заголовок + Очищенный подзаголовок
|
||||
TITLE="$RAW_TITLE - $CLEAN_TAIL"
|
||||
fi
|
||||
else
|
||||
# Тире нет вообще -> Это Фильм
|
||||
MODE="Movie"
|
||||
# Тут придется чистить аккуратно (удаляем только хеш и разрешение)
|
||||
# Удаляем [Hash] в конце (8 символов)
|
||||
TITLE=$(echo "$TEMP_NAME" | sed -E 's/[ ]*\[[a-fA-F0-9]{8}\]$//')
|
||||
# Удаляем (1080p/720p/etc)
|
||||
TITLE=$(echo "$TITLE" | sed -E 's/[ ]*\([0-9]{3,4}[pP]\).*$//')
|
||||
# Удаляем (BD/Web)
|
||||
TITLE=$(echo "$TITLE" | sed -E 's/[ ]*\((BD|Web|WEB|Blu-ray).+$//')
|
||||
fi
|
||||
;;
|
||||
|
||||
|
||||
# # === ГРУППА 1: Стандартные (SubsPlease, Erai-raws, HorribleSubs) ===
|
||||
# # Формат: [Group] Title - 01 (1080p)...
|
||||
# *"[SubsPlease]"*|*"[Erai-raws]"*|*"[HorribleSubs]"*)
|
||||
|
||||
# # 1. УДАЛЕНИЕ ГРУППЫ (NATIVE BASH)
|
||||
# # Удаляем всё от начала до первой закрывающей скобки ]
|
||||
# TEMP_NAME="${BASE_NAME#*]}"
|
||||
# # Удаляем пробел в начале, если остался
|
||||
# TEMP_NAME=$(echo "$TEMP_NAME" | sed 's/^[ ]*//')
|
||||
|
||||
# # Удаляем техническую инфу в конце (1080p, Hash) ПЕРЕД анализом
|
||||
# TEMP_NAME=$(echo "$TEMP_NAME" | sed -E 's/[ ]*(\(|\[)[0-9a-zA-Z].*$//')
|
||||
# # Trim на всякий случай
|
||||
# TEMP_NAME=$(echo "$TEMP_NAME" | sed 's/^[. ,]*//;s/[. ,]*$//')
|
||||
|
||||
# # Мы разбиваем строку по ПОСЛЕДНЕМУ " - "
|
||||
# # Группа 1: Всё до тире (Название)
|
||||
# # Группа 2: Всё после тире (Потенциальный номер)
|
||||
# if [[ "$TEMP_NAME" =~ ^(.+)[[:space:]]-[[:space:]](.+)$ ]]; then
|
||||
# CANDIDATE_TITLE="${BASH_REMATCH[1]}"
|
||||
# CANDIDATE_EP="${BASH_REMATCH[2]}"
|
||||
|
||||
# # --- АНАЛИЗ ТОГО, ЧТО ПОСЛЕ ТИРЕ ---
|
||||
|
||||
# # 1. СТАНДАРТ: Начинается с цифры (01, 01.5, 01v2)
|
||||
# if [[ "$CANDIDATE_EP" =~ ^[0-9]+(\.[0-9]+)?[vV]?[0-9]*$ ]]; then
|
||||
# MODE="Episode"
|
||||
# TITLE="$CANDIDATE_TITLE"
|
||||
# EPISODE="$CANDIDATE_EP"
|
||||
|
||||
# # 2. СПЕШЛЫ: Содержит ключевые слова (Special, SP, OVA, OAD)
|
||||
# # [sS]pecial - регистронезависимый поиск первой буквы
|
||||
# # Поддерживает: "Special 01", "SP1", "OVA", "Egghead SP1"
|
||||
# elif [[ "$CANDIDATE_EP" =~ ([sS]pecial|[sS][pP]|[oO][vV][aA]|[oO][aA][dD]) ]]; then
|
||||
# MODE="Episode"
|
||||
# TITLE="$CANDIDATE_TITLE"
|
||||
# EPISODE="$CANDIDATE_EP"
|
||||
|
||||
# else
|
||||
# # После тире текст без цифр и без ключевых слов (напр. "The Last Attack")
|
||||
# # Значит это подзаголовок фильма
|
||||
# MODE="Movie"
|
||||
# TITLE=$(echo "$TEMP_NAME" | sed -E 's/[ ]*\(.+//')
|
||||
# fi
|
||||
# else
|
||||
# # Тире вообще нет
|
||||
# MODE="Movie"
|
||||
# TITLE=$(echo "$TEMP_NAME" | sed -E 's/[ ]*\(.+//')
|
||||
# fi
|
||||
# ;;
|
||||
|
||||
|
||||
# === ГРУППА 2: Hi10 и подобные (в круглых скобках) ===
|
||||
# Формат: (Hi10) Title - 01 (Quality)...
|
||||
# Или: (Hi10) Title - ED01 ...
|
||||
# *"(Hi10)"*)
|
||||
#
|
||||
# # 1. Удаляем тег группы (xxxx)
|
||||
# TEMP_NAME=$(echo "$BASE_NAME" | sed -E 's/^\(.+?\)[ ]*//')
|
||||
#
|
||||
# # 2. Паттерн сериала (тут разрешаем буквы в номере, т.к. Hi10 использует ED, NCOP)
|
||||
# if [[ "$TEMP_NAME" =~ ^(.+)[[:space:]]-[[:space:]]([a-zA-Z0-9]+) ]]; then
|
||||
# MODE="Episode"
|
||||
# TITLE="${BASH_REMATCH[1]}"
|
||||
# EPISODE="${BASH_REMATCH[2]}"
|
||||
# else
|
||||
# # Фильм
|
||||
# # Удаляем тех инфу, начинающуюся с (BD... или (1080p...
|
||||
# MODE="Movie"
|
||||
# TITLE=$(echo "$TEMP_NAME" | sed -E 's/[ ]*\((BD|1080p|720p).+//')
|
||||
# fi
|
||||
# ;;
|
||||
|
||||
# === ГРУППА 3: Китайские/Нумерованные [01] ===
|
||||
# "["[0-9][0-9]"]"*|"["[0-9][0-9][0-9]"]"*)
|
||||
# # Регулярка вытаскивает номер из скобок
|
||||
# if [[ "$BASE_NAME" =~ ^\[([0-9]+)\][[:space:]]*(.+) ]]; then
|
||||
# MODE="Episode"
|
||||
# EPISODE="${BASH_REMATCH[1]}"
|
||||
# TITLE="${BASH_REMATCH[2]}"
|
||||
# fi
|
||||
# ;;
|
||||
|
||||
# === FALLBACK: Если группа неизвестна ===
|
||||
*)
|
||||
MODE="Unsorted"
|
||||
log_error "Unknown Group/Format for '$FILENAME'. Logic set to Unsorted."
|
||||
;;
|
||||
esac
|
||||
|
||||
# =======================================================
|
||||
# ОБРАБОТКА РЕЗУЛЬТАТОВ
|
||||
# =======================================================
|
||||
|
||||
# Если мы так и не поняли, что это (Unsorted) - сразу выход
|
||||
if [[ "$MODE" == "Unsorted" ]]; then
|
||||
ln "$FILE_PATH" "$ANIME_UNSORTED/$FILENAME" 2>> "$ERROR_LOG"
|
||||
continue
|
||||
fi
|
||||
|
||||
# =======================================================
|
||||
# КОНЕЦ ПАРСИНГА. ДАЛЕЕ ОБЩАЯ ОБРАБОТКА
|
||||
# =======================================================
|
||||
|
||||
# 1. TITLE CASE (Нормализация регистра для Windows)
|
||||
TITLE=$(echo "$TITLE" | tr '[:upper:]' '[:lower:]')
|
||||
TITLE=$(echo "$TITLE" | awk '{for(i=1;i<=NF;i++){ $i=toupper(substr($i,1,1)) substr($i,2) }}1')
|
||||
|
||||
# 2. ЧИСТКА ОТ СПЕЦСИМВОЛОВ WINDOWS
|
||||
TITLE=$(echo "$TITLE" | tr -d ':?"<>|*\\/')
|
||||
# Trim (пробелы по краям)
|
||||
TITLE=$(echo "$TITLE" | sed 's/^[. ,]*//;s/[. ,]*$//')
|
||||
|
||||
# 3. SANITY CHECK (ПРОВЕРКА НА ОШИБКИ)
|
||||
if [[ -z "$TITLE" ]] || [[ ${#TITLE} -lt 2 ]]; then
|
||||
log_error "Parsed Title is empty/short ('$TITLE') for '$FILENAME'. Moving to Unsorted."
|
||||
ln "$FILE_PATH" "$ANIME_UNSORTED/$FILENAME" 2>> "$ERROR_LOG"
|
||||
continue
|
||||
fi
|
||||
|
||||
# 4. СБОРКА ИМЕНИ
|
||||
if [[ "$MODE" == "Episode" ]]; then
|
||||
FINAL_FILENAME="$TITLE - $EPISODE.$EXT"
|
||||
else
|
||||
FINAL_FILENAME="$TITLE.$EXT"
|
||||
fi
|
||||
|
||||
# 5. СОЗДАНИЕ ПАПОК И ССЫЛОК
|
||||
TARGET_DIR="$ANIME_DEST/$TITLE"
|
||||
TARGET_FILE="$TARGET_DIR/$FINAL_FILENAME"
|
||||
INFO_TXT="$TARGET_DIR/info.txt"
|
||||
|
||||
if [ ! -d "$TARGET_DIR" ]; then
|
||||
mkdir -p "$TARGET_DIR"
|
||||
echo "=== Anime Info: $TITLE ===" > "$INFO_TXT"
|
||||
fi
|
||||
|
||||
# if [ ! -f "$TARGET_FILE" ]; then
|
||||
# if ln "$FILE_PATH" "$TARGET_FILE" 2>/tmp/ln_error.$$; then
|
||||
# ln "$FILE_PATH" "$TARGET_FILE"
|
||||
# if [ $? -eq 0 ]; then
|
||||
# log_info "SUCCESS ($MODE): '$FINAL_FILENAME'"
|
||||
# {
|
||||
# echo "------------------------------------------------"
|
||||
# echo "Date Processed: $(date '+%Y-%m-%d %H:%M:%S')"
|
||||
# echo "Type: $MODE"
|
||||
# echo "Episode: ${EPISODE:-N/A}"
|
||||
# echo "Linked File: $FINAL_FILENAME"
|
||||
# echo "Source File: $FILENAME"
|
||||
# echo "Source Torrent: $1"
|
||||
# } >> "$INFO_TXT"
|
||||
# else
|
||||
# ERR_MSG=$(cat /tmp/ln_error.$$)
|
||||
# rm -f /tmp/ln_error.$$
|
||||
# log_error "Link failed for '$FINAL_FILENAME' | Source='$FILE_PATH' | Target='$TARGET_FILE' | Error='$ERR_MSG'"
|
||||
# # log_error "Link failed for '$FINAL_FILENAME'. Filesystem error?"
|
||||
# fi
|
||||
# else
|
||||
# # Записываем в отдельный лог коллизий
|
||||
# log_collision "$FINAL_FILENAME" "$FILENAME"
|
||||
# fi
|
||||
|
||||
# if [ ! -f "$TARGET_FILE" ]; then
|
||||
|
||||
# TMP_ERR="/tmp/torrent_sort_ln_$$.err"
|
||||
|
||||
# if ln "$FILE_PATH" "$TARGET_FILE" 2>"$TMP_ERR"; then
|
||||
|
||||
# log_info "SUCCESS ($MODE): '$FINAL_FILENAME'"
|
||||
|
||||
# {
|
||||
# echo "------------------------------------------------"
|
||||
# echo "Date Processed: $(date '+%Y-%m-%d %H:%M:%S')"
|
||||
# echo "Type: $MODE"
|
||||
# echo "Episode: ${EPISODE:-N/A}"
|
||||
# echo "Linked File: $FINAL_FILENAME"
|
||||
# echo "Source File: $FILENAME"
|
||||
# echo "Source Torrent: $1"
|
||||
# } >> "$INFO_TXT"
|
||||
|
||||
# else
|
||||
|
||||
# LN_ERROR=$(cat "$TMP_ERR" 2>/dev/null)
|
||||
|
||||
# SRC_STAT=$(stat -c 'inode=%i uid=%u gid=%g perms=%a owner=%U group=%G' "$FILE_PATH" 2>/dev/null)
|
||||
# DST_DIR_STAT=$(stat -c 'inode=%i uid=%u gid=%g perms=%a owner=%U group=%G' "$TARGET_DIR" 2>/dev/null)
|
||||
|
||||
# log_error "Link failed for '$FINAL_FILENAME'
|
||||
#Source='$FILE_PATH'
|
||||
#Target='$TARGET_FILE'
|
||||
#ln_error='$LN_ERROR'
|
||||
#source_stat='$SRC_STAT'
|
||||
#target_dir_stat='$DST_DIR_STAT'"
|
||||
|
||||
# fi
|
||||
|
||||
# rm -f "$TMP_ERR"
|
||||
|
||||
# else
|
||||
#
|
||||
# log_collision "$FINAL_FILENAME" "$FILENAME"
|
||||
|
||||
# fi
|
||||
if [ ! -f "$TARGET_FILE" ]; then
|
||||
|
||||
if [ ! -w "$TARGET_DIR" ]; then
|
||||
|
||||
DST_DIR_STAT=$(stat -c 'inode=%i uid=%u gid=%g perms=%a owner=%U group=%G' "$TARGET_DIR" 2>/dev/null)
|
||||
|
||||
log_error "Target directory is not writable
|
||||
|
||||
Source='$FILE_PATH'
|
||||
Target='$TARGET_FILE'
|
||||
target_dir_stat='$DST_DIR_STAT'
|
||||
|
||||
current_user='$(id -un)'
|
||||
current_group='$(id -gn)'
|
||||
current_uid='$(id -u)'
|
||||
current_gid='$(id -g)'"
|
||||
|
||||
continue
|
||||
fi
|
||||
|
||||
TMP_ERR="/tmp/torrent_sort_ln_$$.err"
|
||||
|
||||
if ln "$FILE_PATH" "$TARGET_FILE" 2>"$TMP_ERR"; then
|
||||
|
||||
log_info "SUCCESS ($MODE): '$FINAL_FILENAME'"
|
||||
|
||||
{
|
||||
echo "------------------------------------------------"
|
||||
echo "Date Processed: $(date '+%Y-%m-%d %H:%M:%S')"
|
||||
echo "Type: $MODE"
|
||||
echo "Episode: ${EPISODE:-N/A}"
|
||||
echo "Linked File: $FINAL_FILENAME"
|
||||
echo "Source File: $FILENAME"
|
||||
echo "Source Torrent: $1"
|
||||
} >> "$INFO_TXT"
|
||||
|
||||
else
|
||||
|
||||
LN_ERROR=$(cat "$TMP_ERR" 2>/dev/null)
|
||||
|
||||
SRC_STAT=$(stat -c 'inode=%i uid=%u gid=%g perms=%a owner=%U group=%G' "$FILE_PATH" 2>/dev/null)
|
||||
DST_DIR_STAT=$(stat -c 'inode=%i uid=%u gid=%g perms=%a owner=%U group=%G' "$TARGET_DIR" 2>/dev/null)
|
||||
|
||||
log_error "Link failed for '$FINAL_FILENAME'
|
||||
|
||||
Source='$FILE_PATH'
|
||||
Target='$TARGET_FILE'
|
||||
|
||||
ln_error='$LN_ERROR'
|
||||
|
||||
source_stat='$SRC_STAT'
|
||||
target_dir_stat='$DST_DIR_STAT'
|
||||
|
||||
current_user='$(id -un)'
|
||||
current_group='$(id -gn)'
|
||||
current_uid='$(id -u)'
|
||||
current_gid='$(id -g)'"
|
||||
|
||||
fi
|
||||
|
||||
rm -f "$TMP_ERR"
|
||||
|
||||
else
|
||||
|
||||
log_collision "$FINAL_FILENAME" "$FILENAME"
|
||||
|
||||
fi
|
||||
|
||||
|
||||
done
|
||||
|
||||
fi
|
||||
Reference in New Issue
Block a user