pomice/pomice/playlist_manager.py

305 lines
8.9 KiB
Python

from __future__ import annotations
import json
from datetime import datetime
from pathlib import Path
from typing import Any
from typing import Dict
from typing import List
from typing import Optional
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from .objects import Track
from .queue import Queue
__all__ = ("PlaylistManager",)
class PlaylistManager:
"""Manager for exporting and importing playlists.
Allows saving queue contents to JSON files and loading them back,
useful for persistent playlists and sharing.
"""
@staticmethod
def export_queue(
queue: Queue,
filepath: str,
*,
name: Optional[str] = None,
description: Optional[str] = None,
include_metadata: bool = True,
) -> None:
"""Export a queue to a JSON file.
Parameters
----------
queue: Queue
The queue to export
filepath: str
Path to save the JSON file
name: Optional[str]
Name for the playlist. Defaults to filename.
description: Optional[str]
Description for the playlist
include_metadata: bool
Whether to include requester and timestamp metadata. Defaults to True.
"""
path = Path(filepath)
if name is None:
name = path.stem
tracks_data = []
for track in queue:
track_dict = {
"title": track.title,
"author": track.author,
"uri": track.uri,
"identifier": track.identifier,
"length": track.length,
"is_stream": track.is_stream,
}
if include_metadata:
track_dict["requester_id"] = track.requester.id if track.requester else None
track_dict["requester_name"] = str(track.requester) if track.requester else None
track_dict["timestamp"] = track.timestamp
if track.thumbnail:
track_dict["thumbnail"] = track.thumbnail
if track.isrc:
track_dict["isrc"] = track.isrc
if track.playlist:
track_dict["playlist_name"] = track.playlist.name
tracks_data.append(track_dict)
playlist_data = {
"name": name,
"description": description,
"created_at": datetime.utcnow().isoformat(),
"track_count": len(tracks_data),
"total_duration": sum(t["length"] for t in tracks_data),
"tracks": tracks_data,
"version": "1.0",
}
path.parent.mkdir(parents=True, exist_ok=True)
with open(path, "w", encoding="utf-8") as f:
json.dump(playlist_data, f, indent=2, ensure_ascii=False)
@staticmethod
def import_playlist(filepath: str) -> Dict[str, Any]:
"""Import a playlist from a JSON file.
Parameters
----------
filepath: str
Path to the JSON file
Returns
-------
Dict[str, Any]
Dictionary containing playlist data:
- 'name': Playlist name
- 'description': Playlist description
- 'tracks': List of track data dictionaries
- 'track_count': Number of tracks
- 'total_duration': Total duration in milliseconds
- 'created_at': Creation timestamp
"""
path = Path(filepath)
with open(path, encoding="utf-8") as f:
data = json.load(f)
return data
@staticmethod
def export_track_list(
tracks: List[Track],
filepath: str,
*,
name: Optional[str] = None,
description: Optional[str] = None,
) -> None:
"""Export a list of tracks to a JSON file.
Parameters
----------
tracks: List[Track]
List of tracks to export
filepath: str
Path to save the JSON file
name: Optional[str]
Name for the playlist
description: Optional[str]
Description for the playlist
"""
path = Path(filepath)
if name is None:
name = path.stem
tracks_data = [
{
"title": track.title,
"author": track.author,
"uri": track.uri,
"identifier": track.identifier,
"length": track.length,
"thumbnail": track.thumbnail,
"isrc": track.isrc,
}
for track in tracks
]
playlist_data = {
"name": name,
"description": description,
"created_at": datetime.utcnow().isoformat(),
"track_count": len(tracks_data),
"total_duration": sum(t["length"] for t in tracks_data),
"tracks": tracks_data,
"version": "1.0",
}
path.parent.mkdir(parents=True, exist_ok=True)
with open(path, "w", encoding="utf-8") as f:
json.dump(playlist_data, f, indent=2, ensure_ascii=False)
@staticmethod
def get_track_uris(filepath: str) -> List[str]:
"""Get list of track URIs from a saved playlist.
Parameters
----------
filepath: str
Path to the JSON file
Returns
-------
List[str]
List of track URIs
"""
data = PlaylistManager.import_playlist(filepath)
return [track["uri"] for track in data["tracks"] if track.get("uri")]
@staticmethod
def merge_playlists(
filepaths: List[str],
output_path: str,
*,
name: Optional[str] = None,
description: Optional[str] = None,
remove_duplicates: bool = True,
) -> None:
"""Merge multiple playlists into one.
Parameters
----------
filepaths: List[str]
List of playlist file paths to merge
output_path: str
Path to save the merged playlist
name: Optional[str]
Name for the merged playlist
description: Optional[str]
Description for the merged playlist
remove_duplicates: bool
Whether to remove duplicate tracks (by URI). Defaults to True.
"""
all_tracks = []
seen_uris = set()
for filepath in filepaths:
data = PlaylistManager.import_playlist(filepath)
for track in data["tracks"]:
uri = track.get("uri", "")
if remove_duplicates:
if uri and uri in seen_uris:
continue
if uri:
seen_uris.add(uri)
all_tracks.append(track)
merged_data = {
"name": name or "Merged Playlist",
"description": description or f"Merged from {len(filepaths)} playlists",
"created_at": datetime.utcnow().isoformat(),
"track_count": len(all_tracks),
"total_duration": sum(t["length"] for t in all_tracks),
"tracks": all_tracks,
"version": "1.0",
}
output = Path(output_path)
output.parent.mkdir(parents=True, exist_ok=True)
with open(output, "w", encoding="utf-8") as f:
json.dump(merged_data, f, indent=2, ensure_ascii=False)
@staticmethod
def export_to_m3u(
tracks: List[Track],
filepath: str,
*,
name: Optional[str] = None,
) -> None:
"""Export tracks to M3U playlist format.
Parameters
----------
tracks: List[Track]
List of tracks to export
filepath: str
Path to save the M3U file
name: Optional[str]
Playlist name for the header
"""
path = Path(filepath)
path.parent.mkdir(parents=True, exist_ok=True)
with open(path, "w", encoding="utf-8") as f:
f.write("#EXTM3U\n")
if name:
f.write(f"#PLAYLIST:{name}\n")
for track in tracks:
# Duration in seconds
duration = track.length // 1000
f.write(f"#EXTINF:{duration},{track.author} - {track.title}\n")
f.write(f"{track.uri}\n")
@staticmethod
def get_playlist_info(filepath: str) -> Dict[str, Any]:
"""Get basic information about a saved playlist without loading all tracks.
Parameters
----------
filepath: str
Path to the JSON file
Returns
-------
Dict[str, Any]
Dictionary with playlist metadata (name, track_count, duration, etc.)
"""
data = PlaylistManager.import_playlist(filepath)
return {
"name": data.get("name"),
"description": data.get("description"),
"track_count": data.get("track_count"),
"total_duration": data.get("total_duration"),
"created_at": data.get("created_at"),
"version": data.get("version"),
}