Benutzer-Werkzeuge

Webseiten-Werkzeuge


en:scytheman:zeugs:code:tags2playlist

↑ up

tags2playlist

Description

tags2playlist is a Python script for automatic playlist generation.

Creating playlists manually can be a real pain, especially if you have music from lots of different artists. If you just want to listen to a specific genre, it takes a lot of time to create genre playlists and to update them on adding or removal of new artists. Also, renaming files or directories will lead to corrupt playlists.

tags2playlist will do this automatically if you have a tidy music collection, that is a directory for each artist where the directory name matches the artist's name. This script will look for all subdirectories (=artists) in a given directory containing a file named .tags and add all files in the corresponding subdirectories to all tag playlists described in the .tags file (see example below). As the creation of those .tags files may also take some time, a little helper script is included to retrieve tags automatically from last.fm.

Simple Example

Generating Playlists

Imagine your music is in /path/to/mymusic/ (there is also support for multiple music directories) with the following structure:

/path/to/mymusic/artist one/great song.mp3
/path/to/mymusic/artist one/great album/even greater song.mp3
/path/to/mymusic/artist two
...

You just have to create a file .tags in each of the artist directories containing a list of tags (one per line), e.g.

/path/to/mymusic/artist one/.tags
/path/to/mymusic/artist two/.tags
...

where the content of /path/to/mymusic/artist one/.tags may look like:

hard rock
heavy metal

Now you just have to run the script as tags2playlist /path/to/mymusic/ and it will produce playlists for each of the tags containing music files of the corresponding directory. For the given example, the playlist hard rock will include the songs /path/to/mymusic/artist one/great song.mp3 and /path/to/mymusic/artist one/great album/even greater song.mp3.

Generating .tags Files

If you don't want to create all those .tags files by hand, you can use the included helper script called tagginghelper. This script will automatically generate .tags files by retrieving the top tags for all your artists using last.fm.

Real World Example

Here is the (shortened) output of a real world music collection. First, the .tags files are generated for all music in the directories /mnt/sda6/mp3/ and /mnt/sda6/music/ with the helper script tagginghelper:

$ tagginghelper.py /mnt/sda6/mp3/ /mnt/sda6/music/
traversing directory /mnt/sda6/mp3/
retrieving top 5 tags for ACDC from last.fm
writing 5 tags (hard rock, classic rock, heavy metal, rock, rock and roll) for ACDC to /mnt/sda6/mp3/ACDC/.tags
retrieving top 5 tags for Across The Border from last.fm
writing 5 tags (folk punk, punk, folk rock, irish folk, german) for Across The Border to /mnt/sda6/mp3/Across The Border/.tags
retrieving top 5 tags for After Forever from last.fm
writing 5 tags (gothic metal, symphonic metal, female fronted metal, metal, gothic) for After Forever to /mnt/sda6/mp3/After Forever/.tags
retrieving top 5 tags for Ahead to the Sea from last.fm
writing 5 tags (folk punk, folk, german, folk rock, folk-punk) for Ahead to the Sea to /mnt/sda6/mp3/Ahead to the Sea/.tags
retrieving top 5 tags for Al Andaluz Project from last.fm
writing 5 tags (medieval, mediterranean, ethereal, neomedieval, world) for Al Andaluz Project to /mnt/sda6/mp3/Al Andaluz Project/.tags
retrieving top 5 tags for Al Qaynah from last.fm
writing 5 tags (arabic metal, oriental metal, folk metal, eastern metal, oriental) for Al Qaynah to /mnt/sda6/mp3/Al Qaynah/.tags
retrieving top 5 tags for Alchemy VII from last.fm
writing 5 tags (pagan, neo-pagan, pagan music, neopagan, under 2000 listeners) for Alchemy VII to /mnt/sda6/mp3/Alchemy VII/.tags
retrieving top 5 tags for Alcian Blue from last.fm
writing 5 tags (shoegaze, post-punk, ambient, indie, atmospheric) for Alcian Blue to /mnt/sda6/mp3/Alcian Blue/.tags
retrieving top 5 tags for Alestorm from last.fm
writing 5 tags (pirate metal, folk metal, power metal, true scottish pirate metal, scottish) for Alestorm to /mnt/sda6/mp3/Alestorm/.tags
[...]

Then, all .tags files are processed inside /mnt/sda6/mp3/ and /mnt/sda6/music/ by tags2playlist to generate the corresponding playlists:

$ tags2playlist.py /mnt/sda6/mp3/ /mnt/sda6/music/
searching for .tags in /mnt/sda6/mp3/
parsing /mnt/sda6/mp3/ACDC/.tags
parsing /mnt/sda6/mp3/Across The Border/.tags
parsing /mnt/sda6/mp3/After Forever/.tags    
[...]
parsing /mnt/sda6/mp3/Omega Massif/.tags 
parsing /mnt/sda6/mp3/Soul Whirling Somewhere/.tags
searching for .tags in /mnt/sda6/music/
parsing /mnt/sda6/music/Subtonix/.tags
parsing /mnt/sda6/music/Swallow/.tags   
parsing /mnt/sda6/music/The Sky Drops/.tags
parsing /mnt/sda6/music/Die Art/.tags  
[...]

