Fuego

Entries from June 2006

iTunes and Python: Creating Custom Playlists: UPDATE

6.28.06 · Leave a Comment

All right, the other day I posted a tutorial on manipulating the iTunes COM with Python to give you better searching capabilities than iTunes does natively, and how to make a playlist with the resulting tracks. Since then, I’ve managed to change the code a bit because there were a couple of places where it wasn’t working correctly before (eg including duplicate tracks) and some features I felt were missing. So let’s see the changes, shall we?

def union(l1, l2):
    pl1 = [i.GetITObjectIDs() for i in l1]
    pl2 = [i.GetITObjectIDs() for i in l2]
    pl3 = sorted(list(set(pl1+pl2)),
                 key=lambda x: list(pl1+pl2).index(x))
    return [list(l1+l2)[i] for i in
            [list(pl1+pl2).index(x) for x in pl3]]

def andnot(l1, l2):
    pl1 = [i.GetITObjectIDs() for i in l1]
    pl2 = [i.GetITObjectIDs() for i in l2]
    pl3 = [i for i in pl1 if i not in pl2]
    pl3 = sorted(list(set(pl3)),
                 key=lambda x: pl3.index(x))
    return [l1[i] for i in [pl1.index(x) for x in pl3]]

def intersect(l1, l2):
    pl1 = [i.GetITObjectIDs() for i in l1]
    pl2 = [i.GetITObjectIDs() for i in l2]
    pl3 = [i for i in pl1 if i in pl2]
    pl3 = sorted(list(set(pl3)),
                 key=lambda x: pl3.index(x))
    return [l1[i] for i in [pl1.index(x) for x in pl3]]

def difference(l1, l2):
    pl1 = [i.GetITObjectIDs() for i in l1]
    pl2 = [i.GetITObjectIDs() for i in l2]
    pl3 = [[i for i in pl1 if i not in pl2],
           [i for i in pl2 if i not in pl1]]
    pl3 = [sorted(list(set(pl3[0])),
                  key=lambda x: pl3[0].index(x)),
           sorted(list(set(pl3[1])),
                  key=lambda x: pl3[1].index(x))]
    return [l1[i] for i in
            [pl1.index(x) for x in pl3[0]]] +
           [l2[i] for i in
            [pl2.index(x) for x in pl3[1]]]

Okay, before I talked about how with “OR”, all you had to do was add the two lists together, but that sometimes means duplicates, so I wrote a union function to fix that. Let’s take a look. Like the other functions, it takes in two lists/IITTrackCollections and makes parallel lists containing the IDs of the tracks, this time. Next, it makes a set of the two lists together. However, since sets are unordered, when making pl3 a list again, it sorts it by the index of each track in the combined list, returning it to the correct order. Then it performs the same last step as the others, returning a list which contains the corresponding tracks to the IDs in pl3.

The only change to intersect and difference is a similar fix to pl3. In addition to the previous construction of pl3, I’ve added the same sorting of the list resulting of the set made from pl3.

I also added an andnot function to allow the “not” operator – ie find tracks that match one query, but not another.

Also, as you can see, the duplicates are removed via track ID instead of name, now, so that multiple songs with the same name by different artist, for example, aren’t weeded out.

In addition, I changed custompl to allow for searching not limited to the default searches. For example, you can now search within the year of a track, or its comment, or even the genre or date added, assuming you know the correct format (the field for date added, for example, is “DateAdded” – all the possible fields can be found in the help file for the iTunes COM).

def extend(alist, element):
    alist.extend(element)
    return alist

