spot2txt

Export Spotify Playlist Listings as Plaintext
git clone git://git.alexkarle.com.com/spot2txt
Log | Files | Refs | README | LICENSE

cli.pl (2671B) [raw]


      1 #!/usr/bin/env perl
      2 use strict;
      3 use warnings;
      4 
      5 # utf8 shenanis -- files written and stdout both utf8
      6 use utf8;
      7 use open qw(:std :encoding(UTF-8));
      8 
      9 use LWP::UserAgent;
     10 use JSON::PP;
     11 use Data::Dumper;
     12 
     13 if (!$ENV{SPOTIFY_TOKEN}) {
     14 	die "SPOTIFY_TOKEN not set in environment\n";
     15 }
     16 
     17 if (!@ARGV) {
     18 	die "usage: $0 username\n";
     19 }
     20 
     21 my $user = $ARGV[0];
     22 my $API = "https://api.spotify.com/v1";
     23 my $ua = LWP::UserAgent->new();
     24 $ua->default_header("Authorization" => "Bearer $ENV{SPOTIFY_TOKEN}");
     25 
     26 sub get_playlists {
     27 	my $limit = shift;
     28 	$limit //= 10000;
     29 	my @playlists;
     30 	my $url = "$API/users/$user/playlists";
     31 	while (1) {
     32 		my $res = $ua->get($url);
     33 		if (!$res->is_success) {
     34 			print(Dumper($res));
     35 			my $err = $res->decoded_content;
     36 			die "Failed to fetch playlists: $err";
     37 		}
     38 		my $j = decode_json($res->decoded_content);
     39 		foreach my $item (@{$j->{items}}) {
     40 			push(@playlists, $item);
     41 		}
     42 		if (!$j->{next} || scalar @playlists > $limit) {
     43 			last;
     44 		}
     45 		print("Paging Playlists: $url\n");
     46 		$url = $j->{next};
     47 	}
     48 	return @playlists;
     49 }
     50 
     51 sub describe {
     52 	my $p = shift;
     53 	my $desc = $p->{description} ? "\"$p->{description}\"\n\n" : "";
     54 	my $s = <<EOM;
     55 # $p->{name}
     56 
     57 ${desc}By $p->{owner}->{display_name}
     58 
     59 EOM
     60 	my $url = "$API/playlists/$p->{id}?fields=tracks.next,tracks.items(added_at,track.name,track.artists(name))";
     61 	if ($p->{id} eq "LIBRARY") {
     62 		$url = "$API/me/tracks";
     63 	}
     64 	my $i = 1;
     65 	while (1) {
     66 		my $res = $ua->get($url);
     67 		if (!$res->is_success) {
     68 			print(Dumper($res));
     69 			my $err = $res->decoded_content;
     70 			die "Failed to fetch playlist: $err";
     71 		}
     72 		# NOTE: after paging, the shape changes..
     73 		my $j = decode_json($res->decoded_content);
     74 		my $tracks = $j->{tracks} || $j;
     75 		my $n = scalar @{$tracks->{items}};
     76 		print("$n Tracks\n");
     77 		foreach my $t (@{$tracks->{items}}) {
     78 			(my $d = $t->{added_at}) =~ s/T.*Z$//;
     79 			my $fart = @{$t->{track}->{artists}}[0]->{name};
     80 			$s .= sprintf(
     81 				"[%s] %-4d \"%s\" by %s\n",
     82 				$d, $i, $t->{track}->{name}, $fart,
     83 			);
     84 			$i += 1;
     85 		}
     86 		if (!$tracks->{next}) {
     87 			last;
     88 		}
     89 		# HACK: the fields filter breaks in the next URL,
     90 		# so we have to strip the tracks. prefix
     91 		($url = $tracks->{next}) =~ s/tracks\.//g;
     92 	}
     93 	return $s;
     94 }
     95 
     96 sub writep {
     97 	my $p = shift;
     98 	my $desc = describe($p);
     99 	(my $fname = $p->{name}) =~ s/\s/_/g;
    100 	$fname =~ s/\W//g;
    101 	open(my $fh, ">", "out/$fname.txt") or die $!;
    102 	print $fh $desc;
    103 	close $fh;
    104 }
    105 
    106 my %library = (
    107 	id => "LIBRARY",
    108 	name => "My Saved Songs",
    109 	owner => {
    110 		display_name => $user
    111 	}
    112 );
    113 
    114 mkdir "out" or die $! unless -e "out";
    115 
    116 my @playlists = get_playlists();
    117 foreach my $p (\%library, @playlists) {
    118 	print("Processing $p->{name}\n");
    119 	writep($p);
    120 }
    121 
    122 
    123 exit 0;