writing 286 playlists
writing playlist for tag 4ad to /mnt/sda6/mp3/playlists/4ad.m3u (93 entries)
writing playlist for tag 60s to /mnt/sda6/mp3/playlists/60s.m3u (252 entries)
writing playlist for tag 70s to /mnt/sda6/mp3/playlists/70s.m3u (698 entries)
writing playlist for tag 77 to /mnt/sda6/mp3/playlists/77.m3u (28 entries)
writing playlist for tag 77 style punk to /mnt/sda6/mp3/playlists/77 style punk.m3u (28 entries)
writing playlist for tag 80s to /mnt/sda6/mp3/playlists/80s.m3u (1412 entries)                  
writing playlist for tag 90s to /mnt/sda6/mp3/playlists/90s.m3u (7 entries)                     
writing playlist for tag a cappella to /mnt/sda6/mp3/playlists/a cappella.m3u (32 entries)
writing playlist for tag a cappella metal to /mnt/sda6/mp3/playlists/a cappella metal.m3u (32 entries)
writing playlist for tag acoustic to /mnt/sda6/mp3/playlists/acoustic.m3u (14 entries)                
writing playlist for tag alternative to /mnt/sda6/mp3/playlists/alternative.m3u (1199 entries)
writing playlist for tag alternative rock to /mnt/sda6/mp3/playlists/alternative rock.m3u (170 entries)
writing playlist for tag ambient to /mnt/sda6/mp3/playlists/ambient.m3u (594 entries)
[...]

Format of the .tags File

The format of the .tags file is rather simple. Each line contains a single tag applying to all files in the corresponding directory and all subdirectories. Additionally, the tag may be prepended by a colon and a relative path to a single music file, applying only to this file. Example:

hard rock
heavy metal
greatest song of all times: catfrighteners - sitting on the catstone.mp3

This will add all music files to the playlists hard rock and heavy metal and additionally add the song catfrighteners - sitting on the catstone.mp3 to the playlist named greatest song of all times. There is no limit on the number of tags per .tags file.

Requirements

  • python-lastfm for tagginghelper (shipped, also requires python-decorator and python-elementtree)

Usage

tags2playlist

First, you should adapt the variable plistDir to the directory where you want to put the generated playlists in (the directory has to exist). Then, just pass one or more directories as arguments to the script, like tags2playlist /path/one /path/two /path/three […].

tagginghelper

Just pass one or more directories which the script should traverse to generate the .tags files, like tagginghelper /path/one /path/two /path/three […]. Additionally, you may want to pass the parameter -a to just generate the .tags file for a single artist, like tagginghelper -a /path/one/great artist to just generate the file /path/one/great artist/.tags.

To retrieve more than the default 5 top tags, just edit the numTags variable in the script.

Code

tags2playlist

#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright (C) 2010 Alexander Heinlein <alexander.heinlein@web.de>
#
# tags2playlist: automatic playlist generator
# This script traverses a given directory in the search for .tags files
# in the following format:
#
#  tag1
#  tag2
#  tag3: file1
#  tag3: file2
#  tag4: file3
#  tag5
#
# Each .tags file contains a list of tags separated by newlines. Lines
# without a colon simply specify the tag for files in the current directory
# and all subdirectories. Lines with a colon specify tags for single files.
# After gathering all tags with all corresponding files, a playlist is
# generated for each tag.
#
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301, USA.
#
 
import os
import sys
from collections import defaultdict
 
# files to include in the playlist
mediaFiles = ["mp3", "ogg", "aac", "flac", "wma", "wav", "aiff", "mpc", "ape", "m4a"]
# directory to write playlists to
plistDir = "/mnt/sda6/mp3/playlists"
playlists = defaultdict(list)
 
# colored output
red, yellow, reset = range(3)
colors = { red : "\033[1;31m",
           yellow : "\033[1;33m",
           reset : "\033[1;m" }  
def printCol(text, color):
    print colors[color] + text + colors[reset]
 
# removes non-media files (pretty slow implementation)
def cleanFiles(files):
    clean = list()
 
    for file in files:
       dotPos = file.rfind(".")
       extension = file[dotPos + 1:]
       if extension in mediaFiles:
           clean.append(file)
 
    return clean
 
# returns a list of media files in the current directory and all subdirectories
def getSubFiles(dir):
    subFiles = list()
    for root, dirs, files in os.walk(dir):
        for file in cleanFiles(files):
            subFiles.append(root + "/" + file)
 
    return subFiles
 