def custompl(quer1, logic, quer2):
    res = []
    temp = [quer1,quer2]
    if len(quer1) == 3:
        if len(quer2) == 3:
            return dic[logic](custompl(*quer1),
                              custompl(*quer2))
        res.append(custompl(*quer1))
        temp.remove(quer1)
    elif len(quer2) == 3:
        res.append(custompl(*quer2))
        temp.remove(quer2)
    for query in temp:
        srchstr, field = query
        if field == "Playlist":
            res = res+[reduce(extend,extend(res[0:0],
                      [list(pl.Tracks)
                          for pl in source.Playlists
                              if srchstr in pl.Name]))]
        elif field in dic:
            res.append(list(library.Search(srchstr,
                dic[field])))
        else:
            try:
                res.append([track
                    for track in library.Tracks
                        if srchstr in eval(
                            "track.%s"%field)])
            except TypeError:
                res.append([track
                    for track in library.Tracks
                        if srchstr in repr(
                            eval("track.%s"%field))])
            except AttributeError:
                __import__("sys").stderr.write("ValueError"+
                    ": Sorry, can't find %s field to search
                    "in.\n"%field)
    return dic[logic](res[0],res[1])

As you can see, instead of just checking for “Playlist” as a field, custompl now also checks if the field is in the dictionary, and if it’s not, tries to find it by accessing it with eval. AGAIN, THIS SHOULD NOT BE ATTEMPTED UNLESS YOU TRUST YOUR END USER. For example, by making the following function call, “bad” gets written to your stdout; and with a few modifications, all sorts of havoc can be wreaked: customplaylist((" ", '__setattr__("Name",__import__("sys").stdout.write("bad"))' )) (Note that this doesn’t actually change the name of any tracks, but rather raises an error after writing to the stdout.) Alternatively, you could make a dictionary with all the possible fields to search in and check if the field is in that dictionary and work through it that way to avoid eval.

In any case, first we try to see if the search string is in the field, then, if the field isn’t a string (eg the DateAdded field, which is a time instance), we see if the search string is in Python’s representation of the field ('<PyTime:4/17/2006 12:05:32 AM>' for a sample DateAdded, for example). At some point I’d like to see if I can make it possible to specify whether you want exact pattern matching for each query, ie if srchstr == eval(...) or if srchstr == repr(eval(...)). Anyway, if the field isn’t available, we write the error code to stderr without stopping the program.

You’ll notice that I changed the treatment of playlists a bit. This is because the previous method only kept the tracks in the first playlist found. This way, all the tracks from all the matching playlists are added together, then that list is added to res. The way I acheived this is by taking an empty list (res[0:0]), and using reduce to extend it by the list of tracks from each matching playlist. Then, I added the current res to it, making sure we didn’t lose the tracks already in it. Since we add [reduce(...)], it keeps all the tracks from this query in a separate list, like it should. The way reduce works is to apply the first parameter (a function) to the elements of the second one in twos. For example, reduce(operator.add,range(10)) is the equivalent of (((((((((0+1)+2)+3)+4)+5)+6)+7)+8)+9). So our code is the equivalent of extend(extend(extend(res[0:0],tracklist1),
tracklist2),tracklist3)
etc. So we contain that in a list ([reduce(...)]) and add res to it to get the new res.

I also modified the main function to allow for single query searches:

def customplaylist(querylist,title=None):
    pls = []
    if type(querylist) is str:
        querylist = (eval(
            querylist.replace(" XOR ",", 'X',"
                     ).replace(" AND ",", '+',"
                     ).replace(" OR ",", '|',"
                     ).replace('" in "',"', '"
                     ).replace('"',"'"
                     ).replace(" AND NOT ",", '+!',")))
    if len(querylist) != 1:
        querylist = [querylist]
    try:
        pls.extend(custompl(*querylist[0]))
    except TypeError:
        res = []
        srchstr, field = querylist[0]
        if field == "Playlist":
            res.extend(reduce(extend,
                extend(res[0:0],
                    [list(pl.Tracks)
                        for pl in source.Playlists
                            if srchstr in pl.Name])))
        elif field in dic:
            res.extend(list(library.Search(srchstr,
                dic[field])))
        else:
            try:
                res.extend([track
                    for track in library.Tracks
                        if srchstr in repr(
                            eval("track.%s"%field)
                                           )])
            except TypeError:
                res.extend([track
                    for track in library.Tracks
                        if srchstr == repr(
                            eval("track.%s"%field)
                                           )])
            except AttributeError:
               __import__("sys").stderr.write("ValueError"+
               ": Sorry, can't find %s field to search in."+
               "\n"%field)
        pls.extend(res)
    plname = str(querylist).replace(", 'X',"," XOR "
                          ).replace(", '+',"," AND "
                          ).replace(", '|',"," OR "
                          ).replace("', '","' in '"
                          ).replace("[",""
                          ).replace("]",""
                          ).replace(", '+!',"," AND NOT")
    cpl = win32com.client.CastTo(
              iTunes.CreatePlaylist(title or plname),
              'IITUserPlaylist')
    for track in pls:
        cpl.AddTrack(track)

