Files
tvgrabber/autograbber.py
2026-05-15 00:00:57 +09:30

193 lines
6.4 KiB
Python
Executable File

#!/usr/bin/env python3
import argparse
import sys
import json
from pathlib import Path
from services.iview import iViewService
from services.ten import TenService
# -------------------------
# Service registry
# -------------------------
SERVICES = {
"IVIEW": iViewService(),
"TEN": TenService(),
}
class AutoGrabber:
def __init__(self, download_dir, config_dir, dry_run=False, mark_existing=False):
# The main media output directory (Required)
self.download_dir = Path(download_dir)
# The config directory (Defaults to PWD/.tvgrabber or CLI override)
self.config_dir = Path(config_dir)
self.history_file = self.config_dir / "history"
self.series_file = self.config_dir / "series.json"
self.dry_run = dry_run
self.mark_existing = mark_existing
# Initialize
self.history = self.load_history()
self.series_list = self.load_series()
def load_history(self):
history = {}
# Ensure .tvgrabber exists before we try to handle the history file
if not self.config_dir.exists():
self.config_dir.mkdir(parents=True, exist_ok=True)
if not self.history_file.exists():
self.history_file.touch()
with open(self.history_file, "r") as f:
for line in f:
line = line.strip()
if not line or "|" not in line:
if line: history[line] = None
continue
ep_id, filename = line.split("|", 1)
history[ep_id.strip()] = filename.strip()
return history
def save_history(self):
with open(self.history_file, "w") as f:
for ep_id in sorted(self.history.keys()):
filename = self.history[ep_id]
f.write(f"{ep_id} | {filename}\n" if filename else f"{ep_id}\n")
def load_series(self):
self.series_file = self.config_dir / "series.json"
if not self.series_file.exists():
print(f"❌ Error: Series file not found at {self.series_file}")
print(f"Please ensure your series list is in {self.config_dir}/series.json")
sys.exit(1)
try:
with open(self.series_file, "r") as f:
return json.load(f)
except json.JSONDecodeError as e:
print(f"❌ Failed to parse series.json: {e}")
sys.exit(1)
def run(self):
for show_config in self.series_list:
self.process_show(show_config)
def process_show(self, config):
service_name = config.get("service")
source_title = config.get("source_title")
source_url = config.get("source_url")
if not service_name or not source_title:
print(f"❌ Skipping invalid entry: {config} (Missing service or source_title)")
return
service = SERVICES.get(service_name.upper())
if not service:
print(f"❌ Unknown service: {service_name}")
return
# 2. Setup Defaults for Output Fields
output_title = config.get("output_title") or source_title
source_season = config.get("source_season")
# If output_season isn't provided, use source_season (if it exists)
output_season = config.get("output_season") or source_season
print(f"\n==============================")
print(f"📺 Show: {output_title}")
print(f"📡 Service: {service_name}")
print("==============================")
# 3. Discovery (Ten will handle its own requirement for source_season)
seasons = service.discover_seasons(source_title, source_season=source_season, source_url=source_url)
if not seasons:
# Service will have already printed its specific error message
return
for season_data in seasons:
# We use the resolved output_season (either from JSON or fallback)
# If discovery found a season number and we have no override, we use that.
final_season = output_season or season_data.get("season")
print(f"\n📦 Processing Season {final_season}")
for entry in season_data["data"]["entries"]:
episode = service.normalize_episode(
output_title,
entry,
season_num=final_season
)
ep_id = episode["episode_id"]
filename = episode["filename"]
if ep_id in self.history:
print(f"{filename} (Skipped)")
continue
if self.dry_run:
print(f" 🧪 {filename} (Dry Run)")
continue
if self.mark_existing:
print(f" 📝 {filename} (Marked)")
self.history[ep_id] = filename
self.save_history()
continue
print(f"{filename} (Downloading...)")
if service.download_episode(episode, entry, self.download_dir):
self.history[ep_id] = filename
self.save_history()
def run(self):
for show_config in self.series_list:
self.process_show(show_config)
print("\n✅ Done.")
# -------------------------
# CLI
# -------------------------
def parse_args():
parser = argparse.ArgumentParser(description="Australian FTA TV AutoGrabber")
# Required positional argument for the download directory
parser.add_argument(
"download_dir",
help="REQUIRED: Path to the directory where media will be saved"
)
# Optional config directory override
parser.add_argument(
"-c", "--config",
default="~/.tvgrabber",
help="Path to the config folder containing 'series' and 'history' (default: ~/.tvgrabber)"
)
parser.add_argument("--dry-run", action="store_true")
parser.add_argument("--mark-existing", action="store_true")
return parser.parse_args()
if __name__ == "__main__":
args = parse_args()
# Ensure the download directory exists
out_path = Path(args.download_dir).resolve()
if not out_path.exists() and not args.dry_run:
out_path.mkdir(parents=True, exist_ok=True)
AutoGrabber(
download_dir=out_path,
config_dir=Path(args.config).resolve(),
dry_run=args.dry_run,
mark_existing=args.mark_existing
).run()