import: anime utility scripts from /storage/scripts/

Перенос из vespaserver:/storage/scripts/ перед удалением оригиналов.
Скрипты разовые — клонируются по мере необходимости.
This commit is contained in:
shu 2026-04-29 18:24:58 +03:00
parent 67b460d40c
commit 70814ddc1f
5 changed files with 1918 additions and 2 deletions

209
rename_anime.py Normal file
View file

@ -0,0 +1,209 @@
#!/usr/bin/env python3
"""
Добавляет название аниме (из директории) в конец названия файла перед расширением
и удаляет "[AniTousen]" из начала названия.
Пример:
Директория: Kuroshitsuji
Файл: [AniTousen] TV-1 ED01 - I'm ALIVE! (BECCA).mp3
Результат:
TV-1 ED01 - I'm ALIVE! (BECCA) [Kuroshitsuji].mp3
Использование:
python3 rename_anime.py test
python3 rename_anime.py run --dry-run /path/to/music
python3 rename_anime.py run /path/to/music
"""
import os
import sys
import argparse
from pathlib import Path
AUDIO_EXTENSIONS = {".flac", ".mp3", ".ogg", ".opus", ".m4a"}
def remove_ani_tousen(stem: str) -> str:
"""
Удаляет "[AniTousen]" из начала названия файла.
Было: [AniTousen] TV-1 ED01 - I'm ALIVE! (BECCA)
Стало: TV-1 ED01 - I'm ALIVE! (BECCA)
"""
# Проверяем начинается ли с [AniTousen]
if stem.startswith("[AniTousen] "):
return stem[len("[AniTousen] "):]
elif stem.startswith("[AniTousen]"):
return stem[len("[AniTousen]"):].lstrip()
return stem
def rename_file(filepath: Path, anime_source: str, dry_run: bool = False) -> bool:
"""
Переименовать файл: добавить название аниме в конец и удалить [AniTousen] из начала.
Было: [AniTousen] TV-1 ED01 - I'm ALIVE! (BECCA).mp3
Стало: TV-1 ED01 - I'm ALIVE! (BECCA) [Kuroshitsuji].mp3
"""
if dry_run:
return True
old_name = filepath.name
stem = filepath.stem
suffix = filepath.suffix
# Удаляем [AniTousen] из начала
new_stem = remove_ani_tousen(stem)
# Добавляем название аниме в конец
new_stem = f"{new_stem} [{anime_source}]"
new_name = f"{new_stem}{suffix}"
new_path = filepath.parent / new_name
# Проверяем, не существует ли уже файл с таким именем
if new_path.exists():
print(f" ⚠ Файл уже существует: {new_name}")
return False
try:
filepath.rename(new_path)
return True
except Exception as e:
print(f" ✗ Ошибка переименования: {e}")
return False
def process_directory(root_path: str, dry_run: bool = False):
"""Обработать директорию."""
root = Path(root_path)
if not root.is_dir():
print(f"Директория не найдена: {root_path}")
sys.exit(1)
prefix = "[DRY] " if dry_run else ""
renamed = 0
errors = 0
skipped = 0
for anime_dir in sorted(root.iterdir()):
if not anime_dir.is_dir():
continue
# Пропускаем скрытые директории
if anime_dir.name.startswith('.'):
continue
anime_source = anime_dir.name
# Находим все аудиофайлы рекурсивно
audio_files = sorted([
f for f in anime_dir.rglob("*")
if f.is_file() and f.suffix.lower() in AUDIO_EXTENSIONS
])
if not audio_files:
continue
print(f"\n{prefix}📁 {anime_source} ({len(audio_files)} файлов)")
for filepath in audio_files:
# Проверяем, не добавлено ли уже название аниме
stem = filepath.stem
if stem.endswith(f" [{anime_source}]"):
skipped += 1
print(f"{filepath.name} (уже переименован)")
continue
success = rename_file(filepath, anime_source, dry_run)
if success:
renamed += 1
old_name = filepath.name
new_name = f"{filepath.stem} [{anime_source}]{filepath.suffix}"
print(f"{old_name}")
print(f"{new_name}")
else:
errors += 1
print(f"\n{'=' * 60}")
print(f" {'Будет переименовано' if dry_run else 'Переименовано'}: {renamed}")
print(f" Пропущено (уже переименовано): {skipped}")
print(f" Ошибок: {errors}")
print(f"{'=' * 60}")
def test():
"""Тест логики переименования."""
examples = [
# (старое_имя, название_папки, ожидаемоеовое_имя)
(
"[AniTousen] TV-1 ED01 - I'm ALIVE! (BECCA).mp3",
"Kuroshitsuji",
"TV-1 ED01 - I'm ALIVE! (BECCA) [Kuroshitsuji].mp3"
),
(
"[AniTousen] TV-2 ED EP12 - Get up! (Asano Masumi).flac",
"Ikkitousen",
"TV-2 ED EP12 - Get up! (Asano Masumi) [Ikkitousen].flac"
),
(
"[AniTousen] Movie ED - Song Name.wav",
"Your Name",
"Movie ED - Song Name [Your Name].wav"
),
# Файл без [AniTousen] - просто добавляем аниме
(
"TV OP - Song.mp3",
"Test Anime",
"TV OP - Song [Test Anime].mp3"
),
]
print("Тест переименования:\n")
ok = 0
for old_name, anime, expected_new in examples:
stem = Path(old_name).stem
suffix = Path(old_name).suffix
new_stem = f"{stem} [{anime}]"
new_name = f"{new_stem}{suffix}"
match = new_name == expected_new
status = "" if match else ""
if match:
ok += 1
print(f" {status} {old_name}")
print(f"{new_name}")
if not match:
print(f" ОЖИДАЛОСЬ: {expected_new}")
print()
print(f"Пройдено: {ok}/{len(examples)}")
def main():
parser = argparse.ArgumentParser(description="Переименование аниме саундтреков")
sub = parser.add_subparsers(dest="command")
p_run = sub.add_parser("run", help="Переименовать файлы")
p_run.add_argument("directory", help="Директория с музыкой")
p_run.add_argument("--dry-run", action="store_true", help="Показать что будет сделано без изменений")
sub.add_parser("test", help="Тест логики")
args = parser.parse_args()
if args.command == "test":
test()
elif args.command == "run":
process_directory(args.directory, args.dry_run)
else:
parser.print_help()
if __name__ == "__main__":
main()