We first try to send querylist to custompl, but if there’s only one query, it’ll raise a TypeError, which we catch; and then we proceed to copy the code from custompl, slightly modified. The only difference is that, since there’s only one query, we don’t need the crap at the beginning of custompl, nor do we need as much fancy footwork to make res have two elements, one from each side. We just extend res by all the tracks found.

Finally, we just need to update dic, and we’re on our way.

dic = {"All":0,"Visible":1,"Artist":2,"Album":3,
       "Composer":4,"Name":5,"+":intersect,
       "|":union,"X":difference,"+!":andnot}

All right. Head over here to get my updated full code.

Categories: Music · Programming · Python · Tutorial

A Couple of Firefox Extensions

6.28.06 · Leave a Comment

A short look at a few of the extensions I use for Firefox, in order of when I got them.

DOM Inspector – Actually comes bundled with Firefox, if you select “Developer Tools” on install. Basically, with this extension you can look at the DOM of most webpages, which can definitely be convenient. Can’t say I use it that often, but if you’re looking at an amazing site and you have no idea how it was set up, one menu-click, and voila! You can check out the DOM. To be completely honest, I mostly got this because I usually like getting every possible feature bundled with software when I installed it.

PDF Download – Okay, we’ve all dealt with this – clicking on a link without realizing it’s a PDF and spending 10 minutes waiting for the damn thing to load. Now, you can choose whether to save the PDF, view it as a PDF, view it as HTML (a la google’s treatment of PDF pages), or just forget about it (assuming you changed your mind about looking at it just because it’s a PDF. Again, not one I use terribly often, mostly because I don’t look at many PDF’s, but a definite plus in the “avoiding irritation” category.

Greasemonkey – First thing, many brownie points for a perfectly fitting and imaginative name. What this does is allow you to tinker with the way certain pages load using small Javascripts. Definitely haven’t used this to its full potential, as there’s a gigantic library of user scripts out there, as well as a kickass tutorial. Right now, I only use the “Google Reader Auto-Read” script. Not a must-have extension for me, but I can definitely see this being essential for others.

Morning Coffee – All right, now this one is a must-have for me. Instead of keeping all the crap you’re currently looking at in your head, you can add it to your morning coffee. This one gets brownie points for the name, too. Basically, you know those websites you look at all the time? (Think blogs, webcomics, news websites, etc.) Just add them to your morning coffee and click on the cute little coffee cup to view them all at the same time. Combined with the next extension, this makes it completely impossible to lose track of your “currently surfing” list of websites, even in the event of a crash. It’s easy to use and change: you click on the coffee cup button and select “Add to My Morning Coffee”, then the day(s) you want to add it for. And if that doesn’t provide enough, you can go to “Configure Morning Coffee” to further tweak what you see when. Or delete a site, if that’s what you want. One of my top two extensions.

Tab Mix Plus – My number one extension. Adds all kinds of function to Firefox, starting with convenience: there’s an “X” on each tab to close it, not just at the right for the current tab – and ending with life-saving: a session manager that brings back all your open windows and tabs, with full history, whenever you want to – after closing Firefox, or even after a crash. All you have to do to keep it when you’re closing Firefox is go to Tools => Session Manager => Save This Window or Save All Windows. It will automatically save everything in the event of a crash. There are just so many things this adds, it’s ridiculous – see this PDF file (maybe using PDF Download?) for the full list of functionality and tunability. I recommend this extension 100%.

Inspect This – A nice little addition to the DOM Inspector that adds “Inspect This Element” to the context menu when right-clicking elements on a web-page. Not much to say about this one as, like I mentioned, I don’t use DOM Inspector that much, and I haven’t really had a chance to explore this one, either.

Image Zoom – Another one I haven’t had a chance to explore much, yet; but it sounds like something I could definitely use. It allows you to control the zoom of any image – zoom in, zoom it, fit to screen, custom zoom; you name it, this lets you do it. I mean, who hasn’t wanted to see that picture in more detail, or be able to see the whole damn wallpaper at once?

Video Downloader – This sounds great in theory, but I’m reluctant to recommend it because I think it messes with the default Firefox treatment of video links – sometimes when clicking on a video, one of a type you’ve specified to always open, but not save, that little dialog will come up, asking what you want Firefox to do with it, with everything selected the way it should be, but it’s still annoying to have to click Okay. It might have nothing to do with Video Downloader, but if it does, this definitely lowers the value of the extension. Like I said, looks great on paper, but possibly adds irritation.

And that’s it – those are all the Firefox extensions I use. Obviously, you can find more at http://add-ons.mozilla.org/firefox.

Categories: Firefox · Review

iTunes and Python: Creating Custom Playlists

6.27.06 · Leave a Comment

A blow-by-blow breakdown of how to use the iTunes COM to search for and make playlists out of songs that meet specific criteria through Python (much more complex than the native iTunes search capabilities). I’ll be doing the step-through in a rather legible, but not very line-efficient method; also, I’d like to apologize in advance for the horrible formatting – the columns in this theme have a fixed size, so text just gets cut off if it’s too long; my actual final script is available here (rename the resulting file to .py instead of .py.txt): custompl.py. If you want to look without downloading, it’s also up here.

If you don’t have the iTunes SDK, get it here. If you don’t have Python, or don’t know what it is, check here (Wikipedia entry) and here (Python’s official homepage).

If you already know what you’re doing with Python, skip to the code below. Once you have Python installed, fire up your favorite IDE, or just use IDLE, the one Python’s bundled with, and start typing. If you use IDLE, it will be an interactive prompt, so while you can see the results of what you do immediately, you can’t very easily save what you do directly from the prompt. Instead, you should go to File -> New Window and save the blank page as “<insert script name here>.py”. This will highlight everything for you the same way the interactive prompt will. I suggest doing both – experimenting with the prompt, then saving what works – but you can do it either way.

import win32com.client
iTunes = win32com.client.gencache.EnsureDispatch(
         "iTunes.Application")

Okay, first thing we have to do is import win32com.client, so that we can access the iTunes COM. Next, we connect to iTunes, using win32com.client’s gencache.EnsureDispatch so that we don’t get the funky behavior that Dispatch can sometimes cause.

source = iTunes.LibrarySource
library = iTunes.LibraryPlaylist

All right, now we have the iTunes source (contains all the playlists) and the main Library playlist. Now, if you want to search for songs using the iTunes COM, you have to specify which fields to search in as well as the search string (this is documented in the help file that comes with the iTunes SDK). So what we’re going to do is make a dictionary that will convert the string parameter of which field to search in into a number that iTunes can understand:

field_dict = {"All":0,"Visible":1,"Artist":2,
              "Album":3,"Composer":4,"Name":5}

We’ll worry about searching in playlist names later. Basically, the default choices available for searching iTunes are All fields, all Visible fields, Artist, Album, Composer, or Name of the song. All right, time to get down to some actual searching – just one query at a time to start with (we’ll add multiple searches at once later, and smart titling at the same time):

custom_playlist = iTunes.CreatePlaylist("untitled")
custom_playlist = win32com.client.CastTo(custom_playlist,
                                         'IITUserPlaylist')
query = "Imogen Heap"
field = "Artist"
results = library.Search(query, field_dict[field]))

