KaraokeVideoDownloader/karaoke_downloader/cli.py

183 lines
10 KiB
Python

import sys
import argparse
from pathlib import Path
from karaoke_downloader.downloader import KaraokeDownloader
def main():
parser = argparse.ArgumentParser(
description="Karaoke Video Downloader - Download YouTube playlists and channel videos for karaoke",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
python download_karaoke.py https://www.youtube.com/playlist?list=XYZ
python download_karaoke.py https://www.youtube.com/@SingKingKaraoke/videos
python download_karaoke.py --file data/channels.txt
python download_karaoke.py --reset-channel SingKingKaraoke --delete-files
"""
)
parser.add_argument('url', nargs='?', help='YouTube playlist or channel URL to download')
parser.add_argument('--file', '-f', help='Text file containing playlist or channel URLs (one per line)')
parser.add_argument('--status', '-s', action='store_true', help='Show download status and statistics')
parser.add_argument('--report', '-r', metavar='PLAYLIST_ID', help='Generate a detailed report for a specific playlist')
parser.add_argument('--sync', '--sync-only', action='store_true', help='Only sync playlist without downloading (update tracking)')
parser.add_argument('--cleanup', action='store_true', help='Clean up orphaned tracking entries')
parser.add_argument('--resolution', '--res', choices=['480p', '720p', '1080p', '1440p', '2160p'], default='720p', help='Preferred video resolution (default: 720p)')
parser.add_argument('--limit', '-l', type=int, help='Limit the number of videos to download (e.g., --limit 10)')
parser.add_argument('--refresh', '--force-refresh', action='store_true', help='Force refresh channel cache (ignore cached data)')
parser.add_argument('--cache-info', action='store_true', help='Show channel cache information')
parser.add_argument('--clear-cache', metavar='CHANNEL_ID', nargs='?', const='all', help='Clear cache for specific channel or all channels (use --clear-cache all)')
parser.add_argument('--cache-duration', type=int, metavar='HOURS', help='Set cache duration in hours (default: 24)')
parser.add_argument('--songlist-priority', action='store_true', help='Prioritize downloads based on data/songList.json (default: enabled)')
parser.add_argument('--no-songlist-priority', action='store_true', help='Disable songlist prioritization')
parser.add_argument('--songlist-only', action='store_true', help='Only download songs that are in the songlist (skip all others)')
parser.add_argument('--songlist-status', action='store_true', help='Show songlist download status and statistics')
parser.add_argument('--reset-channel', metavar='CHANNEL_NAME', help='Reset all tracking and files for a channel')
parser.add_argument('--reset-songlist', action='store_true', help='When used with --reset-channel, also reset songlist songs for this channel')
parser.add_argument('--reset-songlist-all', action='store_true', help='Reset all songlist tracking and delete all songlist-downloaded files (global)')
parser.add_argument('--version', '-v', action='version', version='Karaoke Playlist Downloader v1.0')
parser.add_argument('--force-download-plan', action='store_true', help='Force refresh the download plan cache (re-scan all channels for matches)')
parser.add_argument('--latest-per-channel', action='store_true', help='Download the latest N videos from each channel (use with --limit)')
args = parser.parse_args()
yt_dlp_path = Path("downloader/yt-dlp.exe")
if not yt_dlp_path.exists():
print("❌ Error: yt-dlp.exe not found in downloader/ directory")
print("Please ensure yt-dlp.exe is present in the downloader/ folder")
sys.exit(1)
downloader = KaraokeDownloader()
if args.limit:
downloader.download_limit = args.limit
print(f"🎯 Download limit set to: {args.limit} videos")
if args.no_songlist_priority:
downloader.use_songlist_priority = False
print("🎯 Songlist prioritization disabled")
else:
downloader.use_songlist_priority = True
print("🎯 Songlist prioritization enabled")
if args.songlist_only:
downloader.songlist_only = True
print("🎯 Songlist-only mode enabled (will only download songlist songs)")
if args.resolution != '720p':
resolution_map = {
'480p': '480',
'720p': '720',
'1080p': '1080',
'1440p': '1440',
'2160p': '2160'
}
height = resolution_map[args.resolution]
downloader.config["download_settings"]["format"] = f"best[height<={height}][ext=mp4]/best[height<={height}]/best[ext=mp4]/best"
downloader.config["download_settings"]["preferred_resolution"] = args.resolution
print(f"🎬 Using resolution: {args.resolution}")
# --- NEW: Reset channel CLI command ---
if args.reset_channel:
downloader.reset_channel_downloads(
args.reset_channel,
reset_songlist=args.reset_songlist,
delete_files=True # Always delete files if they exist
)
sys.exit(0)
# --- END NEW ---
# --- NEW: If no URL or file is provided, but --songlist-only is set, use all channels in data/channels.txt ---
if args.songlist_only and not args.url and not args.file:
channels_file = Path("data/channels.txt")
if channels_file.exists():
args.file = str(channels_file)
print("📋 No URL or --file provided, defaulting to all channels in data/channels.txt for songlist-only mode.")
else:
print("❌ No URL, --file, or data/channels.txt found. Please provide a channel URL or a file with channel URLs.")
sys.exit(1)
# --- END NEW ---
if args.reset_songlist_all:
from karaoke_downloader.downloader import reset_songlist_all
reset_songlist_all()
print('✅ All songlist tracking and files have been reset.')
sys.exit(0)
if args.status:
stats = downloader.tracker.get_statistics()
print("🎤 Karaoke Downloader Status")
print("=" * 40)
print(f"Total Songs: {stats['total_songs']}")
print(f"Total Playlists: {stats['total_playlists']}")
print(f"Downloaded Songs: {stats['downloaded_songs']}")
print(f"Failed Songs: {stats['failed_songs']}")
print(f"Partial Downloads: {stats['partial_songs']}")
print(f"Total Size: {stats['total_size_mb']} MB")
print(f"Last Updated: {stats['last_updated']}")
sys.exit(0)
elif args.cache_info:
cache_info = downloader.tracker.get_cache_info()
print("💾 Channel Cache Information")
print("=" * 40)
print(f"Total Channels: {cache_info['total_channels']}")
print(f"Total Cached Videos: {cache_info['total_cached_videos']}")
print(f"Cache Duration: {cache_info['cache_duration_hours']} hours")
print(f"Last Updated: {cache_info['last_updated']}")
sys.exit(0)
elif args.clear_cache:
if args.clear_cache == 'all':
downloader.tracker.clear_channel_cache()
print("🧹 Cleared all channel caches")
else:
downloader.tracker.clear_channel_cache(args.clear_cache)
print(f"🧹 Cleared cache for channel: {args.clear_cache}")
sys.exit(0)
elif args.cache_duration:
downloader.tracker.set_cache_duration(args.cache_duration)
print(f"⏰ Cache duration set to {args.cache_duration} hours")
sys.exit(0)
elif args.report:
report = downloader.tracker.export_playlist_report(args.report)
print(report)
sys.exit(0)
elif args.cleanup:
orphaned = downloader.tracker.cleanup_orphaned_files(downloader.downloads_dir)
print(f"🧹 Cleaned up {len(orphaned)} orphaned tracking entries")
sys.exit(0)
elif args.songlist_status:
songlist = downloader._load_songlist()
tracking = downloader.songlist_tracking
print("📋 Songlist Download Status")
print("=" * 40)
print(f"Total songs in list: {len(songlist)}")
print(f"Songs downloaded: {len(tracking)}")
print(f"Songs remaining: {len(songlist) - len(tracking)}")
print(f"Progress: {(len(tracking) / len(songlist) * 100):.1f}%")
if tracking:
print(f"\n📁 Downloaded songs:")
for key, info in list(tracking.items())[:10]:
print(f"{info['artist']} - {info['title']} (from {info['channel']})")
if len(tracking) > 10:
print(f" ... and {len(tracking) - 10} more")
sys.exit(0)
elif args.songlist_only and args.file:
# Read all channel URLs from file
with open(args.file, "r", encoding="utf-8") as f:
channel_urls = [line.strip() for line in f if line.strip() and not line.strip().startswith("#")]
limit = args.limit if args.limit else None
force_refresh_download_plan = args.force_download_plan if hasattr(args, 'force_download_plan') else False
success = downloader.download_songlist_across_channels(channel_urls, limit=limit, force_refresh_download_plan=force_refresh_download_plan)
elif args.url:
success = downloader.download_channel_videos(args.url, force_refresh=args.refresh)
elif args.latest_per_channel and args.file:
# Read all channel URLs from file
with open(args.file, "r", encoding="utf-8") as f:
channel_urls = [line.strip() for line in f if line.strip() and not line.strip().startswith("#")]
limit = args.limit if args.limit else 5
force_refresh_download_plan = args.force_download_plan if hasattr(args, 'force_download_plan') else False
success = downloader.download_latest_per_channel(channel_urls, limit=limit, force_refresh_download_plan=force_refresh_download_plan)
else:
parser.print_help()
sys.exit(1)
downloader.tracker.force_save()
if success:
print("\n🎤 All downloads completed successfully!")
sys.exit(0)
else:
print("\n⚠️ Some downloads may have failed. Check the logs for details.")
sys.exit(1)