Skip to content

Commit 4fe1870

Browse files
committed
csv and json can now be exported at the same time (with the same
command)
1 parent a769974 commit 4fe1870

2 files changed

Lines changed: 36 additions & 23 deletions

File tree

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ Options:
5656
./config.cfg next to this script).
5757
-o, --output PATH Directory to save exported files (default is
5858
./playlists).
59-
-f, --format [csv|json] Output file format (defaults to 'csv').
59+
-f, --format [csv|json] Output file format (defaults to 'csv');
60+
repeatable.
6061
--uris Include album and artist URIs.
6162
--external-ids Include track ISRC and album UPC.
6263
--no-bar Hide progress bar.
@@ -91,8 +92,8 @@ python exportify-cli.py -p COCHE -f json --reverse
9192
# Export playlist whose ID is "2VqAIceMCzBRhzq6zVmDZw" to current directory, sorted by Added At
9293
exportify-cli.exe -p 2VqAIceMCzBRhzq6zVmDZw --output . --sort-key "Added At"
9394
94-
# Export playlist with its URL
95-
exportify-cli.exe -p https://open.spotify.com/playlist/2VqAIceMCzBRhzq6zVmDZw?si=16df8ae16c2d492b
95+
# Export playlist with its URL to both JSON and CSV
96+
exportify-cli.exe -f json -f csv -p https://open.spotify.com/playlist/2VqAIceMCzBRhzq6zVmDZw?si=16df8ae16c2d492b
9697
9798
# Export playlists "Instrumental" and "COCHE" to CSV without progress bar, sorted by Popularity
9899
python exportify-cli.py -p instr -p COCHE -f csv --no-bar --sort-key "popularity"

exportify-cli.py

Lines changed: 32 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -153,31 +153,33 @@ def init_spotify_client(cfg: configparser.ConfigParser) -> spotipy.Spotify:
153153
return spotipy.Spotify(auth_manager=auth, retries=10)
154154

155155

156-
def sanitize_filename(name: str, ext: str) -> str:
156+
def sanitize_filename(name: str) -> str:
157157
"""Convert a playlist name into a safe filename."""
158158
safe = "".join(c if c.isalnum() or c in (" ", "_", "-") else "_" for c in name)
159-
return f"{safe.strip().replace(' ', '_').lower()}.{ext}"
159+
return f"{safe.strip().replace(' ', '_').lower()}"
160160

161161

162-
def write_file(file_path: Path, data: list[dict], file_format: str = "csv") -> None:
162+
def write_file(file_path: Path, data: list[dict], file_formats) -> None:
163163
"""Write list of dicts to file."""
164164
if not data:
165165
logger.warning("No data to write; skipping file.")
166166
return
167167

168-
if file_format == "csv":
168+
if "csv" in file_formats:
169169
headers = list(data[0].keys())
170-
with file_path.open("w", newline="", encoding="utf-8") as csvfile:
170+
with file_path.with_suffix(".csv").open(
171+
"w", newline="", encoding="utf-8"
172+
) as csvfile:
171173
writer = csv.DictWriter(csvfile, fieldnames=headers)
172174
writer.writeheader()
173175
for row in data:
174176
writer.writerow(row)
177+
logger.info(f"Exported to {file_path}.csv")
175178

176-
elif file_format == "json":
177-
with file_path.open("w", encoding="utf-8") as jsonfile:
179+
if "json" in file_formats:
180+
with file_path.with_suffix(".json").open("w", encoding="utf-8") as jsonfile:
178181
json.dump(data, jsonfile, ensure_ascii=False, indent=4)
179-
180-
logger.info(f"Exported to {file_path}")
182+
logger.info(f"Exported to {file_path}.json")
181183

182184

183185
class SpotifyExporter:
@@ -186,7 +188,7 @@ class SpotifyExporter:
186188
def __init__(
187189
self,
188190
spotify_client: spotipy.Spotify,
189-
file_format: str,
191+
file_formats: list[str],
190192
include_uris: bool,
191193
external_ids: bool,
192194
with_bar: bool,
@@ -195,7 +197,7 @@ def __init__(
195197
) -> None:
196198
"""Initialize the exporter with a Spotify client."""
197199
self.spotify = spotify_client
198-
self.file_format = file_format
200+
self.file_formats = file_formats
199201
self.include_uris = include_uris
200202
self.external_ids = external_ids
201203
self.with_bar = with_bar
@@ -345,7 +347,7 @@ def export_playlist(self, playlist: dict, output_dir: Path) -> None:
345347
"""Export a single playlist to CSV file."""
346348
name, pid = playlist["name"], playlist["id"]
347349
output_dir.mkdir(parents=True, exist_ok=True)
348-
filepath = output_dir / sanitize_filename(name, self.file_format)
350+
filepath = output_dir / sanitize_filename(name)
349351

350352
# Format description for progress bar
351353
desc = (
@@ -463,12 +465,13 @@ def export_playlist(self, playlist: dict, output_dir: Path) -> None:
463465
record.pop("Track ISRC", None)
464466
record.pop("Album UPC", None)
465467

466-
write_file(filepath, export_data, self.file_format)
468+
write_file(filepath, export_data, self.file_formats)
467469
self.exported_playlists += 1
468470
self.exported_tracks += len(export_data)
469-
click.echo(
470-
f"Exported {len(export_data)} tracks from '{name}' to {filepath}",
471-
)
471+
for ext in self.file_formats:
472+
click.echo(
473+
f"Exported {len(export_data)} tracks from '{name}' to {filepath}.{ext}",
474+
)
472475

473476

474477
# Custom command class to override usage line
@@ -518,9 +521,10 @@ def format_usage(self, ctx, formatter) -> None:
518521
"-f",
519522
"--format",
520523
"format_param",
521-
type=click.Choice(["csv", "json"]),
524+
multiple=True,
522525
default=None,
523-
help="Output file format (defaults to 'csv').",
526+
type=click.Choice(["csv", "json"]),
527+
help="Output file format (defaults to 'csv'); repeatable.",
524528
)
525529
@optgroup.option(
526530
"--uris",
@@ -592,7 +596,15 @@ def main(
592596
cfg = load_config(cfg_path)
593597

594598
# Resolve config vs CLI
595-
file_format = format_param if format_param else cfg.get("exportify-cli", "format")
599+
file_formats = format_param if format_param else None
600+
if file_formats is None:
601+
file_formats = []
602+
if "csv" in cfg.get("exportify-cli", "format"):
603+
file_formats.append("csv")
604+
if "json" in cfg.get("exportify-cli", "format"):
605+
file_formats.append("json")
606+
file_formats = list(set(file_formats))
607+
596608
output = output_param if output_param else cfg.get("exportify-cli", "output")
597609
include_uris = (
598610
uris_flag if uris_flag is not None else cfg.getboolean("exportify-cli", "uris")
@@ -657,7 +669,7 @@ def main(
657669

658670
exporter = SpotifyExporter(
659671
spotify_client=client,
660-
file_format=file_format,
672+
file_formats=file_formats,
661673
include_uris=include_uris,
662674
external_ids=external_ids,
663675
with_bar=with_bar,

0 commit comments

Comments
 (0)