All right, first we used iTunes to create an untitled playlist. Now, unfortunately, the default when creating a playlist this way, for some reason, doesn’t give you access to all the methods that the documentation suggests. So you have to use win32com.client.CastTo to, well, cast the result to a proper iTunes IITUserPlaylist (documented, again, in the help file). Next we can perform the search and start populating the playlist. Okay, so we searched the library for any tracks whose artist is Imogen Heap – a freakishly good singer, by the way – she used to be part of a band called Frou Frou before going solo . . . Coming back to the code, all we have left to do is populate the playlist we made with the tracks we found.

for track in tracks:
    custom_playlist.AddTrack(track)

And that’s it. Those are the basics. To be able to use multiple queries, quite a few additions to the above basics need to be made. We’ll start with adding logical operators – because there’s nothing more irritating than having a program assume you want all of the conditions to be met when there’s a specific order to which you want and which you can do without:

field_dict = {"All":0,"Visible":1,"Artist":2,"Album":3,
              "Composer":4,"Name":5,"+":intersect,
              "|":__import__("operator").add,"X":difference}

First thing we have to do is update the dictionary to allow for the logical operators: “+” for AND, “|” for OR, “X” for XOR. For those not familiar with logical operators, AND, appropriately enough, requires the fulfillment of both conditions (a AND b), OR requires one or the other or both, and XOR (aka exclusive or) requires one or the other but not both. Now, this may still seem a bit odd as there are no “intersect” or “difference” functions yet, and what’s this business with __import__? Well, we’ll work backwards. The expression __import__("operator").add is roughly equivalent to operator.add, once operator has been imported. I say roughly because __import__ doesn’t permanently import its parameter, it merely allows one to access the method of a class not in the present namespace. Anyway, by putting this reference to operator.add in field_dict, we can achieve the following: field_dict["|"](a,b) will produce the same as a+b. Now, just to be perfectly clear, this is NOT the same as field_dict["+"](a,b). I’m talking about putting a+b in the interactive prompt; actually adding them together. There’s a reason for this, which I’ll explain in due time. First, to define intersect and difference:

