Spotify playlist tracks

I've never really understood why Spotify doesn't allow me to export my playlists into text file... I guess there is a reason (I bet they have a good one), but I just don't see it.

Anyway, most of the times it's quicker to try to fix the problem yourself than waiting for other to fix it...

Since Spotify has a nice API (with no auth needed) for getting tracks information available, I created a small script that would iterate over the songs in my playlist, and printout the information (Artist - Album - Song), and I though it might be usefull for someone else.

First of all, get the list of Spotify URIs from all the songs in your play list:

  • Navigate to your playlist in Spotify, and select all the songs (CRL-A).
  • Click "Copy Spotify URI" from the context menu.
  • Paste in a new file ie: "myplaylist.txt"

The file should look like this one:

$ cat myplaylist.txt
spotify:track:1OGFtaUgHAQjtSk7mhDwr9
spotify:track:39J10NL0mFTAdJbapoo2rC
spotify:track:3G0EKJZy0j3rMG077UawaC
spotify:track:5VdVaUBgj7cBTKplgaIhKu
spotify:track:2EBuFjexd3S3wc9m4Rerh8
...

The idea is to call Spotify API for each track id (spotify:track:<track id>) in the myplaylist.txt file, get the json response and parse the artist,album and song information... As simple as that.

See below the script:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#!/usr/bin/env python
# Getting track details from a list of Spotify URLs via spotify API
# Input file containing the list of URLs mandatory
# Date: 26 Nov 2015

import json, urllib2, sys
from time import sleep

#Checking input file
if len(sys.argv) != 2:
    print("Error: Input file mandatory")
    sys.exit(0)
else:
    input_file = sys.argv[1]

#Spotify track API
api_url = "https://api.spotify.com/v1/tracks/"

#track dictionary
mytracks = []

#Open input file
try:
    fd = open(input_file,"r")
except Exception as e:
    print("Error opening %s" % input_file)
    print(e)
    sys.exit(0)

#getting track details from uri
def get_track_data(url):

    request = urllib2.Request(api_url + url)
    request.add_header('User-Agent', 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)')
    request.add_header('Content-Type','application/json')

    try:
        response = urllib2.urlopen(request)
    except Exception as e:
        print("\nError connecting to spotify API looking details for uri %s" % url)
        return False

    try:
        data = json.load(response)
    except Exception as e:
        print("\nError handling json object looking details for uri %s" % url)
        return False

    try:
        mytracks.append({'album':str(data['album']['name']),'artist':str(data['artists'][0]['name']),'track':str(data['name'])})
        sys.stdout.write(".")
        sys.stdout.flush()
    except Exception as e:
        print("Error getting details for uri %s" % url)
        return False

########
# MAIN #
########

sys.stdout.write("Iterating through tracks in " + input_file)
sys.stdout.flush()

#Calling to get_track_data for each uri in the input file
for track in fd:
    get_track_data(track.split(":")[2])
    #I don't need to DDoS spotify servers :)
    sleep(2)
print("done!")

#Printing the header when the dict has been populated
print("\nArtist - Album - Track")
print("----------------------")

#Printing the results. By default, ordered by 'artist', but it can be ordered by 'album' or 'track'
for track in sorted(mytracks,key=lambda k: k['artist']):
    print("%s - %s - %s" % (track['artist'],track['album'],track['track']))

#close input file
fd.close()

A couple of things:

1) By default, the script is ordering the result by "artist", but it can be easily changed to "album" or "track" in this line:

for track in sorted(mytracks,key=lambda k: k['artist']):

--> for track in sorted(mytracks,key=lambda k: k['album']):

--> for track in sorted(mytracks,key=lambda k: k['track']):

2) I have decided to take it easy with Spotify servers, so I added sleep 2 between each query.

3) No concurrency, multi-threading, etc... I don't plan to run the script on regular basis, so no need to spend more time on this.

Execution example:

$ python spotify_playlist.py myplaylist.txt
Iterating through tracks in myplaylist.txt......done!

Artist - Album - Track
----------------------
Adema - Adema - Giving In
Alice Cooper - The Best Of Alice Cooper - Poison
Alice In Chains - Facelift - Man in the Box
Alice In Chains - Jar Of Flies - Rotten Apple
Alice In Chains - Jar Of Flies - Nutshell
Alien Ant Farm - Anthology - Smooth Criminal

I know, I know... Only great hits in my playlist :)

Does knowing all the idioms/built-in functions make you a better coder?

(Disclaimer: I do not code for a living...)

I like coding in Python, it is my go-to language for almost everything nowadays. Easy to learn, pretty popular and fast enough, so no complains, but sometimes python idioms and built-in function/methods drive me nuts…

I understand you cannot be a good python developer if you don’t know your language, but unless you write python code 24/7, I think we shouldn’t focus on writing 100% pythonic code…. It is not worth it.

Let me put a very basic example. Not sure if you know about checkio, a game/community/platform for learning (or improving) python (100% recommended by the way), where you have hundreds of “missions” (tasks to be solved by using Python), and you can share your solutions with the rest of the community… This is the most interesting part by far, because you can compare your code, and (in my case) realize how bad it is :)

For example, one of the missions asks you to write the code for transposing any given matrix, which could be easily achieved in 7 lines of code if you understand the concept:

Python:

matrix =[[1,2,3], [4,5,6], [7,8,9]]
transpose = []
for j in range(len(matrix[0])):
    row=[]
    for i in matrix:
        row.append(i[j])
    transpose.append(row)

Yes, the code is simple, not optimal, and can be improved a lot, but it can be translated to C in 1 minute:

C:

int j = 3;
int k = 3;
int c, d;
int matrix[3][3]={{1,2,3},{4,5,6},{7,8,9}};
int transpose[3][3];

for (c = 0; c < j; c++)
  for( d = 0 ; d < k ; d++ )
     transpose[d][c] = matrix[c][d];

On the other hand, if I were a python developer, I could come up with something like this:

Python using built-in functions:

matrix =[[1,2,3], [4,5,6], [7,8,9]]
transpose = list(map(list, zip(*matrix)))

(solution by PositronicLlama)

Ok, I love one-liners as much as the next guy and I agree that is a great Pythonic answer… But it makes me wonder:

  • Is that code easy to read and understand? I don’t think anyone with basic/average python skills can understand it.
  • When performance is not an issue, why not writing a small piece of code instead of relying in a function you don't even know how works? I am not a python developer (truth being told, I am not even a developer), so for me it is more interesting to understand how to solve a problem than solving it… Because I can use the same approach in any other language if needed.
  • Are we better developers if we use list/map/zip in the same line? Probably the short answer is yes. I think there is no such thing as “generalist developer”, most of people know a lot about just one language, and if you are really (really) good, you could know 2 or maybe 3 , but that is not that common… So yes, if you want to be a “pr0” python programmer, you would need to know all these python idioms, built-in functions and modules that makes Python so great.

For the “n00b”/average programmers as myself, yeah, they are cool, save a few lines and are damn fast so I use some of them, but you know, it is not that important.

Quick Thought III - Mobiles at home

{% img center /img/DSC_1869-001.jpg 'mobiles' %}

Those are the mobiles at my place at the moment (only half of them are mine), all of them are in "working" condition, but 90% of them are basically useless... I keep all the old Nokia phones just as a piece of history.

A few things come to mind:

  • No way we can sustain this level of electronic wasting. And recycling just means dumping this e-waste someplace in Africa
  • The smarter the cellphone, the dumber the people.
  • Blackberry had the best keyboard ever, until big screens came out and swiftkey took the throne.
  • I don't need a faster/bigger phone. I need a longer battery life!
  • What the hell happened to you Nokia??

