mirror of
https://github.com/darkzoul5/YoutubePlaylistSync.git
synced 2026-07-04 04:53:58 +03:00
113 lines
4.3 KiB
Python
113 lines
4.3 KiB
Python
from __future__ import annotations
|
|
|
|
import argparse
|
|
import asyncio
|
|
import logging
|
|
from pathlib import Path
|
|
|
|
from .config.settings import Settings
|
|
from .core.database.db import Database
|
|
from .core.sync.service import SyncService
|
|
from .core.sync.executor import ActionExecutor
|
|
from .core.events.event_bus import EventBus
|
|
import re
|
|
from .core.utils.yt import extract_playlist_id
|
|
from .core.utils.deps import DependencyError
|
|
from .core.utils.logging_setup import configure_logging
|
|
|
|
|
|
def main(argv: list[str] | None = None) -> int:
|
|
parser = argparse.ArgumentParser(description="YouTube Playlist Sync — compute/apply actions")
|
|
parser.add_argument("--apply", action="store_true", help="Apply actions (otherwise compute-only)")
|
|
parser.add_argument("--db", type=Path, default=Path("db/app.db"), help="Path to SQLite database")
|
|
parser.add_argument("--playlist", type=int, default=None, help="Only run for a specific playlist index (0-based)")
|
|
parser.add_argument("--verbose", action="store_true", help="Print detailed events (rename/recycle/start)")
|
|
parser.add_argument("--debug", action="store_true", help="Enable debug logging to console + app/data/app.log")
|
|
args = parser.parse_args(argv)
|
|
|
|
configure_logging(verbose=bool(args.debug), log_file=Path("app/data/app.log"))
|
|
log = logging.getLogger(__name__)
|
|
|
|
settings = Settings()
|
|
db = Database(args.db.resolve())
|
|
service = SyncService(db)
|
|
bus = EventBus()
|
|
executor = ActionExecutor(db, event_bus=bus)
|
|
|
|
seen_errors: set[str] = set()
|
|
|
|
ansi = re.compile(r"\x1b\[[0-9;]*m")
|
|
|
|
async def on_started(payload):
|
|
if args.verbose:
|
|
vid = payload.get("video_id")
|
|
target = payload.get("target")
|
|
print(f"START: {vid} → {target}")
|
|
|
|
async def on_completed(payload):
|
|
vid = payload.get("video_id")
|
|
target = payload.get("target")
|
|
print(f"OK: {vid} → {target}")
|
|
|
|
async def on_failed(payload):
|
|
raw = str(payload.get("error", "failed"))
|
|
msg = ansi.sub("", raw)
|
|
# Print only once per unique message
|
|
if msg not in seen_errors:
|
|
seen_errors.add(msg)
|
|
# Friendly hint for missing ffmpeg
|
|
if "ffmpeg not found" in msg.lower():
|
|
print("ERROR: ffmpeg not found. Install ffmpeg or set 'ffmpeg_path' in config.")
|
|
else:
|
|
print(f"ERROR: {msg}")
|
|
|
|
# Subscribe to key events
|
|
bus.subscribe("DownloadStarted", on_started)
|
|
bus.subscribe("DownloadCompleted", on_completed)
|
|
bus.subscribe("DownloadFailed", on_failed)
|
|
if args.verbose:
|
|
async def on_rename(payload):
|
|
print(f"RENAME: {payload.get('video_id')} → {payload.get('to')}")
|
|
async def on_recycle(payload):
|
|
print(f"RECYCLE: {payload.get('video_id')} ← {payload.get('name')}")
|
|
bus.subscribe("RenameApplied", on_rename)
|
|
bus.subscribe("FileRecycled", on_recycle)
|
|
|
|
playlists = settings.playlists
|
|
if args.playlist is not None:
|
|
playlists = [playlists[args.playlist]] if 0 <= args.playlist < len(playlists) else []
|
|
|
|
for pl in playlists:
|
|
url = pl.get("url")
|
|
pid = extract_playlist_id(url) or (url or "")
|
|
try:
|
|
actions = service.sync_from_config(pl)
|
|
except ImportError as e:
|
|
msg = str(e)
|
|
if "yt_dlp" in msg or "yt-dlp" in msg:
|
|
print("yt-dlp Python package is required. Install with: pip install -U yt-dlp")
|
|
return 2
|
|
raise
|
|
counts: dict[str, int] = {}
|
|
for a in actions:
|
|
counts[a.type.name] = counts.get(a.type.name, 0) + 1
|
|
summary = ", ".join(f"{k}:{v}" for k, v in sorted(counts.items()))
|
|
print(f"Playlist {pid}: {len(actions)} actions → {summary}")
|
|
log.info("playlist=%s actions=%s summary=%s", pid, len(actions), summary)
|
|
if args.apply and actions:
|
|
try:
|
|
asyncio.run(executor.execute(actions, pl))
|
|
except DependencyError as e:
|
|
print(f"ERROR: {e}")
|
|
log.error("dependency error: %s", e)
|
|
return 2
|
|
db.set_playlist_last_sync(pid)
|
|
print(f"Applied actions for {pid}.")
|
|
log.info("playlist=%s applied_actions=%s", pid, len(actions))
|
|
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|