def intersect(tracks1, tracks2):
    names1 = [i.Name for i in tracks1]
    names2 = [i.Name for i in tracks2]
    names3 = [i for i in names1 if i in names2]
    return [tracks1[i] for i in
              [names1.index(x) for x in names3]]

def difference(tracks1, tracks2):
    names1 = [i.Name for i in l1]
    names2 = [i.Name for i in l2]
    names3 = [[i for i in names1 if i not in names2],
              [i for i in names2 if i not in names1]]
    return [tracks1[i] for i in
              [names1.index(x) for x in names3[0]]]+
           [tracks2[i] for i in
              [names2.index(x) for x in names3[1]]]

Well, well. This is confusing indeed. Let’s take a look at intersect first. This is meant to mirror the intersect method of the built in set class. There are two parameters: each a list of tracks or an IITTrackCollection, composed of IITTracks. Each IITTrack has a Name attribute, so the first thing we do is make some parallel lists containing the Names of the tracks in the arguments. Once these have been made, we make a list of tracks which are in both. Then, we make and return a list of the actual tracks (as opposed to just the Names) which are represented by the Names in the list we just made. Simple enough, right?

Okay, now we get to the really confusing bit. Again, this is meant to mirror the set method of the same name. The parameters are the same as in the previous function, as is the next step. Once we have two lists of Names which correspond to the provided lists, we make a third list which has two elements. The first is a list of all the Names in the first Names list which don’t appear in the second, and the second element is the reverse. With this in hand, we proceed to perform the same step as in the last function – using the Names we want to refer back to the original IITTracks. The only difference is that, since this time we have two lists to refer from (names3[0] and names3[1]), we have to collect the IITTracks from both and add them together. All right, now we can get to the fun part: parsing user input.