As God is my witness (thanks for the quote Scarlett), I'll try not to buy any other smartphone as long as my OnePlus One does the job

UPDATE Aug 25th 2017 I kept my word!!! Sort of :)

I haven't bought a new phone, but I am using someone else's iphone 6... Reusing is good!

Quick Thought II - Babel 2.0

Wouldn't it be easier if we could agree on using just one (ok, let's say two, Apple still think they are different than the rest) instant messenger application???

{% img center /img/Screenshot_2015-10-09-08-44-58.png 'babel' %}

Embedding modules in Python scripts

I am a huge fan of pexpect. For those of you who don't know it, it is a module "based" on the same idea as Expect but in Python. As its author said:

I loved Expect, but I hated TCL, so I wrote this 100% pure Python module that does the same thing

Noah Spurrier

I think it's a great tool for ssh automatization whenever priv/pub keys are not an option.

In my case, since I usually work in isolated systems where I cannot even install local python mudules, and given that I am using just a small part of all the funcionalities provided by this module, I came up with the idea of embedding an old version of pexpect module into my scripts, so I don't have to worry about SCP'ing several files each time I need to run any of them.

The idea is to embed the base64 code of the pexpect.py module into the script, and then create the module on the fly whenever the script is executed (if it is not there already).

Simple stuff.... This is the procedure for Python 2.x:

1) Get the base64 code of the module:

LOCAL $ gzip -c pexpect.py | base64
H4sICFG7GFUAA2luY19wZXhwZWN0LnB5AKxbe3PbNrbX58C43RG8laWnXQ7veud7F3FlhNtbdkj
[...]
8Ys0SwQW+DuQhDqXlYSw8fuPPtrNbsrDtjn65LC5Nf8xR4nZvEa2O5y1R0uUHA4/+n+riV9boSgB
AA==
$

2) Get MD5 checksum:

LOCAL $ md5sum pexpect.py
1d9643479e2bf16939fcdf007f4bf9f9 *pexpect.py

3) Add the necessary modules to the script:

import sys
from hashlib import md5 
from cStringIO import StringIO    
from base64 import b64decode 
from gzip import GzipFile

4) Import the module, or create it if it doesn't exist:

try:      
    import pexpect
except ImportError:  
    #Copy and paste the base64 code from step 1
    pexpect_mod = """   
    H4sICFG7GFUAA2luY19wZXhwZWN0LnB5AKxbe3PbNrbX58C43RG8laWnXQ7veud7F3FlhNtbdkj  
    [...]
    8Ys0SwQW+DuQhDqXlYSw8fuPPtrNbsrDtjn65LC5Nf8xR4nZvEa2O5y1R0uUHA4/+n+riV9boSgB    
    AA==   
    """

    #MD5 sum from step 2
    pexpect_mod_md5 = "1d9643479e2bf16939fcdf007f4bf9f9"

    #Decode the module stored in pexpect_mod and load it in a variable
    with GzipFile(mode='r', fileobj=StringIO(b64decode(pexpect_mod))) as pexpect_mod_fd:
        pexpect_mod_data = pexpect_mod_fd.read()

    #Dump the variable into a file
    with open("pexpect.py","w+b") as pexpect_fd:
        pexpect_fd.write(pexpect_mod_data)

    #Double-check pexpect.py is the same file you have in your local machine    
    with open("pexpect.py", 'rb') as pexpect_fd:
        if pexpect_mod_md5 == md5(pexpect_fd.read()).hexdigest():
            #Import the module
            import pexpect
        else:
            #Exit if the file is not identical 
            print("Error creating pexpect.py module. MD5 checksum incorrect. Exiting")
            sys.exit(-1)

And voila, just you run your script in your remote machine, and there you have the module in your current directory:

REMOTE $ ls -1 pexpect.py*
pexpect.py*
pexpect.pyc*

Needless to say you could follow this same approach for any other file you would need to "attach" to your scripts...

Have fun!