Files
variet-agent/tools/nas_scanner.py

194 lines
6.5 KiB
Python

r"""NAS 폴더 스캐너 — 다운로드된 애니 목록 + 파일 정보 조회.
NAS Animation 폴더 구조:
\\192.168.10.10\NasData\Video\Animation\
[26_1분기]장송의프리렌2기\
[ASW] Sousou no Frieren S2 - 07.mkv
subtitles\...
[25_4분기]그노시아\
...
"""
import logging
import os
import re
from dataclasses import dataclass, field
from pathlib import Path
from typing import Optional
import config
logger = logging.getLogger("variet.tools.nas")
@dataclass
class AnimeFolder:
"""NAS에 있는 애니 폴더 정보."""
folder_name: str # [26_1분기]장송의프리렌2기
full_path: str
title: str # 장송의프리렌2기
year: int # 26
quarter: int # 1
video_count: int = 0
subtitle_count: int = 0
total_size_gb: float = 0.0
video_files: list[str] = field(default_factory=list)
subtitle_files: list[str] = field(default_factory=list)
VIDEO_EXTS = {".mkv", ".mp4", ".avi", ".webm", ".m4v"}
SUB_EXTS = {".ass", ".srt", ".ssa", ".sub", ".smi"}
def _parse_folder_name(name: str) -> tuple[int, int, str]:
"""폴더명에서 연도, 분기, 제목 추출.
[26_1분기]장송의프리렌2기 → (26, 1, '장송의프리렌2기')
"""
m = re.match(r'\[(\d{2})_(\d)분기\](.+)', name)
if m:
return int(m.group(1)), int(m.group(2)), m.group(3)
return 0, 0, name
class NasScanner:
"""NAS Animation 폴더 스캐너."""
def __init__(self, base_path: str = ""):
self.base_path = Path(
base_path or getattr(config, "NAS_ANIME_PATH",
r"\\192.168.10.10\NasData\Video\Animation")
)
def is_accessible(self) -> bool:
"""NAS 접근 가능 여부."""
try:
return self.base_path.exists() and self.base_path.is_dir()
except (OSError, PermissionError):
return False
def list_anime_folders(
self,
year: Optional[int] = None,
quarter: Optional[int] = None,
) -> list[AnimeFolder]:
"""애니 폴더 목록 조회 (분기별 필터 가능)."""
if not self.is_accessible():
logger.error(f"NAS 경로 접근 불가: {self.base_path}")
return []
results = []
try:
for entry in sorted(self.base_path.iterdir()):
if not entry.is_dir():
continue
y, q, title = _parse_folder_name(entry.name)
# 필터링
if year is not None and y != year:
continue
if quarter is not None and q != quarter:
continue
folder = AnimeFolder(
folder_name=entry.name,
full_path=str(entry),
title=title,
year=y,
quarter=q,
)
# 파일 스캔
self._scan_folder(entry, folder)
results.append(folder)
except (OSError, PermissionError) as e:
logger.error(f"NAS 스캔 오류: {e}")
return results
def _scan_folder(self, path: Path, folder: AnimeFolder):
"""폴더 내 영상/자막 파일 집계."""
try:
for item in path.rglob("*"):
if not item.is_file():
continue
ext = item.suffix.lower()
size = item.stat().st_size
if ext in VIDEO_EXTS:
folder.video_count += 1
folder.video_files.append(item.name)
folder.total_size_gb += size / (1024 ** 3)
elif ext in SUB_EXTS:
folder.subtitle_count += 1
folder.subtitle_files.append(item.name)
except (OSError, PermissionError) as e:
logger.warning(f"파일 스캔 오류 ({path}): {e}")
def get_current_quarter_anime(self) -> list[AnimeFolder]:
"""이번 분기 다운로드된 애니 목록."""
from datetime import date
today = date.today()
year = today.year % 100
quarter = (today.month - 1) // 3 + 1
return self.list_anime_folders(year=year, quarter=quarter)
def search(self, keyword: str) -> list[AnimeFolder]:
"""키워드로 NAS 폴더 검색."""
all_folders = self.list_anime_folders()
kw = keyword.lower()
return [f for f in all_folders if kw in f.title.lower() or kw in f.folder_name.lower()]
def get_summary(self, year: Optional[int] = None, quarter: Optional[int] = None) -> dict:
"""요약 통계."""
folders = self.list_anime_folders(year=year, quarter=quarter)
return {
"total_anime": len(folders),
"total_videos": sum(f.video_count for f in folders),
"total_subtitles": sum(f.subtitle_count for f in folders),
"total_size_gb": round(sum(f.total_size_gb for f in folders), 2),
"folders": folders,
}
# ── CLI 진입점 ──
if __name__ == "__main__":
import sys
import json
scanner = NasScanner()
args = sys.argv[1:]
if not args or args[0] == "scan":
# python tools/nas_scanner.py scan [--year 26] [--quarter 1]
year = quarter = None
for i, a in enumerate(args):
if a == "--year" and i + 1 < len(args):
year = int(args[i + 1])
if a == "--quarter" and i + 1 < len(args):
quarter = int(args[i + 1])
folders = scanner.list_anime_folders(year=year, quarter=quarter)
for f in folders:
print(f"📁 {f.folder_name} | 영상 {f.video_count}개 | 자막 {f.subtitle_count}개 | {f.total_size_gb:.1f}GB")
print(f"\n{len(folders)}개 애니, 영상 {sum(f.video_count for f in folders)}")
elif args[0] == "search" and len(args) > 1:
# python tools/nas_scanner.py search "프리렌"
keyword = " ".join(args[1:])
results = scanner.search(keyword)
for f in results:
print(f"📁 {f.folder_name} | 영상 {f.video_count}개 | 자막 {f.subtitle_count}")
if not results:
print(f"'{keyword}' 검색 결과 없음")
elif args[0] == "summary":
# python tools/nas_scanner.py summary
summary = scanner.get_summary()
print(json.dumps(summary, ensure_ascii=False, indent=2, default=str))
else:
print("사용법: python tools/nas_scanner.py [scan|search|summary] [옵션]")