|
3 | 3 | import json |
4 | 4 | import logging |
5 | 5 | import os |
| 6 | +import re |
6 | 7 | import sys |
7 | 8 | from pathlib import Path |
8 | 9 | from typing import Any |
@@ -48,7 +49,7 @@ def validate_config(config: configparser.ConfigParser) -> bool: |
48 | 49 | missing = [k for k in required if not spotify_cfg.get(k, "").strip()] |
49 | 50 | if missing: |
50 | 51 | logger.error( |
51 | | - f"Missing or empty keys in [spotify] section: {', '.join(missing)}" |
| 52 | + f"Missing or empty keys in [spotify] section: {', '.join(missing)}", |
52 | 53 | ) |
53 | 54 | return False |
54 | 55 | redirect = spotify_cfg["redirect_uri"].strip() |
@@ -124,6 +125,13 @@ def load_config(config_path: Path) -> configparser.ConfigParser: |
124 | 125 | return config |
125 | 126 |
|
126 | 127 |
|
| 128 | +def clean_playlist_input(playlist: list[str]) -> None: |
| 129 | + """Detect playlist URLs or URIs and convert them to IDs.""" |
| 130 | + for i, p in enumerate(playlist): |
| 131 | + playlist[i] = re.sub(r"^.*playlists?/([a-zA-Z0-9]{22}).*$", r"\1", p) |
| 132 | + playlist[i] = playlist[i].replace("spotify:playlist:", "") |
| 133 | + |
| 134 | + |
127 | 135 | def init_spotify_client(cfg: configparser.ConfigParser) -> spotipy.Spotify: |
128 | 136 | """Initialize Spotify client with OAuth manager.""" |
129 | 137 | creds = cfg["spotify"] |
@@ -182,6 +190,8 @@ def __init__( |
182 | 190 | self.include_uris = include_uris |
183 | 191 | self.external_ids = external_ids |
184 | 192 | self.with_bar_flag = with_bar_flag |
| 193 | + self.exported_playlists = 0 |
| 194 | + self.exported_tracks = 0 |
185 | 195 |
|
186 | 196 | def _fetch_all_items( |
187 | 197 | self, |
@@ -412,6 +422,8 @@ def export_playlist(self, playlist: dict, output_dir: Path) -> None: |
412 | 422 | export_data.append(record) |
413 | 423 |
|
414 | 424 | write_file(filepath, export_data, self.file_format) |
| 425 | + self.exported_playlists += 1 |
| 426 | + self.exported_tracks += len(export_data) |
415 | 427 | click.echo( |
416 | 428 | f"Exported {len(export_data)} tracks from '{name}' to {filepath}", |
417 | 429 | ) |
@@ -453,7 +465,7 @@ def export_playlist(self, playlist: dict, output_dir: Path) -> None: |
453 | 465 | "-p", |
454 | 466 | "playlist", |
455 | 467 | multiple=True, |
456 | | - help="Names or IDs of playlists to export", |
| 468 | + help="Names, URLs or IDs of playlists to export", |
457 | 469 | ) |
458 | 470 | @click.option( |
459 | 471 | "--list", |
@@ -522,6 +534,9 @@ def main( |
522 | 534 | with_bar_flag=bar_flag, |
523 | 535 | ) |
524 | 536 |
|
| 537 | + playlist = list(playlist) |
| 538 | + clean_playlist_input(playlist) |
| 539 | + |
525 | 540 | fetched_playlists = exporter.get_playlists() |
526 | 541 |
|
527 | 542 | if list_only: |
@@ -588,6 +603,13 @@ def main( |
588 | 603 | for pl in targets: |
589 | 604 | exporter.export_playlist(pl, out_dir) |
590 | 605 |
|
| 606 | + if exporter.exported_playlists > 1: |
| 607 | + click.echo( |
| 608 | + f"Successfully exported {exporter.exported_tracks} tracks " |
| 609 | + f"from {exporter.exported_playlists} playlists.", |
| 610 | + ) |
| 611 | + sys.exit(0) |
| 612 | + |
591 | 613 |
|
592 | 614 | if __name__ == "__main__": |
593 | 615 | main() |
0 commit comments