Benutzer-Werkzeuge

Webseiten-Werkzeuge


en:scytheman:zeugs:code:tags2playlist

Unterschiede

Hier werden die Unterschiede zwischen zwei Versionen gezeigt.

Link zu dieser Vergleichsansicht

Beide Seiten der vorigen Revision Vorhergehende Überarbeitung
Nächste Überarbeitung
Vorhergehende Überarbeitung
en:scytheman:zeugs:code:tags2playlist [2011/09/10 12:52]
127.0.0.1 Externe Bearbeitung
en:scytheman:zeugs:code:tags2playlist [2014/03/01 17:13] (aktuell)
Zeile 1: Zeile 1:
 +[[en:​scytheman|↑ up]]
 +
 +====== tags2playlist ======
 +
 +===== Description =====
 +
 +//​{{scytheman:​zeugs:​tags2playlist.tar.gz|tags2playlist}}//​ is a [[wp>​Python_(programming_language)|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 [[http://​www.last.fm/​|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 [[http://​www.last.fm/​|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//:​
 +
 +<​code>​
 +$ 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
 +[...]
 +</​code>​
 +
 +Then, all //.tags// files are processed inside ///​mnt/​sda6/​mp3///​ and ///​mnt/​sda6/​music///​ by //​tags2playlist//​ to generate the corresponding playlists:
 +<​code>​
 +$ 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)
 +[...]
 +</​code>​
 +
 +===== 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 =====
 +
 +  * [[wp>​Python_(programming_language)|Python]]
 +  * [[http://​code.google.com/​p/​python-lastfm/​|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 ====
 +
 +<code python>
 +#​!/​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()
 +</​code>​
 +
 +==== tagginghelper ====
 +
 +<code python>
 +#​!/​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)
 +</​code>​
  
en/scytheman/zeugs/code/tags2playlist.txt · Zuletzt geändert: 2014/03/01 17:13 (Externe Bearbeitung)