# parse .tags file
def parseTags(dir):
    tagFile = dir + "/.tags"
    print "parsing", tagFile
 
    for line in open(tagFile, 'r'):
        tag = line.strip("\n")
        if not ":" in tag:
            # tag belongs to all files
            subFiles = getSubFiles(dir)
            playlists[tag].extend(subFiles)
        else:
            # tag belongs to just one file
            colPos = tag.find(":")
            file = dir + "/" + tag[colPos + 1:].lstrip(" ")
            tag  = tag[:colPos]
            if not os.path.isfile(file):
                printCol("warning: file " + file + " referenced by " + tagFile + " does not exist or is not a file", yellow)
 
            playlists[tag].append(file)
 
# look for file .tags in current directory and all subdirectories
def walkDirectory(dir):
    if not os.path.isdir(dir):
        printCol("error: " + dir + " does not exist or is not a directory", red)
        return
 
    found = False
    for root, dirs, files in os.walk(dir):
        if '.tags' in files:
            found = True
            parseTags(root)
 
    if found == False:
        printCol("warning: couldn't find any .tags file in " + dir, yellow)
 
# write all playlists
def writePlaylists():
    tags = playlists.keys()
    tags.sort()
 
    for tag in tags:
        plistOut = plistDir + "/" + tag + ".m3u"
        files = playlists[tag]
        print "writing playlist for tag " + tag + " to " + plistOut + " (" + str(len(files)) + " entries)"
 
        files.sort()
        plist = open(plistOut, "w")
        for file in files:
            plist.write(file + "\n")
        plist.close()
 
if __name__ == '__main__':
    if len(sys.argv) < 2:
        print "usage:", sys.argv[0], "directory(ies)"
        sys.exit(0)
 
    for dir in sys.argv[1:]:
        print "searching for .tags in", dir
        walkDirectory(dir)
 
    print
    print "writing", len(playlists.keys()), "playlists"
    writePlaylists()

tagginghelper

#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright (C) 2010 Alexander Heinlein <alexander.heinlein@web.de>
#
# tagginghelper: little helper script for tags2playlist
# This script traverses a given directory and retrieves for each directory
# the top tags for the artist of the same name from last.fm. Then it writes
# .tags files parsable by tags2playlist which will use them to generate tag
# playlists.
#
# Note: the lastfm module requires the decorator and elementtree modules
#
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301, USA.
#
 
import os
import sys
import lastfm
 
# max. number of tags per artist
numTags = 5
 
api_key = 'db6646a55074b09f8be725bcda9088e8'
api = lastfm.Api(api_key)
 
# colored output
red, yellow, reset = range(3)
colors = { red    : "\033[1;31m",
           yellow : "\033[1;33m",
           reset  : "\033[1;m" }
def printCol(text, color):
    print colors[color] + text + colors[reset]
 
# ask last.fm for top tags
def getLastfmTags(artist):
    info = api.get_artist(artist.decode('utf-8'))
    topTags = info.top_tags
 
    tags = list()
    for tag in topTags[:numTags]:
        tags.append(tag.name.lower())
 
    return tags
 
# generate .tags for given artist inside given directory
def tagArtist(artist, dir):
    print "retrieving top", numTags, "tags for", artist, "from last.fm"
    try:
        tags = getLastfmTags(artist)
    except Exception as e:
        printCol("error for " + artist + ": " + str(e), red)
        return
 
    tagListOut = dir + "/" + artist + "/.tags"
    print "writing " + str(len(tags)) + " tags (" + ', '.join(tags) + ") for " + artist + " to " + tagListOut
 
    if len(tags) == 0:
        printCol("warning: no tags for " + artist, yellow)
        return
 
    tagList = open(tagListOut, "w")
    for tag in tags:
        tagList.write(tag + "\n")
    tagList.close()
 
# process each directory name as an artist
def walkDirectory(dir):
    artists = os.listdir(dir)
    artists.sort()
    for artist in artists:
        if not os.path.isdir(dir + "/" + artist):
            continue
        else:
            tagArtist(artist, dir)
 
# print usage information
def printUsage(name):
    print "usage:", name, "[-a] directory(ies)"
    print "little helper for tags2playlist, creates .tags files with the help of last.fm"
    print "-a just processes the given directory(ies) as an artist, useful for updating"
    print "   a single .tags file or to process a newly added directory"
 
if __name__ == '__main__':
    if len(sys.argv) < 2:
        printUsage(sys.argv[0])
        sys.exit(0)
 
    # tag only given directory as one artist
    if(sys.argv[1] == "-a"):
        if len(sys.argv) < 3:
            printUsage(sys.argv[0])
            sys.exit(0)
        else:
            for dir in sys.argv[2:]:
                # get artist (last directory in path) and parent dir
                dir = dir.rstrip("/")
                artistPos = dir.rfind("/")
                artist    = dir[artistPos + 1:]
                parentDir = dir[:artistPos]
                print "processing only directory", dir, "with artist", artist
                tagArtist(artist, parentDir)
    # process all directories
    else:
        for dir in sys.argv[1:]:
            print "traversing directory", dir
            walkDirectory(dir)
en/scytheman/zeugs/code/tags2playlist.txt · Zuletzt geändert: 2014/03/01 17:13 (Externe Bearbeitung)