def customplaylist(querylist,title=None):
    pls = []
    if type(querylist) is str:
        querylist = (eval(querylist.replace(" XOR ",", 'X',"
                                  ).replace(" AND ",", '+',"
                                  ).replace(" OR ",", '|',"
                                  ).replace('" in "',"', '"
                                  ).replace('"',"'")))
    if len(querylist) != 1:
        querylist = [querylist]
    pls.extend(custompl(*querylist[0]))
    plname = str(querylist).replace(", 'X',"," XOR "
                          ).replace(", '+',"," AND "
                          ).replace(", '|',"," OR "
                          ).replace("', '","' in '"
                          ).replace("[",""
                          ).replace("]","")
    cpl = win32com.client.CastTo(
              iTunes.CreatePlaylist(title or plname),
              'IITUserPlaylist')
    for track in pls:
        cpl.AddTrack(track)

Okay, one thing at a time. Some of this should look familiar, since we’ve used some of the pieces before. First off, we take in a list of queries, which should look vaguely like this: ("<search string>", "<field>") or '"<search string>" in "<field>"'. The queries will be interspersed with logical operators where necessary. To see a working example of a call to customplaylist, look at the bottom of the final code, which is available by clicking on the link at the top of this post.

The second parameter is a title, should the user want to name the playlist something other than the string form of querylist, which tends to look like '(("Toasty" in "Playlist") XOR ("Something" in "Artist")) AND (("Sum" in "Playlist") OR ("Killer" in "Album"))', which is the “smart titling” I was talking about before. (Another of the features of the final product is that the search acts like an iTunes search, providing matches of “All Killer, No Filler”, say, for the search string of “Killer”. This means the user gets to be all kinds of lazy when searching.)

Our next step is to create an empty list, which will eventually hold the IITTracks we want to populate our playlist with. What follows that is an ugly way to parse the string version of querylist: you replace all the human legible stuff with the Python equivalent where it needs to be, then eval it so it’s a list and not a string. There are hundreds of rants and essays about this everywhere, so I’ll just put a small word in here. DO NOT USE THIS TECHNIQUE IF YOU DON’T TRUST THE END USER ON YOUR COMPUTER. eval has the potential to do a lot of damage to your computer in the hands of the right, or rather, wrong, person. If you don’t trust the end user, just change the function to look like this, and it won’t support strings anymore:

def customplaylist(querylist,title=None):
    pls = []
    if len(querylist) != 1:
        querylist = [querylist]
    pls.extend(custompl(*querylist[0]))
    plname = str(querylist).replace(", 'X',"," XOR "
                          ).replace(", '+',"," AND "
                          ).replace(", '|',"," OR "
                          ).replace("', '","' in '"
                          ).replace("[",""
                          ).replace("]","")
    cpl = win32com.client.CastTo(
              iTunes.CreatePlaylist(title or plname),
              'IITUserPlaylist')
    for track in pls:
        cpl.AddTrack(track)

Okay, now the next thing we see here is that we check the length of querylist, and if it’s not 1, we make it one by making it the only element of a new list. Why? Because the custompl function, which we haven’t seen yet, works recursively and requires there to be one group to begin with. That much is fairly straightforward.

Next, though, we see an odd call to custompl. What the hell does it mean when there’s an asterisk in front of a parameter? Well, when that parameter is a list, it separates the elements of that list so that each element is treated as a separate argument by the function you’re calling. For example, let’s say you have a function printparams that takes in three parameters, a, b, and c and prints them out. And let’s further say you had a list, foo, with elements "b", "a", and "r". The function call to printparams would look like this: printparams(*foo), and would output b a r. Instead of branching off into custompl here, I’m going to finish up the rest of the main function, and then talk about what’s going on behind the scenes.

Once we’ve finished collecting the tracks we want, we make the name of the playlist by reversing what we saw before for parsing the string version of querylist. Then, we create the playlist and cast it to an IITUserPlaylist in one step. The only thing you haven’t seen is title or plname. The way Python treats True and False, None is equivalent to False, so if the user doesn’t enter a title in the parameter list, we end up with None or plname, which Python evaluates to get plname. This works even if a title has been provided because Python only evaluates the second half of an and expression if the first condition is false. After that, like you saw before, we add our tracks to the playlist and that’s that.

