""" Read playlist exported from iTunes, generate HTML file. By Kalle (http://qalle.net) """ import sys import os.path import time # source file settings SOURCE_ENCODING = "utf-16le" SOURCE_FIELD_SEPARATOR = "\t" SOURCE_COLUMN_NUMBERS = { "name": 0, # song, e.g. "Never Gonna Give You Up" "artist": 1, # e.g. "Rick Astley" "comment": 24, # e.g. "awesome" } # target file settings TARGET_ENCODING = "utf-8" TARGET_NEWLINES = "\n" HTML_SPECIAL_CHARACTERS = { ord("&"): "&", ord("<"): "<", ord(">"): ">", } ARTICLES = ["a", "an", "the"] HELP_TEXT = """\ Reads an iTunes playlist and generates an HTML file. Command line arguments: iTunes playlist file to read A text file exported from iTunes. Usually "Music.txt". HTML file to write The file will be OVERWRITTEN if it already exists. If you get error messages related to the format of the input file, or if the output file looks weird, edit the "SOURCE FILE SETTINGS" near the start of this script.\ """ # initial HTML HTML_START = """\ my playlist

my playlist

""" # final HTML HTML_END = """

Back to front page

\ """ SUMMARY = """\
generated at {generated:s} UTC
artists {artists:d}
songs {songs:d}
\ """ def to_printable(text): return text.encode("ascii", errors = "backslashreplace").decode("ascii") def read_songs(sourceHnd): """Read source file. Return important info as list of dicts.""" # skip first line sourceHnd.seek(0) next(sourceHnd) # list of songs as dicts songs = set() for (lineNum, line) in enumerate(sourceHnd): fields = line.strip().split(SOURCE_FIELD_SEPARATOR) try: name = fields[SOURCE_COLUMN_NUMBERS["name"]] artist = fields[SOURCE_COLUMN_NUMBERS["artist"]] comment = fields[SOURCE_COLUMN_NUMBERS["comment"]] except IndexError: exit("Error: too few columns on line {:d}, exiting.".format(lineNum + 2)) if name == "": name = "(unknown song)" if artist == "": artist = "(unknown artist)" songs.add((name, artist, comment)) return [ {"name": name, "artist": artist, "comment": comment} for (name, artist, comment) in songs ] def move_initial_article(name): """E.g. The Pentti Puntti Band -> Pentti Puntti Band, The""" for article in ARTICLES: prefix = article + " " if name.lower().startswith(article + " "): return name[len(prefix):].strip() + ", " + name[:len(prefix)] return name def sort_songs(songs): # 3. by comment songs.sort(key = lambda song: move_initial_article(song["comment"])) songs.sort(key = lambda song: move_initial_article(song["comment"]).lower()) # 2. by name songs.sort(key = lambda song: move_initial_article(song["name"])) songs.sort(key = lambda song: move_initial_article(song["name"]).lower()) # 1. by artist songs.sort(key = lambda song: move_initial_article(song["artist"])) songs.sort(key = lambda song: move_initial_article(song["artist"]).lower()) return songs def artist_start_line(artist): return '
  • {:s}
  • ", file = targetHnd) print("", file = targetHnd) def write_target_file(songs, targetHnd): # go to start of file targetHnd.seek(0) print(HTML_START, file = targetHnd) print("

    summary

    ", file = targetHnd) print("", file = targetHnd) print(SUMMARY.format( generated = time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime()), songs = len(songs), artists = len(set(song["artist"] for song in songs)), ), file = targetHnd) print("", file = targetHnd) print("

    songs by artist

    ", file = targetHnd) print("", file = targetHnd) write_songs(songs, targetHnd) print(HTML_END, file = targetHnd) # return number of bytes written return targetHnd.tell() def main(): # exit if wrong number of command line arguments if len(sys.argv) != 3: exit(HELP_TEXT) # read command line arguments (source, target) = sys.argv[1:] # source and target files must not be the same try: if os.path.samefile(source, target): exit("Error: source and target files must not be the same.") except OSError: pass print('reading "{:s}"...'.format(to_printable(source))) try: with open(source, "rt", encoding = SOURCE_ENCODING) as sourceHnd: songs = read_songs(sourceHnd) except UnicodeError: exit("Error: source file is not valid in specified character encoding.") except OSError: exit("Error reading source file.") print("songs read:", len(songs)) print("sorting songs...") songs = sort_songs(songs) print('writing "{:s}"...'.format(to_printable(target))) try: with open(target, "wt", encoding = TARGET_ENCODING, newline = TARGET_NEWLINES) as targetHnd: bytesWritten = write_target_file(songs, targetHnd) except UnicodeError: exit("Error: cannot encode file in specified encoding.") except OSError: exit("Error writing target file.") print("OK. Bytes written:", bytesWritten) if __name__ == "__main__": main()