100 lines
3.3 KiB
Python
100 lines
3.3 KiB
Python
#!/usr/bin/env python3
|
|
import argparse
|
|
import os
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
SHOWS_ROOT = Path(__file__).resolve().parent
|
|
DOWNLOADS = SHOWS_ROOT / ".downloads"
|
|
SEED_RATIO = 2.0
|
|
SEED_TIME_MIN = 1440
|
|
PORT = 6881
|
|
VIDEO_EXTS = {'.mkv', '.mp4', '.avi', '.mov', '.m4v', '.ts', '.webm'}
|
|
|
|
def extract_ep(name):
|
|
stem = Path(name).stem
|
|
stem = re.sub(r'^\[[^\]]+\]\s*', '', stem)
|
|
stem = re.sub(r'\([^)]*\)', '', stem)
|
|
stem = re.sub(r'\[[^\]]*\]', '', stem)
|
|
if re.search(r'\b(NCOP|NCED|OP|ED|Menu|Extra|Preview|PV|SP|Special)\b', stem, re.I):
|
|
return None
|
|
m = re.search(r'(?:^|\s|-)\s*(\d{1,3})(?:v\d)?(?=\s|$|\.)', stem)
|
|
return int(m.group(1)) if m else None
|
|
|
|
def organize(stage, name, year, season):
|
|
show_dir = SHOWS_ROOT / f"{name} ({year})"
|
|
season_dir = show_dir / f"Season {int(season):02d}"
|
|
specials_dir = show_dir / "Specials"
|
|
season_dir.mkdir(parents=True, exist_ok=True)
|
|
for f in Path(stage).rglob('*'):
|
|
if not f.is_file() or f.suffix.lower() not in VIDEO_EXTS:
|
|
continue
|
|
ep = extract_ep(f.name)
|
|
if ep is None:
|
|
specials_dir.mkdir(parents=True, exist_ok=True)
|
|
dst = specials_dir / f.name
|
|
else:
|
|
dst = season_dir / f"{name} - S{int(season):02d}E{ep:02d}{f.suffix.lower()}"
|
|
if dst.exists():
|
|
continue
|
|
try:
|
|
os.link(f, dst)
|
|
except OSError:
|
|
shutil.copy2(f, dst)
|
|
print(f"[organize] {dst.relative_to(SHOWS_ROOT)}", flush=True)
|
|
|
|
def cmd_organize(a):
|
|
organize(a.stage, a.name, a.year, a.season)
|
|
|
|
def cmd_download(a):
|
|
torrent = Path(a.torrent).expanduser().resolve()
|
|
if not torrent.exists():
|
|
sys.exit(f"not found: {torrent}")
|
|
stage = DOWNLOADS / torrent.stem
|
|
stage.mkdir(parents=True, exist_ok=True)
|
|
hook = stage / "_hook.sh"
|
|
hook.write_text(
|
|
f'#!/bin/bash\n'
|
|
f'python3 "{Path(__file__).resolve()}" organize '
|
|
f'--stage "{stage}" --name "{a.name}" --year "{a.year}" --season {a.season}\n'
|
|
)
|
|
hook.chmod(0o755)
|
|
cmd = [
|
|
"aria2c", str(torrent),
|
|
f"--dir={stage}",
|
|
f"--listen-port={PORT}", f"--dht-listen-port={PORT}",
|
|
"--enable-dht=true", "--enable-peer-exchange=true", "--bt-enable-lpd=true",
|
|
"--follow-torrent=mem", "--check-integrity=true",
|
|
"--summary-interval=30", "--console-log-level=warn",
|
|
f"--on-bt-download-complete={hook}",
|
|
]
|
|
if a.no_seed:
|
|
cmd.append("--seed-time=0")
|
|
else:
|
|
cmd += [f"--seed-ratio={SEED_RATIO}", f"--seed-time={SEED_TIME_MIN}"]
|
|
subprocess.run(cmd)
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser()
|
|
sub = ap.add_subparsers(dest="cmd", required=True)
|
|
d = sub.add_parser("download")
|
|
d.add_argument("torrent")
|
|
d.add_argument("--name", required=True)
|
|
d.add_argument("--year", required=True)
|
|
d.add_argument("--season", type=int, default=1)
|
|
d.add_argument("--no-seed", action="store_true")
|
|
d.set_defaults(func=cmd_download)
|
|
o = sub.add_parser("organize")
|
|
o.add_argument("--stage", required=True)
|
|
o.add_argument("--name", required=True)
|
|
o.add_argument("--year", required=True)
|
|
o.add_argument("--season", type=int, default=1)
|
|
o.set_defaults(func=cmd_organize)
|
|
a = ap.parse_args()
|
|
a.func(a)
|
|
|
|
if __name__ == "__main__":
|
|
main()
|