All right, so the last thing to look at here is custompl, the function we’re using to really parse querylist and return the right tracks:

def custompl(query1, logic, query2):
    results = []
    temp = [query1,query2]
    if len(query1) == 3:
        if len(query2) == 3:
            return field_dict[logic](custompl(*query1),
                                     custompl(*query2))
        results.append(custompl(*query1))
        temp.remove(query1)
    elif len(query2) == 3:
        results.append(custompl(*query2))
        temp.remove(query2)
    for query in temp:
        search_string, field = query
        if field == "Playlist":
            results.extend([list(pl.Tracks)
                            for pl in source.Playlists
                               if search_string in pl.Name])
        else:
            results.append(list(
                           library.Search(search_string,
                                        field_dict[field])))
    return field_dict[logic](results[0],results[1])

Whoo boy, here we go. Remember how I said that the asterisk makes the function treat the list as its component elements? Well here’s where it comes into play. When it comes down to it, every group, no matter how complicated, has three parts. The left side, the logical operator, and the right side. So first we make an empty list to hold our results. Then we make another list to hold the left and right side. Then we check each side to see if it is, itself, a group, or if it’s really a query (remember, groups have three components, and queries have two: search string and field). If they’re both groups, it applies the function that corresponds with the operator on the result of breaking down the group again by running *it* through the function. And so on until they both aren’t groups anymore. Once only the first one is a group, it adds the results of breaking down that group to the main results and removes it from temp to let the function know not to try to treat it as a query. On the other hand, if only the *right* side is a group, it does the same on that side. Once there are no more groups, it gets to the for loop. This looks at whichever side is left, or both, if they happened to have the same level of grouping, and breaks it down into search string and field.

Here’s where we deal with searching in Playlist names. We extend the results by the list of Tracks of each Playlist whose name even partially matches the search string. What this means is, it adds each track in that list to the end of results, one by one, instead of just adding the whole list as a list. This way, all the tracks on this side are at the same level.

Now, if we’re *not* searching in the Playlist name, we do what we did way back when. We search the library for the search string in the field, and append the list to results, or add it to the end. Now we should have two elements in results: one from each side. Even if there’s an uneven level of grouping on each side, the way we set it up, we still have two elements. Remember? If one side is a group and the other isn’t, we add the results of breaking it down to results, then get to here and add the results of breaking the non-group side down. Two elements. If it’s hard for you to visualize, try coming up with the most convoluted querylist you can imagine and working through custompl to see how it works. By the way, this type of programming, wherein a function calls itself, is called “Recursive” programming. To see simpler examples of this, just google “recursive” and the programming language of your choice. Since we have two elements now, we can apply the logical operator to them to get what we want. Oh yeah, I never explained why “OR” corresponds to “+”. Well, think about it. “OR” means “one or the other or both”. Which means that if we have two lists, every track in each list will be in either one, or the other, or both. We don’t have to do anything fancy to them, just add them together.

Anyway, that’s the whole cake. Once you have the results, you return them back to the main function, where we extend pls by the results, and add the tracks to our iTunes playlist.

Whew! That was a mouthful.

Categories: Music · Programming · Python · Tutorial

burning bike rubber

6.26.06 · 2 Comments

This is just amazing.

Categories: Humor · Video

summer

6.26.06 · Leave a Comment

summer – 6.24.06

in the heat-held moments (time too tired to tick)
of life (breath-defined) and hazy light (this lazy height of mood),
the fireflies popping in and out (our lives, our love – compacted
into a day-to-day existence: generations pass in the time
it takes the heat between us to cool),
thoughts of what might be (and what bites me – the air a hidden swarm
of life, lacking languor, leaving little lines
on my skin), trailing fiery paths (like burning gasoline)
through my head – i'm electrified, but listless
(as i list this litany of wishes for you; but always wordless in the stead
of effort-driven expression) -
life, affirmed yet again (my sigh providing the definition – what once was unseen:
the movement of tiny wings – suddenly there, where nothing was before)
continues, day by day (death by death), breath by breath
'till the leaves join the flies and, aided by the lies, so do i.

