#!/usr/bin/env python3 import argparse import sys from pathlib import Path from services.iview import iViewService # ------------------------- # Service registry # ------------------------- SERVICES = { "IVIEW": iViewService(), } 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" 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): 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") sys.exit(1) series = [] with open(self.series_file, "r") as f: for line in f: line = line.strip() if not line or "/" not in line: continue service_name, remainder = line.split("/", 1) if "|" in remainder: source_title, output_title = remainder.split("|", 1) else: source_title = output_title = remainder series.append((service_name.strip(), source_title.strip(), output_title.strip())) return series def process_show(self, service_name, source_title, output_title): service = SERVICES.get(service_name.upper()) if not service: print(f"โŒ Unknown service: {service_name}") return print(f"\n๐Ÿ“บ Show: {output_title} ({service_name})") seasons = service.discover_seasons(source_title) if not seasons: print("โš ๏ธ No seasons found") return for season_data in seasons: season_num = season_data["season"] print(f"๐Ÿ“ฆ Season {season_num}") for entry in season_data["data"]["entries"]: episode = service.normalize_episode(output_title, entry) 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 service, source, output in self.series_list: self.process_show(service, source, output) 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()