259 lines
8.5 KiB
Python
259 lines
8.5 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Tracking Management Utility for Karaoke Playlist Downloader
|
|
Provides tools to manage and analyze the tracking database.
|
|
"""
|
|
|
|
import argparse
|
|
import sys
|
|
|
|
import json
|
|
from pathlib import Path
|
|
from tracking_manager import SongStatus, TrackingManager
|
|
|
|
|
|
def show_statistics(tracker):
|
|
"""Show overall statistics."""
|
|
stats = tracker.get_statistics()
|
|
|
|
print("🎤 Karaoke Downloader Statistics")
|
|
print("=" * 50)
|
|
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']}")
|
|
|
|
if stats["total_songs"] > 0:
|
|
success_rate = (stats["downloaded_songs"] / stats["total_songs"]) * 100
|
|
print(f"📈 Success Rate: {success_rate:.1f}%")
|
|
|
|
|
|
def list_playlists(tracker):
|
|
"""List all playlists with their statistics."""
|
|
playlists = tracker.data["playlists"]
|
|
|
|
if not playlists:
|
|
print("📭 No playlists found in tracking database.")
|
|
return
|
|
|
|
print("📋 Playlists in Database")
|
|
print("=" * 50)
|
|
|
|
for playlist_id, playlist in playlists.items():
|
|
print(f"\n🎵 {playlist['name']}")
|
|
print(f" ID: {playlist_id}")
|
|
print(f" URL: {playlist['url']}")
|
|
print(f" Songs: {playlist['total_songs']}")
|
|
print(f" Downloaded: {playlist['downloaded_songs']}")
|
|
print(f" Failed: {playlist['failed_songs']}")
|
|
print(f" Added: {playlist['added_date']}")
|
|
print(f" Last Synced: {playlist['last_synced'] or 'Never'}")
|
|
|
|
|
|
def show_playlist_details(tracker, playlist_id):
|
|
"""Show detailed information about a specific playlist."""
|
|
if playlist_id not in tracker.data["playlists"]:
|
|
print(f"❌ Playlist '{playlist_id}' not found in tracking database.")
|
|
return
|
|
|
|
playlist = tracker.data["playlists"][playlist_id]
|
|
songs = tracker.get_playlist_songs(playlist_id)
|
|
|
|
print(f"🎵 Playlist Details: {playlist['name']}")
|
|
print("=" * 50)
|
|
print(f"ID: {playlist_id}")
|
|
print(f"URL: {playlist['url']}")
|
|
print(f"Total Songs: {playlist['total_songs']}")
|
|
print(f"Downloaded: {playlist['downloaded_songs']}")
|
|
print(f"Failed: {playlist['failed_songs']}")
|
|
print(f"Added: {playlist['added_date']}")
|
|
print(f"Last Synced: {playlist['last_synced'] or 'Never'}")
|
|
|
|
print(f"\n📝 Songs:")
|
|
for i, song in enumerate(songs, 1):
|
|
status_icon = {
|
|
SongStatus.DOWNLOADED: "✅",
|
|
SongStatus.CONVERTED: "✅",
|
|
SongStatus.DOWNLOADING: "⏳",
|
|
SongStatus.PARTIAL: "⚠️",
|
|
SongStatus.FAILED: "❌",
|
|
SongStatus.NOT_DOWNLOADED: "⏸️",
|
|
}.get(song["status"], "❓")
|
|
|
|
formats = ", ".join(song["formats"].keys()) if song["formats"] else "None"
|
|
print(f" {i:2d}. {status_icon} {song['title']}")
|
|
print(f" Video ID: {song['video_id']}")
|
|
print(f" Status: {song['status']}")
|
|
print(f" Formats: {formats}")
|
|
if song["last_error"]:
|
|
print(f" Error: {song['last_error']}")
|
|
print()
|
|
|
|
|
|
def show_failed_songs(tracker, playlist_id=None):
|
|
"""Show all failed songs."""
|
|
if playlist_id:
|
|
songs = tracker.get_failed_songs(playlist_id)
|
|
if not songs:
|
|
print(f"✅ No failed songs found in playlist '{playlist_id}'.")
|
|
return
|
|
print(f"❌ Failed Songs in Playlist: {playlist_id}")
|
|
else:
|
|
songs = [
|
|
song
|
|
for song in tracker.data["songs"].values()
|
|
if song["status"] == SongStatus.FAILED
|
|
]
|
|
if not songs:
|
|
print("✅ No failed songs found in any playlist.")
|
|
return
|
|
print("❌ All Failed Songs")
|
|
|
|
print("=" * 50)
|
|
for song in songs:
|
|
playlist_name = tracker.data["playlists"][song["playlist_id"]]["name"]
|
|
print(f"\n🎵 {song['title']}")
|
|
print(f" Playlist: {playlist_name}")
|
|
print(f" Video ID: {song['video_id']}")
|
|
print(f" Attempts: {song['download_attempts']}")
|
|
print(f" Error: {song['last_error']}")
|
|
print(f" Last Updated: {song['last_updated']}")
|
|
|
|
|
|
def show_partial_downloads(tracker, playlist_id=None):
|
|
"""Show all partial downloads."""
|
|
if playlist_id:
|
|
songs = tracker.get_partial_downloads(playlist_id)
|
|
if not songs:
|
|
print(f"✅ No partial downloads found in playlist '{playlist_id}'.")
|
|
return
|
|
print(f"⚠️ Partial Downloads in Playlist: {playlist_id}")
|
|
else:
|
|
songs = [
|
|
song
|
|
for song in tracker.data["songs"].values()
|
|
if song["status"] == SongStatus.PARTIAL
|
|
]
|
|
if not songs:
|
|
print("✅ No partial downloads found in any playlist.")
|
|
return
|
|
print("⚠️ All Partial Downloads")
|
|
|
|
print("=" * 50)
|
|
for song in songs:
|
|
playlist_name = tracker.data["playlists"][song["playlist_id"]]["name"]
|
|
print(f"\n🎵 {song['title']}")
|
|
print(f" Playlist: {playlist_name}")
|
|
print(f" Video ID: {song['video_id']}")
|
|
print(f" Formats: {', '.join(song['formats'].keys())}")
|
|
print(f" Last Updated: {song['last_updated']}")
|
|
|
|
|
|
def cleanup_orphaned_entries(tracker, downloads_dir):
|
|
"""Clean up orphaned tracking entries."""
|
|
orphaned = tracker.cleanup_orphaned_files(downloads_dir)
|
|
|
|
if orphaned:
|
|
print(f"🧹 Cleaned up {len(orphaned)} orphaned tracking entries:")
|
|
for song_id in orphaned:
|
|
song = tracker.data["songs"].get(song_id)
|
|
if song:
|
|
print(f" - {song['title']} (ID: {song['video_id']})")
|
|
else:
|
|
print("✅ No orphaned entries found.")
|
|
|
|
|
|
def export_database(tracker, output_file):
|
|
"""Export the tracking database to a JSON file."""
|
|
try:
|
|
with open(output_file, "w", encoding="utf-8") as f:
|
|
json.dump(tracker.data, f, indent=2, ensure_ascii=False)
|
|
print(f"💾 Database exported to: {output_file}")
|
|
except Exception as e:
|
|
print(f"❌ Failed to export database: {e}")
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description="Tracking Management Utility for Karaoke Playlist Downloader",
|
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
epilog="""
|
|
Examples:
|
|
python manage_tracking.py --stats
|
|
python manage_tracking.py --list-playlists
|
|
python manage_tracking.py --playlist PLAYLIST_ID
|
|
python manage_tracking.py --failed
|
|
python manage_tracking.py --partial
|
|
python manage_tracking.py --cleanup
|
|
python manage_tracking.py --export backup.json
|
|
""",
|
|
)
|
|
|
|
parser.add_argument(
|
|
"--stats", "--statistics", action="store_true", help="Show overall statistics"
|
|
)
|
|
|
|
parser.add_argument(
|
|
"--list-playlists",
|
|
action="store_true",
|
|
help="List all playlists in the database",
|
|
)
|
|
|
|
parser.add_argument(
|
|
"--playlist",
|
|
metavar="PLAYLIST_ID",
|
|
help="Show detailed information about a specific playlist",
|
|
)
|
|
|
|
parser.add_argument("--failed", action="store_true", help="Show all failed songs")
|
|
|
|
parser.add_argument(
|
|
"--partial", action="store_true", help="Show all partial downloads"
|
|
)
|
|
|
|
parser.add_argument(
|
|
"--cleanup", action="store_true", help="Clean up orphaned tracking entries"
|
|
)
|
|
|
|
parser.add_argument(
|
|
"--export", metavar="FILE", help="Export tracking database to JSON file"
|
|
)
|
|
|
|
parser.add_argument(
|
|
"--tracking-file",
|
|
default="karaoke_tracking.json",
|
|
help="Path to tracking database file (default: karaoke_tracking.json)",
|
|
)
|
|
|
|
args = parser.parse_args()
|
|
|
|
# Initialize tracking manager
|
|
tracker = TrackingManager(args.tracking_file)
|
|
|
|
# Process commands
|
|
if args.stats:
|
|
show_statistics(tracker)
|
|
elif args.list_playlists:
|
|
list_playlists(tracker)
|
|
elif args.playlist:
|
|
show_playlist_details(tracker, args.playlist)
|
|
elif args.failed:
|
|
show_failed_songs(tracker)
|
|
elif args.partial:
|
|
show_partial_downloads(tracker)
|
|
elif args.cleanup:
|
|
downloads_dir = Path("downloads")
|
|
cleanup_orphaned_entries(tracker, downloads_dir)
|
|
elif args.export:
|
|
export_database(tracker, args.export)
|
|
else:
|
|
parser.print_help()
|
|
sys.exit(1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|