Categories: Poetry

recently discovered bands

6.25.06 · Leave a Comment

In no particular order:

Gnarls Barkley – the team composed of Cee Lo and DangerMouse. Great stuff. Sounds like gospel at times, like rock at others, sometimes electronica, and sometimes rap. I was turned onto it by the "Crazy" vid from the music awards, of course. Then I used Azureus to grab their album – St. Elsewhere. I'm excited. You should be, too.

Peeping Tom – catchy as all get-out. It's a Mike Patton project, and extremely hard to classify – somewhere between rock, rap, and metal. Think The Mars Volta with more conventional guitar and drums. Found it via work for it, a generally awesome mp3/music blog that you can also find on my blogroll as benlovesmusic. Their self-titled album is available as an Azureus torrent, as well.

Hot Hot Heat – okay, okay. So I was already familiar with Hot Hot Heat. But they're just so damn good. For those not yet familiar, Hot Hot Heat is kick-ass rock/pop with amazingly catchy songs that have great drums and better vocals. Anyway, I found some more of their music (I previously only had Elevator), so I technically did 'discover' their music. To get back to the point, I found Make Up The Breakdown (the torrent name is incorrect – it says "Or" instead of "The") and Knock Knock Knock. :-D

Arctic Monkeys – alright, you got me again. I've known about these guys for a while, and I have no excuse; but they are effing amazing. British music at it's best. If you don't know these guys, you really don't know what you're missing. Their music is everywhere.

Hard-Fi – again, I've known about them for a little while, but they're still pretty damn good. Sort of like the Arctic Monkeys, except less hyped up and more polished.

The Fall of Troy – these guys are aMAZing. I can't find their music hardly anywhere, but they are well worth the effort to find it. They're sort of like rock/pop/screamo/amazing guitar.

Cute Is What We Aim For – ditto. Love their music, but it's damn near impossible to find. Similar style to The Fall of Troy, but more pop and less screamo. Got turned on to both of these bands, actually, by an article on alwaysBeta, surprisingly enough (also on my blogroll). The same article also turned me onto Chiodos, grouped in much the same category musically.

Head Automatica – I don't know much about them, except that their album Popaganda is really clever lyrically and pretty damn catchy, too.

The Faint – another one of those bands I've known for a while, but found new music for. Electronica/Pop/Rock. Awesome sound, especially in their Wet From Birth album – they sound more electronic and less pop in Blank Wave Arcade, and similar in Danse Macabre, but I like Danse Macabre better than Blank Wave Arcade. I just found a few scattered mp3s of theirs that I didn't have before, but they're still effing genius.

and finally, Ima Robot – I love this band, but to some it might be an aquired taste. Definitely listenable, lots of drums and guitar, great bass lines, too. The main point of possible contention is the quirky voice of the lead singer, which I happen to like, but some might not. Again, I found some random mp3s of theirs in scattered locations, but not a whole album.

I also finally downloaded The Grey Album in its entirety and Rooney's self-titled album, as well as Taking Back Sunday's Louder Now!

I've become pretty impressed with the mash-ups of some DJs, too – Party Ben and McSleazy in particular.

All right, that's about it, folks. Go forth and be fruitful and all that. Enjoy the quality music.

Categories: Music

hello

6.25.06 · Leave a Comment

so. first wordpress post.  first impressions:

  • disappointed, severely so, with the lack of widgets
  • mostly just disappointed with the differences between .com and .org, ie wordpress hosted or self-hosted
  • still vaguely excited about the step up (at least, that was my initial thought) from xanga

anyway, i wanted to set up a page that would automatically grab my poems from my poetry xanga and update in realtime so that whenever i posted there, the text of my poems page here would be updated, too.  but wordpress doesn't support code in pages nor in posts, so that was a no-go.  oh well, not a terribly big deal.

i guess we'll have to see how things progress from here.

Categories: Uncategorized