eyeD3-0.6.18/0000755000175000017500000000000011663615630012170 5ustar travistraviseyeD3-0.6.18/bin/0000755000175000017500000000000011663615630012740 5ustar travistraviseyeD3-0.6.18/bin/eyeD30000755000175000017500000014352511663615630013651 0ustar travistravis#!/usr/bin/env python ################################################################################ # Copyright (C) 2002-2008 Travis Shirk # # 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 2 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # ################################################################################ import os, sys, math, locale; import optparse; from optparse import *; from stat import *; try: from eyeD3 import *; from eyeD3.tag import *; from eyeD3.frames import *; from eyeD3.utils import *; except ImportError: # For development sys.path.append("../src"); from eyeD3 import *; from eyeD3.tag import *; from eyeD3.frames import *; from eyeD3.utils import *; ENCODING = locale.getpreferredencoding(); class ConsoleColors(dict): use = 1; def __init__(self): self["normal"] = chr(0x1b) + "[0m"; self["header"] = chr(0x1b) + "[32m"; self["warning"] = chr(0x1b) + "[33m"; self["error"] = chr(0x1b) + "[31m"; self["bold"] = chr(0x1b) + "[1m"; def enabled(self, b): self.use = b; # Accessor override. def __getitem__(self, key): if self.use: return dict.__getitem__(self, key); else: return ""; colors = ConsoleColors(); class CommandException: msg = ""; def __init__(self, msg): self.msg = msg; def __str__(self): return self.msg; ################################################################################ class OptionParserHelpFormatter(HelpFormatter): def __init__(self, indent_increment=2, max_help_position=24, width=79, short_first=1): HelpFormatter.__init__ (self, indent_increment, max_help_position, width, short_first); def format_usage(self, usage): return "\n%s %s\n" % (self.format_heading("Usage"), usage); def format_heading(self, heading): return "%s\n%s\n" % (heading, "=-"[self.level] * len(heading)); def format_option_strings(self, option): # This is the optparse implementation -- stolen, with spacing mods. if option.takes_value(): metavar = option.metavar or option.dest.upper(); short_opts = [sopt + " " + metavar for sopt in option._short_opts]; long_opts = [lopt + "=" + metavar for lopt in option._long_opts]; else: short_opts = option._short_opts; long_opts = option._long_opts; if self.short_first: opts = short_opts + long_opts; else: opts = long_opts + short_opts; return ", ".join(opts); ################################################################################ def getOptionParser(): versionStr = \ """eyeD3 %s (C) Copyright 2002-2011 %s This program comes with ABSOLUTELY NO WARRANTY! See COPYING for details. Run with --help/-h for usage information or see the man page 'eyeD3(1)' """ % (eyeD3.eyeD3Version, eyeD3.eyeD3Maintainer) usageStr = "%prog [OPTS] file [file...]"; helpFormatter = OptionParserHelpFormatter(); optParser = OptionParser(usage=usageStr, version=versionStr, formatter=helpFormatter); optParser.disable_interspersed_args(); # Version options. versOpts = OptionGroup(optParser, "Tag Versions"); versOpts.add_option("-1", "--v1", action="store_const", const=eyeD3.ID3_V1, dest="tagVersion", default=eyeD3.ID3_ANY_VERSION, help="Only read/write ID3 v1.x tags. By default, "\ "v1.x tags are only read if there is not a v2.x "\ "tag."); versOpts.add_option("-2", "--v2", action="store_const", const=eyeD3.ID3_V2, dest="tagVersion", default=eyeD3.ID3_ANY_VERSION, help="Only read/write ID3 v2.x tags."); versOpts.add_option("--to-v1.1", action="store_const", const=eyeD3.ID3_V1_1, dest="convert_version", default=0, help="Convert the file's tag to ID3 v1.1." " (Or 1.0 if there is no track number.)") versOpts.add_option("--to-v2.3", action="store_const", const=eyeD3.ID3_V2_3, dest="convert_version", default=0, help="Convert the file's tag to ID3 v2.3") versOpts.add_option("--to-v2.4", action="store_const", const=eyeD3.ID3_V2_4, dest="convert_version", default=0, help="Convert the file's tag to ID3 v2.4") optParser.add_option_group(versOpts) # Tag data options. grp1 = OptionGroup(optParser, "Tag Data"); grp1.add_option("-a", "--artist", action="store", type="string", dest="artist", metavar="STRING", help="Set artist"); grp1.add_option("-A", "--album", action="store", type="string", dest="album", metavar="STRING", help="Set album"); grp1.add_option("-t", "--title", action="store", type="string", dest="title", metavar="STRING", help="Set title"); grp1.add_option("-n", "--track", action="store", type="string", dest="track", metavar="NUM", help="Set track number"); grp1.add_option("-N", "--track-total", action="store", type="string", dest="track_total", metavar="NUM", help="Set total number of tracks"); grp1.add_option("-G", "--genre", action="store", type="string", dest="genre", metavar="GENRE", help="Set genre. The argument is a valid genre string or "\ "number. See --list-genres"); grp1.add_option("-d", "--disc", action="store", type="string", dest="disc", metavar="NUM", help="Set disc number") grp1.add_option("-D", "--disc-total", action="store", type="string", dest="disc_total", metavar="NUM", help="Set total number of discs") grp1.add_option("-Y", "--year", action="store", type="string", dest="year", metavar="STRING", help="Set a four digit year."); grp1.add_option("-c", "--comment", action="append", type="string", dest="comments", metavar="[LANGUAGE]:[DESCRIPTION]:COMMENT", help="Set comment"); grp1.add_option("-L", "--lyrics", action="append", type="string", dest="lyrics", metavar="[LANGUAGE]:[DESCRIPTION]:LYRICS", help="Set lyrics"); grp1.add_option("-p", "--publisher", action="store", type="string", dest="publisher", metavar="STRING", help="Set the publisher/label text"); grp1.add_option("--remove-comments", action="store_true", dest="remove_comments", help="Remove all comment frames."); grp1.add_option("--remove-lyrics", action="store_true", dest="remove_lyrics", help="Remove all lyrics frames."); grp1.add_option("--add-image", action="append", type="string", dest="images", metavar="IMG_PATH:TYPE[:DESCRIPTION]", help="Add an image to the tag. The description and type "\ "optional, but when used, both ':' delimiters must "\ "be present. The type MUST be an string that " "corresponds to one given with --list-image-types. "\ "If the IMG_PATH value is empty the APIC frame with "\ "TYPE is removed."); grp1.add_option("--remove-images", action="store_true", dest="remove_images", help="Remove all image (APIC) frames.") grp1.add_option("--add-object", action="append", type="string", dest="objects", metavar="OBJ_PATH[:DESCRIPTION[:MIME-TYPE[:FILENAME]]", help="Add an encapsulated object to the tag. The " "description "\ "and filename are optional, but when used, the ':' "\ "delimiters must be present. If the OBJ_PATH value "\ "is empty the GEOB frame with DESCRIPTION is removed."); grp1.add_option("-i", "--write-images", type="string", action="store", dest="writeImages", metavar="DIR", default=None, help="Causes all attached images (APIC frames) to be "\ "written to the specified directory."); grp1.add_option("-o", "--write-objects", type="string", action="store", dest="writeObjects", metavar="DIR", default=None, help="Causes all attached objects (GEOB frames) to be "\ "written to the specified directory."); grp1.add_option("--set-text-frame", action="append", type="string", dest="textFrames", metavar="FID:TEXT", default=[], help="Set the value of a text frame. To remove the "\ "frame, specify an empty value. "\ "e.g., --set-text-frame=\"TDRC:\""); grp1.add_option("--set-user-text-frame", action="append", type="string", dest="userTextFrames", metavar="DESC:TEXT", default=[], help="Set the value of a user text frame (i.e., TXXX). "\ "To remove the frame, specify an empty value. "\ "e.g., --set-user-text-frame=\"SomeDesc:\""); grp1.add_option("--set-url-frame", action="append", type="string", dest="url_frames", metavar="FID:URL", default=[], help="Set the value of a URL frame. To remove the "\ "frame, specify an empty value. "\ "e.g., --set-url-frame=\"WCOM:\""); grp1.add_option("--set-user-url-frame", action="append", type="string", dest="user_url_frames", metavar="DESC:URL", default=[], help="Set the value of a user URL frame (i.e., WXXX). "\ "To remove the frame, specify an empty value. "\ "e.g., --set-user-url-frame=\"SomeDesc:\""); grp1.add_option("--play-count", action="store", type="string", dest="play_count", metavar="[+]N", default=None, help="If this argument value begins with '+' the tag's "\ "play count (PCNT) is incremented by N, otherwise the "\ "value is set to exactly N."); grp1.add_option("--bpm", action="store", type="string", dest="bpm", metavar="N", default=None, help="Set the beats per minute value."); grp1.add_option("--unique-file-id", action="append", type="string", dest="unique_file_ids", metavar="OWNER_ID:ID", default=None, help="Add a UFID frame. If the ID arg is empty the UFID "\ "frame with OWNER_ID is removed. An OWNER_ID MUST "\ "be specified."); grp1.add_option("--set-encoding", action="store", type="string", dest="textEncoding", metavar="latin1|utf8|utf16-BE|utf16-LE", default=None, help="Set the encoding that is used for _all_ text "\ "frames. "\ "This only takes affect when the tag is updated as "\ "the "\ "result of a frame value being set with another "\ "option (e.g., --artist=) or --force-update is "\ "present."); grp1.add_option("--remove-v1", action="store_true", dest="remove_v1", default=0, help="Remove ID3 v1.x tag."); grp1.add_option("--remove-v2", action="store_true", dest="remove_v2", default=0, help="Remove ID3 v2.x tag."); grp1.add_option("--remove-all", action="store_true", dest="remove_all", default=0, help="Remove both ID3 v1.x and v2.x tags."); optParser.add_option_group(grp1); # Misc. options. grp3 = OptionGroup(optParser, "Misc. Options"); grp3.add_option("--rename", type="string", action="store", dest="rename_pattern", metavar="NAME", help="Rename file (the extension is not affected) based on "\ "data in the tag using substitution variables: "\ "%A (artist), %a (album), %t (title), "\ "%n (track number), and %N (total number of tracks)"); grp3.add_option("--fs-encoding", type="string", action="store", dest="fs_encoding", default=sys.getfilesystemencoding(), metavar="ENCODING", help="Use the specified character encoding for the "\ "filename when renaming files"); grp3.add_option("-l", "--list-genres", action="store_true", dest="showGenres", help="Display the table of ID3 genres and exit"); grp3.add_option("--list-image-types", action="store_true", dest="showImagesTypes", help="List all possible image types"); grp3.add_option("--strict", action="store_true", dest="strict", help="Fail for tags that violate "\ "the ID3 specification."); grp3.add_option("--itunes", action="store_true", dest="itunes", help="Store tags in an iTunes compatible " "way."); grp3.add_option("--jep-118", action="store_true", dest="jep_118", help="Output the tag per the format described in JEP-0118. "\ "See http://www.xmpp.org/extensions/xep-0118.html"); grp3.add_option("--rfc822", action="store_true", dest="rfc822", help="Output the tag in RFC822-like format"); grp3.add_option("--nfo", action="store_true", dest="nfo", help="Output NFO information.") grp3.add_option("--lametag", action="store_true", dest="lametag", help="Prints the LAME Tag.") grp3.add_option("--force-update", action="store_true", dest="force_update", default=0, help="Update the tag regardless of whether any frames are "\ "set with new values."); grp3.add_option("--no-color", action="store_true", dest="nocolor", help="Disable color output"); grp3.add_option("--no-zero-padding", action="store_false", dest="zeropad", default=True, help="Don't pad track or disc numbers with 0's"); grp3.add_option("--no-tagging-time-frame", action="store_true", dest="no_tdtg", default=0, help="When saving tags do not add a TDTG (tagging time) " "frame") grp3.add_option("-F", dest="field_delim", default=':', metavar="DELIM", help="Specify a new delimiter for option values that " "contain multiple fields (default delimiter is ':')") grp3.add_option("-v", "--verbose", action="store_true", dest="verbose", help="Show all available information"); grp3.add_option("--debug", action="store_true", dest="debug", help="Trace program execution."); grp3.add_option('--run-profiler', action='store_true', dest='run_profiler', help='Run using python profiler.') optParser.add_option_group(grp3); return optParser; ################################################################################ def printGenres(): genres = []; displayed = [] # Filter out 'Unknown' for g in eyeD3.genres: if g != "Unknown": genres.append(g); cols = 2; offset = int(math.ceil(float(len(genres)) / cols)); for i in range(offset): c1, c2 = '', '' if i < len(genres): if i not in displayed: c1 = "%3d: %s" % (i, genres[i]); displayed.append(i) else: c1 = ""; if (i * 2) < len(genres): try: if (i + offset) not in displayed: c2 = "%3d: %s" % (i + offset, genres[i + offset]); displayed.append(i + offset) except IndexError: pass else: c2 = ""; print c1 + (" " * (40 - len(c1))) + c2; print "" ################################################################################ def printImageTypes(): print "Available image types for --add-image:"; for type in range(eyeD3.frames.ImageFrame.MIN_TYPE, eyeD3.frames.ImageFrame.MAX_TYPE + 1): print "\t%s" % (eyeD3.frames.ImageFrame.picTypeToString(type)); ################################################################################ def boldText(s, c = None): if c: return colors["bold"] + c + s + colors["normal"]; return colors["bold"] + s + colors["normal"]; def printMsg(s): sys.stdout.write(s + '\n'); def printWarning(s): sys.stderr.write(colors["warning"] + str(s) + colors["normal"] + '\n'); def printError(s): sys.stderr.write(colors["error"] + str(s) + colors["normal"] + '\n'); ################################################################################ class TagDriverBase(eyeD3.utils.FileHandler): def __init__(self, opts): self.opts = opts; def handleFile(self, f): self.audioFile = None; self.tag = None; try: if eyeD3.tag.isMp3File(f): self.audioFile = eyeD3.tag.Mp3AudioFile(f, self.opts.tagVersion); self.tag = self.audioFile.getTag(); else: self.tag = eyeD3.Tag(); if not self.tag.link(f, self.opts.tagVersion): self.tag = None; except (eyeD3.tag.InvalidAudioFormatException, eyeD3.tag.TagException, IOError), ex: printError(ex); return self.R_CONT; class NFODriver(TagDriverBase): def __init__(self, opts): TagDriverBase.__init__(self, opts) self.albums = {} def handleFile(self, f): TagDriverBase.handleFile(self, f) if self.audioFile and self.audioFile.getTag(): tag = self.audioFile.getTag() album = self.tag.getAlbum() if album and not self.albums.has_key(album): self.albums[album] = [] self.albums[album].append(self.audioFile) elif album: self.albums[album].append(self.audioFile) def handleDone(self): if not self.albums: return import time for album in self.albums: audio_files = self.albums[album] audio_files.sort(key=lambda af: af.getTag().getTrackNum()) max_title_len = 0 avg_bitrate = 0 encoder_info = '' for audio_file in audio_files: tag = audio_file.getTag() # Compute maximum title length title_len = len(tag.getTitle()) if title_len > max_title_len: max_title_len = title_len # Compute average bitrate avg_bitrate += audio_file.getBitRate()[1] # Grab the last lame version in case not all files have one if audio_file.lameTag.has_key('encoder_version'): encoder_info = (audio_file.lameTag['encoder_version'] or encoder_info) avg_bitrate = avg_bitrate / len(audio_files) printMsg("Artist: %s" % audio_files[0].getTag().getArtist()) printMsg("Album : %s" % album) printMsg("Year : %s" % audio_files[0].getTag().getYear()) genre = audio_files[0].getTag().getGenre() if genre: genre = genre.getName() else: genre = "" printMsg("Genre : %s" % genre) printMsg("") printMsg("Source: ") printMsg("Encoder: %s" % encoder_info) printMsg("Codec : mp3") printMsg("Bitrate: ~%s K/s @ %s Hz, %s" % (avg_bitrate, audio_files[0].header.sampleFreq, audio_files[0].header.mode)) printMsg("Tag : ID3 %s" % audio_files[0].getTag().getVersionStr()) printMsg("") printMsg("Ripped By: ") printMsg("") printMsg("Track Listing") printMsg("-------------") count = 0 total_time = 0 total_size = 0 for audio_file in audio_files: tag = audio_file.getTag() count += 1 title = tag.getTitle() title_len = len(title) padding = " " * ((max_title_len - title_len) + 3) total_time += audio_file.getPlayTime() total_size += audio_file.getSize() zero_pad = "0" * (len(str(len(audio_files))) - len(str(count))) printMsg(" %s%d. %s%s(%s)" % (zero_pad, count, title, padding, audio_file.getPlayTimeString())) printMsg("") printMsg("Total play time: %s" % eyeD3.utils.format_track_time(total_time)) printMsg("Total size : %s" % eyeD3.utils.format_size(total_size)) printMsg("") printMsg("=" * 78) printMsg(".NFO file created with eyeD3 %s on %s" % (eyeD3.eyeD3Version, time.asctime())) printMsg("For more information about eyeD3 go to %s" % "http://eyeD3.nicfit.net/") printMsg("=" * 78) class JEP118Driver(TagDriverBase): def __init__(self, opts): TagDriverBase.__init__(self, opts) def handleFile(self, f): TagDriverBase.handleFile(self, f) if self.tag: xml = eyeD3.tag.tagToUserTune(self.audioFile or self.tag); printMsg(xml.encode(ENCODING, "replace")); return self.R_CONT; class Rfc822Driver(TagDriverBase): def handleFile(self, f): TagDriverBase.handleFile(self, f) if self.tag: s = eyeD3.tag.tagToRfc822(self.audioFile or self.tag); printMsg(s.encode(ENCODING, "replace")); return self.R_CONT; class EyeD3Driver(TagDriverBase): def __init__(self, opts): TagDriverBase.__init__(self, opts) def handleFile(self, f): self.printHeader(f); TagDriverBase.handleFile(self, f) self.printAudioInfo(self.audioFile); if not self.tag: printError("No ID3 %s tag found!" %\ eyeD3.utils.versionToString(self.opts.tagVersion)); try: # Handle frame removals. if self.tag and self.handleRemoves(self.tag): self.tag = None; # Create a new tag in case values are being added, or the tag # was removed. newTag = 0; if not self.tag: self.tag = eyeD3.Tag(f); self.tag.header.setVersion(self.opts.tagVersion); newTag = 1; # Handle frame edits. try: tagModified = self.handleEdits(self.tag) or self.opts.force_update; except CommandException, ex: printError(ex); return self.R_HALT; if newTag and not tagModified and not self.opts.convert_version: return self.R_CONT; # Handle updating the tag if requested. if tagModified: # Update the tag. printWarning("Writing tag..."); self.tag.do_tdtg = not self.opts.no_tdtg if not self.tag.update(): printError("Error writing tag: %s" % f); return self.R_HALT; else: if newTag: # No edits were performed so we can ditch the _new_ tag. self.tag = None; except (eyeD3.tag.TagException, eyeD3.frames.FrameException), ex: printError(ex); return self.R_CONT; # Print tag. try: if self.tag: self.printTag(self.tag); if self.opts.verbose: printMsg("-" * 79); printMsg("ID3 Frames:"); for frm in self.tag: printMsg(unicode(frm).encode(ENCODING, "replace")) except (UnicodeEncodeError, UnicodeDecodeError, CommandException), ex: printError(ex); return self.R_CONT; # Handle file renaming. # FIXME: Should a audioFile be required here? if self.audioFile and self.tag and self.opts.rename_pattern: self.handleRenames(self.audioFile, self.opts.rename_pattern, self.opts.fs_encoding); return self.R_CONT; def handleRemoves(self, tag): # Remove if requested. removeVersion = 0; status = 0; rmStr = ""; if self.opts.remove_all: removeVersion = eyeD3.ID3_ANY_VERSION; rmStr = "v1.x and/or v2.x"; elif self.opts.remove_v1: removeVersion = eyeD3.ID3_V1; rmStr = "v1.x"; elif self.opts.remove_v2: removeVersion = eyeD3.ID3_V2; rmStr = "v2.x"; if removeVersion: status = tag.remove(removeVersion); statusStr = self.boolToStatus(status); printWarning("Removing ID3 %s tag: %s" % (rmStr, statusStr)); return status; def handleEdits(self, tag): retval = 0; # First set new version if requested if self.opts.convert_version: from eyeD3.utils import versionToString new_version = self.opts.convert_version if new_version == self.tag.getVersion(): printWarning("No conversion necessary, tag is already %s" % versionToString(new_version)) else: printWarning("Converting tag to ID3 %s" % versionToString(new_version)) tag.setVersion(new_version) retval |= 1 artist = self.opts.artist; if artist != None: printWarning("Setting artist: %s" % artist); tag.setArtist(artist); retval |= 1; album = self.opts.album; if album != None: printWarning("Setting album: %s" % album); tag.setAlbum(album); retval |= 1; title = self.opts.title; if title != None: printWarning("Setting title: %s" % title); tag.setTitle(title); retval |= 1; discNum = self.opts.disc discTotal = self.opts.disc_total if discNum != None or discTotal != None: if discNum: printWarning("Setting disc: %s" % str(discNum)) discNum = int(discNum) else: discNum = tag.getDiscNum()[0] if discTotal: printWarning("Setting disc total: %s" % str(discTotal)) discTotal = int(discTotal) else: discTotal = tag.getDiscNum()[1] tag.setDiscNum((discNum, discTotal), zeropad = self.opts.zeropad) retval |= 1 trackNum = self.opts.track; trackTotal = self.opts.track_total; if trackNum != None or trackTotal != None: if trackNum: printWarning("Setting track: %s" % str(trackNum)); trackNum = int(trackNum); else: trackNum = tag.getTrackNum()[0]; if trackTotal: printWarning("Setting track total: %s" % str(trackTotal)); trackTotal = int(trackTotal); else: trackTotal = tag.getTrackNum()[1]; tag.setTrackNum((trackNum, trackTotal), zeropad = self.opts.zeropad); retval |= 1; genre = self.opts.genre; if genre != None: printWarning("Setting track genre: %s" % genre); tag.setGenre(genre); retval |= 1; year = self.opts.year; if year != None: printWarning("Setting year: %s" % year); tag.setDate(year); retval |= 1; play_count = self.opts.play_count; if play_count != None: incr = False; try: if play_count[0] == '+': incr = True; play_count = long(play_count[1:]); else: play_count = long(play_count); except ValueError: raise CommandException("Invalid --play-count value: %s" %\ play_count); if play_count < 0: raise CommandException("Play count argument %d < 0" %\ (play_count)); if incr: printWarning("Incrementing play count: +%d" % (play_count)); tag.incrementPlayCount(play_count); else: printWarning("Setting play count: %d" % (play_count)); tag.setPlayCount(play_count); retval |= 1; bpm = self.opts.bpm; if bpm != None: try: bpm_float = float(bpm) bpm = int(bpm_float + 0.5); if bpm <= 0: raise ValueError(); printWarning("Setting BPM: %d" % (bpm)); tag.setBPM(bpm); retval |= 1; except ValueError: raise CommandException("Invalid --bpm value: %s" % bpm); pub = self.opts.publisher; if pub != None: printWarning("Setting publisher: %s" % (pub)); tag.setPublisher(pub); retval |= 1; comments = self.opts.comments; if self.opts.remove_comments: count = tag.removeComments(); printWarning("Removing %d comment frames" % count); retval |= 1; elif comments: for c in comments: try: (lang,desc,comm) = c.split(self.opts.field_delim, 2) if not lang: lang = eyeD3.DEFAULT_LANG; if not comm: printWarning("Removing comment: %s" % (desc)); else: printWarning("Setting comment: [%s]: %s" % (desc, comm)); tag.addComment(comm, desc, lang); retval |= 1; except ValueError: printError("Invalid Comment; see --help: %s" % c); retval &= 0; lyrics = self.opts.lyrics; if self.opts.remove_lyrics: count = tag.removeLyrics(); printWarning("Removing %d lyrics frames" % count); retval |= 1; elif lyrics: for l in lyrics: try: (lang, desc, lyrics) = l.split(self.opts.field_delim, 2) if not lang: lang = eyeD3.DEFAULT_LANG; if not lyrics: printWarning("Removing lyrics: %s" % (desc)); else: printWarning("Setting lyrics: [%s]: %s" % (desc, lyrics)); tag.addLyrics(lyrics, desc, lang); retval |= 1; except ValueError: printError("Invalid Lyrics; see --help: %s" % l); retval &= 0; if self.opts.remove_images: count = tag.removeImages() printWarning("Removing %d image frames" % count); retval |= 1; elif self.opts.images: for i in self.opts.images: img_args = i.split(self.opts.field_delim) if len(img_args) < 2: raise TagException("Invalid --add-image argument: %s" % i); else: ptype = eyeD3.frames.ImageFrame.stringToPicType(img_args[1]); path = img_args[0]; if not path: printWarning("Removing image %s" % path); tag.addImage(ptype, None, None); else: printWarning("Adding image %s" % path); desc = u""; if (len(img_args) > 2) and img_args[2]: desc = unicode(img_args[2]); tag.addImage(ptype, path, desc); retval |= 1; # GEOB frames if self.opts.objects: for i in self.opts.objects: obj_args = i.split(self.opts.field_delim) if len(obj_args) < 1: raise TagException("Invalid --add-object argument: %s" % i); else: path = obj_args[0]; desc = u""; if (len(obj_args) > 1) and obj_args[1]: desc = unicode(obj_args[1]); if not path: printWarning("Removing object %s" % desc); tag.addObject(None, None, desc, None); else: mime = ""; filename = None; if (len(obj_args) > 2) and obj_args[2]: mime = obj_args[2]; if (len(obj_args) > 3): filename = unicode(obj_args[3]); printWarning("Adding object %s" % path); tag.addObject(path, mime, desc, filename); retval |= 1; if self.opts.textFrames or self.opts.userTextFrames: for tf in self.opts.textFrames: tf_args = tf.split(self.opts.field_delim, 1) if len(tf_args) < 2: raise TagException("Invalid --set-text-frame argument: "\ "%s" % tf) else: if tf_args[1]: printWarning("Setting %s frame to '%s'" % (tf_args[0], tf_args[1])); else: printWarning("Removing %s frame" % (tf_args[0])); try: tag.setTextFrame(tf_args[0], tf_args[1]); retval |= 1; except FrameException, ex: printError(ex); retval &= 0; for tf in self.opts.userTextFrames: tf_args = tf.split(self.opts.field_delim, 1) if len(tf_args) < 2: raise TagException("Invalid --set-user-text-frame argument: "\ "%s" % tf) else: if tf_args[1]: printWarning("Setting '%s' TXXX frame to '%s'" %\ (tf_args[0], tf_args[1])) else: printWarning("Removing '%s' TXXX frame" % (tf_args[0])) try: tag.addUserTextFrame(tf_args[0], tf_args[1]) retval |= 1 except FrameException, ex: printError(ex) retval &= 0 if self.opts.url_frames or self.opts.user_url_frames: # Make a list of tuples (is_user_frame, arg) frames = [(f in self.opts.user_url_frames, f) for f in (self.opts.url_frames + self.opts.user_url_frames)] for user_frame, f in frames: args = f.split(self.opts.field_delim, 1) if len(args) < 2: raise TagException("Invalid argument: %s" % f) desc, fid, url = None, None, None if user_frame: # FIXME fid = "WXXX" desc, url = args else: fid, url = args if url: printWarning("Setting %s frame to '%s'" % (fid, url)) else: printWarning("Removing %s frame" % fid) try: if not user_frame: tag.setURLFrame(fid, url) else: tag.addUserURLFrame(desc, url) retval |= 1 except FrameException, ex: printError(ex) retval &= 0 if self.opts.textEncoding: e = self.opts.textEncoding; if e == "latin1": enc = LATIN1_ENCODING; elif e == "utf8": enc = UTF_8_ENCODING; elif e == "utf16-BE": enc = UTF_16BE_ENCODING; elif e == "utf16-LE": enc = UTF_16_ENCODING; else: raise TagException("Invalid encoding: %s" % (e)); tag.setTextEncoding(enc); unique_file_ids = self.opts.unique_file_ids; if unique_file_ids: for ufid in unique_file_ids: try: sep = ufid.rfind(self.opts.field_delim) if sep < 0: raise ValueError() owner_id = ufid[:sep] id = ufid[sep + 1:] if not owner_id: raise ValueError(); if not id: printWarning("Removing unique file ID: %s" % owner_id); else: printWarning("Setting unique file ID: [%s]: %s" %\ (owner_id, id)); tag.addUniqueFileID(owner_id, id); retval |= 1; except ValueError: printError("Invalid unique file id argument; see --help: %s" %\ ufid); retval &= 0; return retval; def handleRenames(self, f, pattern, fs_encoding): try: name = f.getTag().tagToString(pattern); printWarning("Renaming file to '%s'" % (name.encode(fs_encoding, 'replace'))); f.rename(name, fs_encoding); except TagException, ex: printError(ex); def boolToStatus(self, b): if b: return "SUCCESS"; else: return "FAIL"; def printHeader(self, filePath): fileSize = os.stat(filePath)[ST_SIZE] size_str = eyeD3.utils.format_size(fileSize) print ""; print "%s\t%s[ %s ]%s" % (boldText(os.path.basename(filePath), colors["header"]), colors["header"], size_str, colors["normal"]); print ("-" * 79); def printAudioInfo(self, audioInfo): if isinstance(audioInfo, eyeD3.Mp3AudioFile): print boldText("Time: ") +\ "%s\tMPEG%d, Layer %s\t[ %s @ %s Hz - %s ]" %\ (audioInfo.getPlayTimeString(), audioInfo.header.version, "I" * audioInfo.header.layer, audioInfo.getBitRateString(), audioInfo.header.sampleFreq, audioInfo.header.mode); print ("-" * 79); else: # Handle what it is known and silently ignore anything else. pass; def printTag(self, tag): if isinstance(tag, eyeD3.Tag): printMsg("ID3 %s:" % tag.getVersionStr()); printMsg("%s: %s\t\t%s: %s" % (boldText("title"), tag.getTitle().encode(ENCODING, "replace"), boldText("artist"), tag.getArtist().encode(ENCODING, "replace"))); printMsg("%s: %s\t\t%s: %s" % (boldText("album"), tag.getAlbum().encode(ENCODING, "replace"), boldText("year"), tag.getYear())); trackStr = ""; (trackNum, trackTotal) = tag.getTrackNum(); if trackNum != None: trackStr = str(trackNum); if trackTotal: trackStr += "/%d" % trackTotal; genre = None; try: genre = tag.getGenre(); except eyeD3.GenreException, ex: printError(ex); genreStr = ""; if genre: genreStr = "%s: %s (id %s)" % (boldText("genre"), genre.getName(), str(genre.getId())); printMsg("%s: %s\t\t%s" % (boldText("track"), trackStr, genreStr)); # TPOS discStr = "" (discNum, discTotal) = tag.getDiscNum() if discNum != None: discStr = str(discNum) if discTotal: discStr += "/%d" % discTotal printMsg("%s: %s" % (boldText("disc"), discStr)) # PCNT play_count = tag.getPlayCount(); if play_count != None: printMsg("%s %d" % (boldText("Play Count:"), play_count)); # TBPM bpm = tag.getBPM(); if bpm != None: printMsg("%s %d" % (boldText("BPM:"), bpm)); # TPUB pub = tag.getPublisher(); if pub != None: printMsg("%s %s" % (boldText("Publisher/label:"), pub)); # UFID unique_file_ids = tag.getUniqueFileIDs(); if unique_file_ids: for ufid in unique_file_ids: printMsg("%s [%s] %s" % (boldText("Unique File ID:"), ufid.owner_id, ufid.id)); # COMM comments = tag.getComments(); for c in comments: cLang = c.lang; if cLang == None: cLang = ""; cDesc = c.description; if cDesc == None: cDesc = ""; cText = c.comment; printMsg("%s: [Description: %s] [Lang: %s]\n%s" %\ (boldText("Comment"), cDesc, cLang, cText.encode(ENCODING,"replace"))); # USLT lyrics = tag.getLyrics(); for l in lyrics: lLang = l.lang; if lLang == None: lLang = ""; lDesc = l.description; if lDesc == None: lDesc = ""; lText = l.lyrics.replace('\r', '\n'); printMsg("%s: [Description: %s] [Lang: %s]\n%s" %\ (boldText("Lyrics"), lDesc, lLang, lText.encode(ENCODING,"replace"))); userTextFrames = tag.getUserTextFrames(); if userTextFrames: print ""; for f in userTextFrames: desc = f.description; if not desc: desc = ""; text = f.text; print "%s: [Description: %s]\n%s" % \ (boldText("UserTextFrame"), desc, text.encode(ENCODING,"replace")); urls = tag.getURLs() if urls: print "" for u in urls: if u.header.id != eyeD3.frames.USERURL_FID: print "%s: %s" % (u.header.id, u.url) else: print "%s [Description: %s]: %s" % (u.header.id, u.description, u.url) images = tag.getImages(); if images: print ""; for img in images: print "%s: [Size: %d bytes] [Type: %s]" % \ (boldText(img.picTypeToString(img.pictureType) + " Image"), len(img.imageData), img.mimeType); print "Description: %s" % img.description; print "\n"; if self.opts.writeImages: img_path = self.opts.writeImages + os.sep; if not os.path.exists(img_path): raise CommandException("Directory does not exist: %s" %\ img_path); img_file = img.getDefaultFileName(); count = 1; while os.path.exists(img_path + img_file): img_file = img.getDefaultFileName(str(count)); count += 1; printWarning("Writing %s..." % (img_path + img_file)); img.writeFile(img_path, img_file); objects = tag.getObjects(); if objects: print ""; for obj in objects: print "%s: [Size: %d bytes] [Type: %s]" % \ (boldText("GEOB"), len(obj.objectData), obj.mimeType); print "Description: %s" % obj.description; print "Filename: %s" % obj.filename; print "\n"; if self.opts.writeObjects: obj_path = self.opts.writeObjects + os.sep; if not os.path.exists(obj_path): raise CommandException("Directory does not exist: %s" %\ obj_path); obj_file = obj.getDefaultFileName(); count = 1; while os.path.exists(obj_path + obj_file): obj_file = obj.getDefaultFileName(str(count)); count += 1; printWarning("Writing %s..." % (obj_path + obj_file)); obj.writeFile(obj_path, obj_file); else: raise TypeError("Unknown tag type: " + str(type(tag))); class LameTagDriver(EyeD3Driver): def __init__(self, opts): EyeD3Driver.__init__(self, opts) def handleFile(self, f): TagDriverBase.handleFile(self, f) self.printHeader(f); if not self.audioFile or not self.audioFile.lameTag: printMsg('No LAME Tag') return self.R_CONT format = '%-20s: %s' lt = self.audioFile.lameTag if not lt.has_key('infotag_crc'): try: printMsg('%s: %s' % ('Encoder Version', lt['encoder_version'])) except KeyError: pass return self.R_CONT values = [] values.append(('Encoder Version', lt['encoder_version'])) values.append(('LAME Tag Revision', lt['tag_revision'])) values.append(('VBR Method', lt['vbr_method'])) values.append(('Lowpass Filter', lt['lowpass_filter'])) if lt.has_key('replaygain'): try: peak = lt['replaygain']['peak_amplitude'] db = 20 * math.log10(peak) val = '%.8f (%+.1f dB)' % (peak, db) values.append(('Peak Amplitude', val)) except KeyError: pass for type in ['radio', 'audiofile']: try: gain = lt['replaygain'][type] name = '%s Replay Gain' % gain['name'].capitalize() val = '%s dB (%s)' % (gain['adjustment'], gain['originator']) values.append((name, val)) except KeyError: pass values.append(('Encoding Flags', ' '.join((lt['encoding_flags'])))) if lt['nogap']: values.append(('No Gap', ' and '.join(lt['nogap']))) values.append(('ATH Type', lt['ath_type'])) values.append(('Bitrate (%s)' % lt['bitrate'][1], lt['bitrate'][0])) values.append(('Encoder Delay', '%s samples' % lt['encoder_delay'])) values.append(('Encoder Padding', '%s samples' % lt['encoder_padding'])) values.append(('Noise Shaping', lt['noise_shaping'])) values.append(('Stereo Mode', lt['stereo_mode'])) values.append(('Unwise Settings', lt['unwise_settings'])) values.append(('Sample Frequency', lt['sample_freq'])) values.append(('MP3 Gain', '%s (%+.1f dB)' % (lt['mp3_gain'], lt['mp3_gain'] * 1.5))) values.append(('Preset', lt['preset'])) values.append(('Surround Info', lt['surround_info'])) values.append(('Music Length', '%s' % format_size(lt['music_length']))) values.append(('Music CRC-16', '%04X' % lt['music_crc'])) values.append(('LAME Tag CRC-16', '%04X' % lt['infotag_crc'])) for v in values: printMsg(format % (v)) ################################################################################ def main(): progname = os.path.basename(sys.argv[0]) # Process command line. optParser = getOptionParser(); (options, args) = optParser.parse_args(); # Handle -l, --list-genres if options.showGenres: printGenres(); return 0; # Handle --list-image-types if options.showImagesTypes: printImageTypes(); return 0; if len(args) == 0: optParser.error("File/directory argument(s) required"); return 1; if options.jep_118: # Handle --jep-118 app = JEP118Driver(options); elif options.rfc822: app = Rfc822Driver(options); elif options.nfo: # Handle --nfo app = NFODriver(options); elif options.lametag: # Handle --lametag app = LameTagDriver(options) else: # Main driver if options.debug: # Handle --debug eyeD3.utils.TRACE = 1; if options.strict: # Handle --strict eyeD3.utils.STRICT_ID3 = 1; if options.itunes: # Handle --itunes eyeD3.utils.ITUNES_COMPAT = 1 if options.nocolor: # Handle --nocolor colors.enabled(0); app = EyeD3Driver(options); # Process files/directories for f in args: if os.path.isfile(f): retval = app.handleFile(f); elif os.path.isdir(f): fwalker = FileWalker(app, f); retval = fwalker.go(); else: printError("File Not Found: %s" % f); retval = 1; return retval; ####################################################################### if __name__ == "__main__": retval = 0 profiling = False profile_out = None try: if "--run-profiler" in sys.argv: profiling = True profile_out = 'eyeD3-profiler.out' import profile profile.run('main()', profile_out) else: retval = main(); except KeyboardInterrupt: retval = 0 except Exception, ex: import traceback print >>sys.stderr, "Uncaught exception:", str(ex) print >>sys.stderr, traceback.format_exc() retval = 1 finally: if profiling: import pstats p = pstats.Stats(profile_out) p.sort_stats('cumulative').print_stats(100) sys.exit(retval) eyeD3-0.6.18/doc/0000755000175000017500000000000011663615630012735 5ustar travistraviseyeD3-0.6.18/doc/eyeD3.1.in0000644000175000017500000001721711663615630014405 0ustar travistravis.TH EYED3 "1" "@MANPAGE_DATE@" "eyeD3 @PACKAGE_VERSION@" "" .SH "NAME" .B eyeD3 \- displays and manipulates id3-tags on mp3 files .SH "SYNOPSIS" .B eyeD3 .RI [ options ] .RI file .RI [ file... ] .br .B eyeD3 .RI [ options ] .RI dir .RI [ dir... ] .br .SH "DESCRIPTION" .B eyeD3 Manipulates ID3 tags in mp3 files and is able to read/write and convert between ID3 v1.0, v1.1, v2.3 and v2.4 tags. High-level access is provided to most frames, including APIC (i.e., images) frames. .SH "OPTIONS" \fB\-h, \fB\-\-help\fR Shows a brief help string and exit. .TP \fB\-\-version\fR Show program's version number and exit. .TP \fB\-\-no\-color\fR Disable color output. .TP \fB\-v\fR, \fB\-\-verbose\fR Show all available tag information. .TP \fB\-\-debug\fR Trace program execution; for debugging. .SH "TAG VERSIONS" .TP \fB\-1, \fB\-\-v1\fR Only read/write ID3 v1 tags. By default, v1 tags are only read if there is not a v2 tag. .TP \fB\-2, \fB\-\-v2\fR Only read/write ID3 v2.x tags. .TP \fB\-\-to\-v1.1\fR Convert the file's tag to ID3 v1.1. (Or 1.0 if there is no track number) .TP \fB\-\-to\-v2.3\fR Convert the file's tag to ID3 v2.3. .TP \fB\-\-to\-v2.4\fR Convert the file's tag to ID3 v2.4 .SH "TAG DATA" Options for manipulating tag data have the side-effect of removing a frame when the data specified is the empty string. For example, --artist="" (or -a "") would cause the artist frame to be removed. This is especially useful for removing comment frames. .TP \fB-a \fRSTRING, \fB\-\-artist=\fRSTRING\fR Set artist the to STRING. .TP \fB-A \fRSTRING, \fB\-\-album=\fRSTRING\fR Set album to STRING. .TP \fB-t \fRSTRING, \fB\-\-title=\fRSTRING\fR Set track title to STRING. .TP \fB-n \fRNUM, \fB\-\-track=\fRNUM\fR Set track number to NUM. .TP \fB-N \fRNUM, \fB\-\-track\-total=\fRNUM\fR Set total number of tracks to NUM. .TP \fB-G \fRSTRING, \fB\-\-genre=\fRSTRING\fR Sets genre to STRING. (See --list-genres) .TP \fB-Y \fRYEAR, \fB\-\-year=\fRYEAR\fR Set the four digit year. .TP \fB-p \fRSTRING, \fB\-\-publisher=\fRSTRING\fR Set the publisher/label text. .TP \fB\-\-comment=\fR[LANGUAGE]:DESCRIPTION:COMMENT\fR Add (or remove when COMMENT is "") a comment. Note that the argument value MUST always contain exactly two ':' characters to delimit the fields even when the default language is being used. The DESCRIPTION string is the comment title. The optional LANGUAGE string MUST be a three-character ISO 639 language code. The default is "eng" for English. A tag may not have more than one comment frame with the same DESCRIPTION and LANGUAGE values. .TP \fB\-\-remove-comments\fR Remove all comment frames from the tag. .TP \fB\-\-lyrics=\fR[LANGUAGE]:DESCRIPTION:LYRICS\fR Add (or remove when LYRICS is "") a comment. Note that the argument value MUST always contain exactly two ':' characters to delimit the fields even when the default language is being used. The DESCRIPTION string is the lyrics title. The optional LANGUAGE string MUST be a three-character ISO 639 language code. The default is "eng" for English. A tag may not have more than one lyrics frame with the same DESCRIPTION and LANGUAGE values. .TP \fB\-\-remove-lyrics\fR Remove all lyrics frames from the tag. .TP \fB\-\-add\-image=\fRIMG_PATH:TYPE[:DESCRIPTION]\fR Add an image to the tag (APIC frame). The IMG_PATH is the image file to add to the tag. If the path value is empty the image frame with a value of TYPE is removed from the tag. The TYPE is one of the types listed when 'eyeD3 --list-image-types' is run. The DESCRIPTION is optional and will default to "". A tag may not have more than one image frame with the same TYPE values. .TP \fB\-\-remove-images\fR Remove all image (APIC) frames from the tag. .TP \fB\-\-add\-object=\fROBJ_PATH[:DESCRIPTION[:MIME-TYPE[:FILENAME]]]\fR Add an encapsulated object to the tag (GEOB frame). The description and filename are optional, but when used, the ':' delimiters must be present. If the OBJ_PATH value is empty the GEOB frame with DESCRIPTION is removed. A tag may not have more than one object (GEOB) frame with the same DESCRIPTION values. .TP \fB\-i \fRDIR, \fB\-\-write\-images=\fRDIR\fR Causes all attached images (APIC frames) to be written to the specified directory. They are named by their "image type". For example, if the image is a png and the type is OTHER, a file named OTHER.png is written to the desired directory. If the file name already exist, a unique numeric value is appended to the type string. .TP \fB\-o \fRDIR, \fB\-\-write\-objects=\fRDIR\fR Causes all attached objects (GEOB frames) to be written to the specified directory. The files are written with the name "." in the specified directory. .TP \fB\-\-set\-text\-frame=\fRFID:TEXT\fR Set the value of a specific (i.e., FID) text frame. To remove the frame, specify an empty value. e.g., --set-text-frame="TDRC:" .TP \fB\-\-set\-user\-text\-frame=\fRDESC:TEXT\fR Set the value of a TXXX text frame with a unique description. To remove the frame, specify an empty value. e.g., --set-user-text-frame="Description:" .TP \fB\-\-set\-url\-frame=\fRFID:URL\fR Set the value of a specific (i.e., FID) URL frame. To remove the frame, specify an empty URL. e.g., --set-url-frame="WCOM:" .TP \fB\-\-set\-user\-url\-frame=\fRDESC:URL\fR Set the value of a WXXX frame with unique description string. To remove the frame, specify an empty URL. e.g., --set-user-url-frame="Description:" .TP \fB\-\-play\-count=\fR[+]N\fR Set the play count (PCNT). If the argument value begins with '+' the value is incremented by N, otherwise it is set to exactly N. .TP \fB\-\-bpm=\fRN\fR Set the beats per minute value. The value MUST be greater than 0. .TP \fB\-\-unique\-file-id=\fROWNER_ID:ID\fR Add a UFID frame. If the ID arg is empty the UFID frame with OWNER_ID is removed. An OWNER_ID MUST be specified. .TP \fB\-\-set\-encoding=\fRlatin1|utf8|utf16-LE|utf16-BE\fR Set the encoding that is used for _all_ text frames in the tag. The encoding is applied only when the tag is updated, therefore a frame must be set or --force-update is present. Note that, unfortunately, utf8 is not supported by ID3 v2.3 tags. .TP \fB\-\-remove-v1\fR Remove ID3 v1.x tag. .TP \fB\-\-remove-v2\fR Remove ID3 v2.x tag. .TP \fB\-\-remove-all\fR Remove all tags. .SH "MISC. OPTIONS" .TP \fB\-\-rename=\fRPATTERN\fR Rename the file based on PATTERN which may contain the following substitution variables: %A (artist), %a (album), %t (title), %n (track number), and %N (the total track count). The PATTERN string MUST not contain the file name extenstion. .TP \fB\-\-fs\-encoding=\fRENCODING\fR Use the specified character encoding for filenames when renaming files. The default value is iso-8859-1. .TP \fB\-l\fR, \fB\-\-list\-genres\fR Display the table of "valid" ID3 genres and exit. .TP \fB\-\-list\-image\-types\fR List all possible image types for APIC frames. .TP \fB\-\-strict\fR Fail for tags that violate the ID3 specification. .TP \fB\-\-jep\-118\fR Output the tag per the format described in JEP-0118. See http://www.xmpp.org/extensions/xep-0118.html .TP \fB\-\-nfo\fR Output NFO information for each album directory. .TP \fB\-\-lametag\fR Prints the LAME Tag. .TP \fB\-\-force\-update\fR Update the tag regardless of whether any frames were set on the command line. .TP \fB\-\-no\-zero\-padding\fR Don't pad track or disc numbers with 0's. .TP \fB\-\-no\-tagging\-time\-frame\fR When saving tags do not add a TDTG (tagging time) frame. .TP \fB-F \fRDELIM\fR Specify a new delimiter for option values that contain multiple fields (default delimiter is ':') .SH SEE ALSO http://eyed3.nicfit.net/ .SH AUTHOR eyeD3 was written by Travis Shirk . This manpage was written by Alexander Wirt for the Debian Distribution and Travis Shirk. eyeD3-0.6.18/Makefile.in0000644000175000017500000001223611663615630014241 0ustar travistravis# # Copyright (C) 2002-2004 Travis Shirk # # 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 2 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # DIST_NAME=@PACKAGE_TARNAME@-@PACKAGE_VERSION@ DIST_TAR=${DIST_NAME}.tar DIST_GZ=${DIST_TAR}.gz DIST_RPM=${DIST_NAME}-@RPM_RELEASE@.noarch.rpm DIST_SRPM=${DIST_NAME}-@RPM_RELEASE@.src.rpm DIST_WWW=${DIST_NAME}-www.tar.gz PYTHON=@PYTHON@ RPM_DIR=/usr/local/rpm EBUILD_VERSION=@EBUILD_VERSION@ ifdef RPM_BUILD_ROOT DESTDIR=${RPM_BUILD_ROOT} endif ifdef DESTDIR SETUP_ARGS=--root ${DESTDIR} endif prefix=@prefix@ exec_prefix:=@exec_prefix@ bindir:=$(subst //,/,${DESTDIR}/@bindir@) datarootdir:=$(subst //,/,${DESTDIR}/@datarootdir@) mandir:=$(subst //,/,${DESTDIR}/@mandir@) datadir:=$(subst //,/,${DESTDIR}/@datadir@) docdir:=$(subst //,/,${DESTDIR}/@datadir@/doc/${DIST_NAME}) # Redefine these prefix:=$(subst //,/,${DESTDIR}/${prefix}) exec_prefix:=$(subst //,/,${DESTDIR}/${exec_prefix}) .PHONY: all install clean dist-clean maintainer-clean dist changelog \ tags TAGS rpm rpm-clean release docs docs-clean \ www push-www all: module docs module: ${PYTHON} setup.py build install: ${PYTHON} setup.py install ${SETUP_ARGS} install -m 755 -d ${bindir} install -m 755 bin/eyeD3 ${bindir} install -m 755 -d ${docdir} install -m 644 README ${docdir} install -m 644 AUTHORS ${docdir} install -m 644 COPYING ${docdir} gzip -f -9 ${docdir}/COPYING install -m 644 ChangeLog ${docdir} gzip -f -9 ${docdir}/ChangeLog install -m 755 -d ${mandir}/man1 install -m 644 doc/eyeD3.1 ${mandir}/man1 gzip -f -9 ${mandir}/man1/eyeD3.1 @# @# NOTE: When adding new files, the eyeD3.spec.in file and @# the uninstall target must be updated as well. @# clean: -rm -rf build find . -name \*.pyc -exec rm '{}' \; dist-clean: clean -rm -rf autom4te*.cache ${DIST_NAME} ${DIST_GZ} ${DIST_WWW} ${DIST_RPM} ${DIST_SRPM} ${DIST_WWW} -rm setup.py -rm doc/eyeD3.1.gz -rm -f config.* *.bak -rm -rf src/config.h src/eyeD3/__init__.py -find . -name \*.pyc -exec rm '{}' \; -rm Makefile maintainer-clean: docs-clean -rm -f configure src/config.h.in -rm etc/eyeD3.spec -rm doc/eyeD3.1 -rm README.t2t ${MAKE} dist-clean dist: docs dist-clean mkdir ${DIST_NAME} cp ChangeLog AUTHORS COPYING README README.html TODO NEWS\ INSTALL ${DIST_NAME} cp acsite.m4 configure setup.py.in Makefile.in ${DIST_NAME} mkdir ${DIST_NAME}/etc cp etc/eyeD3.spec.in ${DIST_NAME}/etc cp etc/gentoo/eyeD3-${EBUILD_VERSION}.ebuild ${DIST_NAME}/etc mkdir ${DIST_NAME}/src cp -r src/eyeD3 ${DIST_NAME}/src mkdir ${DIST_NAME}/bin cp bin/eyeD3 ${DIST_NAME}/bin mkdir ${DIST_NAME}/doc cp doc/eyeD3.1.in ${DIST_NAME}/doc find ${DIST_NAME} -type d -name .svn -print | xargs rm -rf find ${DIST_NAME} -type d -name ui -print | xargs rm -rf tar cf ${DIST_TAR} ${DIST_NAME} gzip ${DIST_TAR} rm -rf ${DIST_NAME} ./autogen.sh # XXX: rpmbuild is failing, but I stopped caring about RPMs along time ago, # leaving it up to maintainers #release: dist rpm www sloccount release: dist www sloccount # Re-bootstap to undo dist-clean ./autogen.sh > /dev/null 2>&1 rpm: rpm-clean cp etc/eyeD3.spec ${RPM_DIR}/SPECS cp ${DIST_GZ} ${RPM_DIR}/SOURCES rpmbuild -ba ${RPM_DIR}/SPECS/eyeD3.spec cp ${RPM_DIR}/RPMS/noarch/${DIST_RPM} . cp ${RPM_DIR}/SRPMS/${DIST_SRPM} . rpm-clean: -rm ${RPM_DIR}/SPECS/eyeD3.spec -rm ${RPM_DIR}/SOURCES/${DIST_GZ} -rm -rf ${RPM_DIR}/BUILD/${DIST_NAME} -rm ${RPM_DIR}/RPMS/noarch/${DIST_RPM} -rm ${RPM_DIR}/SRPMS/${DIST_SRPM} changelog: svn2cl.sh -i --authors=.changelog_authors tags TAGS: @if test -f tags; then \ rm tags; \ fi @ctags -R --exclude='tmp/*' --exclude='build/*' docs: if test -f README.t2t; then\ ${MAKE} README;\ ${MAKE} README.html;\ fi README: README.t2t txt2tags -t txt --stdout README.t2t >| README README.html: README.t2t txt2tags -t html --stdout README.t2t >| README.html docs-clean: -rm README -rm README.html patch-clean: find . -name \*.rej | xargs rm find . -name \*.orig | xargs rm www: -rm -rf ./www -mkdir -p www/eyeD3/releases -mkdir www/eyeD3/releases/gentoo cp README.html ChangeLog COPYING NEWS TODO www/eyeD3 cd www/eyeD3 && ln -s README.html index.html cp ${DIST_GZ} www/eyeD3/releases cp etc/gentoo/eyeD3-${EBUILD_VERSION}.ebuild www/eyeD3/releases/gentoo #-mkdir www/eyeD3/releases/rpm #cp ${DIST_RPM} www/eyeD3/releases/rpm #cp ${DIST_SRPM} www/eyeD3/releases/rpm tar czvf ${DIST_WWW} www rm -rf www push-www: scp ${DIST_WWW} nicfit:. ssh nicfit 'tar xzvf ${DIST_WWW}' sloccount: sloccount ./src ./bin sloccount --cached --details ./src ./bin eyeD3-0.6.18/README.html0000644000175000017500000002567511663615630014032 0ustar travistravis eyeD3 0.6.18

eyeD3 0.6.18

Travis Shirk <travis@pobox.com>
11.24.2011

General Information

eyeD3 is a Python module and program for processing ID3 tags. Information about mp3 files (i.e bit rate, sample frequency, play time, etc.) is also provided. The formats supported are ID3 v1.0/v1.1 and v2.3/v2.4.

The current stable version is 0.6.18. It and earlier versions are available here.

See the NEWS and/or ChangeLog for changes.

Requirements

Installation

Source tarball

     gzip -dc eyeD3-0.6.18.tar.gz | tar xvf -
     cd eyeD3-0.6.18
     ./configure
     make
     make install (as root)

Gentoo

     emerge eyeD3
See here for more info.

Ubuntu

See here for more info.

Debian

See here for more info.

Suse

See here for more info.

Slackware

See linuxpackages.net for more info.

Solaris SPARC and x86

Packages available from Blastwave.

How to Use eyeD3

The 'eyeD3' utility program can perform most ID3 tasks, and it is also the best example of how to use the API. It supports the following features:
  
  Usage
  =====
    eyeD3 [OPTS] file [file...]
  
  Options
  =======
    --version             show program's version number and exit
    -h, --help            show this help message and exit
  
  Tag Versions
  ------------
      -1, --v1            Only read/write ID3 v1.x tags. By default, v1.x tags
                          are only read if there is not a v2.x tag.
      -2, --v2            Only read/write ID3 v2.x tags.
      --to-v1.1           Convert the file's tag to ID3 v1.1. (Or 1.0 if there is
                          no track number.)
      --to-v2.3           Convert the file's tag to ID3 v2.3
      --to-v2.4           Convert the file's tag to ID3 v2.4
  
  Tag Data
  --------
      -a STRING, --artist=STRING
                          Set artist
      -A STRING, --album=STRING
                          Set album
      -t STRING, --title=STRING
                          Set title
      -n NUM, --track=NUM
                          Set track number
      -N NUM, --track-total=NUM
                          Set total number of tracks
      -G GENRE, --genre=GENRE
                          Set genre. The argument is a valid genre string or
                          number.  See --list-genres
      -d NUM, --disc=NUM  Set disc number
      -D NUM, --disc-total=NUM
                          Set total number of discs
      -Y STRING, --year=STRING
                          Set a four digit year.
      -c [LANGUAGE]:[DESCRIPTION]:COMMENT, --comment=[LANGUAGE]:[DESCRIPTION]:COMMENT
                          Set comment
      -L [LANGUAGE]:[DESCRIPTION]:LYRICS, --lyrics=[LANGUAGE]:[DESCRIPTION]:LYRICS
                          Set lyrics
      -p STRING, --publisher=STRING
                          Set the publisher/label text
      --remove-comments   Remove all comment frames.
      --remove-lyrics     Remove all lyrics frames.
      --add-image=IMG_PATH:TYPE[:DESCRIPTION]
                          Add an image to the tag.  The description and type
                          optional, but when used, both ':' delimiters must be
                          present.  The type MUST be an string that corresponds
                          to one given with --list-image-types. If the IMG_PATH
                          value is empty the APIC frame with TYPE is removed.
      --remove-images     Remove all image (APIC) frames.
      --add-object=OBJ_PATH[:DESCRIPTION[:MIME-TYPE[:FILENAME]]
                          Add an encapsulated object to the tag.  The description
                          and filename are optional, but when used, the ':'
                          delimiters must be present.  If the OBJ_PATH value is
                          empty the GEOB frame with DESCRIPTION is removed.
      -i DIR, --write-images=DIR
                          Causes all attached images (APIC frames) to be written
                          to the specified directory.
      -o DIR, --write-objects=DIR
                          Causes all attached objects (GEOB frames) to be written
                          to the specified directory.
      --set-text-frame=FID:TEXT
                          Set the value of a text frame.  To remove the frame,
                          specify an empty value.  e.g., --set-text-frame="TDRC:"
      --set-user-text-frame=DESC:TEXT
                          Set the value of a user text frame (i.e., TXXX). To
                          remove the frame, specify an empty value.  e.g., --set-
                          user-text-frame="SomeDesc:"
      --set-url-frame=FID:URL
                          Set the value of a URL frame.  To remove the frame,
                          specify an empty value.  e.g., --set-url-frame="WCOM:"
      --set-user-url-frame=DESC:URL
                          Set the value of a user URL frame (i.e., WXXX). To
                          remove the frame, specify an empty value.  e.g., --set-
                          user-url-frame="SomeDesc:"
      --play-count=[+]N   If this argument value begins with '+' the tag's play
                          count (PCNT) is incremented by N, otherwise the value
                          is set to exactly N.
      --bpm=N             Set the beats per minute value.
      --unique-file-id=OWNER_ID:ID
                          Add a UFID frame.  If the ID arg is empty the UFID
                          frame with OWNER_ID is removed.  An OWNER_ID MUST be
                          specified.
      --set-encoding=latin1|utf8|utf16-BE|utf16-LE
                          Set the encoding that is used for _all_ text frames.
                          This only takes affect when the tag is updated as the
                          result of a frame value being set with another option
                          (e.g., --artist=) or --force-update is present.
      --remove-v1         Remove ID3 v1.x tag.
      --remove-v2         Remove ID3 v2.x tag.
      --remove-all        Remove both ID3 v1.x and v2.x tags.
  
  Misc. Options
  -------------
      --rename=NAME       Rename file (the extension is not affected) based on
                          data in the tag using substitution variables: %A
                          (artist), %a (album), %t (title), %n (track number),
                          and %N (total number of tracks)
      --fs-encoding=ENCODING
                          Use the specified character encoding for the filename
                          when renaming files
      -l, --list-genres   Display the table of ID3 genres and exit
      --list-image-types  List all possible image types
      --strict            Fail for tags that violate the ID3 specification.
      --itunes            Store tags in an iTunes compatible way.
      --jep-118           Output the tag per the format described in JEP-0118.
                          See http://www.xmpp.org/extensions/xep-0118.html
      --rfc822            Output the tag in RFC822-like format
      --nfo               Output NFO information.
      --lametag           Prints the LAME Tag.
      --force-update      Update the tag regardless of whether any frames are set
                          with new values.
      --no-color          Disable color output
      --no-zero-padding   Don't pad track or disc numbers with 0's
      --no-tagging-time-frame
                          When saving tags do not add a TDTG (tagging time) frame
      -F DELIM            Specify a new delimiter for option values that contain
                          multiple fields (default delimiter is ':')
      -v, --verbose       Show all available information
      --debug             Trace program execution.
      --run-profiler      Run using python profiler.

Some simple programming examples follow here, excluding any error handling, of course :)

Reading the contents of an mp3 file containing either v1 or v2 tag info:

     import eyeD3
     tag = eyeD3.Tag()
     tag.link("/some/file.mp3")
     print tag.getArtist()
     print tag.getAlbum()
     print tag.getTitle()

Read an mp3 file (track length, bitrate, etc.) and access it's tag:

  if eyeD3.isMp3File(f):
     audioFile = eyeD3.Mp3AudioFile(f)
     tag = audioFile.getTag()

Specific tag versions can be selected:

     tag.link("/some/file.mp3", eyeD3.ID3_V2)
     tag.link("/some/file.mp3", eyeD3.ID3_V1)
     tag.link("/some/file.mp3", eyeD3.ID3_ANY_VERSION)  # The default.

Or you can iterate over the raw frames:

     tag = eyeD3.Tag()
     tag.link("/some/file.mp3")
     for frame in tag.frames:
        print frame

Once a tag is linked to a file it can be modified and saved:

     tag.setArtist(u"Cro-Mags")
     tag.setAlbum(u"Age of Quarrel")
     tag.update()

If the tag linked in was v2 and you'd like to save it as v1:

     tag.update(eyeD3.ID3_V1_1)

Read in a tag and remove it from the file:

     tag.link("/some/file.mp3")
     tag.remove()
     tag.update()

Add a new tag:

     tag = eyeD3.Tag()
     tag.link('/some/file.mp3')    # no tag in this file, link returned False
     tag.header.setVersion(eyeD3.ID3_V2_3)
     tag.setArtist('Fugazi')
     tag.update()

Support

eyeD3 now has a mailing list. Send a message to <eyed3-devel-subscribe@nicfit.net> to subscribe.

Bugs and Patches

Find bugs! Please submit all comments, bug reports, or feature requests to Travis Shirk <travis@pobox.com>. Those of the patch variety are especially welcome :)

See Also

eyeD3 is free software, refer to the COPYING file for details.

See the TODO file for possible enhancements.

eyeD3-0.6.18/ChangeLog0000644000175000017500000003610711663615630013751 0ustar travistravis2011-11-25 03:29 'Travis Shirk ' * [r1095] bin/eyeD3, src/eyeD3/tag.py: Proper xml escaping for --jep-118 output and a new RFC 822 output format (--rfc822). Thanks to Neil Schemenauer 2011-11-25 02:28 'Travis Shirk ' * [r1094] bin/eyeD3: Updated copyright year 2011-11-25 02:27 'Travis Shirk ' * [r1093] Makefile.in: Fixed autoconf warning about using datarootdir 2011-11-24 07:21 'Travis Shirk ' * [r1091] patches/fix-unexplained-ascii-codec-conversion.patch, patches/metamp3: Added some patches for future review 2011-11-24 07:00 'Travis Shirk ' * [r1090] src/eyeD3/tag.py: Allow delimited genre strings (, ; | etc.) 2011-11-24 07:00 'Travis Shirk ' * [r1089] bin/eyeD3, src/eyeD3/frames.py: Bug fixes 2011-11-24 06:15 'Travis Shirk ' * [r1088] README.t2t.in, src/eyeD3/frames.py, src/eyeD3/tag.py, src/eyeD3/utils.py: Use of the 'magic' module for better mime-type guessing (it will open the file) when the standard mimetype module fails simply using the filename. Ville Skyttä ville.skytta@iki.fi via bounce2.pobox.com 5/25/10 Hi Travis, Attached is a patch for eyeD3 that improves its MIME type guessing, using libmagic python bindings if available for content based guessing when the standard mimetypes module fails to return a type. By the way I see the eyeD3 homepage says Python >= 2.5 is required, but README says Python >= 2.3, maybe you'll want to correct one of those. 2011-11-24 06:10 'Travis Shirk ' * [r1087] Makefile.in: Added patch-clean 2011-11-24 05:24 'Travis Shirk ' * [r1086] src/eyeD3/tag.py: Do not clobber existing file names with rename 2011-11-24 04:54 'Travis Shirk ' * [r1085] src/eyeD3/tag.py: oops, debug prints removed 2011-11-24 04:46 'Travis Shirk ' * [r1084] bin/eyeD3, src/eyeD3/tag.py, src/eyeD3/utils.py: Itunes genre support, by Ben Isaacs (Ben XO me@ben-xo.com) This patch enables eyeD3 (from http://eyed3.nicfit.net/) to write genre fields that are always compatible with iTunes. Just add --itunes as an option when running the command line tool, or if you're using eyeD3 as a lib, set eyeD3.utils.ITUNES_COMPAT = 1 . iTunes ignores genre fields with a number above 125 (which happens to include 'Drum & Bass' - see why I'm suddenly interested?) The patch makes eyeD3 treat tags numbered 126+ as custom genres (by excluding the number, which is usually hidden from the user anyway). 2011-11-24 04:26 'Travis Shirk ' * [r1083] patches/id3_2_2.patch, patches/notag.patch, patches/stdin-datemadness.patch, patches/term.py, patches/text2date.patch, patches/utf16-delim.patch, patches/utf16.patch: Cleaned up patch queue 2011-11-24 03:57 'Travis Shirk ' * [r1082] bin/eyeD3, src/eyeD3/tag.py: Work around invalid bpm values (and some cleanup) 2011-11-24 03:24 'Travis Shirk ' * [r1081] patches/fs_encoding.patch: Already applied 2011-11-24 03:22 'Travis Shirk ' * [r1080] src/eyeD3/frames.py: Handle invalid user text data fields where only one part of the desc/text pair is present. Return it as the desc. 2009-12-29 01:30 'Travis Shirk ' * [r209] etc/gentoo/eyeD3-0.6.18.ebuild: Added 2009-12-19 22:57 'Travis Shirk ' * [r193] src/eyeD3/frames.py: Fixed slow read(1) loop when GEOB frames are malformed. Patch from Stephen Fairchild 2009-12-19 22:18 'Travis Shirk ' * [r192] bin/eyeD3, src/eyeD3/tag.py: Disc number support (TPOS) with -d and -D - Patch by Nathaniel Clark 2009-12-17 04:27 'Travis Shirk ' * [r189] README.t2t.in: Fixed blackwave link 2009-11-15 04:28 'Travis Shirk ' * [r188] src/eyeD3/tag.py: FATdrtop genre allowable 2009-11-15 04:24 'Travis Shirk ' * [r187] src/eyeD3/frames.py: Fixed log msg where len(None) was taken, thanks to Charles Cazabon 2009-11-15 04:07 'Travis Shirk ' * [r186] src/eyeD3/tag.py: Added %Y (year) and %G (genre) subst vars for file renames. Thanks to Otávio Pontes 2009-09-08 00:11 'Travis Shirk ' * [r185] bin/eyeD3: Allow setting new encoding and version conversion at once. eyeD3 --set-encoding=utf8 --to-v2.4 test.mp3 (e.g.) 2009-05-26 23:12 'Travis Shirk ' * [r156] configure.in: Version bump 2009-05-26 23:11 'Travis Shirk ' * [r155] src/eyeD3/frames.py: Fixed a formatting bug 2009-02-07 20:54 'Travis Shirk ' * [r154] .: svnmerge blocking 2009-02-07 20:52 'Travis Shirk ' * [r152] bin/eyeD3, src/eyeD3/frames.py, src/eyeD3/tag.py: Cleanup to make merging easier 2009-02-03 01:33 'Travis Shirk ' * [r150] NEWS, README.t2t.in: Stupid doc mistakes found after release, of course. 2009-02-02 03:54 'Travis Shirk ' * [r148] ChangeLog: Update for 0.6.7 2009-02-02 03:54 'Travis Shirk ' * [r147] NEWS: Relese updates. 2009-02-02 02:46 'Travis Shirk ' * [r146] etc/gentoo/eyeD3-0.6.17.ebuild: Added 2009-02-02 02:44 'Travis Shirk ' * [r145] src/eyeD3/frames.py: unicode wrapper to support bad unicode workarounds 2009-02-02 02:43 'Travis Shirk ' * [r144] src/eyeD3/tag.py: Allow '.' in genres 2009-01-26 02:03 'Travis Shirk ' * [r141] bin/eyeD3, doc/eyeD3.1.in, src/eyeD3/frames.py, src/eyeD3/tag.py: Support for URL frames (W***) and WXXX. 2009-01-22 06:18 'Travis Shirk ' * [r140] src/eyeD3/frames.py: fix for more invalid utf16 2008-10-18 21:10 'Travis Shirk ' * [r139] bin/eyeD3: Fixed NFO output bug.. losing one track per album. 2008-10-17 00:10 'Travis Shirk ' * [r137] acsite.m4, configure.in: Version bumped to 0.6.17 and better autoconf python checker 2008-10-12 23:57 'Travis Shirk ' * [r136] src/eyeD3/tag.py: Allow ' in genres, for example, Drun 'n' Bass 2008-10-12 23:41 'Travis Shirk ' * [r135] src/eyeD3/frames.py: Handle PLCT frames with less than 4 bytes of data whick is technically BAD TAG. 2008-10-12 23:28 'Travis Shirk ' * [r134] bin/eyeD3: Bug fix to show ALL genres with --list-genres 2008-10-12 23:03 'Travis Shirk ' * [r133] bin/eyeD3: non-zero exit status for exceptions. 2008-06-08 18:24 'Travis Shirk ' * [r129] ChangeLog, NEWS, etc/gentoo/eyeD3-0.6.16.ebuild: Release 0.6.16 updates 2008-06-08 17:32 'Travis Shirk ' * [r128] src/eyeD3/tag.py: Added removeUserTextFrame -- Patch by David Grant 2008-06-01 19:10 'Travis Shirk ' * [r125] NEWS: Updated NEWS 2008-06-01 18:54 'Travis Shirk ' * [r124] README.t2t.in: Updated with a new tag example. 2008-06-01 18:13 'Travis Shirk ' * [r122] bin/eyeD3: Limit to 79 characters+newline instead of 80 for consoles. Closes http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=474656 2008-06-01 18:02 'Travis Shirk ' * [r121] src/eyeD3/frames.py: Infinite loop fix while searching for APIC data. Really bad typo bug fix :) 2008-06-01 17:42 'Travis Shirk ' * [r118] bin/eyeD3: profiling restructure 2008-06-01 17:15 'Travis Shirk ' * [r117] configure.in: bumped version to 0.6.16 2008-03-02 21:11 'Travis Shirk ' * [r114] bin/eyeD3: Bumped copyright year 2008-03-02 21:07 'Travis Shirk ' * [r113] ChangeLog: Release update 2008-03-02 00:08 'Travis Shirk ' * [r112] bin/eyeD3, doc/eyeD3.1.in, src/eyeD3/tag.py: Man page update and cleanup 2008-03-01 22:54 'Travis Shirk ' * [r110] NEWS: new date 2008-03-01 22:52 'Travis Shirk ' * [r109] ChangeLog: release update 2008-03-01 22:49 'Travis Shirk ' * [r108] README.t2t.in: Added ubuntu and slackware package locations 2008-03-01 22:15 'Travis Shirk ' * [r107] AUTHORS, NEWS, README.t2t.in, etc/gentoo/eyeD3-0.6.15.ebuild: Release updates 2008-03-01 22:13 'Travis Shirk ' * [r106] bin/eyeD3, src/eyeD3/tag.py: Added Tag.removeImages and --removeImages and ZZ-F drive args. 2008-02-24 00:25 'Travis Shirk ' * [r101] src/eyeD3/tag.py: Remove duplicate code 2008-02-23 19:30 'Travis Shirk ' * [r99] src/eyeD3/tag.py: Allow floating point BPMs but round since the spec says this should be an integer 2008-02-19 02:40 'Travis Shirk ' * [r97] src/eyeD3/frames.py: When tag header unsync bit is set deunsync all frame data at once for ID3 <= 2.3, or per frame for ID3 >= 2.4 2008-02-17 23:14 'Travis Shirk ' * [r95] ChangeLog: Updated 2008-02-17 22:37 'Travis Shirk ' * [r94] src/eyeD3/frames.py: Synchronization bug fixes merges from trunk 2008-01-29 04:14 'Travis Shirk ' * [r92] configure.in: version bump 2008-01-15 19:18 'Travis Shirk ' * [r91] doc/eyeD3.1.in: Fixed --track-genre to read --genre 2008-01-01 04:39 'Travis Shirk ' * [r89] src/eyeD3/__init__.py.in, src/eyeD3/frames.py: do_locale=True makes getpreferredencoding return correct results 2007-11-25 18:51 'Travis Shirk ' * [r76] .: 0.6 branch 2007-10-21 21:16 'Travis Shirk ' * [r68] Added --no-tagging-time-frame option 2007-07-22 23:52 'Todd Zullinger ' * [r67] minor fixes to --lametag output 2007-07-04 05:32 'Travis Shirk ' * [r66] Typo fix 2007-07-04 05:28 'Travis Shirk ' * [r65] Accept floating point BPM values on the command line by rounding to an int. The Tag.setBPM method enforces a proper int, but does not round, this is the responsibility of user code 2007-06-09 23:15 'Travis Shirk ' * [r64] APIC picture type fix: Michael Schout 2007-06-07 03:27 'Travis Shirk ' * [r62] ID3 v1 comment encoding fix. Patch by Renaud Saint-Gratien 2007-05-13 18:34 'Travis Shirk ' * [r55] Removed test dir 2007-05-12 23:20 'Travis Shirk ' * [r49] Initialized merge tracking via "svnmerge" with revisions "1-705" from svn://puddy.nicfit.lan/eyeD3/branches/nextgen 2007-05-12 22:06 'Travis Shirk ' * [r44] Cleaned out some old patches 2007-05-08 03:29 'Travis Shirk ' * [r43] Include eyeD3 usage info in the README automatically. 2007-05-08 02:16 'Travis Shirk ' * [r41] Release updates 2007-05-08 02:00 'Travis Shirk ' * [r40] Zero pad track nums, gives nice alignment in the times column 2007-05-08 02:00 'Travis Shirk ' * [r39] Accept '!' in genres strings since "Oi!" is common. 2007-05-08 00:54 'Todd Zullinger ' * [r38] add --lametag option to display LAME tags 2007-05-08 00:28 'Todd Zullinger ' * [r37] a few more small cleanups 2007-05-07 23:30 'Todd Zullinger ' * [r36] fixup lametag bitrate 2007-05-07 17:42 'Todd Zullinger ' * [r35] some cleanups before release add a docstring detailing the data mined from the LAME tag 2007-05-07 17:40 'Todd Zullinger ' * [r34] allow blame to be properly assigned to me 2007-05-06 23:32 'Travis Shirk ' * [r33] Release updates 2007-05-06 23:16 'Travis Shirk ' * [r32] Bug fix for duplicating TYER frames 2007-05-06 22:37 'Travis Shirk ' * [r31] Allow for | in genre names to supports things such as 'Rock|Punk|Pop-Punk' Slimdevices software allows this, thanks to John SJ Anderson for patching this. 2007-05-06 22:15 'Travis Shirk ' * [r30] Lame header support patch (Todd Zullinger) Output encoder version in 'eyeD3 --nfo' output 2007-05-06 21:44 'Travis Shirk ' * [r29] Regression tests. 2007-05-06 21:43 'Travis Shirk ' * [r28] Ran into another mp3 that eyeD3 could not read... refatored mp3 header locating and validity checks... eyeD3 reads my entire collection again. 2007-05-04 01:33 'Travis Shirk ' * [r27] When there is no tag in the file there is no need to add 10 (ID3 tag header size) to the seek location for the mp3 data. 2007-05-01 01:48 'Travis Shirk ' * [r25] Release updates 2007-05-01 01:36 'Travis Shirk ' * [r24] 'make install' does not build anything, thus doing it as root still means the normal user can clean. 2007-04-30 07:43 'Travis Shirk ' * [r23] Compressed results 2007-04-30 07:42 'Travis Shirk ' * [r22] regression results 2007-04-30 00:45 'Travis Shirk ' * [r21] Test harness fixes 2007-04-30 00:43 'Travis Shirk ' * [r20] Xing header fixes, fixes #5 2007-04-29 23:58 'Travis Shirk ' * [r19] Tester updates 2007-04-29 09:31 'Travis Shirk ' * [r18] Release updates 2007-04-29 09:17 'Travis Shirk ' * [r17] Release updates 2007-04-29 06:12 'Travis Shirk ' * [r16] Time computation fixes when MP3 frames headers were mistakingly found, thanks to Virgil Dupras for reporting this and providing the test case. 2007-04-29 05:41 'Travis Shirk ' * [r15] Added option --run-profiler 2007-04-28 22:03 'Travis Shirk ' * [r14] NFO output support (--nfo) 2007-04-22 22:29 'Travis Shirk ' * [r13] Update 2007-04-22 22:29 'Travis Shirk ' * [r12] Determine local encoding without using setlocale 2007-03-06 17:59 'Todd Zullinger ' * [r11] note --no-zero-padding option 2007-03-06 03:26 'Travis Shirk ' * [r10] Updated 2007-03-04 18:08 'Travis Shirk ' * [r8] Removed cruft 2007-03-04 17:55 'Todd Zullinger ' * [r7] add --no-zero-padding option to manpage 2007-03-04 17:43 'Todd Zullinger ' * [r6] add --no-zero-padding option to allow disabling of zero padding track numbers 2007-03-04 03:21 'Travis Shirk ' * [r5] Test 2007-03-04 02:57 'Travis Shirk ' * [r4] Testing new repo 2007-03-04 02:51 'Travis Shirk ' * [r3] Added 2007-03-04 02:49 'Travis Shirk ' * [r2] Added eyeD3 2007-03-04 02:31 root * [r1] Added eyeD3 eyeD3-0.6.18/etc/0000755000175000017500000000000011663615630012743 5ustar travistraviseyeD3-0.6.18/etc/eyeD3.spec.in0000644000175000017500000000344011663615630015176 0ustar travistravis# # Copyright (C) 2002, 2006-2007 Travis Shirk # # 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 2 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # %define name eyeD3 %define version @PACKAGE_VERSION@ %define maintainer @PACKAGE_BUGREPORT@ %define release @RPM_RELEASE@ Summary: A python module for processing mp3 files. Name: %{name} Version:%{version} Release: %{release} License: GPL BuildArch: noarch URL: http://eyed3.nicfit.net/ Group: Development/Libraries Source0: %{name}-%{version}.tar.gz Packager: %{maintainer} BuildRoot: /var/tmp/%{name}-%{version}-buildroot Requires: python2 >= 2.5 %description eyeD3 is a Python module and program for processing ID3 tags. Information about mp3 files (i.e bit rate, sample frequency, play time, etc.) is also provided. The formats supported are ID3 v1.0/v1.1 and v2.3/v2.4. %prep %setup -q %build ./configure --prefix=%{_prefix} --mandir=%{_mandir} make %install make RPM_BUILD_ROOT=${RPM_BUILD_ROOT} all install %files %defattr(-, root, root, 0755) %{_libdir}/python*/site-packages/%{name} %doc AUTHORS ChangeLog COPYING INSTALL NEWS README* THANKS TODO %{_bindir}/eyeD3 %{_mandir}/man1/eyeD3.1.gz %clean rm -rf ${RPM_BUILD_ROOT} eyeD3-0.6.18/etc/eyeD3-0.6.18.ebuild0000644000175000017500000000123011663615630015626 0ustar travistravis# Copyright 1999-2007 Gentoo Foundation # Distributed under the terms of the GNU General Public License v2 # $Header: /opt/data/files/travis/cvsroot/eyeD3/etc/gentoo/Attic/eyeD3-0.6.12.ebuild,v 1.1.2.1 2007/02/18 23:34:46 travis Exp $ NEED_PYTHON=2.5 inherit distutils DESCRIPTION="Module for manipulating ID3 (v1 + v2) tags in Python" HOMEPAGE="http://eyed3.nicfit.net/" SRC_URI="http://eyed3.nicfit.net/releases/${P}.tar.gz" IUSE="" LICENSE="GPL-2" SLOT="0" KEYWORDS="~amd64 ~ia64 ~ppc ~sparc ~x86" src_compile() { econf || die distutils_src_compile || die } src_install() { make DESTDIR="${D}" all install || die dodoc NEWS TODO dohtml README.html } eyeD3-0.6.18/NEWS0000644000175000017500000003036511663615630012676 0ustar travistravisRelease Notes: =============== eyeD3 0.6.18 - 11.25.2011 (Nobunny loves you) New features: * Support for disc number frames (TPOS). Thanks to Nathaniel Clark * Added %Y (year) and %G (genre) substitution variables for file renames. Thanks to Otávio Pontes * Improved XML (--jep-118) escaping and a new option (--rfc822) to output in RFC 822 format. Thanks to Neil Schemenauer * --rename will NOT clobber existing files. * New option --itunes to write only iTunes accepted genres. Thanks to Ben Isaacs * If available the 'magic' module will be used to determine mimetypes when the filename is not enough. Thanks to Ville Skyttä * --set-encoding can be used along with a version conversion arg to apply a new encoding to the new tag. * Increased performance for mp3 header search when malformed GEOB frames are encountered. Thanks to Stephen Fairchild * Less crashing when invalid user text frames are encountered. * Less crashing when invalid BPM values (empty/non-numeric) are encountered. eyeD3 0.6.17 - 02.01.2009 (The Point of No Return) Bug fixes: * Workaround invalid utf16 * Show all genres during --list-genres * Workaround invalid PLCT frames. * Show all tracks during --nfo output. New features: * Support for URL frames (W??? and WXXX) * Program exit code for the 'eyeD3' command line tool eyeD3 0.6.16 - 06.09.2008 (Gimme Danger) Bug fixes: * Typo fix of sysnc/unsync data. Thanks to Gergan Penkov * Infinite loop fix when dealing with malformed APIC frames. * Tag.removeUserTextFrame helper. Thanks to David Grant eyeD3 0.6.15 - 03.02.2008 (Doin' The Cockroach) Bug fixes: * ID3 v1 comment encoding (latin1) bug fix (Renaud Saint-Gratien ) * APIC picture type fix (Michael Schout ) * Fixed console Unicode encoding for display. * Fixed frame de-unsnychronization bugs. * Round float BPMs to int (per the spec) New features: * Lame tag output enhancements. * Added --no-tagging-time to quell updates to TDTG frames. * Added -F to specify a different delimiter for multi-field options. Useful when one of the fields contains a ':' * Added --remove-images to remove all APIC frames eyeD3 0.6.14 - 05.08.2007 (Breakthrough) Bugs fixes since 0.6.13: - Fixed a nasty corruption of the first mp3 header when writing to files that do not already contain a tag. - Fixed a bug that would duplicate TYER frames when setting new values. - Fixed the reading/validation of some odd (i.e.,rare) mp3 headers Features added since 0.6.13: - Encoding info extracted from Lame mp3 headers [Todd Zullinger] - Genre names will now support '|' to allow for genres like "Rock|Punk|Pop-Punk" and '!' for "Oi!" eyeD3 0.6.13 - 04.30.2007 (Undercovers On) - Numerous write fixes, especially for v2.4 tags. Thanks to Alexander Thomas for finding these. - Add --no-zero-padding option to allow disabling of zero padding track numbers - Add --nfo option to output NFO format files about music directories. - Time computation fixes when MP3 frames headers were mistakingly found. eyeD3 0.6.12 - 02.18.2007 (Rid Of Me) - Handle Mac style line ending in lyrics and display with the proper output encoding. [Todd Zullinger] - TDTG support and other date frame fixes. [Todd Zullinger] - Output encoding bug fixes. [Todd Zullinger] eyeD3 0.6.11 - 11.05.2006 (Disintegration) - Support for GEOB (General encapsulated object) frames from Aaron VonderHaar - Decreased memory consumption during tag rewrites/removals. - Allow the "reserved" mpeg version bits when not in strict mode. - Solaris packages available via Blastwave - http://www.blastwave.org/packages.php/pyeyed3 eyeD3 0.6.10 - 03.19.2006 (Teh Mesk release) - Unsynchronized lyrics (USLT) frame support [Todd Zullinger ] - UTF16 bug fixes - More forgiving of invalid User URL frames (WXXX) - RPM spec file fixes [Knight Walker ] - More details in --verbose display eyeD3 0.6.9 - 01.08.2005 (The Broken Social Scene Release) - eyeD3 (the CLI) processes directories more efficiently - A specific file system encoding can be specified for file renaming, see --fs-encoding (Andrew de Quincey) - Faster mp3 header search for empty and/or corrupt mp3 files - Extended header fixes - Bug fix for saving files with no current tag - What would a release be without unicode fixes, this time it's unicode filename output and JEP 0118 output. eyeD3 0.6.8 - 08.29.2005 (The Anal Cunt Release) - Frame header size bug. A _serious_ bug since writes MAY be affected (note: I've had no problems reported so far). eyeD3 0.6.7 - 08.28.2005 (The Autopsy Release) - Beats per minute (TPBM) interface - Publisher/label (TPUB) interface - When not in strict mode exceptions for invalid tags are quelled more often - Support for iTunes ID3 spec violations regarding multiple APIC frames - Bug fix where lang in CommentFrame was unicode where it MUST be ascii - Bug fixed for v2.2 frame header sizes - Bug fixed for v2.2 PIC frames - File rename bug fixes - Added -c option as an alias for --comment - -i/--write-images now takes a destination path arg. Due to optparse non-support for optional arguments the path MUST be specified. This option no longer clobbers existing files. eyeD3 0.6.6 - 05.15.2005 (The Electric Wizard Release) - APIC frames can now be removed. - An interface for TBPM (beats per minute) frames. - Utf-16 bug fixes and better unicode display/output - RPM spec file fixes eyeD3 0.6.5 - 04.16.2005 - Read-only support for ID3 v2.2 - TPOS frame support (disc number in set). - Bug fixes eyeD3 0.6.4 - 02.05.2005 - Native support for play count (PCNT), and unique file id (UFID) frames. - More relaxed genre processing. - Sync-safe bug fixed when the tag header requests sync-safety and not the frames themselves. - configure should successfly detect python release candidates and betas. eyeD3 0.6.3 - 11.23.2004 - Much better unicode support when writing to the tag. - Added Tag.setEncoding (--set-encoding) and --force-update - Handle MP3 frames that violate spec when in non-strict mode. (Henning Kiel ) - Fix for Debian bug report #270964 - Various bug fixes. eyeD3 0.6.2 - 8.29.2004 (Happy Birthday Mom!) - TagFile.rename and Tag.tagToString (eyeD3 --rename=PATTERN). The latter supports substitution of tag values: %A is artist, %t is title, %a is album, %n is track number, and %N is track total. - eyeD3 man page. - User text frame (TXXX) API and --set-user-text-frame. - Python 2.2/Optik compatibility works now. - ebuild for Gentoo (http://eyed3.nicfit.net/releases/gentoo/) eyeD3 0.6.1 - 5/14/2004 (Oz/2 Ohh my!) - Unicode support - UTF-8, UTF-16, and UTF-16BE - Adding images (APIC frames) is supported (--add-image, Tag.addImage(), etc.) - Added a --relaxed option to be much more forgiving about tags that violate the spec. Quite useful for removing such tags. - Added Tag.setTextFrame (--set-text-frame=FID:TEXT) - Added --remove-comments. - Now requires Python 2.3. Sorry, but I like cutting-edge python features. - Better handling and conversion (2.3 <=> 2.4) of the multiple date frames. - Output format per JEP 0118: User Tune, excluding xsd:duration format for (http://www.jabber.org/jeps/jep-0118.html) - Lot's of bug fixes. - Added a mailing list. Subscribe by sending a message to eyed3-devel-subscribe@nicfit.net eyeD3 0.5.1 - 7/17/2003 (It's Too Damn Hot to Paint Release) - Temporary files created during ID3 saving are now properly cleaned up. - Fixed a "bug" when date frames are present but contain empty strings. - Added a --no-color option to the eyeD3 driver. - Workaround invalid tag sizes by implyied padding. - Updated README eyeD3 0.5.0 - 6/7/2003 (The Long Time Coming Release) - ID3 v2.x saving. - The eyeD3 driver/sample program is much more complete, allowing for most common tag operations such as tag display, editing, removal, etc. Optik is required to use this program. See the README. - Complete access to all artist and title frames (i.e. TPE* and TIT*) - Full v2.4 date support (i.e. TDRC). - Case insensitive genres and compression fixes. (Gary Shao) - ExtendedHeader support, including CRC checksums. - Frame groups now supported. - Syncsafe integer conversion bug fixes. - Bug fixes related to data length indicator bytes. - Genre and lot's of other bug fixes. eyeD3 0.4.0 - 11/11/2002 (The Anniversary Release *smooch*) - Added the ability to save tags in ID v1.x format, including when the linked file was IDv2. Original backups are created by default for the time being... - Added deleting of v1 and v2 frames from the file. - Zlib frame data decompression is now working. - bin/eyeD3 now displays user text frames, mp3 copyright and originality, URLs, all comments, and images. Using the --write-images arg will write each APIC image data to disk. - Added eyeD3.isMp3File(), Tag.clear(), Tag.getImages(), Tag.getURLs(), Tag.getCDID(), FrameSet.removeFrame(), Tag.save(), ImageFrame.writeFile(), etc... - Modified bin/eyeD3 to grok non Mp3 files. This allows testing with files containing only tag data and lays some groundwork for future OGG support. - Fixed ImageFrame mime type problem. - Fixed picture type scoping problems. eyeD3 0.3.1 - 10/24/2002 - RPM packages added. - Fixed a bug related to ID3 v1.1 track numbers. (Aubin Paul) - Mp3AudioFile matchs *.mp3 and *.MP3. (Aubin Paul) eyeD3 0.3.0 - 10/21/2002 - Added a higher level class called Mp3AudioFile. - MP3 frame (including Xing) decoding for obtaining bit rate, play time, etc. - Added APIC frame support (eyeD3.frames.Image). - BUG FIX: Tag unsynchronization and deunsynchronization now works correctly and is ID3 v2.4 compliant. - Tags can be linked with file names or file objects. - More tag structure abstractions (TagHeader, Frame, FrameSet, etc.). - BUG FIX: GenreExceptions were not being caught in eyeD3 driver. eyeD3 0.2.0 - 8/15/2002 - ID3_Tag was renamed to Tag. - Added Genre and GenreMap (eyeD3.genres is defined as the latter type) - Added support of ID3 v1 and v2 comments. - The ID3v2Frame file was renamed ID3v2 and refactoring work has started with the addition of TagHeader. eyeD3 0.1.0 - 7/31/2002 - Initial release. People who have contributed to eyeD3 (who may before this NEWS file): Ryan Finnie - Provided some useful functions for manipulating binary data in his PyID3 project which eyeD3 forked from. Aubin Paul - Bug fixes and initial Debian package contributions. Check out http://freevo.sourceforge.net/ Gary Shao - Bug fixes and case-insensitive genre support. Rick Carter - Provided lot's of broken tags for the --relaxed testing. XMMS Author(s) - The cross platform multimedia library was an invaluable reference when writing the mp3 header decoder. Alexander Wirt - Author of the original eyeD3 man page and fixed Python 2.2 / Optik compatibility. - Debian package maintainer Henning Kiel - Patches for bogus frame headers, Unicode bug fix Michal ÄŒihaÅ™ - acsite.m4 patch for Python betas and release candidates laxori666@yahoo.com - More relaxed genre support patch. Arkadiy Belousov - Read support for ID3 v2.2 Erik Osheim - TPOS frame support (Disc number in set), and numerous bug fixes Joe Wreschnig - Bug fixes Jesper L. Nielsen - File rename bug fix Andrew de Quincey - Fixes for unicode display and file system encoding support Todd Zullinger - USLT frame support, and numerous great patches. Aaron VonderHaar - GEOB frame support eyeD3-0.6.18/README0000644000175000017500000002414011663615630013051 0ustar travistraviseyeD3 0.6.18 Travis Shirk 11.24.2011 General Information =================== eyeD3 is a Python module and program for processing ID3 tags. Information about mp3 files (i.e bit rate, sample frequency, play time, etc.) is also provided. The formats supported are ID3 v1.0/v1.1 (http://id3lib.sourceforge.net/id3/id3v1.html) and v2.3/v2.4 (http://id3lib.sourceforge.net/id3/develop.html). The current stable version is 0.6.18. It and earlier versions are available here (http://eyed3.nicfit.net/releases/). See the NEWS (NEWS) and/or ChangeLog (ChangeLog) for changes. Requirements ============ - Python (http://www.python.org/) >= 2.5 - python-magic (http://www.darwinsys.com/file/) optional Installation ============ Source tarball -------------- gzip -dc eyeD3-0.6.18.tar.gz | tar xvf - cd eyeD3-0.6.18 ./configure make make install (as root) Gentoo ------ emerge eyeD3 See here (http://gentoo-portage.com/dev-python/eyeD3) for more info. Ubuntu ------ See here (http://packages.ubuntu.com/search?keywords=eyeD3&searchon=names&suite=all§ion=all) for more info. Debian ------ See here (http://packages.debian.org/eyed3) for more info. Suse ---- See here (http://www.novell.com/products/linuxpackages/professional/python-eyed3.html) for more info. Slackware --------- See linuxpackages.net (http://www.linuxpackages.net/search_view.php?by=name&name=eyeD3&ver=) for more info. Solaris SPARC and x86 --------------------- Packages available from Blastwave (http://www.blastwave.org/pkg/search.ftd?qs=pyeyeD3). How to Use eyeD3 ================ The 'eyeD3' utility program can perform most ID3 tasks, and it is also the best example of how to use the API. It supports the following features: Usage ===== eyeD3 [OPTS] file [file...] Options ======= --version show program's version number and exit -h, --help show this help message and exit Tag Versions ------------ -1, --v1 Only read/write ID3 v1.x tags. By default, v1.x tags are only read if there is not a v2.x tag. -2, --v2 Only read/write ID3 v2.x tags. --to-v1.1 Convert the file's tag to ID3 v1.1. (Or 1.0 if there is no track number.) --to-v2.3 Convert the file's tag to ID3 v2.3 --to-v2.4 Convert the file's tag to ID3 v2.4 Tag Data -------- -a STRING, --artist=STRING Set artist -A STRING, --album=STRING Set album -t STRING, --title=STRING Set title -n NUM, --track=NUM Set track number -N NUM, --track-total=NUM Set total number of tracks -G GENRE, --genre=GENRE Set genre. The argument is a valid genre string or number. See --list-genres -d NUM, --disc=NUM Set disc number -D NUM, --disc-total=NUM Set total number of discs -Y STRING, --year=STRING Set a four digit year. -c [LANGUAGE]:[DESCRIPTION]:COMMENT, --comment=[LANGUAGE]:[DESCRIPTION]:COMMENT Set comment -L [LANGUAGE]:[DESCRIPTION]:LYRICS, --lyrics=[LANGUAGE]:[DESCRIPTION]:LYRICS Set lyrics -p STRING, --publisher=STRING Set the publisher/label text --remove-comments Remove all comment frames. --remove-lyrics Remove all lyrics frames. --add-image=IMG_PATH:TYPE[:DESCRIPTION] Add an image to the tag. The description and type optional, but when used, both ':' delimiters must be present. The type MUST be an string that corresponds to one given with --list-image-types. If the IMG_PATH value is empty the APIC frame with TYPE is removed. --remove-images Remove all image (APIC) frames. --add-object=OBJ_PATH[:DESCRIPTION[:MIME-TYPE[:FILENAME]] Add an encapsulated object to the tag. The description and filename are optional, but when used, the ':' delimiters must be present. If the OBJ_PATH value is empty the GEOB frame with DESCRIPTION is removed. -i DIR, --write-images=DIR Causes all attached images (APIC frames) to be written to the specified directory. -o DIR, --write-objects=DIR Causes all attached objects (GEOB frames) to be written to the specified directory. --set-text-frame=FID:TEXT Set the value of a text frame. To remove the frame, specify an empty value. e.g., --set-text-frame="TDRC:" --set-user-text-frame=DESC:TEXT Set the value of a user text frame (i.e., TXXX). To remove the frame, specify an empty value. e.g., --set- user-text-frame="SomeDesc:" --set-url-frame=FID:URL Set the value of a URL frame. To remove the frame, specify an empty value. e.g., --set-url-frame="WCOM:" --set-user-url-frame=DESC:URL Set the value of a user URL frame (i.e., WXXX). To remove the frame, specify an empty value. e.g., --set- user-url-frame="SomeDesc:" --play-count=[+]N If this argument value begins with '+' the tag's play count (PCNT) is incremented by N, otherwise the value is set to exactly N. --bpm=N Set the beats per minute value. --unique-file-id=OWNER_ID:ID Add a UFID frame. If the ID arg is empty the UFID frame with OWNER_ID is removed. An OWNER_ID MUST be specified. --set-encoding=latin1|utf8|utf16-BE|utf16-LE Set the encoding that is used for _all_ text frames. This only takes affect when the tag is updated as the result of a frame value being set with another option (e.g., --artist=) or --force-update is present. --remove-v1 Remove ID3 v1.x tag. --remove-v2 Remove ID3 v2.x tag. --remove-all Remove both ID3 v1.x and v2.x tags. Misc. Options ------------- --rename=NAME Rename file (the extension is not affected) based on data in the tag using substitution variables: %A (artist), %a (album), %t (title), %n (track number), and %N (total number of tracks) --fs-encoding=ENCODING Use the specified character encoding for the filename when renaming files -l, --list-genres Display the table of ID3 genres and exit --list-image-types List all possible image types --strict Fail for tags that violate the ID3 specification. --itunes Store tags in an iTunes compatible way. --jep-118 Output the tag per the format described in JEP-0118. See http://www.xmpp.org/extensions/xep-0118.html --rfc822 Output the tag in RFC822-like format --nfo Output NFO information. --lametag Prints the LAME Tag. --force-update Update the tag regardless of whether any frames are set with new values. --no-color Disable color output --no-zero-padding Don't pad track or disc numbers with 0's --no-tagging-time-frame When saving tags do not add a TDTG (tagging time) frame -F DELIM Specify a new delimiter for option values that contain multiple fields (default delimiter is ':') -v, --verbose Show all available information --debug Trace program execution. --run-profiler Run using python profiler. Some simple programming examples follow here, excluding any error handling, of course :) Reading the contents of an mp3 file containing either v1 or v2 tag info: import eyeD3 tag = eyeD3.Tag() tag.link("/some/file.mp3") print tag.getArtist() print tag.getAlbum() print tag.getTitle() Read an mp3 file (track length, bitrate, etc.) and access it's tag: if eyeD3.isMp3File(f): audioFile = eyeD3.Mp3AudioFile(f) tag = audioFile.getTag() Specific tag versions can be selected: tag.link("/some/file.mp3", eyeD3.ID3_V2) tag.link("/some/file.mp3", eyeD3.ID3_V1) tag.link("/some/file.mp3", eyeD3.ID3_ANY_VERSION) # The default. Or you can iterate over the raw frames: tag = eyeD3.Tag() tag.link("/some/file.mp3") for frame in tag.frames: print frame Once a tag is linked to a file it can be modified and saved: tag.setArtist(u"Cro-Mags") tag.setAlbum(u"Age of Quarrel") tag.update() If the tag linked in was v2 and you'd like to save it as v1: tag.update(eyeD3.ID3_V1_1) Read in a tag and remove it from the file: tag.link("/some/file.mp3") tag.remove() tag.update() Add a new tag: tag = eyeD3.Tag() tag.link('/some/file.mp3') # no tag in this file, link returned False tag.header.setVersion(eyeD3.ID3_V2_3) tag.setArtist('Fugazi') tag.update() Support ======= eyeD3 now has a mailing list. Send a message to to subscribe. Bugs and Patches ================ Find bugs! Please submit all comments, bug reports, or feature requests to Travis Shirk . Those of the patch variety are especially welcome :) See Also ======== eyeD3 is free software, refer to the COPYING (COPYING) file for details. See the TODO (TODO) file for possible enhancements. eyeD3-0.6.18/COPYING0000644000175000017500000004313111663615630013225 0ustar travistravis GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 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 2 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. eyeD3-0.6.18/AUTHORS0000644000175000017500000000047411663615630013245 0ustar travistravisTravis Shirk - eyeD3 author/maintainer Todd Zullinger - eyeD3 developer Aaron VonderHaar - support for GEOB tags Ryan Finnie - Wrote PyID3 which contained the binfuncs module which eyeD3 currenly uses eyeD3-0.6.18/INSTALL0000644000175000017500000002240611663615630013225 0ustar travistravisInstallation Instructions ************************* Copyright (C) 1994, 1995, 1996, 1999, 2000, 2001, 2002, 2004, 2005 Free Software Foundation, Inc. This file is free documentation; the Free Software Foundation gives unlimited permission to copy, distribute and modify it. Basic Installation ================== These are generic installation instructions. The `configure' shell script attempts to guess correct values for various system-dependent variables used during compilation. It uses those values to create a `Makefile' in each directory of the package. It may also create one or more `.h' files containing system-dependent definitions. Finally, it creates a shell script `config.status' that you can run in the future to recreate the current configuration, and a file `config.log' containing compiler output (useful mainly for debugging `configure'). It can also use an optional file (typically called `config.cache' and enabled with `--cache-file=config.cache' or simply `-C') that saves the results of its tests to speed up reconfiguring. (Caching is disabled by default to prevent problems with accidental use of stale cache files.) If you need to do unusual things to compile the package, please try to figure out how `configure' could check whether to do them, and mail diffs or instructions to the address given in the `README' so they can be considered for the next release. If you are using the cache, and at some point `config.cache' contains results you don't want to keep, you may remove or edit it. The file `configure.ac' (or `configure.in') is used to create `configure' by a program called `autoconf'. You only need `configure.ac' if you want to change it or regenerate `configure' using a newer version of `autoconf'. The simplest way to compile this package is: 1. `cd' to the directory containing the package's source code and type `./configure' to configure the package for your system. If you're using `csh' on an old version of System V, you might need to type `sh ./configure' instead to prevent `csh' from trying to execute `configure' itself. Running `configure' takes awhile. While running, it prints some messages telling which features it is checking for. 2. Type `make' to compile the package. 3. Optionally, type `make check' to run any self-tests that come with the package. 4. Type `make install' to install the programs and any data files and documentation. 5. You can remove the program binaries and object files from the source code directory by typing `make clean'. To also remove the files that `configure' created (so you can compile the package for a different kind of computer), type `make distclean'. There is also a `make maintainer-clean' target, but that is intended mainly for the package's developers. If you use it, you may have to get all sorts of other programs in order to regenerate files that came with the distribution. Compilers and Options ===================== Some systems require unusual options for compilation or linking that the `configure' script does not know about. Run `./configure --help' for details on some of the pertinent environment variables. You can give `configure' initial values for configuration parameters by setting variables in the command line or in the environment. Here is an example: ./configure CC=c89 CFLAGS=-O2 LIBS=-lposix *Note Defining Variables::, for more details. Compiling For Multiple Architectures ==================================== You can compile the package for more than one kind of computer at the same time, by placing the object files for each architecture in their own directory. To do this, you must use a version of `make' that supports the `VPATH' variable, such as GNU `make'. `cd' to the directory where you want the object files and executables to go and run the `configure' script. `configure' automatically checks for the source code in the directory that `configure' is in and in `..'. If you have to use a `make' that does not support the `VPATH' variable, you have to compile the package for one architecture at a time in the source code directory. After you have installed the package for one architecture, use `make distclean' before reconfiguring for another architecture. Installation Names ================== By default, `make install' will install the package's files in `/usr/local/bin', `/usr/local/man', etc. You can specify an installation prefix other than `/usr/local' by giving `configure' the option `--prefix=PREFIX'. You can specify separate installation prefixes for architecture-specific files and architecture-independent files. If you give `configure' the option `--exec-prefix=PREFIX', the package will use PREFIX as the prefix for installing programs and libraries. Documentation and other data files will still use the regular prefix. In addition, if you use an unusual directory layout you can give options like `--bindir=DIR' to specify different values for particular kinds of files. Run `configure --help' for a list of the directories you can set and what kinds of files go in them. If the package supports it, you can cause programs to be installed with an extra prefix or suffix on their names by giving `configure' the option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. Optional Features ================= Some packages pay attention to `--enable-FEATURE' options to `configure', where FEATURE indicates an optional part of the package. They may also pay attention to `--with-PACKAGE' options, where PACKAGE is something like `gnu-as' or `x' (for the X Window System). The `README' should mention any `--enable-' and `--with-' options that the package recognizes. For packages that use the X Window System, `configure' can usually find the X include and library files automatically, but if it doesn't, you can use the `configure' options `--x-includes=DIR' and `--x-libraries=DIR' to specify their locations. Specifying the System Type ========================== There may be some features `configure' cannot figure out automatically, but needs to determine by the type of machine the package will run on. Usually, assuming the package is built to be run on the _same_ architectures, `configure' can figure that out, but if it prints a message saying it cannot guess the machine type, give it the `--build=TYPE' option. TYPE can either be a short name for the system type, such as `sun4', or a canonical name which has the form: CPU-COMPANY-SYSTEM where SYSTEM can have one of these forms: OS KERNEL-OS See the file `config.sub' for the possible values of each field. If `config.sub' isn't included in this package, then this package doesn't need to know the machine type. If you are _building_ compiler tools for cross-compiling, you should use the `--target=TYPE' option to select the type of system they will produce code for. If you want to _use_ a cross compiler, that generates code for a platform different from the build platform, you should specify the "host" platform (i.e., that on which the generated programs will eventually be run) with `--host=TYPE'. Sharing Defaults ================ If you want to set default values for `configure' scripts to share, you can create a site shell script called `config.site' that gives default values for variables like `CC', `cache_file', and `prefix'. `configure' looks for `PREFIX/share/config.site' if it exists, then `PREFIX/etc/config.site' if it exists. Or, you can set the `CONFIG_SITE' environment variable to the location of the site script. A warning: not all `configure' scripts look for a site script. Defining Variables ================== Variables not defined in a site shell script can be set in the environment passed to `configure'. However, some packages may run configure again during the build, and the customized values of these variables may be lost. In order to avoid this problem, you should set them in the `configure' command line, using `VAR=value'. For example: ./configure CC=/usr/local2/bin/gcc causes the specified `gcc' to be used as the C compiler (unless it is overridden in the site shell script). Here is a another example: /bin/bash ./configure CONFIG_SHELL=/bin/bash Here the `CONFIG_SHELL=/bin/bash' operand causes subsequent configuration-related scripts to be executed by `/bin/bash'. `configure' Invocation ====================== `configure' recognizes the following options to control how it operates. `--help' `-h' Print a summary of the options to `configure', and exit. `--version' `-V' Print the version of Autoconf used to generate the `configure' script, and exit. `--cache-file=FILE' Enable the cache: use and save the results of the tests in FILE, traditionally `config.cache'. FILE defaults to `/dev/null' to disable caching. `--config-cache' `-C' Alias for `--cache-file=config.cache'. `--quiet' `--silent' `-q' Do not print messages saying which checks are being made. To suppress all normal output, redirect it to `/dev/null' (any error messages will still be shown). `--srcdir=DIR' Look for the package's source code in directory DIR. Usually `configure' can determine that directory automatically. `configure' also accepts some other, not widely useful, options. Run `configure --help' for more details. eyeD3-0.6.18/configure0000755000175000017500000035135611663615630014114 0ustar travistravis#! /bin/sh # Guess values for system-dependent variables and create Makefiles. # Generated by GNU Autoconf 2.68 for eyeD3 0.6.18. # # Report bugs to >. # # # Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001, # 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software # Foundation, Inc. # # # This configure script is free software; the Free Software Foundation # gives unlimited permission to copy, distribute and modify it. # # GNU GPL ## -------------------- ## ## M4sh Initialization. ## ## -------------------- ## # Be more Bourne compatible DUALCASE=1; export DUALCASE # for MKS sh if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which # is contrary to our usage. Disable this feature. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST else case `(set -o) 2>/dev/null` in #( *posix*) : set -o posix ;; #( *) : ;; esac fi as_nl=' ' export as_nl # Printing a long string crashes Solaris 7 /usr/bin/printf. as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo # Prefer a ksh shell builtin over an external printf program on Solaris, # but without wasting forks for bash or zsh. if test -z "$BASH_VERSION$ZSH_VERSION" \ && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then as_echo='print -r --' as_echo_n='print -rn --' elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then as_echo='printf %s\n' as_echo_n='printf %s' else if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' as_echo_n='/usr/ucb/echo -n' else as_echo_body='eval expr "X$1" : "X\\(.*\\)"' as_echo_n_body='eval arg=$1; case $arg in #( *"$as_nl"*) expr "X$arg" : "X\\(.*\\)$as_nl"; arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; esac; expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" ' export as_echo_n_body as_echo_n='sh -c $as_echo_n_body as_echo' fi export as_echo_body as_echo='sh -c $as_echo_body as_echo' fi # The user is always right. if test "${PATH_SEPARATOR+set}" != set; then PATH_SEPARATOR=: (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || PATH_SEPARATOR=';' } fi # IFS # We need space, tab and new line, in precisely that order. Quoting is # there to prevent editors from complaining about space-tab. # (If _AS_PATH_WALK were called with IFS unset, it would disable word # splitting by setting IFS to empty value.) IFS=" "" $as_nl" # Find who we are. Look in the path if we contain no directory separator. as_myself= case $0 in #(( *[\\/]* ) as_myself=$0 ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break done IFS=$as_save_IFS ;; esac # We did not find ourselves, most probably we were run as `sh COMMAND' # in which case we are not to be found in the path. if test "x$as_myself" = x; then as_myself=$0 fi if test ! -f "$as_myself"; then $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 exit 1 fi # Unset variables that we do not need and which cause bugs (e.g. in # pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1" # suppresses any "Segmentation fault" message there. '((' could # trigger a bug in pdksh 5.2.14. for as_var in BASH_ENV ENV MAIL MAILPATH do eval test x\${$as_var+set} = xset \ && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : done PS1='$ ' PS2='> ' PS4='+ ' # NLS nuisances. LC_ALL=C export LC_ALL LANGUAGE=C export LANGUAGE # CDPATH. (unset CDPATH) >/dev/null 2>&1 && unset CDPATH if test "x$CONFIG_SHELL" = x; then as_bourne_compatible="if test -n \"\${ZSH_VERSION+set}\" && (emulate sh) >/dev/null 2>&1; then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on \${1+\"\$@\"}, which # is contrary to our usage. Disable this feature. alias -g '\${1+\"\$@\"}'='\"\$@\"' setopt NO_GLOB_SUBST else case \`(set -o) 2>/dev/null\` in #( *posix*) : set -o posix ;; #( *) : ;; esac fi " as_required="as_fn_return () { (exit \$1); } as_fn_success () { as_fn_return 0; } as_fn_failure () { as_fn_return 1; } as_fn_ret_success () { return 0; } as_fn_ret_failure () { return 1; } exitcode=0 as_fn_success || { exitcode=1; echo as_fn_success failed.; } as_fn_failure && { exitcode=1; echo as_fn_failure succeeded.; } as_fn_ret_success || { exitcode=1; echo as_fn_ret_success failed.; } as_fn_ret_failure && { exitcode=1; echo as_fn_ret_failure succeeded.; } if ( set x; as_fn_ret_success y && test x = \"\$1\" ); then : else exitcode=1; echo positional parameters were not saved. fi test x\$exitcode = x0 || exit 1" as_suggested=" as_lineno_1=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_1a=\$LINENO as_lineno_2=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_2a=\$LINENO eval 'test \"x\$as_lineno_1'\$as_run'\" != \"x\$as_lineno_2'\$as_run'\" && test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1" if (eval "$as_required") 2>/dev/null; then : as_have_required=yes else as_have_required=no fi if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null; then : else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR as_found=false for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. as_found=: case $as_dir in #( /*) for as_base in sh bash ksh sh5; do # Try only shells that exist, to save several forks. as_shell=$as_dir/$as_base if { test -f "$as_shell" || test -f "$as_shell.exe"; } && { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$as_shell"; } 2>/dev/null; then : CONFIG_SHELL=$as_shell as_have_required=yes if { $as_echo "$as_bourne_compatible""$as_suggested" | as_run=a "$as_shell"; } 2>/dev/null; then : break 2 fi fi done;; esac as_found=false done $as_found || { if { test -f "$SHELL" || test -f "$SHELL.exe"; } && { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$SHELL"; } 2>/dev/null; then : CONFIG_SHELL=$SHELL as_have_required=yes fi; } IFS=$as_save_IFS if test "x$CONFIG_SHELL" != x; then : # We cannot yet assume a decent shell, so we have to provide a # neutralization value for shells without unset; and this also # works around shells that cannot unset nonexistent variables. # Preserve -v and -x to the replacement shell. BASH_ENV=/dev/null ENV=/dev/null (unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV export CONFIG_SHELL case $- in # (((( *v*x* | *x*v* ) as_opts=-vx ;; *v* ) as_opts=-v ;; *x* ) as_opts=-x ;; * ) as_opts= ;; esac exec "$CONFIG_SHELL" $as_opts "$as_myself" ${1+"$@"} fi if test x$as_have_required = xno; then : $as_echo "$0: This script requires a shell more modern than all" $as_echo "$0: the shells that I found on your system." if test x${ZSH_VERSION+set} = xset ; then $as_echo "$0: In particular, zsh $ZSH_VERSION has bugs and should" $as_echo "$0: be upgraded to zsh 4.3.4 or later." else $as_echo "$0: Please tell bug-autoconf@gnu.org and Travis Shirk $0: about your system, including any $0: error possibly output before this message. Then install $0: a modern shell, or manually run the script under such a $0: shell if you do have one." fi exit 1 fi fi fi SHELL=${CONFIG_SHELL-/bin/sh} export SHELL # Unset more variables known to interfere with behavior of common tools. CLICOLOR_FORCE= GREP_OPTIONS= unset CLICOLOR_FORCE GREP_OPTIONS ## --------------------- ## ## M4sh Shell Functions. ## ## --------------------- ## # as_fn_unset VAR # --------------- # Portably unset VAR. as_fn_unset () { { eval $1=; unset $1;} } as_unset=as_fn_unset # as_fn_set_status STATUS # ----------------------- # Set $? to STATUS, without forking. as_fn_set_status () { return $1 } # as_fn_set_status # as_fn_exit STATUS # ----------------- # Exit the shell with STATUS, even in a "trap 0" or "set -e" context. as_fn_exit () { set +e as_fn_set_status $1 exit $1 } # as_fn_exit # as_fn_mkdir_p # ------------- # Create "$as_dir" as a directory, including parents if necessary. as_fn_mkdir_p () { case $as_dir in #( -*) as_dir=./$as_dir;; esac test -d "$as_dir" || eval $as_mkdir_p || { as_dirs= while :; do case $as_dir in #( *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( *) as_qdir=$as_dir;; esac as_dirs="'$as_qdir' $as_dirs" as_dir=`$as_dirname -- "$as_dir" || $as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_dir" : 'X\(//\)[^/]' \| \ X"$as_dir" : 'X\(//\)$' \| \ X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || $as_echo X"$as_dir" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` test -d "$as_dir" && break done test -z "$as_dirs" || eval "mkdir $as_dirs" } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" } # as_fn_mkdir_p # as_fn_append VAR VALUE # ---------------------- # Append the text in VALUE to the end of the definition contained in VAR. Take # advantage of any shell optimizations that allow amortized linear growth over # repeated appends, instead of the typical quadratic growth present in naive # implementations. if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then : eval 'as_fn_append () { eval $1+=\$2 }' else as_fn_append () { eval $1=\$$1\$2 } fi # as_fn_append # as_fn_arith ARG... # ------------------ # Perform arithmetic evaluation on the ARGs, and store the result in the # global $as_val. Take advantage of shells that can avoid forks. The arguments # must be portable across $(()) and expr. if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then : eval 'as_fn_arith () { as_val=$(( $* )) }' else as_fn_arith () { as_val=`expr "$@" || test $? -eq 1` } fi # as_fn_arith # as_fn_error STATUS ERROR [LINENO LOG_FD] # ---------------------------------------- # Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are # provided, also output the error to LOG_FD, referencing LINENO. Then exit the # script with STATUS, using 1 if that was 0. as_fn_error () { as_status=$1; test $as_status -eq 0 && as_status=1 if test "$4"; then as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 fi $as_echo "$as_me: error: $2" >&2 as_fn_exit $as_status } # as_fn_error if expr a : '\(a\)' >/dev/null 2>&1 && test "X`expr 00001 : '.*\(...\)'`" = X001; then as_expr=expr else as_expr=false fi if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then as_basename=basename else as_basename=false fi if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then as_dirname=dirname else as_dirname=false fi as_me=`$as_basename -- "$0" || $as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ X"$0" : 'X\(//\)$' \| \ X"$0" : 'X\(/\)' \| . 2>/dev/null || $as_echo X/"$0" | sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/ q } /^X\/\(\/\/\)$/{ s//\1/ q } /^X\/\(\/\).*/{ s//\1/ q } s/.*/./; q'` # Avoid depending upon Character Ranges. as_cr_letters='abcdefghijklmnopqrstuvwxyz' as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' as_cr_Letters=$as_cr_letters$as_cr_LETTERS as_cr_digits='0123456789' as_cr_alnum=$as_cr_Letters$as_cr_digits as_lineno_1=$LINENO as_lineno_1a=$LINENO as_lineno_2=$LINENO as_lineno_2a=$LINENO eval 'test "x$as_lineno_1'$as_run'" != "x$as_lineno_2'$as_run'" && test "x`expr $as_lineno_1'$as_run' + 1`" = "x$as_lineno_2'$as_run'"' || { # Blame Lee E. McMahon (1931-1989) for sed's syntax. :-) sed -n ' p /[$]LINENO/= ' <$as_myself | sed ' s/[$]LINENO.*/&-/ t lineno b :lineno N :loop s/[$]LINENO\([^'$as_cr_alnum'_].*\n\)\(.*\)/\2\1\2/ t loop s/-\n.*// ' >$as_me.lineno && chmod +x "$as_me.lineno" || { $as_echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; } # Don't try to exec as it changes $[0], causing all sort of problems # (the dirname of $[0] is not the place where we might find the # original and so on. Autoconf is especially sensitive to this). . "./$as_me.lineno" # Exit status is that of the last command. exit } ECHO_C= ECHO_N= ECHO_T= case `echo -n x` in #((((( -n*) case `echo 'xy\c'` in *c*) ECHO_T=' ';; # ECHO_T is single tab character. xy) ECHO_C='\c';; *) echo `echo ksh88 bug on AIX 6.1` > /dev/null ECHO_T=' ';; esac;; *) ECHO_N='-n';; esac rm -f conf$$ conf$$.exe conf$$.file if test -d conf$$.dir; then rm -f conf$$.dir/conf$$.file else rm -f conf$$.dir mkdir conf$$.dir 2>/dev/null fi if (echo >conf$$.file) 2>/dev/null; then if ln -s conf$$.file conf$$ 2>/dev/null; then as_ln_s='ln -s' # ... but there are two gotchas: # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. # In both cases, we have to default to `cp -p'. ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || as_ln_s='cp -p' elif ln conf$$.file conf$$ 2>/dev/null; then as_ln_s=ln else as_ln_s='cp -p' fi else as_ln_s='cp -p' fi rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file rmdir conf$$.dir 2>/dev/null if mkdir -p . 2>/dev/null; then as_mkdir_p='mkdir -p "$as_dir"' else test -d ./-p && rmdir ./-p as_mkdir_p=false fi if test -x / >/dev/null 2>&1; then as_test_x='test -x' else if ls -dL / >/dev/null 2>&1; then as_ls_L_option=L else as_ls_L_option= fi as_test_x=' eval sh -c '\'' if test -d "$1"; then test -d "$1/."; else case $1 in #( -*)set "./$1";; esac; case `ls -ld'$as_ls_L_option' "$1" 2>/dev/null` in #(( ???[sx]*):;;*)false;;esac;fi '\'' sh ' fi as_executable_p=$as_test_x # Sed expression to map a string onto a valid CPP name. as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" # Sed expression to map a string onto a valid variable name. as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" test -n "$DJDIR" || exec 7<&0 &1 # Name of the host. # hostname on some systems (SVR3.2, old GNU/Linux) returns a bogus exit status, # so uname gets run too. ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q` # # Initializations. # ac_default_prefix=/usr/local ac_clean_files= ac_config_libobj_dir=. LIBOBJS= cross_compiling=no subdirs= MFLAGS= MAKEFLAGS= # Identity of this package. PACKAGE_NAME='eyeD3' PACKAGE_TARNAME='eyeD3' PACKAGE_VERSION='0.6.18' PACKAGE_STRING='eyeD3 0.6.18' PACKAGE_BUGREPORT='Travis Shirk ' PACKAGE_URL='' ac_subst_vars='EYED3_HELP LTLIBOBJS LIBOBJS PYTHON_VERSION PYTHON SET_MAKE MANPAGE_DATE BUILD_DATE EBUILD_VERSION RPM_RELEASE target_alias host_alias build_alias LIBS ECHO_T ECHO_N ECHO_C DEFS mandir localedir libdir psdir pdfdir dvidir htmldir infodir docdir oldincludedir includedir localstatedir sharedstatedir sysconfdir datadir datarootdir libexecdir sbindir bindir program_transform_name prefix exec_prefix PACKAGE_URL PACKAGE_BUGREPORT PACKAGE_STRING PACKAGE_VERSION PACKAGE_TARNAME PACKAGE_NAME PATH_SEPARATOR SHELL' ac_subst_files='' ac_user_opts=' enable_option_checking ' ac_precious_vars='build_alias host_alias target_alias' # Initialize some variables set by options. ac_init_help= ac_init_version=false ac_unrecognized_opts= ac_unrecognized_sep= # The variables have the same names as the options, with # dashes changed to underlines. cache_file=/dev/null exec_prefix=NONE no_create= no_recursion= prefix=NONE program_prefix=NONE program_suffix=NONE program_transform_name=s,x,x, silent= site= srcdir= verbose= x_includes=NONE x_libraries=NONE # Installation directory options. # These are left unexpanded so users can "make install exec_prefix=/foo" # and all the variables that are supposed to be based on exec_prefix # by default will actually change. # Use braces instead of parens because sh, perl, etc. also accept them. # (The list follows the same order as the GNU Coding Standards.) bindir='${exec_prefix}/bin' sbindir='${exec_prefix}/sbin' libexecdir='${exec_prefix}/libexec' datarootdir='${prefix}/share' datadir='${datarootdir}' sysconfdir='${prefix}/etc' sharedstatedir='${prefix}/com' localstatedir='${prefix}/var' includedir='${prefix}/include' oldincludedir='/usr/include' docdir='${datarootdir}/doc/${PACKAGE_TARNAME}' infodir='${datarootdir}/info' htmldir='${docdir}' dvidir='${docdir}' pdfdir='${docdir}' psdir='${docdir}' libdir='${exec_prefix}/lib' localedir='${datarootdir}/locale' mandir='${datarootdir}/man' ac_prev= ac_dashdash= for ac_option do # If the previous option needs an argument, assign it. if test -n "$ac_prev"; then eval $ac_prev=\$ac_option ac_prev= continue fi case $ac_option in *=?*) ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;; *=) ac_optarg= ;; *) ac_optarg=yes ;; esac # Accept the important Cygnus configure options, so we can diagnose typos. case $ac_dashdash$ac_option in --) ac_dashdash=yes ;; -bindir | --bindir | --bindi | --bind | --bin | --bi) ac_prev=bindir ;; -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*) bindir=$ac_optarg ;; -build | --build | --buil | --bui | --bu) ac_prev=build_alias ;; -build=* | --build=* | --buil=* | --bui=* | --bu=*) build_alias=$ac_optarg ;; -cache-file | --cache-file | --cache-fil | --cache-fi \ | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c) ac_prev=cache_file ;; -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \ | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*) cache_file=$ac_optarg ;; --config-cache | -C) cache_file=config.cache ;; -datadir | --datadir | --datadi | --datad) ac_prev=datadir ;; -datadir=* | --datadir=* | --datadi=* | --datad=*) datadir=$ac_optarg ;; -datarootdir | --datarootdir | --datarootdi | --datarootd | --dataroot \ | --dataroo | --dataro | --datar) ac_prev=datarootdir ;; -datarootdir=* | --datarootdir=* | --datarootdi=* | --datarootd=* \ | --dataroot=* | --dataroo=* | --dataro=* | --datar=*) datarootdir=$ac_optarg ;; -disable-* | --disable-*) ac_useropt=`expr "x$ac_option" : 'x-*disable-\(.*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid feature name: $ac_useropt" ac_useropt_orig=$ac_useropt ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "enable_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--disable-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval enable_$ac_useropt=no ;; -docdir | --docdir | --docdi | --doc | --do) ac_prev=docdir ;; -docdir=* | --docdir=* | --docdi=* | --doc=* | --do=*) docdir=$ac_optarg ;; -dvidir | --dvidir | --dvidi | --dvid | --dvi | --dv) ac_prev=dvidir ;; -dvidir=* | --dvidir=* | --dvidi=* | --dvid=* | --dvi=* | --dv=*) dvidir=$ac_optarg ;; -enable-* | --enable-*) ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid feature name: $ac_useropt" ac_useropt_orig=$ac_useropt ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "enable_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--enable-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval enable_$ac_useropt=\$ac_optarg ;; -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \ | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \ | --exec | --exe | --ex) ac_prev=exec_prefix ;; -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \ | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \ | --exec=* | --exe=* | --ex=*) exec_prefix=$ac_optarg ;; -gas | --gas | --ga | --g) # Obsolete; use --with-gas. with_gas=yes ;; -help | --help | --hel | --he | -h) ac_init_help=long ;; -help=r* | --help=r* | --hel=r* | --he=r* | -hr*) ac_init_help=recursive ;; -help=s* | --help=s* | --hel=s* | --he=s* | -hs*) ac_init_help=short ;; -host | --host | --hos | --ho) ac_prev=host_alias ;; -host=* | --host=* | --hos=* | --ho=*) host_alias=$ac_optarg ;; -htmldir | --htmldir | --htmldi | --htmld | --html | --htm | --ht) ac_prev=htmldir ;; -htmldir=* | --htmldir=* | --htmldi=* | --htmld=* | --html=* | --htm=* \ | --ht=*) htmldir=$ac_optarg ;; -includedir | --includedir | --includedi | --included | --include \ | --includ | --inclu | --incl | --inc) ac_prev=includedir ;; -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \ | --includ=* | --inclu=* | --incl=* | --inc=*) includedir=$ac_optarg ;; -infodir | --infodir | --infodi | --infod | --info | --inf) ac_prev=infodir ;; -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*) infodir=$ac_optarg ;; -libdir | --libdir | --libdi | --libd) ac_prev=libdir ;; -libdir=* | --libdir=* | --libdi=* | --libd=*) libdir=$ac_optarg ;; -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \ | --libexe | --libex | --libe) ac_prev=libexecdir ;; -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \ | --libexe=* | --libex=* | --libe=*) libexecdir=$ac_optarg ;; -localedir | --localedir | --localedi | --localed | --locale) ac_prev=localedir ;; -localedir=* | --localedir=* | --localedi=* | --localed=* | --locale=*) localedir=$ac_optarg ;; -localstatedir | --localstatedir | --localstatedi | --localstated \ | --localstate | --localstat | --localsta | --localst | --locals) ac_prev=localstatedir ;; -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \ | --localstate=* | --localstat=* | --localsta=* | --localst=* | --locals=*) localstatedir=$ac_optarg ;; -mandir | --mandir | --mandi | --mand | --man | --ma | --m) ac_prev=mandir ;; -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*) mandir=$ac_optarg ;; -nfp | --nfp | --nf) # Obsolete; use --without-fp. with_fp=no ;; -no-create | --no-create | --no-creat | --no-crea | --no-cre \ | --no-cr | --no-c | -n) no_create=yes ;; -no-recursion | --no-recursion | --no-recursio | --no-recursi \ | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) no_recursion=yes ;; -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \ | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \ | --oldin | --oldi | --old | --ol | --o) ac_prev=oldincludedir ;; -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \ | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \ | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*) oldincludedir=$ac_optarg ;; -prefix | --prefix | --prefi | --pref | --pre | --pr | --p) ac_prev=prefix ;; -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*) prefix=$ac_optarg ;; -program-prefix | --program-prefix | --program-prefi | --program-pref \ | --program-pre | --program-pr | --program-p) ac_prev=program_prefix ;; -program-prefix=* | --program-prefix=* | --program-prefi=* \ | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*) program_prefix=$ac_optarg ;; -program-suffix | --program-suffix | --program-suffi | --program-suff \ | --program-suf | --program-su | --program-s) ac_prev=program_suffix ;; -program-suffix=* | --program-suffix=* | --program-suffi=* \ | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*) program_suffix=$ac_optarg ;; -program-transform-name | --program-transform-name \ | --program-transform-nam | --program-transform-na \ | --program-transform-n | --program-transform- \ | --program-transform | --program-transfor \ | --program-transfo | --program-transf \ | --program-trans | --program-tran \ | --progr-tra | --program-tr | --program-t) ac_prev=program_transform_name ;; -program-transform-name=* | --program-transform-name=* \ | --program-transform-nam=* | --program-transform-na=* \ | --program-transform-n=* | --program-transform-=* \ | --program-transform=* | --program-transfor=* \ | --program-transfo=* | --program-transf=* \ | --program-trans=* | --program-tran=* \ | --progr-tra=* | --program-tr=* | --program-t=*) program_transform_name=$ac_optarg ;; -pdfdir | --pdfdir | --pdfdi | --pdfd | --pdf | --pd) ac_prev=pdfdir ;; -pdfdir=* | --pdfdir=* | --pdfdi=* | --pdfd=* | --pdf=* | --pd=*) pdfdir=$ac_optarg ;; -psdir | --psdir | --psdi | --psd | --ps) ac_prev=psdir ;; -psdir=* | --psdir=* | --psdi=* | --psd=* | --ps=*) psdir=$ac_optarg ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil) silent=yes ;; -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) ac_prev=sbindir ;; -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ | --sbi=* | --sb=*) sbindir=$ac_optarg ;; -sharedstatedir | --sharedstatedir | --sharedstatedi \ | --sharedstated | --sharedstate | --sharedstat | --sharedsta \ | --sharedst | --shareds | --shared | --share | --shar \ | --sha | --sh) ac_prev=sharedstatedir ;; -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \ | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \ | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \ | --sha=* | --sh=*) sharedstatedir=$ac_optarg ;; -site | --site | --sit) ac_prev=site ;; -site=* | --site=* | --sit=*) site=$ac_optarg ;; -srcdir | --srcdir | --srcdi | --srcd | --src | --sr) ac_prev=srcdir ;; -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*) srcdir=$ac_optarg ;; -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \ | --syscon | --sysco | --sysc | --sys | --sy) ac_prev=sysconfdir ;; -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \ | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*) sysconfdir=$ac_optarg ;; -target | --target | --targe | --targ | --tar | --ta | --t) ac_prev=target_alias ;; -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*) target_alias=$ac_optarg ;; -v | -verbose | --verbose | --verbos | --verbo | --verb) verbose=yes ;; -version | --version | --versio | --versi | --vers | -V) ac_init_version=: ;; -with-* | --with-*) ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid package name: $ac_useropt" ac_useropt_orig=$ac_useropt ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "with_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--with-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval with_$ac_useropt=\$ac_optarg ;; -without-* | --without-*) ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid package name: $ac_useropt" ac_useropt_orig=$ac_useropt ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "with_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--without-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval with_$ac_useropt=no ;; --x) # Obsolete; use --with-x. with_x=yes ;; -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \ | --x-incl | --x-inc | --x-in | --x-i) ac_prev=x_includes ;; -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \ | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*) x_includes=$ac_optarg ;; -x-libraries | --x-libraries | --x-librarie | --x-librari \ | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l) ac_prev=x_libraries ;; -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \ | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*) x_libraries=$ac_optarg ;; -*) as_fn_error $? "unrecognized option: \`$ac_option' Try \`$0 --help' for more information" ;; *=*) ac_envvar=`expr "x$ac_option" : 'x\([^=]*\)='` # Reject names that are not valid shell variable names. case $ac_envvar in #( '' | [0-9]* | *[!_$as_cr_alnum]* ) as_fn_error $? "invalid variable name: \`$ac_envvar'" ;; esac eval $ac_envvar=\$ac_optarg export $ac_envvar ;; *) # FIXME: should be removed in autoconf 3.0. $as_echo "$as_me: WARNING: you should use --build, --host, --target" >&2 expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null && $as_echo "$as_me: WARNING: invalid host type: $ac_option" >&2 : "${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}" ;; esac done if test -n "$ac_prev"; then ac_option=--`echo $ac_prev | sed 's/_/-/g'` as_fn_error $? "missing argument to $ac_option" fi if test -n "$ac_unrecognized_opts"; then case $enable_option_checking in no) ;; fatal) as_fn_error $? "unrecognized options: $ac_unrecognized_opts" ;; *) $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;; esac fi # Check all directory arguments for consistency. for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \ datadir sysconfdir sharedstatedir localstatedir includedir \ oldincludedir docdir infodir htmldir dvidir pdfdir psdir \ libdir localedir mandir do eval ac_val=\$$ac_var # Remove trailing slashes. case $ac_val in */ ) ac_val=`expr "X$ac_val" : 'X\(.*[^/]\)' \| "X$ac_val" : 'X\(.*\)'` eval $ac_var=\$ac_val;; esac # Be sure to have absolute directory names. case $ac_val in [\\/$]* | ?:[\\/]* ) continue;; NONE | '' ) case $ac_var in *prefix ) continue;; esac;; esac as_fn_error $? "expected an absolute directory name for --$ac_var: $ac_val" done # There might be people who depend on the old broken behavior: `$host' # used to hold the argument of --host etc. # FIXME: To remove some day. build=$build_alias host=$host_alias target=$target_alias # FIXME: To remove some day. if test "x$host_alias" != x; then if test "x$build_alias" = x; then cross_compiling=maybe $as_echo "$as_me: WARNING: if you wanted to set the --build type, don't use --host. If a cross compiler is detected then cross compile mode will be used" >&2 elif test "x$build_alias" != "x$host_alias"; then cross_compiling=yes fi fi ac_tool_prefix= test -n "$host_alias" && ac_tool_prefix=$host_alias- test "$silent" = yes && exec 6>/dev/null ac_pwd=`pwd` && test -n "$ac_pwd" && ac_ls_di=`ls -di .` && ac_pwd_ls_di=`cd "$ac_pwd" && ls -di .` || as_fn_error $? "working directory cannot be determined" test "X$ac_ls_di" = "X$ac_pwd_ls_di" || as_fn_error $? "pwd does not report name of working directory" # Find the source files, if location was not specified. if test -z "$srcdir"; then ac_srcdir_defaulted=yes # Try the directory containing this script, then the parent directory. ac_confdir=`$as_dirname -- "$as_myself" || $as_expr X"$as_myself" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_myself" : 'X\(//\)[^/]' \| \ X"$as_myself" : 'X\(//\)$' \| \ X"$as_myself" : 'X\(/\)' \| . 2>/dev/null || $as_echo X"$as_myself" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` srcdir=$ac_confdir if test ! -r "$srcdir/$ac_unique_file"; then srcdir=.. fi else ac_srcdir_defaulted=no fi if test ! -r "$srcdir/$ac_unique_file"; then test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .." as_fn_error $? "cannot find sources ($ac_unique_file) in $srcdir" fi ac_msg="sources are in $srcdir, but \`cd $srcdir' does not work" ac_abs_confdir=`( cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error $? "$ac_msg" pwd)` # When building in place, set srcdir=. if test "$ac_abs_confdir" = "$ac_pwd"; then srcdir=. fi # Remove unnecessary trailing slashes from srcdir. # Double slashes in file names in object file debugging info # mess up M-x gdb in Emacs. case $srcdir in */) srcdir=`expr "X$srcdir" : 'X\(.*[^/]\)' \| "X$srcdir" : 'X\(.*\)'`;; esac for ac_var in $ac_precious_vars; do eval ac_env_${ac_var}_set=\${${ac_var}+set} eval ac_env_${ac_var}_value=\$${ac_var} eval ac_cv_env_${ac_var}_set=\${${ac_var}+set} eval ac_cv_env_${ac_var}_value=\$${ac_var} done # # Report the --help message. # if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF \`configure' configures eyeD3 0.6.18 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... To assign environment variables (e.g., CC, CFLAGS...), specify them as VAR=VALUE. See below for descriptions of some of the useful variables. Defaults for the options are specified in brackets. Configuration: -h, --help display this help and exit --help=short display options specific to this package --help=recursive display the short help of all the included packages -V, --version display version information and exit -q, --quiet, --silent do not print \`checking ...' messages --cache-file=FILE cache test results in FILE [disabled] -C, --config-cache alias for \`--cache-file=config.cache' -n, --no-create do not create output files --srcdir=DIR find the sources in DIR [configure dir or \`..'] Installation directories: --prefix=PREFIX install architecture-independent files in PREFIX [$ac_default_prefix] --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX [PREFIX] By default, \`make install' will install all the files in \`$ac_default_prefix/bin', \`$ac_default_prefix/lib' etc. You can specify an installation prefix other than \`$ac_default_prefix' using \`--prefix', for instance \`--prefix=\$HOME'. For better control, use the options below. Fine tuning of the installation directories: --bindir=DIR user executables [EPREFIX/bin] --sbindir=DIR system admin executables [EPREFIX/sbin] --libexecdir=DIR program executables [EPREFIX/libexec] --sysconfdir=DIR read-only single-machine data [PREFIX/etc] --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] --localstatedir=DIR modifiable single-machine data [PREFIX/var] --libdir=DIR object code libraries [EPREFIX/lib] --includedir=DIR C header files [PREFIX/include] --oldincludedir=DIR C header files for non-gcc [/usr/include] --datarootdir=DIR read-only arch.-independent data root [PREFIX/share] --datadir=DIR read-only architecture-independent data [DATAROOTDIR] --infodir=DIR info documentation [DATAROOTDIR/info] --localedir=DIR locale-dependent data [DATAROOTDIR/locale] --mandir=DIR man documentation [DATAROOTDIR/man] --docdir=DIR documentation root [DATAROOTDIR/doc/eyeD3] --htmldir=DIR html documentation [DOCDIR] --dvidir=DIR dvi documentation [DOCDIR] --pdfdir=DIR pdf documentation [DOCDIR] --psdir=DIR ps documentation [DOCDIR] _ACEOF cat <<\_ACEOF _ACEOF fi if test -n "$ac_init_help"; then case $ac_init_help in short | recursive ) echo "Configuration of eyeD3 0.6.18:";; esac cat <<\_ACEOF Report bugs to >. _ACEOF ac_status=$? fi if test "$ac_init_help" = "recursive"; then # If there are subdirs, report their specific --help. for ac_dir in : $ac_subdirs_all; do test "x$ac_dir" = x: && continue test -d "$ac_dir" || { cd "$srcdir" && ac_pwd=`pwd` && srcdir=. && test -d "$ac_dir"; } || continue ac_builddir=. case "$ac_dir" in .) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` # A ".." for each directory in $ac_dir_suffix. ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` case $ac_top_builddir_sub in "") ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; esac ;; esac ac_abs_top_builddir=$ac_pwd ac_abs_builddir=$ac_pwd$ac_dir_suffix # for backward compatibility: ac_top_builddir=$ac_top_build_prefix case $srcdir in .) # We are building in place. ac_srcdir=. ac_top_srcdir=$ac_top_builddir_sub ac_abs_top_srcdir=$ac_pwd ;; [\\/]* | ?:[\\/]* ) # Absolute name. ac_srcdir=$srcdir$ac_dir_suffix; ac_top_srcdir=$srcdir ac_abs_top_srcdir=$srcdir ;; *) # Relative name. ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix ac_top_srcdir=$ac_top_build_prefix$srcdir ac_abs_top_srcdir=$ac_pwd/$srcdir ;; esac ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix cd "$ac_dir" || { ac_status=$?; continue; } # Check for guested configure. if test -f "$ac_srcdir/configure.gnu"; then echo && $SHELL "$ac_srcdir/configure.gnu" --help=recursive elif test -f "$ac_srcdir/configure"; then echo && $SHELL "$ac_srcdir/configure" --help=recursive else $as_echo "$as_me: WARNING: no configuration information is in $ac_dir" >&2 fi || ac_status=$? cd "$ac_pwd" || { ac_status=$?; break; } done fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF eyeD3 configure 0.6.18 generated by GNU Autoconf 2.68 Copyright (C) 2010 Free Software Foundation, Inc. This configure script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it. GNU GPL _ACEOF exit fi ## ------------------------ ## ## Autoconf initialization. ## ## ------------------------ ## cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. It was created by eyeD3 $as_me 0.6.18, which was generated by GNU Autoconf 2.68. Invocation command line was $ $0 $@ _ACEOF exec 5>>config.log { cat <<_ASUNAME ## --------- ## ## Platform. ## ## --------- ## hostname = `(hostname || uname -n) 2>/dev/null | sed 1q` uname -m = `(uname -m) 2>/dev/null || echo unknown` uname -r = `(uname -r) 2>/dev/null || echo unknown` uname -s = `(uname -s) 2>/dev/null || echo unknown` uname -v = `(uname -v) 2>/dev/null || echo unknown` /usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null || echo unknown` /bin/uname -X = `(/bin/uname -X) 2>/dev/null || echo unknown` /bin/arch = `(/bin/arch) 2>/dev/null || echo unknown` /usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null || echo unknown` /usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null || echo unknown` /usr/bin/hostinfo = `(/usr/bin/hostinfo) 2>/dev/null || echo unknown` /bin/machine = `(/bin/machine) 2>/dev/null || echo unknown` /usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null || echo unknown` /bin/universe = `(/bin/universe) 2>/dev/null || echo unknown` _ASUNAME as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. $as_echo "PATH: $as_dir" done IFS=$as_save_IFS } >&5 cat >&5 <<_ACEOF ## ----------- ## ## Core tests. ## ## ----------- ## _ACEOF # Keep a trace of the command line. # Strip out --no-create and --no-recursion so they do not pile up. # Strip out --silent because we don't want to record it for future runs. # Also quote any args containing shell meta-characters. # Make two passes to allow for proper duplicate-argument suppression. ac_configure_args= ac_configure_args0= ac_configure_args1= ac_must_keep_next=false for ac_pass in 1 2 do for ac_arg do case $ac_arg in -no-create | --no-c* | -n | -no-recursion | --no-r*) continue ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil) continue ;; *\'*) ac_arg=`$as_echo "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; esac case $ac_pass in 1) as_fn_append ac_configure_args0 " '$ac_arg'" ;; 2) as_fn_append ac_configure_args1 " '$ac_arg'" if test $ac_must_keep_next = true; then ac_must_keep_next=false # Got value, back to normal. else case $ac_arg in *=* | --config-cache | -C | -disable-* | --disable-* \ | -enable-* | --enable-* | -gas | --g* | -nfp | --nf* \ | -q | -quiet | --q* | -silent | --sil* | -v | -verb* \ | -with-* | --with-* | -without-* | --without-* | --x) case "$ac_configure_args0 " in "$ac_configure_args1"*" '$ac_arg' "* ) continue ;; esac ;; -* ) ac_must_keep_next=true ;; esac fi as_fn_append ac_configure_args " '$ac_arg'" ;; esac done done { ac_configure_args0=; unset ac_configure_args0;} { ac_configure_args1=; unset ac_configure_args1;} # When interrupted or exit'd, cleanup temporary files, and complete # config.log. We remove comments because anyway the quotes in there # would cause problems or look ugly. # WARNING: Use '\'' to represent an apostrophe within the trap. # WARNING: Do not start the trap code with a newline, due to a FreeBSD 4.0 bug. trap 'exit_status=$? # Save into config.log some information that might help in debugging. { echo $as_echo "## ---------------- ## ## Cache variables. ## ## ---------------- ##" echo # The following way of writing the cache mishandles newlines in values, ( for ac_var in `(set) 2>&1 | sed -n '\''s/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'\''`; do eval ac_val=\$$ac_var case $ac_val in #( *${as_nl}*) case $ac_var in #( *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 $as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; esac case $ac_var in #( _ | IFS | as_nl) ;; #( BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( *) { eval $ac_var=; unset $ac_var;} ;; esac ;; esac done (set) 2>&1 | case $as_nl`(ac_space='\'' '\''; set) 2>&1` in #( *${as_nl}ac_space=\ *) sed -n \ "s/'\''/'\''\\\\'\'''\''/g; s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\''\\2'\''/p" ;; #( *) sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" ;; esac | sort ) echo $as_echo "## ----------------- ## ## Output variables. ## ## ----------------- ##" echo for ac_var in $ac_subst_vars do eval ac_val=\$$ac_var case $ac_val in *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; esac $as_echo "$ac_var='\''$ac_val'\''" done | sort echo if test -n "$ac_subst_files"; then $as_echo "## ------------------- ## ## File substitutions. ## ## ------------------- ##" echo for ac_var in $ac_subst_files do eval ac_val=\$$ac_var case $ac_val in *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; esac $as_echo "$ac_var='\''$ac_val'\''" done | sort echo fi if test -s confdefs.h; then $as_echo "## ----------- ## ## confdefs.h. ## ## ----------- ##" echo cat confdefs.h echo fi test "$ac_signal" != 0 && $as_echo "$as_me: caught signal $ac_signal" $as_echo "$as_me: exit $exit_status" } >&5 rm -f core *.core core.conftest.* && rm -f -r conftest* confdefs* conf$$* $ac_clean_files && exit $exit_status ' 0 for ac_signal in 1 2 13 15; do trap 'ac_signal='$ac_signal'; as_fn_exit 1' $ac_signal done ac_signal=0 # confdefs.h avoids OS command line length limits that DEFS can exceed. rm -f -r conftest* confdefs.h $as_echo "/* confdefs.h */" > confdefs.h # Predefined preprocessor variables. cat >>confdefs.h <<_ACEOF #define PACKAGE_NAME "$PACKAGE_NAME" _ACEOF cat >>confdefs.h <<_ACEOF #define PACKAGE_TARNAME "$PACKAGE_TARNAME" _ACEOF cat >>confdefs.h <<_ACEOF #define PACKAGE_VERSION "$PACKAGE_VERSION" _ACEOF cat >>confdefs.h <<_ACEOF #define PACKAGE_STRING "$PACKAGE_STRING" _ACEOF cat >>confdefs.h <<_ACEOF #define PACKAGE_BUGREPORT "$PACKAGE_BUGREPORT" _ACEOF cat >>confdefs.h <<_ACEOF #define PACKAGE_URL "$PACKAGE_URL" _ACEOF # Let the site file select an alternate cache file if it wants to. # Prefer an explicitly selected file to automatically selected ones. ac_site_file1=NONE ac_site_file2=NONE if test -n "$CONFIG_SITE"; then # We do not want a PATH search for config.site. case $CONFIG_SITE in #(( -*) ac_site_file1=./$CONFIG_SITE;; */*) ac_site_file1=$CONFIG_SITE;; *) ac_site_file1=./$CONFIG_SITE;; esac elif test "x$prefix" != xNONE; then ac_site_file1=$prefix/share/config.site ac_site_file2=$prefix/etc/config.site else ac_site_file1=$ac_default_prefix/share/config.site ac_site_file2=$ac_default_prefix/etc/config.site fi for ac_site_file in "$ac_site_file1" "$ac_site_file2" do test "x$ac_site_file" = xNONE && continue if test /dev/null != "$ac_site_file" && test -r "$ac_site_file"; then { $as_echo "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5 $as_echo "$as_me: loading site script $ac_site_file" >&6;} sed 's/^/| /' "$ac_site_file" >&5 . "$ac_site_file" \ || { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "failed to load site script $ac_site_file See \`config.log' for more details" "$LINENO" 5; } fi done if test -r "$cache_file"; then # Some versions of bash will fail to source /dev/null (special files # actually), so we avoid doing that. DJGPP emulates it as a regular file. if test /dev/null != "$cache_file" && test -f "$cache_file"; then { $as_echo "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5 $as_echo "$as_me: loading cache $cache_file" >&6;} case $cache_file in [\\/]* | ?:[\\/]* ) . "$cache_file";; *) . "./$cache_file";; esac fi else { $as_echo "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5 $as_echo "$as_me: creating cache $cache_file" >&6;} >$cache_file fi # Check that the precious variables saved in the cache have kept the same # value. ac_cache_corrupted=false for ac_var in $ac_precious_vars; do eval ac_old_set=\$ac_cv_env_${ac_var}_set eval ac_new_set=\$ac_env_${ac_var}_set eval ac_old_val=\$ac_cv_env_${ac_var}_value eval ac_new_val=\$ac_env_${ac_var}_value case $ac_old_set,$ac_new_set in set,) { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5 $as_echo "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;} ac_cache_corrupted=: ;; ,set) { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was not set in the previous run" >&5 $as_echo "$as_me: error: \`$ac_var' was not set in the previous run" >&2;} ac_cache_corrupted=: ;; ,);; *) if test "x$ac_old_val" != "x$ac_new_val"; then # differences in whitespace do not lead to failure. ac_old_val_w=`echo x $ac_old_val` ac_new_val_w=`echo x $ac_new_val` if test "$ac_old_val_w" != "$ac_new_val_w"; then { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' has changed since the previous run:" >&5 $as_echo "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;} ac_cache_corrupted=: else { $as_echo "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&5 $as_echo "$as_me: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&2;} eval $ac_var=\$ac_old_val fi { $as_echo "$as_me:${as_lineno-$LINENO}: former value: \`$ac_old_val'" >&5 $as_echo "$as_me: former value: \`$ac_old_val'" >&2;} { $as_echo "$as_me:${as_lineno-$LINENO}: current value: \`$ac_new_val'" >&5 $as_echo "$as_me: current value: \`$ac_new_val'" >&2;} fi;; esac # Pass precious variables to config.status. if test "$ac_new_set" = set; then case $ac_new_val in *\'*) ac_arg=$ac_var=`$as_echo "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;; *) ac_arg=$ac_var=$ac_new_val ;; esac case " $ac_configure_args " in *" '$ac_arg' "*) ;; # Avoid dups. Use of quotes ensures accuracy. *) as_fn_append ac_configure_args " '$ac_arg'" ;; esac fi done if $ac_cache_corrupted; then { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} { $as_echo "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5 $as_echo "$as_me: error: changes in the environment can compromise the build" >&2;} as_fn_error $? "run \`make distclean' and/or \`rm $cache_file' and start over" "$LINENO" 5 fi ## -------------------- ## ## Main body of script. ## ## -------------------- ## ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu RPM_RELEASE=1 EBUILD_RELEASE= EBUILD_VERSION=${PACKAGE_VERSION} if test -n "$EBUILD_RELEASE"; then EBUILD_VERSION=${EBUILD_VERSION}-${EBUILD_RELEASE} fi BUILD_DATE=`date` MANPAGE_DATE=`date +'%b. %d, %Y'` { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ${MAKE-make} sets \$(MAKE)" >&5 $as_echo_n "checking whether ${MAKE-make} sets \$(MAKE)... " >&6; } set x ${MAKE-make} ac_make=`$as_echo "$2" | sed 's/+/p/g; s/[^a-zA-Z0-9_]/_/g'` if eval \${ac_cv_prog_make_${ac_make}_set+:} false; then : $as_echo_n "(cached) " >&6 else cat >conftest.make <<\_ACEOF SHELL = /bin/sh all: @echo '@@@%%%=$(MAKE)=@@@%%%' _ACEOF # GNU make sometimes prints "make[1]: Entering ...", which would confuse us. case `${MAKE-make} -f conftest.make 2>/dev/null` in *@@@%%%=?*=@@@%%%*) eval ac_cv_prog_make_${ac_make}_set=yes;; *) eval ac_cv_prog_make_${ac_make}_set=no;; esac rm -f conftest.make fi if eval test \$ac_cv_prog_make_${ac_make}_set = yes; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } SET_MAKE= else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } SET_MAKE="MAKE=${MAKE-make}" fi PYTHON="" for python in python python2 python2.5; do unset ac_cv_path_PYTHON for ac_prog in $python do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_path_PYTHON+:} false; then : $as_echo_n "(cached) " >&6 else case $PYTHON in [\\/]* | ?:[\\/]*) ac_cv_path_PYTHON="$PYTHON" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ac_cv_path_PYTHON="$as_dir/$ac_word$ac_exec_ext" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS ;; esac fi PYTHON=$ac_cv_path_PYTHON if test -n "$PYTHON"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PYTHON" >&5 $as_echo "$PYTHON" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi test -n "$PYTHON" && break done if test -z "${PYTHON}"; then continue fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking if ${PYTHON} is version >= 2.5" >&5 $as_echo_n "checking if ${PYTHON} is version >= 2.5... " >&6; } version=`${PYTHON} -c 'import sys; print "%d.%d" % (sys.version_info[0], sys.version_info[1])'` # Used to indicate true or false condition ax_compare_version=false # Convert the two version strings to be compared into a format that # allows a simple string comparison. The end result is that a version # string of the form 1.12.5-r617 will be converted to the form # 0001001200050617. In other words, each number is zero padded to four # digits, and non digits are removed. ax_compare_version_A=`echo "${version}" | sed -e 's/\([0-9]*\)/Z\1Z/g' \ -e 's/Z\([0-9]\)Z/Z0\1Z/g' \ -e 's/Z\([0-9][0-9]\)Z/Z0\1Z/g' \ -e 's/Z\([0-9][0-9][0-9]\)Z/Z0\1Z/g' \ -e 's/[^0-9]//g'` ax_compare_version_B=`echo "2.5" | sed -e 's/\([0-9]*\)/Z\1Z/g' \ -e 's/Z\([0-9]\)Z/Z0\1Z/g' \ -e 's/Z\([0-9][0-9]\)Z/Z0\1Z/g' \ -e 's/Z\([0-9][0-9][0-9]\)Z/Z0\1Z/g' \ -e 's/[^0-9]//g'` ax_compare_version=`echo "x$ax_compare_version_A x$ax_compare_version_B" | sed 's/^ *//' | sort -r | sed "s/x${ax_compare_version_A}/true/;s/x${ax_compare_version_B}/false/;1q"` if test "$ax_compare_version" = "true" ; then : fi if test ${ax_compare_version} = "true"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } break else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } PYTHON="" fi done if test -z ${PYTHON}; then as_fn_error $? "python version 2.5 could not be found" "$LINENO" 5 fi PYTHON_VERSION=$version ac_config_files="$ac_config_files Makefile setup.py etc/eyeD3.spec src/eyeD3/__init__.py doc/eyeD3.1" cat >confcache <<\_ACEOF # This file is a shell script that caches the results of configure # tests run on this system so they can be shared between configure # scripts and configure runs, see configure's option --config-cache. # It is not useful on other systems. If it contains results you don't # want to keep, you may remove or edit it. # # config.status only pays attention to the cache file if you give it # the --recheck option to rerun configure. # # `ac_cv_env_foo' variables (set or unset) will be overridden when # loading this file, other *unset* `ac_cv_foo' will be assigned the # following values. _ACEOF # The following way of writing the cache mishandles newlines in values, # but we know of no workaround that is simple, portable, and efficient. # So, we kill variables containing newlines. # Ultrix sh set writes to stderr and can't be redirected directly, # and sets the high bit in the cache file unless we assign to the vars. ( for ac_var in `(set) 2>&1 | sed -n 's/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'`; do eval ac_val=\$$ac_var case $ac_val in #( *${as_nl}*) case $ac_var in #( *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 $as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; esac case $ac_var in #( _ | IFS | as_nl) ;; #( BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( *) { eval $ac_var=; unset $ac_var;} ;; esac ;; esac done (set) 2>&1 | case $as_nl`(ac_space=' '; set) 2>&1` in #( *${as_nl}ac_space=\ *) # `set' does not quote correctly, so add quotes: double-quote # substitution turns \\\\ into \\, and sed turns \\ into \. sed -n \ "s/'/'\\\\''/g; s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p" ;; #( *) # `set' quotes correctly as required by POSIX, so do not add quotes. sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" ;; esac | sort ) | sed ' /^ac_cv_env_/b end t clear :clear s/^\([^=]*\)=\(.*[{}].*\)$/test "${\1+set}" = set || &/ t end s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/ :end' >>confcache if diff "$cache_file" confcache >/dev/null 2>&1; then :; else if test -w "$cache_file"; then if test "x$cache_file" != "x/dev/null"; then { $as_echo "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5 $as_echo "$as_me: updating cache $cache_file" >&6;} if test ! -f "$cache_file" || test -h "$cache_file"; then cat confcache >"$cache_file" else case $cache_file in #( */* | ?:*) mv -f confcache "$cache_file"$$ && mv -f "$cache_file"$$ "$cache_file" ;; #( *) mv -f confcache "$cache_file" ;; esac fi fi else { $as_echo "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5 $as_echo "$as_me: not updating unwritable cache $cache_file" >&6;} fi fi rm -f confcache test "x$prefix" = xNONE && prefix=$ac_default_prefix # Let make expand exec_prefix. test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' # Transform confdefs.h into DEFS. # Protect against shell expansion while executing Makefile rules. # Protect against Makefile macro expansion. # # If the first sed substitution is executed (which looks for macros that # take arguments), then branch to the quote section. Otherwise, # look for a macro that doesn't take arguments. ac_script=' :mline /\\$/{ N s,\\\n,, b mline } t clear :clear s/^[ ]*#[ ]*define[ ][ ]*\([^ (][^ (]*([^)]*)\)[ ]*\(.*\)/-D\1=\2/g t quote s/^[ ]*#[ ]*define[ ][ ]*\([^ ][^ ]*\)[ ]*\(.*\)/-D\1=\2/g t quote b any :quote s/[ `~#$^&*(){}\\|;'\''"<>?]/\\&/g s/\[/\\&/g s/\]/\\&/g s/\$/$$/g H :any ${ g s/^\n// s/\n/ /g p } ' DEFS=`sed -n "$ac_script" confdefs.h` ac_libobjs= ac_ltlibobjs= U= for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue # 1. Remove the extension, and $U if already installed. ac_script='s/\$U\././;s/\.o$//;s/\.obj$//' ac_i=`$as_echo "$ac_i" | sed "$ac_script"` # 2. Prepend LIBOBJDIR. When used with automake>=1.10 LIBOBJDIR # will be set to the directory where LIBOBJS objects are built. as_fn_append ac_libobjs " \${LIBOBJDIR}$ac_i\$U.$ac_objext" as_fn_append ac_ltlibobjs " \${LIBOBJDIR}$ac_i"'$U.lo' done LIBOBJS=$ac_libobjs LTLIBOBJS=$ac_ltlibobjs : "${CONFIG_STATUS=./config.status}" ac_write_fail=0 ac_clean_files_save=$ac_clean_files ac_clean_files="$ac_clean_files $CONFIG_STATUS" { $as_echo "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5 $as_echo "$as_me: creating $CONFIG_STATUS" >&6;} as_write_fail=0 cat >$CONFIG_STATUS <<_ASEOF || as_write_fail=1 #! $SHELL # Generated by $as_me. # Run this file to recreate the current configuration. # Compiler output produced by configure, useful for debugging # configure, is in config.log if it exists. debug=false ac_cs_recheck=false ac_cs_silent=false SHELL=\${CONFIG_SHELL-$SHELL} export SHELL _ASEOF cat >>$CONFIG_STATUS <<\_ASEOF || as_write_fail=1 ## -------------------- ## ## M4sh Initialization. ## ## -------------------- ## # Be more Bourne compatible DUALCASE=1; export DUALCASE # for MKS sh if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which # is contrary to our usage. Disable this feature. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST else case `(set -o) 2>/dev/null` in #( *posix*) : set -o posix ;; #( *) : ;; esac fi as_nl=' ' export as_nl # Printing a long string crashes Solaris 7 /usr/bin/printf. as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo # Prefer a ksh shell builtin over an external printf program on Solaris, # but without wasting forks for bash or zsh. if test -z "$BASH_VERSION$ZSH_VERSION" \ && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then as_echo='print -r --' as_echo_n='print -rn --' elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then as_echo='printf %s\n' as_echo_n='printf %s' else if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' as_echo_n='/usr/ucb/echo -n' else as_echo_body='eval expr "X$1" : "X\\(.*\\)"' as_echo_n_body='eval arg=$1; case $arg in #( *"$as_nl"*) expr "X$arg" : "X\\(.*\\)$as_nl"; arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; esac; expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" ' export as_echo_n_body as_echo_n='sh -c $as_echo_n_body as_echo' fi export as_echo_body as_echo='sh -c $as_echo_body as_echo' fi # The user is always right. if test "${PATH_SEPARATOR+set}" != set; then PATH_SEPARATOR=: (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || PATH_SEPARATOR=';' } fi # IFS # We need space, tab and new line, in precisely that order. Quoting is # there to prevent editors from complaining about space-tab. # (If _AS_PATH_WALK were called with IFS unset, it would disable word # splitting by setting IFS to empty value.) IFS=" "" $as_nl" # Find who we are. Look in the path if we contain no directory separator. as_myself= case $0 in #(( *[\\/]* ) as_myself=$0 ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break done IFS=$as_save_IFS ;; esac # We did not find ourselves, most probably we were run as `sh COMMAND' # in which case we are not to be found in the path. if test "x$as_myself" = x; then as_myself=$0 fi if test ! -f "$as_myself"; then $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 exit 1 fi # Unset variables that we do not need and which cause bugs (e.g. in # pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1" # suppresses any "Segmentation fault" message there. '((' could # trigger a bug in pdksh 5.2.14. for as_var in BASH_ENV ENV MAIL MAILPATH do eval test x\${$as_var+set} = xset \ && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : done PS1='$ ' PS2='> ' PS4='+ ' # NLS nuisances. LC_ALL=C export LC_ALL LANGUAGE=C export LANGUAGE # CDPATH. (unset CDPATH) >/dev/null 2>&1 && unset CDPATH # as_fn_error STATUS ERROR [LINENO LOG_FD] # ---------------------------------------- # Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are # provided, also output the error to LOG_FD, referencing LINENO. Then exit the # script with STATUS, using 1 if that was 0. as_fn_error () { as_status=$1; test $as_status -eq 0 && as_status=1 if test "$4"; then as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 fi $as_echo "$as_me: error: $2" >&2 as_fn_exit $as_status } # as_fn_error # as_fn_set_status STATUS # ----------------------- # Set $? to STATUS, without forking. as_fn_set_status () { return $1 } # as_fn_set_status # as_fn_exit STATUS # ----------------- # Exit the shell with STATUS, even in a "trap 0" or "set -e" context. as_fn_exit () { set +e as_fn_set_status $1 exit $1 } # as_fn_exit # as_fn_unset VAR # --------------- # Portably unset VAR. as_fn_unset () { { eval $1=; unset $1;} } as_unset=as_fn_unset # as_fn_append VAR VALUE # ---------------------- # Append the text in VALUE to the end of the definition contained in VAR. Take # advantage of any shell optimizations that allow amortized linear growth over # repeated appends, instead of the typical quadratic growth present in naive # implementations. if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then : eval 'as_fn_append () { eval $1+=\$2 }' else as_fn_append () { eval $1=\$$1\$2 } fi # as_fn_append # as_fn_arith ARG... # ------------------ # Perform arithmetic evaluation on the ARGs, and store the result in the # global $as_val. Take advantage of shells that can avoid forks. The arguments # must be portable across $(()) and expr. if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then : eval 'as_fn_arith () { as_val=$(( $* )) }' else as_fn_arith () { as_val=`expr "$@" || test $? -eq 1` } fi # as_fn_arith if expr a : '\(a\)' >/dev/null 2>&1 && test "X`expr 00001 : '.*\(...\)'`" = X001; then as_expr=expr else as_expr=false fi if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then as_basename=basename else as_basename=false fi if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then as_dirname=dirname else as_dirname=false fi as_me=`$as_basename -- "$0" || $as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ X"$0" : 'X\(//\)$' \| \ X"$0" : 'X\(/\)' \| . 2>/dev/null || $as_echo X/"$0" | sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/ q } /^X\/\(\/\/\)$/{ s//\1/ q } /^X\/\(\/\).*/{ s//\1/ q } s/.*/./; q'` # Avoid depending upon Character Ranges. as_cr_letters='abcdefghijklmnopqrstuvwxyz' as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' as_cr_Letters=$as_cr_letters$as_cr_LETTERS as_cr_digits='0123456789' as_cr_alnum=$as_cr_Letters$as_cr_digits ECHO_C= ECHO_N= ECHO_T= case `echo -n x` in #((((( -n*) case `echo 'xy\c'` in *c*) ECHO_T=' ';; # ECHO_T is single tab character. xy) ECHO_C='\c';; *) echo `echo ksh88 bug on AIX 6.1` > /dev/null ECHO_T=' ';; esac;; *) ECHO_N='-n';; esac rm -f conf$$ conf$$.exe conf$$.file if test -d conf$$.dir; then rm -f conf$$.dir/conf$$.file else rm -f conf$$.dir mkdir conf$$.dir 2>/dev/null fi if (echo >conf$$.file) 2>/dev/null; then if ln -s conf$$.file conf$$ 2>/dev/null; then as_ln_s='ln -s' # ... but there are two gotchas: # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. # In both cases, we have to default to `cp -p'. ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || as_ln_s='cp -p' elif ln conf$$.file conf$$ 2>/dev/null; then as_ln_s=ln else as_ln_s='cp -p' fi else as_ln_s='cp -p' fi rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file rmdir conf$$.dir 2>/dev/null # as_fn_mkdir_p # ------------- # Create "$as_dir" as a directory, including parents if necessary. as_fn_mkdir_p () { case $as_dir in #( -*) as_dir=./$as_dir;; esac test -d "$as_dir" || eval $as_mkdir_p || { as_dirs= while :; do case $as_dir in #( *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( *) as_qdir=$as_dir;; esac as_dirs="'$as_qdir' $as_dirs" as_dir=`$as_dirname -- "$as_dir" || $as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_dir" : 'X\(//\)[^/]' \| \ X"$as_dir" : 'X\(//\)$' \| \ X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || $as_echo X"$as_dir" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` test -d "$as_dir" && break done test -z "$as_dirs" || eval "mkdir $as_dirs" } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" } # as_fn_mkdir_p if mkdir -p . 2>/dev/null; then as_mkdir_p='mkdir -p "$as_dir"' else test -d ./-p && rmdir ./-p as_mkdir_p=false fi if test -x / >/dev/null 2>&1; then as_test_x='test -x' else if ls -dL / >/dev/null 2>&1; then as_ls_L_option=L else as_ls_L_option= fi as_test_x=' eval sh -c '\'' if test -d "$1"; then test -d "$1/."; else case $1 in #( -*)set "./$1";; esac; case `ls -ld'$as_ls_L_option' "$1" 2>/dev/null` in #(( ???[sx]*):;;*)false;;esac;fi '\'' sh ' fi as_executable_p=$as_test_x # Sed expression to map a string onto a valid CPP name. as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" # Sed expression to map a string onto a valid variable name. as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" exec 6>&1 ## ----------------------------------- ## ## Main body of $CONFIG_STATUS script. ## ## ----------------------------------- ## _ASEOF test $as_write_fail = 0 && chmod +x $CONFIG_STATUS || ac_write_fail=1 cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # Save the log message, to keep $0 and so on meaningful, and to # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" This file was extended by eyeD3 $as_me 0.6.18, which was generated by GNU Autoconf 2.68. Invocation command line was CONFIG_FILES = $CONFIG_FILES CONFIG_HEADERS = $CONFIG_HEADERS CONFIG_LINKS = $CONFIG_LINKS CONFIG_COMMANDS = $CONFIG_COMMANDS $ $0 $@ on `(hostname || uname -n) 2>/dev/null | sed 1q` " _ACEOF case $ac_config_files in *" "*) set x $ac_config_files; shift; ac_config_files=$*;; esac cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 # Files that config.status was made for. config_files="$ac_config_files" _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 ac_cs_usage="\ \`$as_me' instantiates files and other configuration actions from templates according to the current configuration. Unless the files and actions are specified as TAGs, all are instantiated by default. Usage: $0 [OPTION]... [TAG]... -h, --help print this help, then exit -V, --version print version number and configuration settings, then exit --config print configuration, then exit -q, --quiet, --silent do not print progress messages -d, --debug don't remove temporary files --recheck update $as_me by reconfiguring in the same conditions --file=FILE[:TEMPLATE] instantiate the configuration file FILE Configuration files: $config_files Report bugs to >." _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ eyeD3 config.status 0.6.18 configured by $0, generated by GNU Autoconf 2.68, with options \\"\$ac_cs_config\\" Copyright (C) 2010 Free Software Foundation, Inc. This config.status script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it." ac_pwd='$ac_pwd' srcdir='$srcdir' test -n "\$AWK" || AWK=awk _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # The default lists apply if the user does not specify any file. ac_need_defaults=: while test $# != 0 do case $1 in --*=?*) ac_option=`expr "X$1" : 'X\([^=]*\)='` ac_optarg=`expr "X$1" : 'X[^=]*=\(.*\)'` ac_shift=: ;; --*=) ac_option=`expr "X$1" : 'X\([^=]*\)='` ac_optarg= ac_shift=: ;; *) ac_option=$1 ac_optarg=$2 ac_shift=shift ;; esac case $ac_option in # Handling of the options. -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r) ac_cs_recheck=: ;; --version | --versio | --versi | --vers | --ver | --ve | --v | -V ) $as_echo "$ac_cs_version"; exit ;; --config | --confi | --conf | --con | --co | --c ) $as_echo "$ac_cs_config"; exit ;; --debug | --debu | --deb | --de | --d | -d ) debug=: ;; --file | --fil | --fi | --f ) $ac_shift case $ac_optarg in *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; '') as_fn_error $? "missing file argument" ;; esac as_fn_append CONFIG_FILES " '$ac_optarg'" ac_need_defaults=false;; --he | --h | --help | --hel | -h ) $as_echo "$ac_cs_usage"; exit ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil | --si | --s) ac_cs_silent=: ;; # This is an error. -*) as_fn_error $? "unrecognized option: \`$1' Try \`$0 --help' for more information." ;; *) as_fn_append ac_config_targets " $1" ac_need_defaults=false ;; esac shift done ac_configure_extra_args= if $ac_cs_silent; then exec 6>/dev/null ac_configure_extra_args="$ac_configure_extra_args --silent" fi _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 if \$ac_cs_recheck; then set X '$SHELL' '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion shift \$as_echo "running CONFIG_SHELL=$SHELL \$*" >&6 CONFIG_SHELL='$SHELL' export CONFIG_SHELL exec "\$@" fi _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 exec 5>>config.log { echo sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX ## Running $as_me. ## _ASBOX $as_echo "$ac_log" } >&5 _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # Handling of arguments. for ac_config_target in $ac_config_targets do case $ac_config_target in "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;; "setup.py") CONFIG_FILES="$CONFIG_FILES setup.py" ;; "etc/eyeD3.spec") CONFIG_FILES="$CONFIG_FILES etc/eyeD3.spec" ;; "src/eyeD3/__init__.py") CONFIG_FILES="$CONFIG_FILES src/eyeD3/__init__.py" ;; "doc/eyeD3.1") CONFIG_FILES="$CONFIG_FILES doc/eyeD3.1" ;; *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;; esac done # If the user did not use the arguments to specify the items to instantiate, # then the envvar interface is used. Set only those that are not. # We use the long form for the default assignment because of an extremely # bizarre bug on SunOS 4.1.3. if $ac_need_defaults; then test "${CONFIG_FILES+set}" = set || CONFIG_FILES=$config_files fi # Have a temporary directory for convenience. Make it in the build tree # simply because there is no reason against having it here, and in addition, # creating and moving files from /tmp can sometimes cause problems. # Hook for its removal unless debugging. # Note that there is a small window in which the directory will not be cleaned: # after its creation but before its name has been assigned to `$tmp'. $debug || { tmp= ac_tmp= trap 'exit_status=$? : "${ac_tmp:=$tmp}" { test ! -d "$ac_tmp" || rm -fr "$ac_tmp"; } && exit $exit_status ' 0 trap 'as_fn_exit 1' 1 2 13 15 } # Create a (secure) tmp directory for tmp files. { tmp=`(umask 077 && mktemp -d "./confXXXXXX") 2>/dev/null` && test -d "$tmp" } || { tmp=./conf$$-$RANDOM (umask 077 && mkdir "$tmp") } || as_fn_error $? "cannot create a temporary directory in ." "$LINENO" 5 ac_tmp=$tmp # Set up the scripts for CONFIG_FILES section. # No need to generate them if there are no CONFIG_FILES. # This happens for instance with `./config.status config.h'. if test -n "$CONFIG_FILES"; then ac_cr=`echo X | tr X '\015'` # On cygwin, bash can eat \r inside `` if the user requested igncr. # But we know of no other shell where ac_cr would be empty at this # point, so we can use a bashism as a fallback. if test "x$ac_cr" = x; then eval ac_cr=\$\'\\r\' fi ac_cs_awk_cr=`$AWK 'BEGIN { print "a\rb" }' /dev/null` if test "$ac_cs_awk_cr" = "a${ac_cr}b"; then ac_cs_awk_cr='\\r' else ac_cs_awk_cr=$ac_cr fi echo 'BEGIN {' >"$ac_tmp/subs1.awk" && _ACEOF { echo "cat >conf$$subs.awk <<_ACEOF" && echo "$ac_subst_vars" | sed 's/.*/&!$&$ac_delim/' && echo "_ACEOF" } >conf$$subs.sh || as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 ac_delim_num=`echo "$ac_subst_vars" | grep -c '^'` ac_delim='%!_!# ' for ac_last_try in false false false false false :; do . ./conf$$subs.sh || as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 ac_delim_n=`sed -n "s/.*$ac_delim\$/X/p" conf$$subs.awk | grep -c X` if test $ac_delim_n = $ac_delim_num; then break elif $ac_last_try; then as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 else ac_delim="$ac_delim!$ac_delim _$ac_delim!! " fi done rm -f conf$$subs.sh cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 cat >>"\$ac_tmp/subs1.awk" <<\\_ACAWK && _ACEOF sed -n ' h s/^/S["/; s/!.*/"]=/ p g s/^[^!]*!// :repl t repl s/'"$ac_delim"'$// t delim :nl h s/\(.\{148\}\)..*/\1/ t more1 s/["\\]/\\&/g; s/^/"/; s/$/\\n"\\/ p n b repl :more1 s/["\\]/\\&/g; s/^/"/; s/$/"\\/ p g s/.\{148\}// t nl :delim h s/\(.\{148\}\)..*/\1/ t more2 s/["\\]/\\&/g; s/^/"/; s/$/"/ p b :more2 s/["\\]/\\&/g; s/^/"/; s/$/"\\/ p g s/.\{148\}// t delim ' >$CONFIG_STATUS || ac_write_fail=1 rm -f conf$$subs.awk cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 _ACAWK cat >>"\$ac_tmp/subs1.awk" <<_ACAWK && for (key in S) S_is_set[key] = 1 FS = "" } { line = $ 0 nfields = split(line, field, "@") substed = 0 len = length(field[1]) for (i = 2; i < nfields; i++) { key = field[i] keylen = length(key) if (S_is_set[key]) { value = S[key] line = substr(line, 1, len) "" value "" substr(line, len + keylen + 3) len += length(value) + length(field[++i]) substed = 1 } else len += 1 + keylen } print line } _ACAWK _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then sed "s/$ac_cr\$//; s/$ac_cr/$ac_cs_awk_cr/g" else cat fi < "$ac_tmp/subs1.awk" > "$ac_tmp/subs.awk" \ || as_fn_error $? "could not setup config files machinery" "$LINENO" 5 _ACEOF # VPATH may cause trouble with some makes, so we remove sole $(srcdir), # ${srcdir} and @srcdir@ entries from VPATH if srcdir is ".", strip leading and # trailing colons and then remove the whole line if VPATH becomes empty # (actually we leave an empty line to preserve line numbers). if test "x$srcdir" = x.; then ac_vpsub='/^[ ]*VPATH[ ]*=[ ]*/{ h s/// s/^/:/ s/[ ]*$/:/ s/:\$(srcdir):/:/g s/:\${srcdir}:/:/g s/:@srcdir@:/:/g s/^:*// s/:*$// x s/\(=[ ]*\).*/\1/ G s/\n// s/^[^=]*=[ ]*$// }' fi cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 fi # test -n "$CONFIG_FILES" eval set X " :F $CONFIG_FILES " shift for ac_tag do case $ac_tag in :[FHLC]) ac_mode=$ac_tag; continue;; esac case $ac_mode$ac_tag in :[FHL]*:*);; :L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5;; :[FH]-) ac_tag=-:-;; :[FH]*) ac_tag=$ac_tag:$ac_tag.in;; esac ac_save_IFS=$IFS IFS=: set x $ac_tag IFS=$ac_save_IFS shift ac_file=$1 shift case $ac_mode in :L) ac_source=$1;; :[FH]) ac_file_inputs= for ac_f do case $ac_f in -) ac_f="$ac_tmp/stdin";; *) # Look for the file first in the build tree, then in the source tree # (if the path is not absolute). The absolute path cannot be DOS-style, # because $ac_f cannot contain `:'. test -f "$ac_f" || case $ac_f in [\\/$]*) false;; *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";; esac || as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5;; esac case $ac_f in *\'*) ac_f=`$as_echo "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac as_fn_append ac_file_inputs " '$ac_f'" done # Let's still pretend it is `configure' which instantiates (i.e., don't # use $as_me), people would be surprised to read: # /* config.h. Generated by config.status. */ configure_input='Generated from '` $as_echo "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g' `' by configure.' if test x"$ac_file" != x-; then configure_input="$ac_file. $configure_input" { $as_echo "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5 $as_echo "$as_me: creating $ac_file" >&6;} fi # Neutralize special characters interpreted by sed in replacement strings. case $configure_input in #( *\&* | *\|* | *\\* ) ac_sed_conf_input=`$as_echo "$configure_input" | sed 's/[\\\\&|]/\\\\&/g'`;; #( *) ac_sed_conf_input=$configure_input;; esac case $ac_tag in *:-:* | *:-) cat >"$ac_tmp/stdin" \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;; esac ;; esac ac_dir=`$as_dirname -- "$ac_file" || $as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$ac_file" : 'X\(//\)[^/]' \| \ X"$ac_file" : 'X\(//\)$' \| \ X"$ac_file" : 'X\(/\)' \| . 2>/dev/null || $as_echo X"$ac_file" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` as_dir="$ac_dir"; as_fn_mkdir_p ac_builddir=. case "$ac_dir" in .) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` # A ".." for each directory in $ac_dir_suffix. ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` case $ac_top_builddir_sub in "") ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; esac ;; esac ac_abs_top_builddir=$ac_pwd ac_abs_builddir=$ac_pwd$ac_dir_suffix # for backward compatibility: ac_top_builddir=$ac_top_build_prefix case $srcdir in .) # We are building in place. ac_srcdir=. ac_top_srcdir=$ac_top_builddir_sub ac_abs_top_srcdir=$ac_pwd ;; [\\/]* | ?:[\\/]* ) # Absolute name. ac_srcdir=$srcdir$ac_dir_suffix; ac_top_srcdir=$srcdir ac_abs_top_srcdir=$srcdir ;; *) # Relative name. ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix ac_top_srcdir=$ac_top_build_prefix$srcdir ac_abs_top_srcdir=$ac_pwd/$srcdir ;; esac ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix case $ac_mode in :F) # # CONFIG_FILE # _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # If the template does not know about datarootdir, expand it. # FIXME: This hack should be removed a few years after 2.60. ac_datarootdir_hack=; ac_datarootdir_seen= ac_sed_dataroot=' /datarootdir/ { p q } /@datadir@/p /@docdir@/p /@infodir@/p /@localedir@/p /@mandir@/p' case `eval "sed -n \"\$ac_sed_dataroot\" $ac_file_inputs"` in *datarootdir*) ac_datarootdir_seen=yes;; *@datadir@*|*@docdir@*|*@infodir@*|*@localedir@*|*@mandir@*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5 $as_echo "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;} _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_datarootdir_hack=' s&@datadir@&$datadir&g s&@docdir@&$docdir&g s&@infodir@&$infodir&g s&@localedir@&$localedir&g s&@mandir@&$mandir&g s&\\\${datarootdir}&$datarootdir&g' ;; esac _ACEOF # Neutralize VPATH when `$srcdir' = `.'. # Shell code in configure.ac might set extrasub. # FIXME: do we really want to maintain this feature? cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_sed_extra="$ac_vpsub $extrasub _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 :t /@[a-zA-Z_][a-zA-Z_0-9]*@/!b s|@configure_input@|$ac_sed_conf_input|;t t s&@top_builddir@&$ac_top_builddir_sub&;t t s&@top_build_prefix@&$ac_top_build_prefix&;t t s&@srcdir@&$ac_srcdir&;t t s&@abs_srcdir@&$ac_abs_srcdir&;t t s&@top_srcdir@&$ac_top_srcdir&;t t s&@abs_top_srcdir@&$ac_abs_top_srcdir&;t t s&@builddir@&$ac_builddir&;t t s&@abs_builddir@&$ac_abs_builddir&;t t s&@abs_top_builddir@&$ac_abs_top_builddir&;t t $ac_datarootdir_hack " eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$ac_tmp/subs.awk" \ >$ac_tmp/out || as_fn_error $? "could not create $ac_file" "$LINENO" 5 test -z "$ac_datarootdir_hack$ac_datarootdir_seen" && { ac_out=`sed -n '/\${datarootdir}/p' "$ac_tmp/out"`; test -n "$ac_out"; } && { ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' \ "$ac_tmp/out"`; test -z "$ac_out"; } && { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir' which seems to be undefined. Please make sure it is defined" >&5 $as_echo "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir' which seems to be undefined. Please make sure it is defined" >&2;} rm -f "$ac_tmp/stdin" case $ac_file in -) cat "$ac_tmp/out" && rm -f "$ac_tmp/out";; *) rm -f "$ac_file" && mv "$ac_tmp/out" "$ac_file";; esac \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;; esac done # for ac_tag as_fn_exit 0 _ACEOF ac_clean_files=$ac_clean_files_save test $ac_write_fail = 0 || as_fn_error $? "write failure creating $CONFIG_STATUS" "$LINENO" 5 # configure is writing to config.log, and then calls config.status. # config.status does its own redirection, appending to config.log. # Unfortunately, on DOS this fails, as config.log is still kept open # by configure, so config.status won't be able to write to it; its # output is simply discarded. So we exec the FD to /dev/null, # effectively closing config.log, so it can be properly (re)opened and # appended to by config.status. When coming back to configure, we # need to make the FD available again. if test "$no_create" != yes; then ac_cs_success=: ac_config_status_args= test "$silent" = yes && ac_config_status_args="$ac_config_status_args --quiet" exec 5>/dev/null $SHELL $CONFIG_STATUS $ac_config_status_args || ac_cs_success=false exec 5>>config.log # Use ||, not &&, to avoid exiting from the if with $? = 1, which # would make configure fail if this is the last instruction. $ac_cs_success || as_fn_exit 1 fi if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5 $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;} fi if test -f README.t2t.in; then ac_config_files="$ac_config_files README.t2t" export PYTHONPATH="`pwd`/src" EYED3_HELP="`./bin/eyeD3 --help`" cat >confcache <<\_ACEOF # This file is a shell script that caches the results of configure # tests run on this system so they can be shared between configure # scripts and configure runs, see configure's option --config-cache. # It is not useful on other systems. If it contains results you don't # want to keep, you may remove or edit it. # # config.status only pays attention to the cache file if you give it # the --recheck option to rerun configure. # # `ac_cv_env_foo' variables (set or unset) will be overridden when # loading this file, other *unset* `ac_cv_foo' will be assigned the # following values. _ACEOF # The following way of writing the cache mishandles newlines in values, # but we know of no workaround that is simple, portable, and efficient. # So, we kill variables containing newlines. # Ultrix sh set writes to stderr and can't be redirected directly, # and sets the high bit in the cache file unless we assign to the vars. ( for ac_var in `(set) 2>&1 | sed -n 's/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'`; do eval ac_val=\$$ac_var case $ac_val in #( *${as_nl}*) case $ac_var in #( *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 $as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; esac case $ac_var in #( _ | IFS | as_nl) ;; #( BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( *) { eval $ac_var=; unset $ac_var;} ;; esac ;; esac done (set) 2>&1 | case $as_nl`(ac_space=' '; set) 2>&1` in #( *${as_nl}ac_space=\ *) # `set' does not quote correctly, so add quotes: double-quote # substitution turns \\\\ into \\, and sed turns \\ into \. sed -n \ "s/'/'\\\\''/g; s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p" ;; #( *) # `set' quotes correctly as required by POSIX, so do not add quotes. sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" ;; esac | sort ) | sed ' /^ac_cv_env_/b end t clear :clear s/^\([^=]*\)=\(.*[{}].*\)$/test "${\1+set}" = set || &/ t end s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/ :end' >>confcache if diff "$cache_file" confcache >/dev/null 2>&1; then :; else if test -w "$cache_file"; then if test "x$cache_file" != "x/dev/null"; then { $as_echo "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5 $as_echo "$as_me: updating cache $cache_file" >&6;} if test ! -f "$cache_file" || test -h "$cache_file"; then cat confcache >"$cache_file" else case $cache_file in #( */* | ?:*) mv -f confcache "$cache_file"$$ && mv -f "$cache_file"$$ "$cache_file" ;; #( *) mv -f confcache "$cache_file" ;; esac fi fi else { $as_echo "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5 $as_echo "$as_me: not updating unwritable cache $cache_file" >&6;} fi fi rm -f confcache test "x$prefix" = xNONE && prefix=$ac_default_prefix # Let make expand exec_prefix. test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' # Transform confdefs.h into DEFS. # Protect against shell expansion while executing Makefile rules. # Protect against Makefile macro expansion. # # If the first sed substitution is executed (which looks for macros that # take arguments), then branch to the quote section. Otherwise, # look for a macro that doesn't take arguments. ac_script=' :mline /\\$/{ N s,\\\n,, b mline } t clear :clear s/^[ ]*#[ ]*define[ ][ ]*\([^ (][^ (]*([^)]*)\)[ ]*\(.*\)/-D\1=\2/g t quote s/^[ ]*#[ ]*define[ ][ ]*\([^ ][^ ]*\)[ ]*\(.*\)/-D\1=\2/g t quote b any :quote s/[ `~#$^&*(){}\\|;'\''"<>?]/\\&/g s/\[/\\&/g s/\]/\\&/g s/\$/$$/g H :any ${ g s/^\n// s/\n/ /g p } ' DEFS=`sed -n "$ac_script" confdefs.h` ac_libobjs= ac_ltlibobjs= U= for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue # 1. Remove the extension, and $U if already installed. ac_script='s/\$U\././;s/\.o$//;s/\.obj$//' ac_i=`$as_echo "$ac_i" | sed "$ac_script"` # 2. Prepend LIBOBJDIR. When used with automake>=1.10 LIBOBJDIR # will be set to the directory where LIBOBJS objects are built. as_fn_append ac_libobjs " \${LIBOBJDIR}$ac_i\$U.$ac_objext" as_fn_append ac_ltlibobjs " \${LIBOBJDIR}$ac_i"'$U.lo' done LIBOBJS=$ac_libobjs LTLIBOBJS=$ac_ltlibobjs : "${CONFIG_STATUS=./config.status}" ac_write_fail=0 ac_clean_files_save=$ac_clean_files ac_clean_files="$ac_clean_files $CONFIG_STATUS" { $as_echo "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5 $as_echo "$as_me: creating $CONFIG_STATUS" >&6;} as_write_fail=0 cat >$CONFIG_STATUS <<_ASEOF || as_write_fail=1 #! $SHELL # Generated by $as_me. # Run this file to recreate the current configuration. # Compiler output produced by configure, useful for debugging # configure, is in config.log if it exists. debug=false ac_cs_recheck=false ac_cs_silent=false SHELL=\${CONFIG_SHELL-$SHELL} export SHELL _ASEOF cat >>$CONFIG_STATUS <<\_ASEOF || as_write_fail=1 ## -------------------- ## ## M4sh Initialization. ## ## -------------------- ## # Be more Bourne compatible DUALCASE=1; export DUALCASE # for MKS sh if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which # is contrary to our usage. Disable this feature. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST else case `(set -o) 2>/dev/null` in #( *posix*) : set -o posix ;; #( *) : ;; esac fi as_nl=' ' export as_nl # Printing a long string crashes Solaris 7 /usr/bin/printf. as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo # Prefer a ksh shell builtin over an external printf program on Solaris, # but without wasting forks for bash or zsh. if test -z "$BASH_VERSION$ZSH_VERSION" \ && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then as_echo='print -r --' as_echo_n='print -rn --' elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then as_echo='printf %s\n' as_echo_n='printf %s' else if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' as_echo_n='/usr/ucb/echo -n' else as_echo_body='eval expr "X$1" : "X\\(.*\\)"' as_echo_n_body='eval arg=$1; case $arg in #( *"$as_nl"*) expr "X$arg" : "X\\(.*\\)$as_nl"; arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; esac; expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" ' export as_echo_n_body as_echo_n='sh -c $as_echo_n_body as_echo' fi export as_echo_body as_echo='sh -c $as_echo_body as_echo' fi # The user is always right. if test "${PATH_SEPARATOR+set}" != set; then PATH_SEPARATOR=: (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || PATH_SEPARATOR=';' } fi # IFS # We need space, tab and new line, in precisely that order. Quoting is # there to prevent editors from complaining about space-tab. # (If _AS_PATH_WALK were called with IFS unset, it would disable word # splitting by setting IFS to empty value.) IFS=" "" $as_nl" # Find who we are. Look in the path if we contain no directory separator. as_myself= case $0 in #(( *[\\/]* ) as_myself=$0 ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break done IFS=$as_save_IFS ;; esac # We did not find ourselves, most probably we were run as `sh COMMAND' # in which case we are not to be found in the path. if test "x$as_myself" = x; then as_myself=$0 fi if test ! -f "$as_myself"; then $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 exit 1 fi # Unset variables that we do not need and which cause bugs (e.g. in # pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1" # suppresses any "Segmentation fault" message there. '((' could # trigger a bug in pdksh 5.2.14. for as_var in BASH_ENV ENV MAIL MAILPATH do eval test x\${$as_var+set} = xset \ && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : done PS1='$ ' PS2='> ' PS4='+ ' # NLS nuisances. LC_ALL=C export LC_ALL LANGUAGE=C export LANGUAGE # CDPATH. (unset CDPATH) >/dev/null 2>&1 && unset CDPATH # as_fn_error STATUS ERROR [LINENO LOG_FD] # ---------------------------------------- # Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are # provided, also output the error to LOG_FD, referencing LINENO. Then exit the # script with STATUS, using 1 if that was 0. as_fn_error () { as_status=$1; test $as_status -eq 0 && as_status=1 if test "$4"; then as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 fi $as_echo "$as_me: error: $2" >&2 as_fn_exit $as_status } # as_fn_error # as_fn_set_status STATUS # ----------------------- # Set $? to STATUS, without forking. as_fn_set_status () { return $1 } # as_fn_set_status # as_fn_exit STATUS # ----------------- # Exit the shell with STATUS, even in a "trap 0" or "set -e" context. as_fn_exit () { set +e as_fn_set_status $1 exit $1 } # as_fn_exit # as_fn_unset VAR # --------------- # Portably unset VAR. as_fn_unset () { { eval $1=; unset $1;} } as_unset=as_fn_unset # as_fn_append VAR VALUE # ---------------------- # Append the text in VALUE to the end of the definition contained in VAR. Take # advantage of any shell optimizations that allow amortized linear growth over # repeated appends, instead of the typical quadratic growth present in naive # implementations. if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then : eval 'as_fn_append () { eval $1+=\$2 }' else as_fn_append () { eval $1=\$$1\$2 } fi # as_fn_append # as_fn_arith ARG... # ------------------ # Perform arithmetic evaluation on the ARGs, and store the result in the # global $as_val. Take advantage of shells that can avoid forks. The arguments # must be portable across $(()) and expr. if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then : eval 'as_fn_arith () { as_val=$(( $* )) }' else as_fn_arith () { as_val=`expr "$@" || test $? -eq 1` } fi # as_fn_arith if expr a : '\(a\)' >/dev/null 2>&1 && test "X`expr 00001 : '.*\(...\)'`" = X001; then as_expr=expr else as_expr=false fi if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then as_basename=basename else as_basename=false fi if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then as_dirname=dirname else as_dirname=false fi as_me=`$as_basename -- "$0" || $as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ X"$0" : 'X\(//\)$' \| \ X"$0" : 'X\(/\)' \| . 2>/dev/null || $as_echo X/"$0" | sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/ q } /^X\/\(\/\/\)$/{ s//\1/ q } /^X\/\(\/\).*/{ s//\1/ q } s/.*/./; q'` # Avoid depending upon Character Ranges. as_cr_letters='abcdefghijklmnopqrstuvwxyz' as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' as_cr_Letters=$as_cr_letters$as_cr_LETTERS as_cr_digits='0123456789' as_cr_alnum=$as_cr_Letters$as_cr_digits ECHO_C= ECHO_N= ECHO_T= case `echo -n x` in #((((( -n*) case `echo 'xy\c'` in *c*) ECHO_T=' ';; # ECHO_T is single tab character. xy) ECHO_C='\c';; *) echo `echo ksh88 bug on AIX 6.1` > /dev/null ECHO_T=' ';; esac;; *) ECHO_N='-n';; esac rm -f conf$$ conf$$.exe conf$$.file if test -d conf$$.dir; then rm -f conf$$.dir/conf$$.file else rm -f conf$$.dir mkdir conf$$.dir 2>/dev/null fi if (echo >conf$$.file) 2>/dev/null; then if ln -s conf$$.file conf$$ 2>/dev/null; then as_ln_s='ln -s' # ... but there are two gotchas: # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. # In both cases, we have to default to `cp -p'. ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || as_ln_s='cp -p' elif ln conf$$.file conf$$ 2>/dev/null; then as_ln_s=ln else as_ln_s='cp -p' fi else as_ln_s='cp -p' fi rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file rmdir conf$$.dir 2>/dev/null # as_fn_mkdir_p # ------------- # Create "$as_dir" as a directory, including parents if necessary. as_fn_mkdir_p () { case $as_dir in #( -*) as_dir=./$as_dir;; esac test -d "$as_dir" || eval $as_mkdir_p || { as_dirs= while :; do case $as_dir in #( *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( *) as_qdir=$as_dir;; esac as_dirs="'$as_qdir' $as_dirs" as_dir=`$as_dirname -- "$as_dir" || $as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_dir" : 'X\(//\)[^/]' \| \ X"$as_dir" : 'X\(//\)$' \| \ X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || $as_echo X"$as_dir" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` test -d "$as_dir" && break done test -z "$as_dirs" || eval "mkdir $as_dirs" } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" } # as_fn_mkdir_p if mkdir -p . 2>/dev/null; then as_mkdir_p='mkdir -p "$as_dir"' else test -d ./-p && rmdir ./-p as_mkdir_p=false fi if test -x / >/dev/null 2>&1; then as_test_x='test -x' else if ls -dL / >/dev/null 2>&1; then as_ls_L_option=L else as_ls_L_option= fi as_test_x=' eval sh -c '\'' if test -d "$1"; then test -d "$1/."; else case $1 in #( -*)set "./$1";; esac; case `ls -ld'$as_ls_L_option' "$1" 2>/dev/null` in #(( ???[sx]*):;;*)false;;esac;fi '\'' sh ' fi as_executable_p=$as_test_x # Sed expression to map a string onto a valid CPP name. as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" # Sed expression to map a string onto a valid variable name. as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" exec 6>&1 ## ----------------------------------- ## ## Main body of $CONFIG_STATUS script. ## ## ----------------------------------- ## _ASEOF test $as_write_fail = 0 && chmod +x $CONFIG_STATUS || ac_write_fail=1 cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # Save the log message, to keep $0 and so on meaningful, and to # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" This file was extended by eyeD3 $as_me 0.6.18, which was generated by GNU Autoconf 2.68. Invocation command line was CONFIG_FILES = $CONFIG_FILES CONFIG_HEADERS = $CONFIG_HEADERS CONFIG_LINKS = $CONFIG_LINKS CONFIG_COMMANDS = $CONFIG_COMMANDS $ $0 $@ on `(hostname || uname -n) 2>/dev/null | sed 1q` " _ACEOF case $ac_config_files in *" "*) set x $ac_config_files; shift; ac_config_files=$*;; esac cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 # Files that config.status was made for. config_files="$ac_config_files" _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 ac_cs_usage="\ \`$as_me' instantiates files and other configuration actions from templates according to the current configuration. Unless the files and actions are specified as TAGs, all are instantiated by default. Usage: $0 [OPTION]... [TAG]... -h, --help print this help, then exit -V, --version print version number and configuration settings, then exit --config print configuration, then exit -q, --quiet, --silent do not print progress messages -d, --debug don't remove temporary files --recheck update $as_me by reconfiguring in the same conditions --file=FILE[:TEMPLATE] instantiate the configuration file FILE Configuration files: $config_files Report bugs to >." _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ eyeD3 config.status 0.6.18 configured by $0, generated by GNU Autoconf 2.68, with options \\"\$ac_cs_config\\" Copyright (C) 2010 Free Software Foundation, Inc. This config.status script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it." ac_pwd='$ac_pwd' srcdir='$srcdir' test -n "\$AWK" || AWK=awk _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # The default lists apply if the user does not specify any file. ac_need_defaults=: while test $# != 0 do case $1 in --*=?*) ac_option=`expr "X$1" : 'X\([^=]*\)='` ac_optarg=`expr "X$1" : 'X[^=]*=\(.*\)'` ac_shift=: ;; --*=) ac_option=`expr "X$1" : 'X\([^=]*\)='` ac_optarg= ac_shift=: ;; *) ac_option=$1 ac_optarg=$2 ac_shift=shift ;; esac case $ac_option in # Handling of the options. -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r) ac_cs_recheck=: ;; --version | --versio | --versi | --vers | --ver | --ve | --v | -V ) $as_echo "$ac_cs_version"; exit ;; --config | --confi | --conf | --con | --co | --c ) $as_echo "$ac_cs_config"; exit ;; --debug | --debu | --deb | --de | --d | -d ) debug=: ;; --file | --fil | --fi | --f ) $ac_shift case $ac_optarg in *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; '') as_fn_error $? "missing file argument" ;; esac as_fn_append CONFIG_FILES " '$ac_optarg'" ac_need_defaults=false;; --he | --h | --help | --hel | -h ) $as_echo "$ac_cs_usage"; exit ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil | --si | --s) ac_cs_silent=: ;; # This is an error. -*) as_fn_error $? "unrecognized option: \`$1' Try \`$0 --help' for more information." ;; *) as_fn_append ac_config_targets " $1" ac_need_defaults=false ;; esac shift done ac_configure_extra_args= if $ac_cs_silent; then exec 6>/dev/null ac_configure_extra_args="$ac_configure_extra_args --silent" fi _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 if \$ac_cs_recheck; then set X '$SHELL' '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion shift \$as_echo "running CONFIG_SHELL=$SHELL \$*" >&6 CONFIG_SHELL='$SHELL' export CONFIG_SHELL exec "\$@" fi _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 exec 5>>config.log { echo sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX ## Running $as_me. ## _ASBOX $as_echo "$ac_log" } >&5 _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # Handling of arguments. for ac_config_target in $ac_config_targets do case $ac_config_target in "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;; "setup.py") CONFIG_FILES="$CONFIG_FILES setup.py" ;; "etc/eyeD3.spec") CONFIG_FILES="$CONFIG_FILES etc/eyeD3.spec" ;; "src/eyeD3/__init__.py") CONFIG_FILES="$CONFIG_FILES src/eyeD3/__init__.py" ;; "doc/eyeD3.1") CONFIG_FILES="$CONFIG_FILES doc/eyeD3.1" ;; "README.t2t") CONFIG_FILES="$CONFIG_FILES README.t2t" ;; *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;; esac done # If the user did not use the arguments to specify the items to instantiate, # then the envvar interface is used. Set only those that are not. # We use the long form for the default assignment because of an extremely # bizarre bug on SunOS 4.1.3. if $ac_need_defaults; then test "${CONFIG_FILES+set}" = set || CONFIG_FILES=$config_files fi # Have a temporary directory for convenience. Make it in the build tree # simply because there is no reason against having it here, and in addition, # creating and moving files from /tmp can sometimes cause problems. # Hook for its removal unless debugging. # Note that there is a small window in which the directory will not be cleaned: # after its creation but before its name has been assigned to `$tmp'. $debug || { tmp= ac_tmp= trap 'exit_status=$? : "${ac_tmp:=$tmp}" { test ! -d "$ac_tmp" || rm -fr "$ac_tmp"; } && exit $exit_status ' 0 trap 'as_fn_exit 1' 1 2 13 15 } # Create a (secure) tmp directory for tmp files. { tmp=`(umask 077 && mktemp -d "./confXXXXXX") 2>/dev/null` && test -d "$tmp" } || { tmp=./conf$$-$RANDOM (umask 077 && mkdir "$tmp") } || as_fn_error $? "cannot create a temporary directory in ." "$LINENO" 5 ac_tmp=$tmp # Set up the scripts for CONFIG_FILES section. # No need to generate them if there are no CONFIG_FILES. # This happens for instance with `./config.status config.h'. if test -n "$CONFIG_FILES"; then ac_cr=`echo X | tr X '\015'` # On cygwin, bash can eat \r inside `` if the user requested igncr. # But we know of no other shell where ac_cr would be empty at this # point, so we can use a bashism as a fallback. if test "x$ac_cr" = x; then eval ac_cr=\$\'\\r\' fi ac_cs_awk_cr=`$AWK 'BEGIN { print "a\rb" }' /dev/null` if test "$ac_cs_awk_cr" = "a${ac_cr}b"; then ac_cs_awk_cr='\\r' else ac_cs_awk_cr=$ac_cr fi echo 'BEGIN {' >"$ac_tmp/subs1.awk" && _ACEOF { echo "cat >conf$$subs.awk <<_ACEOF" && echo "$ac_subst_vars" | sed 's/.*/&!$&$ac_delim/' && echo "_ACEOF" } >conf$$subs.sh || as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 ac_delim_num=`echo "$ac_subst_vars" | grep -c '^'` ac_delim='%!_!# ' for ac_last_try in false false false false false :; do . ./conf$$subs.sh || as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 ac_delim_n=`sed -n "s/.*$ac_delim\$/X/p" conf$$subs.awk | grep -c X` if test $ac_delim_n = $ac_delim_num; then break elif $ac_last_try; then as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 else ac_delim="$ac_delim!$ac_delim _$ac_delim!! " fi done rm -f conf$$subs.sh cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 cat >>"\$ac_tmp/subs1.awk" <<\\_ACAWK && _ACEOF sed -n ' h s/^/S["/; s/!.*/"]=/ p g s/^[^!]*!// :repl t repl s/'"$ac_delim"'$// t delim :nl h s/\(.\{148\}\)..*/\1/ t more1 s/["\\]/\\&/g; s/^/"/; s/$/\\n"\\/ p n b repl :more1 s/["\\]/\\&/g; s/^/"/; s/$/"\\/ p g s/.\{148\}// t nl :delim h s/\(.\{148\}\)..*/\1/ t more2 s/["\\]/\\&/g; s/^/"/; s/$/"/ p b :more2 s/["\\]/\\&/g; s/^/"/; s/$/"\\/ p g s/.\{148\}// t delim ' >$CONFIG_STATUS || ac_write_fail=1 rm -f conf$$subs.awk cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 _ACAWK cat >>"\$ac_tmp/subs1.awk" <<_ACAWK && for (key in S) S_is_set[key] = 1 FS = "" } { line = $ 0 nfields = split(line, field, "@") substed = 0 len = length(field[1]) for (i = 2; i < nfields; i++) { key = field[i] keylen = length(key) if (S_is_set[key]) { value = S[key] line = substr(line, 1, len) "" value "" substr(line, len + keylen + 3) len += length(value) + length(field[++i]) substed = 1 } else len += 1 + keylen } print line } _ACAWK _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then sed "s/$ac_cr\$//; s/$ac_cr/$ac_cs_awk_cr/g" else cat fi < "$ac_tmp/subs1.awk" > "$ac_tmp/subs.awk" \ || as_fn_error $? "could not setup config files machinery" "$LINENO" 5 _ACEOF # VPATH may cause trouble with some makes, so we remove sole $(srcdir), # ${srcdir} and @srcdir@ entries from VPATH if srcdir is ".", strip leading and # trailing colons and then remove the whole line if VPATH becomes empty # (actually we leave an empty line to preserve line numbers). if test "x$srcdir" = x.; then ac_vpsub='/^[ ]*VPATH[ ]*=[ ]*/{ h s/// s/^/:/ s/[ ]*$/:/ s/:\$(srcdir):/:/g s/:\${srcdir}:/:/g s/:@srcdir@:/:/g s/^:*// s/:*$// x s/\(=[ ]*\).*/\1/ G s/\n// s/^[^=]*=[ ]*$// }' fi cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 fi # test -n "$CONFIG_FILES" eval set X " :F $CONFIG_FILES " shift for ac_tag do case $ac_tag in :[FHLC]) ac_mode=$ac_tag; continue;; esac case $ac_mode$ac_tag in :[FHL]*:*);; :L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5;; :[FH]-) ac_tag=-:-;; :[FH]*) ac_tag=$ac_tag:$ac_tag.in;; esac ac_save_IFS=$IFS IFS=: set x $ac_tag IFS=$ac_save_IFS shift ac_file=$1 shift case $ac_mode in :L) ac_source=$1;; :[FH]) ac_file_inputs= for ac_f do case $ac_f in -) ac_f="$ac_tmp/stdin";; *) # Look for the file first in the build tree, then in the source tree # (if the path is not absolute). The absolute path cannot be DOS-style, # because $ac_f cannot contain `:'. test -f "$ac_f" || case $ac_f in [\\/$]*) false;; *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";; esac || as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5;; esac case $ac_f in *\'*) ac_f=`$as_echo "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac as_fn_append ac_file_inputs " '$ac_f'" done # Let's still pretend it is `configure' which instantiates (i.e., don't # use $as_me), people would be surprised to read: # /* config.h. Generated by config.status. */ configure_input='Generated from '` $as_echo "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g' `' by configure.' if test x"$ac_file" != x-; then configure_input="$ac_file. $configure_input" { $as_echo "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5 $as_echo "$as_me: creating $ac_file" >&6;} fi # Neutralize special characters interpreted by sed in replacement strings. case $configure_input in #( *\&* | *\|* | *\\* ) ac_sed_conf_input=`$as_echo "$configure_input" | sed 's/[\\\\&|]/\\\\&/g'`;; #( *) ac_sed_conf_input=$configure_input;; esac case $ac_tag in *:-:* | *:-) cat >"$ac_tmp/stdin" \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;; esac ;; esac ac_dir=`$as_dirname -- "$ac_file" || $as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$ac_file" : 'X\(//\)[^/]' \| \ X"$ac_file" : 'X\(//\)$' \| \ X"$ac_file" : 'X\(/\)' \| . 2>/dev/null || $as_echo X"$ac_file" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` as_dir="$ac_dir"; as_fn_mkdir_p ac_builddir=. case "$ac_dir" in .) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` # A ".." for each directory in $ac_dir_suffix. ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` case $ac_top_builddir_sub in "") ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; esac ;; esac ac_abs_top_builddir=$ac_pwd ac_abs_builddir=$ac_pwd$ac_dir_suffix # for backward compatibility: ac_top_builddir=$ac_top_build_prefix case $srcdir in .) # We are building in place. ac_srcdir=. ac_top_srcdir=$ac_top_builddir_sub ac_abs_top_srcdir=$ac_pwd ;; [\\/]* | ?:[\\/]* ) # Absolute name. ac_srcdir=$srcdir$ac_dir_suffix; ac_top_srcdir=$srcdir ac_abs_top_srcdir=$srcdir ;; *) # Relative name. ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix ac_top_srcdir=$ac_top_build_prefix$srcdir ac_abs_top_srcdir=$ac_pwd/$srcdir ;; esac ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix case $ac_mode in :F) # # CONFIG_FILE # _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # If the template does not know about datarootdir, expand it. # FIXME: This hack should be removed a few years after 2.60. ac_datarootdir_hack=; ac_datarootdir_seen= ac_sed_dataroot=' /datarootdir/ { p q } /@datadir@/p /@docdir@/p /@infodir@/p /@localedir@/p /@mandir@/p' case `eval "sed -n \"\$ac_sed_dataroot\" $ac_file_inputs"` in *datarootdir*) ac_datarootdir_seen=yes;; *@datadir@*|*@docdir@*|*@infodir@*|*@localedir@*|*@mandir@*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5 $as_echo "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;} _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_datarootdir_hack=' s&@datadir@&$datadir&g s&@docdir@&$docdir&g s&@infodir@&$infodir&g s&@localedir@&$localedir&g s&@mandir@&$mandir&g s&\\\${datarootdir}&$datarootdir&g' ;; esac _ACEOF # Neutralize VPATH when `$srcdir' = `.'. # Shell code in configure.ac might set extrasub. # FIXME: do we really want to maintain this feature? cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_sed_extra="$ac_vpsub $extrasub _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 :t /@[a-zA-Z_][a-zA-Z_0-9]*@/!b s|@configure_input@|$ac_sed_conf_input|;t t s&@top_builddir@&$ac_top_builddir_sub&;t t s&@top_build_prefix@&$ac_top_build_prefix&;t t s&@srcdir@&$ac_srcdir&;t t s&@abs_srcdir@&$ac_abs_srcdir&;t t s&@top_srcdir@&$ac_top_srcdir&;t t s&@abs_top_srcdir@&$ac_abs_top_srcdir&;t t s&@builddir@&$ac_builddir&;t t s&@abs_builddir@&$ac_abs_builddir&;t t s&@abs_top_builddir@&$ac_abs_top_builddir&;t t $ac_datarootdir_hack " eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$ac_tmp/subs.awk" \ >$ac_tmp/out || as_fn_error $? "could not create $ac_file" "$LINENO" 5 test -z "$ac_datarootdir_hack$ac_datarootdir_seen" && { ac_out=`sed -n '/\${datarootdir}/p' "$ac_tmp/out"`; test -n "$ac_out"; } && { ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' \ "$ac_tmp/out"`; test -z "$ac_out"; } && { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir' which seems to be undefined. Please make sure it is defined" >&5 $as_echo "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir' which seems to be undefined. Please make sure it is defined" >&2;} rm -f "$ac_tmp/stdin" case $ac_file in -) cat "$ac_tmp/out" && rm -f "$ac_tmp/out";; *) rm -f "$ac_file" && mv "$ac_tmp/out" "$ac_file";; esac \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;; esac done # for ac_tag as_fn_exit 0 _ACEOF ac_clean_files=$ac_clean_files_save test $ac_write_fail = 0 || as_fn_error $? "write failure creating $CONFIG_STATUS" "$LINENO" 5 # configure is writing to config.log, and then calls config.status. # config.status does its own redirection, appending to config.log. # Unfortunately, on DOS this fails, as config.log is still kept open # by configure, so config.status won't be able to write to it; its # output is simply discarded. So we exec the FD to /dev/null, # effectively closing config.log, so it can be properly (re)opened and # appended to by config.status. When coming back to configure, we # need to make the FD available again. if test "$no_create" != yes; then ac_cs_success=: ac_config_status_args= test "$silent" = yes && ac_config_status_args="$ac_config_status_args --quiet" exec 5>/dev/null $SHELL $CONFIG_STATUS $ac_config_status_args || ac_cs_success=false exec 5>>config.log # Use ||, not &&, to avoid exiting from the if with $? = 1, which # would make configure fail if this is the last instruction. $ac_cs_success || as_fn_exit 1 fi if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5 $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;} fi fi eyeD3-0.6.18/TODO0000644000175000017500000000266211663615630012666 0ustar travistravisPossible Enhancements: ====================== - From id3.org v1 compatibility tests: - id3v1_272_extra.mp3 -- utf-8 data in v1. Is this even valid??? - id3v1_007_basic_W.mp3 -- Need to ignore/error on junk data, depending on non-strict/strict ? Decoding errors on all genres higher than 128? X id3v1_014_year_F.mp3 is supposed to cause decoding error, but that's bunk. - From id3.org v2.2.0 compatibility tests: - 0101805.tag - Oi! genre - From id3.org v2.3.0 compatibility tests: 0120568.tag - encoding error ~ bin/eyeD3 XML output. ~ Regression/unit test app. - Native Support for: MCDI, IPLS/TIPL, TEXT, USLT SYLT, TDAT (2.3 rendering TDRC) - thatspot.tag - Parses fine, but native support for TMED, USLT, POPM, TLEN, and IPLS is needed. Useful options that could be added: ================================== - -P, --force-padding Enforce padding of tag data - --padded-size Set padded size - -E, --export[=FILENAME] Export tag(s) to file Extra Credit: ============== - Handle appended ID3v2 tags, SEEK frames, and footers. - Native support for all frames! - Abide by read only and tag/file alter flags in tag header when setting/saving. - Abide by extended header restrictions when setting/saving. - Ogg tag reading - Ogg tag writing - Ogg audio spec support - GUI eyeD3-0.6.18/acsite.m40000644000175000017500000001613111663615630013704 0ustar travistravisdnl dnl Copyright (C) 2002 Travis Shirk dnl dnl This program is free software; you can redistribute it and/or modify dnl it under the terms of the GNU General Public License as published by dnl the Free Software Foundation; either version 2 of the License, or dnl (at your option) any later version. dnl dnl This program is distributed in the hope that it will be useful, dnl but WITHOUT ANY WARRANTY; without even the implied warranty of dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the dnl GNU General Public License for more details. dnl dnl You should have received a copy of the GNU General Public License dnl along with this program; if not, write to the Free Software dnl Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA dnl AC_DEFUN([ACX_CHECK_PYTHON], [ PYTHON="" for python in python python2 python$1; do dnl Unset to avoid cache hits that did not pass the version test unset ac_cv_path_PYTHON AC_PATH_PROGS([PYTHON], [$python]) if test -z "${PYTHON}"; then continue fi AC_MSG_CHECKING([if ${PYTHON} is version >= $1]) version=`${PYTHON} -c 'import sys; print "%d.%d" % (sys.version_info[[0]], sys.version_info[[1]])'` AX_COMPARE_VERSION([${version}], [ge], [$1]) if test ${ax_compare_version} = "true"; then AC_MSG_RESULT([yes]) break else AC_MSG_RESULT([no]) PYTHON="" fi done if test -z ${PYTHON}; then AC_MSG_ERROR([python version $1 could not be found]) fi PYTHON_VERSION=$version AC_SUBST([PYTHON_VERSION]) ]) AC_DEFUN([ACX_ID3LIB], [ ID3LIB_CXXFLAGS="" ID3LIB_LIBS="" AC_MSG_CHECKING([for id3lib]) for rootDir in /usr /usr/local; do if test -r ${rootDir}/include/id3/tag.h && test -r ${rootDir}/lib/libid3.so -o -r ${rootDir}/lib/libid3.a && test -r /usr/lib/libz.so -o -r /usr/lib/libz.a; then if test ${rootDir} != "/usr"; then ID3LIB_CXXFLAGS="-I${rootDir}/include" ID3LIB_LIBS="-L${rootDir}/lib -lid3 -lz" else ID3LIB_LIBS="-lid3 -lz" fi fi done if test -z "${ID3LIB_LIBS}"; then AC_MSG_RESULT([no]) HAVE_ID3LIB="no" AC_SUBST([HAVE_ID3LIB]) else AC_MSG_RESULT([yes]) AC_SUBST([ID3LIB_CXXFLAGS]) AC_SUBST([ID3LIB_LIBS]) AC_DEFINE([HAVE_ID3LIB], 1, [id3lib headers and libs available]) HAVE_ID3LIB="yes" AC_SUBST([HAVE_ID3LIB]) fi ]) AC_DEFUN([ACX_CHECK_OPTIK], [ AC_MSG_CHECKING([for Optik]) if ${PYTHON} -c 'import optik' > /dev/null 2>&1; then AC_MSG_RESULT([yes]) else AC_MSG_RESULT([no]) AC_MSG_WARN([The eyeD3 utility application requires Optik.]) fi ]) dnl ######################################################################### AC_DEFUN([AX_COMPARE_VERSION], [ # Used to indicate true or false condition ax_compare_version=false # Convert the two version strings to be compared into a format that # allows a simple string comparison. The end result is that a version # string of the form 1.12.5-r617 will be converted to the form # 0001001200050617. In other words, each number is zero padded to four # digits, and non digits are removed. AS_VAR_PUSHDEF([A],[ax_compare_version_A]) A=`echo "$1" | sed -e 's/\([[0-9]]*\)/Z\1Z/g' \ -e 's/Z\([[0-9]]\)Z/Z0\1Z/g' \ -e 's/Z\([[0-9]][[0-9]]\)Z/Z0\1Z/g' \ -e 's/Z\([[0-9]][[0-9]][[0-9]]\)Z/Z0\1Z/g' \ -e 's/[[^0-9]]//g'` AS_VAR_PUSHDEF([B],[ax_compare_version_B]) B=`echo "$3" | sed -e 's/\([[0-9]]*\)/Z\1Z/g' \ -e 's/Z\([[0-9]]\)Z/Z0\1Z/g' \ -e 's/Z\([[0-9]][[0-9]]\)Z/Z0\1Z/g' \ -e 's/Z\([[0-9]][[0-9]][[0-9]]\)Z/Z0\1Z/g' \ -e 's/[[^0-9]]//g'` dnl # In the case of le, ge, lt, and gt, the strings are sorted as necessary dnl # then the first line is used to determine if the condition is true. dnl # The sed right after the echo is to remove any indented white space. m4_case(m4_tolower($2), [lt],[ ax_compare_version=`echo "x$A x$B" | sed 's/^ *//' | sort -r | sed "s/x${A}/false/;s/x${B}/true/;1q"` ], [gt],[ ax_compare_version=`echo "x$A x$B" | sed 's/^ *//' | sort | sed "s/x${A}/false/;s/x${B}/true/;1q"` ], [le],[ ax_compare_version=`echo "x$A x$B" | sed 's/^ *//' | sort | sed "s/x${A}/true/;s/x${B}/false/;1q"` ], [ge],[ ax_compare_version=`echo "x$A x$B" | sed 's/^ *//' | sort -r | sed "s/x${A}/true/;s/x${B}/false/;1q"` ],[ dnl Split the operator from the subversion count if present. m4_bmatch(m4_substr($2,2), [0],[ # A count of zero means use the length of the shorter version. # Determine the number of characters in A and B. ax_compare_version_len_A=`echo "$A" | awk '{print(length)}'` ax_compare_version_len_B=`echo "$B" | awk '{print(length)}'` # Set A to no more than B's length and B to no more than A's length. A=`echo "$A" | sed "s/\(.\{$ax_compare_version_len_B\}\).*/\1/"` B=`echo "$B" | sed "s/\(.\{$ax_compare_version_len_A\}\).*/\1/"` ], [[0-9]+],[ # A count greater than zero means use only that many subversions A=`echo "$A" | sed "s/\(\([[0-9]]\{4\}\)\{m4_substr($2,2)\}\).*/\1/"` B=`echo "$B" | sed "s/\(\([[0-9]]\{4\}\)\{m4_substr($2,2)\}\).*/\1/"` ], [.+],[ AC_WARNING( [illegal OP numeric parameter: $2]) ],[]) # Pad zeros at end of numbers to make same length. ax_compare_version_tmp_A="$A`echo $B | sed 's/./0/g'`" B="$B`echo $A | sed 's/./0/g'`" A="$ax_compare_version_tmp_A" # Check for equality or inequality as necessary. m4_case(m4_tolower(m4_substr($2,0,2)), [eq],[ test "x$A" = "x$B" && ax_compare_version=true ], [ne],[ test "x$A" != "x$B" && ax_compare_version=true ],[ AC_WARNING([illegal OP parameter: $2]) ]) ]) AS_VAR_POPDEF([A])dnl AS_VAR_POPDEF([B])dnl dnl # Execute ACTION-IF-TRUE / ACTION-IF-FALSE. if test "$ax_compare_version" = "true" ; then m4_ifvaln([$4],[$4],[:])dnl m4_ifvaln([$5],[else $5])dnl fi ]) dnl AX_COMPARE_VERSION dnl AS_AC_EXPAND(VAR, CONFIGURE_VAR) dnl dnl example dnl AS_AC_EXPAND(SYSCONFDIR, $sysconfdir) dnl will set SYSCONFDIR to /usr/local/etc if prefix=/usr/local AC_DEFUN([AS_AC_EXPAND], [ EXP_VAR=[$1] FROM_VAR=[$2] dnl first expand prefix and exec_prefix if necessary prefix_save=$prefix exec_prefix_save=$exec_prefix dnl if no prefix given, then use /usr/local, the default prefix if test "x$prefix" = "xNONE"; then prefix=$ac_default_prefix fi dnl if no exec_prefix given, then use prefix if test "x$exec_prefix" = "xNONE"; then exec_prefix=$prefix fi full_var="$FROM_VAR" dnl loop until it doesn't change anymore while true; do new_full_var="`eval echo $full_var`" if test "x$new_full_var"="x$full_var"; then break; fi full_var=$new_full_var done dnl clean up full_var=$new_full_var AC_SUBST([$1], "$full_var") dnl restore prefix and exec_prefix prefix=$prefix_save exec_prefix=$exec_prefix_save ]) eyeD3-0.6.18/setup.py.in0000644000175000017500000000102211663615630014302 0ustar travistravisfrom distutils.core import setup; setup( name="eyeD3", url="http://eyed3.nicfit.net/", description="Python ID3 and mp3 Tools", author="Travis Shirk", author_email="travis@pobox.com", version="@PACKAGE_VERSION@", package_dir={'eyeD3': 'src/eyeD3'}, packages=["eyeD3"], long_description=""" eyeD3 is a Python module and program for processing ID3 tags. Information about mp3 files (i.e bit rate, sample frequency, play time, etc.) is also provided. The formats supported are ID3 v1.0/v1.1 and v2.3/v2.4. """ ) eyeD3-0.6.18/src/0000755000175000017500000000000011663615630012757 5ustar travistraviseyeD3-0.6.18/src/eyeD3/0000755000175000017500000000000011663615630013730 5ustar travistraviseyeD3-0.6.18/src/eyeD3/tag.py0000644000175000017500000020465111663615630015065 0ustar travistravis############################################################################### # Copyright (C) 2002-2007 Travis Shirk # # 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 2 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # ################################################################################ import re, os, string, stat, shutil, tempfile, binascii; from stat import *; from eyeD3 import *; import eyeD3.utils; import eyeD3.mp3; from frames import *; from binfuncs import *; import math; from xml.sax.saxutils import escape ID3_V1_COMMENT_DESC = "ID3 v1 Comment"; ################################################################################ class TagException(Exception): '''error reading tag''' ################################################################################ class TagHeader: SIZE = 10; version = None; majorVersion = None; minorVersion = None; revVersion = None; # Flag bits unsync = 0; extended = 0; experimental = 0; # v2.4 addition footer = 0; # The size in the most recently parsed header. tagSize = 0; # Constructor def __init__(self): self.clear(); def clear(self): self.setVersion(None); self.unsync = 0; self.extended = 0; self.experimental = 0; self.tagSize = 0; def setVersion(self, v): if v == None: self.version = None; self.majorVersion = None; self.minorVersion = None; self.revVersion = None; return; if v == ID3_CURRENT_VERSION: if self.majorVersion == None or self.minorVersion == None: v = ID3_DEFAULT_VERSION; else: return; elif v == ID3_ANY_VERSION: v = ID3_DEFAULT_VERSION; # Handle 3-element lists or tuples. if isinstance(v, tuple) or isinstance(v, list): self.version = eyeD3.utils.versionsToConstant(v); (self.majorVersion, self.minorVersion, self.revVersion) = v; # Handle int constants. elif isinstance(v, int): (self.majorVersion, self.minorVersion, self.revVersion) = eyeD3.utils.constantToVersions(v); self.version = v; else: raise TypeError("Wrong type: %s" % str(type(v))); # Given a file handle this method attempts to identify and then parse # a ID3 v2 header. If successful, the parsed values are stored in # the instance variable. If the files does not contain an ID3v2 tag # false is returned. A TagException is thrown if a tag is found, but is # not valid or corrupt. def parse(self, f): self.clear(); # The first three bytes of a v2 header is "ID3". if f.read(3) != "ID3": return 0; TRACE_MSG("Located ID3 v2 tag"); # The next 2 bytes are the minor and revision versions. version = f.read(2); major = 2; minor = ord(version[0]); rev = ord(version[1]); TRACE_MSG("TagHeader [major]: " + str(major)); TRACE_MSG("TagHeader [minor]: " + str(minor)); TRACE_MSG("TagHeader [revis]: " + str(rev)); if not (major == 2 and (minor >= 2 and minor <= 4)): raise TagException("ID3 v" + str(major) + "." + str(minor) +\ " is not supported."); # Get all the version madness in sync. self.setVersion([major, minor, rev]); # The first 4 bits of the next byte are flags. (self.unsync, self.extended, self.experimental, self.footer) = bytes2bin(f.read(1))[0:4]; TRACE_MSG("TagHeader [flags]: unsync(%d) extended(%d) "\ "experimental(%d) footer(%d)" % (self.unsync, self.extended, self.experimental, self.footer)); # The size of the extended header (optional), frames, and padding # afer unsynchronization. This is a sync safe integer, so only the # bottom 7 bits of each byte are used. tagSizeStr = f.read(4); TRACE_MSG("TagHeader [size string]: 0x%02x%02x%02x%02x" %\ (ord(tagSizeStr[0]), ord(tagSizeStr[1]), ord(tagSizeStr[2]), ord(tagSizeStr[3]))); self.tagSize = bin2dec(bytes2bin(tagSizeStr, 7)); TRACE_MSG("TagHeader [size]: %d (0x%x)" % (self.tagSize, self.tagSize)); return 1; def render(self, tagLen = None): if tagLen != None: self.tagSize = tagLen; data = "ID3"; data += chr(self.minorVersion) + chr(self.revVersion); # not not the values so we only get 1's and 0's. data += bin2bytes([not not self.unsync, not not self.extended, not not self.experimental, not not self.footer, 0, 0, 0, 0]); TRACE_MSG("Setting tag size to %d" % tagLen); szBytes = bin2bytes(bin2synchsafe(dec2bin(tagLen, 32))); data += szBytes; TRACE_MSG("TagHeader rendered %d bytes" % len(data)); return data; ################################################################################ class ExtendedTagHeader: size = 0; flags = 0; crc = 0; restrictions = 0; def isUpdate(self): return self.flags & 0x40; def hasCRC(self): return self.flags & 0x20; def hasRestrictions(self, minor_version = None): return self.flags & 0x10; def setSizeRestrictions(self, v): assert(v >= 0 and v <= 3); self.restrictions = (v << 6) | (self.restrictions & 0x3f); def getSizeRestrictions(self): return self.restrictions >> 6; def getSizeRestrictionsString(self): val = self.getSizeRestrictions(); if val == 0x00: return "No more than 128 frames and 1 MB total tag size."; elif val == 0x01: return "No more than 64 frames and 128 KB total tag size."; elif val == 0x02: return "No more than 32 frames and 40 KB total tag size."; elif val == 0x03: return "No more than 32 frames and 4 KB total tag size."; def setTextEncodingRestrictions(self, v): assert(v == 0 or v == 1); self.restrictions ^= 0x20; def getTextEncodingRestrictions(self): return self.restrictions & 0x20; def getTextEncodingRestrictionsString(self): if self.getTextEncodingRestrictions(): return "Strings are only encoded with ISO-8859-1 [ISO-8859-1] or "\ "UTF-8 [UTF-8]."; else: return "None"; def setTextFieldSizeRestrictions(self, v): assert(v >= 0 and v <= 3); self.restrictions = (v << 3) | (self.restrictions & 0xe7); def getTextFieldSizeRestrictions(self): return (self.restrictions >> 3) & 0x03; def getTextFieldSizeRestrictionsString(self): val = self.getTextFieldSizeRestrictions(); if val == 0x00: return "None"; elif val == 0x01: return "No string is longer than 1024 characters."; elif val == 0x02: return "No string is longer than 128 characters."; elif val == 0x03: return "No string is longer than 30 characters."; def setImageEncodingRestrictions(self, v): assert(v == 0 or v == 1); self.restrictions ^= 0x04; def getImageEncodingRestrictions(self): return self.restrictions & 0x04; def getImageEncodingRestrictionsString(self): if self.getImageEncodingRestrictions(): return "Images are encoded only with PNG [PNG] or JPEG [JFIF]."; else: return "None"; def setImageSizeRestrictions(self, v): assert(v >= 0 and v <= 3); self.restrictions = v | (self.restrictions & 0xfc); def getImageSizeRestrictions(self): return self.restrictions & 0x03; def getImageSizeRestrictionsString(self): val = self.getImageSizeRestrictions(); if val == 0x00: return "None"; elif val == 0x01: return "All images are 256x256 pixels or smaller."; elif val == 0x02: return "All images are 64x64 pixels or smaller."; elif val == 0x03: return "All images are exactly 64x64 pixels, unless required "\ "otherwise."; def _syncsafeCRC(self): bites = "" bites += chr((self.crc >> 28) & 0x7f); bites += chr((self.crc >> 21) & 0x7f); bites += chr((self.crc >> 14) & 0x7f); bites += chr((self.crc >> 7) & 0x7f); bites += chr((self.crc >> 0) & 0x7f); return bites; def render(self, header, frameData, padding=0): assert(header.majorVersion == 2); data = ""; crc = None; if header.minorVersion == 4: # Version 2.4 size = 6; # Extended flags. if self.isUpdate(): data += "\x00"; if self.hasCRC(): data += "\x05"; # XXX: Using the absolute value of the CRC. The spec is unclear # about the type of this data. self.crc = int(math.fabs(binascii.crc32(frameData +\ ("\x00" * padding)))); crc_data = self._syncsafeCRC(); if len(crc_data) < 5: crc_data = ("\x00" * (5 - len(crc_data))) + crc_data assert(len(crc_data) == 5) data += crc_data if self.hasRestrictions(): data += "\x01"; assert(len(self.restrictions) == 1); data += self.restrictions; TRACE_MSG("Rendered extended header data (%d bytes)" % len(data)); # Extended header size. size = bin2bytes(bin2synchsafe(dec2bin(len(data) + 6, 32))) assert(len(size) == 4); data = size + "\x01" + bin2bytes(dec2bin(self.flags)) + data; TRACE_MSG("Rendered extended header of size %d" % len(data)); else: # Version 2.3 size = 6; # Note, the 4 size bytes are not included in the size # Extended flags. f = [0] * 16; if self.hasCRC(): f[0] = 1; # XXX: Using the absolute value of the CRC. The spec is unclear # about the type of this value. self.crc = int(math.fabs(binascii.crc32(frameData +\ ("\x00" * padding)))); crc = bin2bytes(dec2bin(self.crc)); assert(len(crc) == 4); size += 4; flags = bin2bytes(f); assert(len(flags) == 2); # Extended header size. size = bin2bytes(dec2bin(size, 32)) assert(len(size) == 4); # Padding size paddingSize = bin2bytes(dec2bin(padding, 32)); data = size + flags + paddingSize; if crc: data += crc; return data; # Only call this when you *know* there is an extened header. def parse(self, fp, header): assert(header.majorVersion == 2); TRACE_MSG("Parsing extended header @ 0x%x" % fp.tell()); # First 4 bytes is the size of the extended header. data = fp.read(4); if header.minorVersion == 4: # sync-safe sz = bin2dec(bytes2bin(data, 7)); self.size = sz TRACE_MSG("Extended header size (includes the 4 size bytes): %d" % sz); data = fp.read(sz - 4); if ord(data[0]) != 1 or (ord(data[1]) & 0x8f): # As of 2.4 the first byte is 1 and the second can only have # bits 6, 5, and 4 set. raise TagException("Invalid Extended Header"); offset = 2; self.flags = ord(data[1]); TRACE_MSG("Extended header flags: %x" % self.flags); if self.isUpdate(): TRACE_MSG("Extended header has update bit set"); assert(ord(data[offset]) == 0); offset += 1; if self.hasCRC(): TRACE_MSG("Extended header has CRC bit set"); assert(ord(data[offset]) == 5); offset += 1; crcData = data[offset:offset + 5]; # This is sync-safe. self.crc = bin2dec(bytes2bin(crcData, 7)); TRACE_MSG("Extended header CRC: %d" % self.crc); offset += 5; if self.hasRestrictions(): TRACE_MSG("Extended header has restrictions bit set"); assert(ord(data[offset]) == 5); offset += 1; self.restrictions = ord(data[offset]); offset += 1; else: # v2.3 is totally different... *sigh* sz = bin2dec(bytes2bin(data)); TRACE_MSG("Extended header size (not including 4 size bytes): %d" % sz) self.size = sz + 4 # +4 to include size bytes tmpFlags = fp.read(2); # Read the padding size, but it'll be computed during the parse. ps = fp.read(4); TRACE_MSG("Extended header says there is %d bytes of padding" % bin2dec(bytes2bin(ps))); # Make this look like a v2.4 mask. self.flags = ord(tmpFlags[0]) >> 2; if self.hasCRC(): TRACE_MSG("Extended header has CRC bit set"); crcData = fp.read(4); self.crc = bin2dec(bytes2bin(crcData)); TRACE_MSG("Extended header CRC: %d" % self.crc); ################################################################################ # ID3 tag class. The class is capable of reading v1 and v2 tags. ID3 v1.x # are converted to v2 frames. class Tag: # Latin1 is the default (0x00) encoding = DEFAULT_ENCODING; # ID3v1 tags do not contain a header. The only ID3v1 values stored # in this header are the major/minor version. header = TagHeader(); # Optional in v2 tags. extendedHeader = ExtendedTagHeader(); # Contains the tag's frames. ID3v1 fields are read and converted # the the corresponding v2 frame. frames = None; # Used internally for iterating over frames. iterIndex = None; # If this value is None the tag is not linked to any particular file.. linkedFile = None; # add TDTG (or TXXX) - Tagging Time - when saved do_tdtg = True # Constructor. An empty tag is created and the link method is used # to read an mp3 file's v1.x or v2.x tag. You can optionally set a # file name, but it will not be read, but may be written to. def __init__(self, fileName = None): if fileName: self.linkedFile = LinkedFile(fileName); self.clear(); def clear(self): self.header = TagHeader(); self.frames = FrameSet(self.header); self.iterIndex = None; # Returns an read-only iterator for all frames. def __iter__(self): if len(self.frames): self.iterIndex = 0; else: self.iterIndex = None; return self; def next(self): if self.iterIndex == None or self.iterIndex == len(self.frames): raise StopIteration; frm = self.frames[self.iterIndex]; self.iterIndex += 1; return frm; # Returns true when an ID3 tag is read from f which may be a file name # or an aleady opened file object. In the latter case, the file object # is not closed when this method returns. # # By default, both ID3 v2 and v1 tags are parsed in that order. # If a v2 tag is found then a v1 parse is not performed. This behavior # can be refined by passing ID3_V1 or ID3_V2 as the second argument # instead of the default ID3_ANY_VERSION. # # Converts all ID3v1 data into ID3v2 frames internally. # May throw IOError, or TagException if parsing fails. def link(self, f, v = ID3_ANY_VERSION): self.linkedFile = None; self.clear(); fileName = ""; if isinstance(f, file): fileName = f.name; elif isinstance(f, str) or isinstance(f, unicode): fileName = f; else: raise TagException("Invalid type passed to Tag.link: " + str(type(f))); if v != ID3_V1 and v != ID3_V2 and v != ID3_ANY_VERSION: raise TagException("Invalid version: " + hex(v)); tagFound = 0; padding = 0; TRACE_MSG("Linking File: " + fileName); if v == ID3_V1: if self.__loadV1Tag(f): tagFound = 1; elif v == ID3_V2: padding = self.__loadV2Tag(f); if padding >= 0: tagFound = 1; elif v == ID3_ANY_VERSION: padding = self.__loadV2Tag(f); if padding >= 0: tagFound = 1; else: padding = 0; if self.__loadV1Tag(f): tagFound = 1; self.linkedFile = LinkedFile(fileName); if tagFound: # In the case of a v1.x tag this is zero. self.linkedFile.tagSize = self.header.tagSize; self.linkedFile.tagPadding = padding; else: self.linkedFile.tagSize = 0; self.linkedFile.tagPadding = 0; return tagFound; # Write the current tag state to the linked file. # The version of the ID3 file format that should be written can # be passed as an argument; the default is ID3_CURRENT_VERSION. def update(self, version = ID3_CURRENT_VERSION, backup = 0): if not self.linkedFile: raise TagException("The Tag is not linked to a file."); if backup: shutil.copyfile(self.linkedFile.name, self.linkedFile.name + ".orig"); self.setVersion(version); version = self.getVersion(); if version == ID3_V2_2: raise TagException("Unable to write ID3 v2.2"); # If v1.0 is being requested explicitly then so be it, if not and there is # a track number then bumping to v1.1 is /probably/ best. if self.header.majorVersion == 1 and self.header.minorVersion == 0 and\ self.getTrackNum()[0] != None and version != ID3_V1_0: version = ID3_V1_1; self.setVersion(version); # If there are no frames then simply remove the current tag. if len(self.frames) == 0: self.remove(version); self.header = TagHeader(); self.frames.setTagHeader(self.header); self.linkedFile.tagPadding = 0; self.linkedFile.tagSize = 0; return; if version & ID3_V1: self.__saveV1Tag(version); return 1; elif version & ID3_V2: self.__saveV2Tag(version); return 1; else: raise TagException("Invalid version: %s" % hex(version)); return 0; # Remove the tag. The version argument can selectively remove specific # ID3 tag versions; the default is ID3_CURRENT_VERSION meaning the version # of the current tag. A value of ID3_ANY_VERSION causes all tags to be # removed. def remove(self, version = ID3_CURRENT_VERSION): if not self.linkedFile: raise TagException("The Tag is not linked to a file; nothing to "\ "remove."); if version == ID3_CURRENT_VERSION: version = self.getVersion(); retval = 0; if version & ID3_V1 or version == ID3_ANY_VERSION: tagFile = file(self.linkedFile.name, "r+b"); tagFile.seek(-128, 2); if tagFile.read(3) == "TAG": TRACE_MSG("Removing ID3 v1.x Tag"); tagFile.seek(-3, 1); tagFile.truncate(); retval |= 1; tagFile.close(); if ((version & ID3_V2) or (version == ID3_ANY_VERSION)) and\ self.header.tagSize: tagFile = file(self.linkedFile.name, "r+b"); if tagFile.read(3) == "ID3": TRACE_MSG("Removing ID3 v2.x Tag"); tagSize = self.header.tagSize + self.header.SIZE; tagFile.seek(tagSize); # Open tmp file tmpName = tempfile.mktemp(); tmpFile = file(tmpName, "w+b"); # Write audio data in chunks self.__copyRemaining(tagFile, tmpFile); tagFile.truncate(); tagFile.close(); tmpFile.close(); # Move tmp to orig. shutil.copyfile(tmpName, self.linkedFile.name); os.unlink(tmpName); retval |= 1; return retval; # Get artist. There are a few frames that can contain this information, # and they are subtley different. # eyeD3.frames.ARTIST_FID - Lead performer(s)/Soloist(s) # eyeD3.frames.BAND_FID - Band/orchestra/accompaniment # eyeD3.frames.CONDUCTOR_FID - Conductor/performer refinement # eyeD3.frames.REMIXER_FID - Interpreted, remixed, or otherwise modified by # # Any of these values can be passed as an argument to select the artist # of interest. By default, the first one found (searched in the above order) # is the value returned. Most tags only have the ARTIST_FID, btw. # # When no artist is found, an empty string is returned. # def getArtist(self, artistID = ARTIST_FIDS): if isinstance(artistID, list): frameIDs = artistID; else: frameIDs = [artistID]; for fid in frameIDs: f = self.frames[fid]; if f: return f[0].text; return u""; def getAlbum(self): f = self.frames[ALBUM_FID]; if f: return f[0].text; else: return u""; # Get the track title. By default the main title is returned. Optionally, # you can pass: # eyeD3.frames.TITLE_FID - The title; the default. # eyeD3.frames.SUBTITLE_FID - The subtitle. # eyeD3.frames.CONTENT_TITLE_FID - Conten group description???? Rare. # An empty string is returned when no title exists. def getTitle(self, titleID = TITLE_FID): f = self.frames[titleID]; if f: return f[0].text; else: return u""; def getDate(self, fid = None): if not fid: for fid in DATE_FIDS: if self.frames[fid]: return self.frames[fid]; return None; return self.frames[fid]; def getYear(self, fid = None): dateFrame = self.getDate(fid); if dateFrame: return dateFrame[0].getYear(); else: return None; # Throws GenreException when the tag contains an unrecognized genre format. # Note this method returns a eyeD3.Genre object, not a raw string. def getGenre(self): f = self.frames[GENRE_FID]; if f and f[0].text: g = Genre(); g.parse(f[0].text); return g; else: return None; def _getNum(self, fid): tn = None tt = None f = self.frames[fid]; if f: n = f[0].text.split('/') if len(n) == 1: tn = self.toInt(n[0]) elif len(n) == 2: tn = self.toInt(n[0]) tt = self.toInt(n[1]) return (tn, tt) # Returns a tuple with the first value containing the track number and the # second the total number of tracks. One or both of these values may be # None depending on what is available in the tag. def getTrackNum(self): return self._getNum(TRACKNUM_FID) # Like TrackNum, except for DiscNum--that is, position in a set. Most # tags won't have this or it will be 1/1. def getDiscNum(self): return self._getNum(DISCNUM_FID) # Since multiple comment frames are allowed this returns a list with 0 # or more elements. The elements are not the comment strings, they are # eyeD3.frames.CommentFrame objects. def getComments(self): return self.frames[COMMENT_FID]; # Since multiple lyrics frames are allowed this returns a list with 0 # or more elements. The elements are not the lyrics strings, they are # eyeD3.frames.LyricsFrame objects. def getLyrics(self): return self.frames[LYRICS_FID]; # Returns a list (possibly empty) of eyeD3.frames.ImageFrame objects. def getImages(self): return self.frames[IMAGE_FID]; # Returns a list (possibly empty) of eyeD3.frames.ObjectFrame objects. def getObjects(self): return self.frames[OBJECT_FID]; # Returns a list (possibly empty) of eyeD3.frames.URLFrame objects. # Both URLFrame and UserURLFrame objects are returned. UserURLFrames # add a description and encoding, and have a different frame ID. def getURLs(self): urls = [] for fid in URL_FIDS: urls.extend(self.frames[fid]) urls.extend(self.frames[USERURL_FID]) return urls def getUserTextFrames(self): return self.frames[USERTEXT_FID]; def getCDID(self): return self.frames[CDID_FID]; def getVersion(self): return self.header.version; def getVersionStr(self): return versionToString(self.header.version); def strToUnicode(self, s): t = type(s); if t != unicode and t == str: s = unicode(s, eyeD3.LOCAL_ENCODING); elif t != unicode and t != str: raise TagException("Wrong type passed to strToUnicode: %s" % str(t)); return s; # Set the artist name. Arguments equal to None or "" cause the frame to # be removed. An optional second argument can be passed to select the # actual artist frame that should be set. By default, the main artist frame # (TPE1) is the value used. def setArtist(self, a, id = ARTIST_FID): self.setTextFrame(id, self.strToUnicode(a)); def setAlbum(self, a): self.setTextFrame(ALBUM_FID, self.strToUnicode(a)); def setTitle(self, t, titleID = TITLE_FID): self.setTextFrame(titleID, self.strToUnicode(t)); def setDate(self, year, month = None, dayOfMonth = None, hour = None, minute = None, second = None, fid = None): if not year and not fid: dateFrames = self.getDate(); if dateFrames: self.frames.removeFramesByID(dateFrames[0].header.id) return elif not year: self.frames.removeFramesByID(fid) else: self.frames.removeFramesByID(frames.OBSOLETE_YEAR_FID) dateStr = self.strToUnicode(str(year)); if len(dateStr) != 4: raise TagException("Invalid Year field: " + dateStr); if month: dateStr += "-" + self.__padDateField(month); if dayOfMonth: dateStr += "-" + self.__padDateField(dayOfMonth); if hour: dateStr += "T" + self.__padDateField(hour); if minute: dateStr += ":" + self.__padDateField(minute); if second: dateStr += ":" + self.__padDateField(second); if not fid: fid = "TDRL"; dateFrame = self.frames[fid]; try: if dateFrame: dateFrame[0].setDate(self.encoding + dateStr); else: header = FrameHeader(self.header); header.id = fid; dateFrame = DateFrame(header, encoding = self.encoding, date_str = self.strToUnicode(dateStr)); self.frames.addFrame(dateFrame); except FrameException, ex: raise TagException(str(ex)); # Three types are accepted for the genre parameter. A Genre object, an # acceptable (see Genre.parse) genre string, or an integer genre id. # Arguments equal to None or "" cause the frame to be removed. def setGenre(self, g): if g == None or g == "": self.frames.removeFramesByID(GENRE_FID); return; if isinstance(g, Genre): self.frames.setTextFrame(GENRE_FID, self.strToUnicode(str(g)), self.encoding); elif isinstance(g, str): gObj = Genre(); gObj.parse(g); self.frames.setTextFrame(GENRE_FID, self.strToUnicode(str(gObj)), self.encoding); elif isinstance(g, int): gObj = Genre(); gObj.id = g; self.frames.setTextFrame(GENRE_FID, self.strToUnicode(str(gObj)), self.encoding); else: raise TagException("Invalid type passed to setGenre: %s" + str(type(g))); # Accepts a tuple with the first value containing the track number and the # second the total number of tracks. One or both of these values may be # None. If both values are None, the frame is removed. def setTrackNum(self, n, zeropad = True): self.setNum(TRACKNUM_FID, n, zeropad) def setDiscNum(self, n, zeropad = True): self.setNum(DISCNUM_FID, n, zeropad) def setNum(self, fid, n, zeropad = True): if n[0] == None and n[1] == None: self.frames.removeFramesByID(fid); return; totalStr = ""; if n[1] != None: if zeropad and n[1] >= 0 and n[1] <= 9: totalStr = "0" + str(n[1]); else: totalStr = str(n[1]); t = n[0]; if t == None: t = 0; trackStr = str(t); # Pad with zeros according to how large the total count is. if zeropad: if len(trackStr) == 1: trackStr = "0" + trackStr; if len(trackStr) < len(totalStr): trackStr = ("0" * (len(totalStr) - len(trackStr))) + trackStr; s = ""; if trackStr and totalStr: s = trackStr + "/" + totalStr; elif trackStr and not totalStr: s = trackStr; self.frames.setTextFrame(fid, self.strToUnicode(s), self.encoding); # Add a comment. This adds a comment unless one is already present with # the same language and description in which case the current value is # either changed (cmt != "") or removed (cmt equals "" or None). def addComment(self, cmt, desc = u"", lang = DEFAULT_LANG): if not cmt: # A little more then a call to removeFramesByID is involved since we # need to look at more than the frame ID. comments = self.frames[COMMENT_FID]; for c in comments: if c.lang == lang and c.description == desc: self.frames.remove(c); break; else: self.frames.setCommentFrame(self.strToUnicode(cmt), self.strToUnicode(desc), lang, self.encoding); # Add lyrics. Semantics similar to addComment def addLyrics(self, lyr, desc = u"", lang = DEFAULT_LANG): if not lyr: # A little more than a call to removeFramesByID is involved since we # need to look at more than the frame ID. lyrics = self.frames[LYRICS_FID]; for l in lyrics: if l.lang == lang and l.description == desc: self.frames.remove(l); break; else: self.frames.setLyricsFrame(self.strToUnicode(lyr), self.strToUnicode(desc), lang, self.encoding); # Semantics similar to addComment def addUserTextFrame(self, desc, text): if not text: u_frames = self.frames[USERTEXT_FID] for u in u_frames: if u.description == desc: self.frames.remove(u); break else: self.frames.setUserTextFrame(self.strToUnicode(text), self.strToUnicode(desc), self.encoding); def addUserURLFrame(self, desc, url): if not url: u_frames = self.frames[USERURL_FID] for u in u_frames: if u.description == desc: self.frames.remove(u) break else: self.frames.setUserURLFrame(str(url), self.strToUnicode(desc), self.encoding) def removeUserTextFrame(self, desc): self.addUserTextFrame(desc, "") def removeUserURLFrame(self, desc): self.addUserURLFrame(desc, "") def removeComments(self): return self.frames.removeFramesByID(COMMENT_FID); def removeLyrics(self): return self.frames.removeFramesByID(LYRICS_FID); def removeImages(self): return self.frames.removeFramesByID(IMAGE_FID) def addImage(self, type, image_file_path, desc = u""): if image_file_path: image_frame = ImageFrame.create(type, image_file_path, desc); self.frames.addFrame(image_frame); else: image_frames = self.frames[IMAGE_FID]; for i in image_frames: if i.pictureType == type: self.frames.remove(i); break; def addObject(self, object_file_path, mime = "", desc = u"", filename = None ): object_frames = self.frames[OBJECT_FID]; for i in object_frames: if i.description == desc: self.frames.remove(i); if object_file_path: object_frame = ObjectFrame.create(object_file_path, mime, desc, filename); self.frames.addFrame(object_frame); def getPlayCount(self): if self.frames[PLAYCOUNT_FID]: pc = self.frames[PLAYCOUNT_FID][0]; assert(isinstance(pc, PlayCountFrame)); return pc.count; else: return None; def setPlayCount(self, count): assert(count >= 0); if self.frames[PLAYCOUNT_FID]: pc = self.frames[PLAYCOUNT_FID][0]; assert(isinstance(pc, PlayCountFrame)); pc.count = count; else: frameHeader = FrameHeader(self.header); frameHeader.id = PLAYCOUNT_FID; pc = PlayCountFrame(frameHeader, count = count); self.frames.addFrame(pc); def incrementPlayCount(self, n = 1): pc = self.getPlayCount(); if pc != None: self.setPlayCount(pc + n); else: self.setPlayCount(n); def getUniqueFileIDs(self): return self.frames[UNIQUE_FILE_ID_FID]; def addUniqueFileID(self, owner_id, id): if not id: ufids = self.frames[UNIQUE_FILE_ID_FID]; for ufid in ufids: if ufid.owner_id == owner_id: self.frames.remove(ufid); break; else: self.frames.setUniqueFileIDFrame(owner_id, id); def getBPM(self): bpm = self.frames[BPM_FID]; if bpm: try: bpm = float(bpm[0].text) except ValueError: # Invalid bpm value, in the spirit of not crashing... bpm = 0.0 finally: # Round floats since the spec says this is an integer return int(round(bpm)) else: return None; def setBPM(self, bpm): self.setTextFrame(BPM_FID, self.strToUnicode(str(bpm))) def getPublisher(self): pub = self.frames[PUBLISHER_FID]; if pub: return pub[0].text or None; def setPublisher(self, p): self.setTextFrame(PUBLISHER_FID, self.strToUnicode(str(p))); # Test ID3 major version. def isV1(self): return self.header.majorVersion == 1; def isV2(self): return self.header.majorVersion == 2; def setVersion(self, v): if v == ID3_V1: v = ID3_V1_1; elif v == ID3_V2: v = ID3_DEFAULT_VERSION; if v != ID3_CURRENT_VERSION: self.header.setVersion(v); self.frames.setTagHeader(self.header); def setTextFrame(self, fid, txt): if not txt: self.frames.removeFramesByID(fid); else: self.frames.setTextFrame(fid, self.strToUnicode(txt), self.encoding); def setURLFrame(self, fid, url): if not url: self.frames.removeFramesByID(fid) else: self.frames.setURLFrame(fid, url) def setTextEncoding(self, enc): if enc != LATIN1_ENCODING and enc != UTF_16_ENCODING and\ enc != UTF_16BE_ENCODING and enc != UTF_8_ENCODING: raise TagException("Invalid encoding") elif self.getVersion() & ID3_V1 and enc != LATIN1_ENCODING: raise TagException("ID3 v1.x supports ISO-8859 encoding only") elif self.getVersion() <= ID3_V2_3 and enc == UTF_8_ENCODING: # This is unfortunate. raise TagException("UTF-8 is not supported by ID3 v2.3") self.encoding = enc for f in self.frames: f.encoding = enc def tagToString(self, pattern): # %A - artist # %a - album # %t - title # %n - track number # %N - track total # %Y - year # %G - genre s = self._subst(pattern, "%A", self.getArtist()) s = self._subst(s, "%a", self.getAlbum()) s = self._subst(s, "%t", self.getTitle()) s = self._subst(s, "%n", self._prettyTrack(self.getTrackNum()[0])) s = self._subst(s, "%N", self._prettyTrack(self.getTrackNum()[1])) s = self._subst(s, "%Y", self.getYear()) s = self._subst(s, "%G", self.getGenre().name) return s def _prettyTrack(self, track): if not track: return None track_str = str(track) if len(track_str) == 1: track_str = "0" + track_str return track_str def _subst(self, name, pattern, repl): regex = re.compile(pattern) if regex.search(name) and repl: # No '/' characters allowed (repl, subs) = re.compile("/").subn("-", repl); (name, subs) = regex.subn(repl, name) return name; def __saveV1Tag(self, version): assert(version & ID3_V1); # Build tag buffer. tag = "TAG"; tag += self._fixToWidth(self.getTitle().encode("latin_1"), 30); tag += self._fixToWidth(self.getArtist().encode("latin_1"), 30); tag += self._fixToWidth(self.getAlbum().encode("latin_1"), 30); y = self.getYear(); if y is None: y = ""; tag += self._fixToWidth(y.encode("latin_1"), 4); cmt = ""; for c in self.getComments(): if c.description == ID3_V1_COMMENT_DESC: cmt = c.comment; # We prefer this one over ""; break; elif c.description == "": cmt = c.comment; # Keep searching in case we find the description eyeD3 uses. cmt = self._fixToWidth(cmt.encode("latin_1"), 30); if version != ID3_V1_0: track = self.getTrackNum()[0]; if track != None: cmt = cmt[0:28] + "\x00" + chr(int(track) & 0xff); tag += cmt; if not self.getGenre() or self.getGenre().getId() is None: genre = 0; else: genre = self.getGenre().getId(); tag += chr(genre & 0xff); assert(len(tag) == 128); tagFile = file(self.linkedFile.name, "r+b"); # Write the tag over top an original or append it. try: tagFile.seek(-128, 2); if tagFile.read(3) == "TAG": tagFile.seek(-128, 2); else: tagFile.seek(0, 2); except IOError: # File is smaller than 128 bytes. tagFile.seek(0, 2); tagFile.write(tag); tagFile.flush(); tagFile.close(); def _fixToWidth(self, s, n): retval = str(s); retval = retval[0:n]; retval = retval + ("\x00" * (n - len(retval))); return retval; # Returns false when an ID3 v1 tag is not present, or contains no data. def __loadV1Tag(self, f): if isinstance(f, str) or isinstance(f, unicode): fp = file(f, "rb") closeFile = 1; else: fp = f; closeFile = 0; # Seek to the end of the file where all ID3v1 tags are written. fp.seek(0, 2); strip_chars = string.whitespace + "\x00"; if fp.tell() > 127: fp.seek(-128, 2); id3tag = fp.read(128); if id3tag[0:3] == "TAG": TRACE_MSG("Located ID3 v1 tag"); # 1.0 is implied until a 1.1 feature is recognized. self.setVersion(ID3_V1_0); title = re.sub("\x00+$", "", id3tag[3:33].strip(strip_chars)); TRACE_MSG("Tite: " + title); if title: self.setTitle(unicode(title, "latin1")); artist = re.sub("\x00+$", "", id3tag[33:63].strip(strip_chars)); TRACE_MSG("Artist: " + artist); if artist: self.setArtist(unicode(artist, "latin1")); album = re.sub("\x00+$", "", id3tag[63:93].strip(strip_chars)); TRACE_MSG("Album: " + album); if album: self.setAlbum(unicode(album, "latin1")); year = re.sub("\x00+$", "", id3tag[93:97].strip(strip_chars)); TRACE_MSG("Year: " + year); try: if year and int(year): self.setDate(year); except ValueError: # Bogus year strings. pass; if re.sub("\x00+$", "", id3tag[97:127]): comment = id3tag[97:127]; TRACE_MSG("Comment: " + comment); if comment[-2] == "\x00" and comment[-1] != "\x00": # Parse track number (added to ID3v1.1) if present. TRACE_MSG("Comment contains track number per v1.1 spec"); track = ord(comment[-1]); self.setTrackNum((track, None)); TRACE_MSG("Track: " + str(track)); TRACE_MSG("Track Num found, setting version to v1.1s"); self.setVersion(ID3_V1_1); comment = comment[:-2]; else: track = None comment = re.sub("\x00+$", "", comment).rstrip(); TRACE_MSG("Comment: " + comment); if comment: self.addComment(unicode(comment, 'latin1'), ID3_V1_COMMENT_DESC); genre = ord(id3tag[127:128]) TRACE_MSG("Genre ID: " + str(genre)); self.setGenre(genre); if closeFile: fp.close() return len(self.frames); def __saveV2Tag(self, version): assert(version & ID3_V2); TRACE_MSG("Rendering tag version: " + versionToString(version)); self.setVersion(version); currPadding = 0; currTagSize = 0 # We may be converting from 1.x to 2.x so we need to find any # current v2.x tag otherwise we're gonna hork the file. tmpTag = Tag(); if tmpTag.link(self.linkedFile.name, ID3_V2): TRACE_MSG("Found current v2.x tag:"); currTagSize = tmpTag.linkedFile.tagSize; TRACE_MSG("Current tag size: %d" % currTagSize); currPadding = tmpTag.linkedFile.tagPadding; TRACE_MSG("Current tag padding: %d" % currPadding); if self.do_tdtg: t = time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime()); # Tag it! if self.header.minorVersion == 4: # TDTG for 2.4 h = FrameHeader(self.header); h.id = "TDTG"; dateFrame = DateFrame(h, date_str = self.strToUnicode(t), encoding = self.encoding); self.frames.removeFramesByID("TDTG"); self.frames.addFrame(dateFrame); else: # TXXX (Tagging time) for older versions self.frames.removeFramesByID("TDTG"); self.addUserTextFrame('Tagging time', t) # Render all frames first so the data size is known for the tag header. frameData = ""; for f in self.frames: TRACE_MSG("Rendering frame: " + f.header.id); raw_frame = f.render(); TRACE_MSG("Rendered %d bytes" % len(raw_frame)); frameData += raw_frame; # Handle the overall tag header unsync bit. Frames themselves duplicate # this bit. if self.header.unsync: TRACE_MSG("Unsyncing all frames (sync-safe)"); frameData = frames.unsyncData(frameData); rewriteFile = 0; paddingSize = 0; DEFAULT_PADDING = 1024 def compute_padding(): if currPadding <= DEFAULT_PADDING: return DEFAULT_PADDING else: return currPadding # Extended header extHeaderData = ""; if self.header.extended: # This is sorta lame. We don't know the total framesize until # this is rendered, yet we can't render it witout knowing the # amount of padding for the crc. Force it. rewriteFile = 1; TRACE_MSG("Rendering extended header"); paddingSize = compute_padding() extHeaderData += self.extendedHeader.render(self.header, frameData, paddingSize); new_size = 10 + len(extHeaderData) + len(frameData) + paddingSize if rewriteFile or new_size >= currTagSize: TRACE_MSG("File rewrite required"); rewriteFile = 1; if paddingSize <= 0: paddingSize = compute_padding() elif paddingSize <= 0: paddingSize = currTagSize - (new_size - 10) TRACE_MSG("Adding %d bytes of padding" % paddingSize) frameData += ("\x00" * paddingSize); # Recompute with padding new_size = 10 + len(extHeaderData) + len(frameData) header_tag_size = new_size - 10 # Render the tag header. TRACE_MSG("Rendering %s tag header with size %d" % (versionToString(self.getVersion()), header_tag_size)) headerData = self.header.render(header_tag_size) # Assemble frame. tagData = headerData + extHeaderData + frameData; # Write the tag. if not rewriteFile: tagFile = file(self.linkedFile.name, "r+b"); TRACE_MSG("Writing %d bytes of tag data" % len(tagData)); tagFile.write(tagData); tagFile.close(); else: # Open tmp file tmpName = tempfile.mktemp(); tmpFile = file(tmpName, "w+b"); TRACE_MSG("Writing %d bytes of tag data" % len(tagData)); tmpFile.write(tagData); # Write audio data in chunks tagFile = file(self.linkedFile.name, "rb"); if currTagSize != 0: seek_point = currTagSize + 10 else: seek_point = 0 TRACE_MSG("Seeking to beginning of audio data, byte %d (%x)" % (seek_point, seek_point)) tagFile.seek(seek_point) self.__copyRemaining(tagFile, tmpFile); tagFile.close(); tmpFile.close(); # Move tmp to orig. shutil.copyfile(tmpName, self.linkedFile.name); os.unlink(tmpName); # Update our state. TRACE_MSG("Tag write complete. Updating state."); self.linkedFile.tagPadding = paddingSize; # XXX: getSize could cache sizes so to prevent rendering again. self.linkedFile.tagSize = self.frames.getSize(); # Returns >= 0 to indicate the padding size of the read frame; -1 returned # when not tag was found. def __loadV2Tag(self, f): if isinstance(f, str) or isinstance(f, unicode): fp = file(f, "rb") closeFile = 1; else: fp = f; closeFile = 0; padding = -1; try: # Look for a tag and if found load it. if not self.header.parse(fp): return -1; # Read the extended header if present. if self.header.extended: self.extendedHeader.parse(fp, self.header); # Header is definitely there so at least one frame *must* follow. self.frames.setTagHeader(self.header); padding = self.frames.parse(fp, self.header, self.extendedHeader); TRACE_MSG("Tag contains %d bytes of padding." % padding); except FrameException, ex: fp.close(); raise TagException(str(ex)); except TagException: fp.close(); raise; if closeFile: fp.close(); return padding; def toInt(self, s): try: return int(s); except ValueError: return None; except TypeError: return None; def __padDateField(self, f): fStr = str(f); if len(fStr) == 2: pass; elif len(fStr) == 1: fStr = "0" + fStr; else: raise TagException("Invalid date field: " + fStr); return fStr; def __copyRemaining(self, src_fp, dest_fp): # Write audio data in chunks done = False amt = 1024 * 512 while not done: data = src_fp.read(amt) if data: dest_fp.write(data) else: done = True del data # DEPRECATED # This method will return the first comment in the FrameSet # and not all of them. Multiple COMM frames are common and useful. Use # getComments which returns a list. def getComment(self): f = self.frames[COMMENT_FID]; if f: return f[0].comment; else: return None; ################################################################################ class GenreException(Exception): '''Problem looking up genre''' ################################################################################ class Genre: id = None; name = None; def __init__(self, id = None, name = None): if id is not None: self.setId(id); elif name is not None: self.setName(name); def getId(self): return self.id; def getName(self): return self.name; # Sets the genre id. The objects name field is set to the corresponding # value obtained from eyeD3.genres. # # Throws GenreException when name does not map to a valid ID3 v1.1. id. # This behavior can be disabled by passing 0 as the second argument. def setId(self, id): if not isinstance(id, int): raise TypeError("Invalid genre id: " + str(id)); try: name = genres[id]; except Exception, ex: if utils.strictID3(): raise GenreException("Invalid genre id: " + str(id)); if utils.strictID3() and not name: raise GenreException("Genre id maps to a null name: " + str(id)); self.id = id; self.name = name; # Sets the genre name. The objects id field is set to the corresponding # value obtained from eyeD3.genres. # # Throws GenreException when name does not map to a valid ID3 v1.1. name. # This behavior can be disabled by passing 0 as the second argument. def setName(self, name): if not isinstance(name, str): raise GenreException("Invalid genre name: " + str(name)); try: id = genres[name]; if (not utils.itunesCompat()) or (id <= GenreMap.ITUNES_GENRE_MAX): # Get titled case. name = genres[id]; except: if utils.strictID3(): raise GenreException("Invalid genre name: " + name); id = None; self.id = id; self.name = name; # Sets the genre id and name. # # Throws GenreException when eyeD3.genres[id] != name (case insensitive). # This behavior can be disabled by passing 0 as the second argument. def set(self, id, name): if not isinstance(id, int): raise GenreException("Invalid genre id: " + id); if not isinstance(name, str): raise GenreException("Invalid genre name: " + str(name)); if not utils.strictID3(): self.id = id; self.name = name; else: try: if genres[name] != id: raise GenreException("eyeD3.genres[" + str(id) + "] " +\ "does not match " + name); self.id = id; self.name = name; except: raise GenreException("eyeD3.genres[" + str(id) + "] " +\ "does not match " + name); # Parses genre information from genreStr. # The following formats are supported: # 01, 2, 23, 125 - ID3 v1 style. # (01), (2), (129)Hardcore, (9)Metal - ID3 v2 style with and without # refinement. # # Throws GenreException when an invalid string is passed. def parse(self, genreStr): genreStr =\ str(genreStr.encode('utf-8')).strip(string.whitespace + '\x00'); self.id = None; self.name = None; if not genreStr: return; # XXX: Utf-16 conversions leave a null byte at the end of the string. while genreStr[len(genreStr) - 1] == "\x00": genreStr = genreStr[:len(genreStr) - 1]; if len(genreStr) == 0: break; # ID3 v1 style. # Match 03, 34, 129. regex = re.compile("[0-9][0-9]?[0-9]?$"); if regex.match(genreStr): if len(genreStr) != 1 and genreStr[0] == '0': genreStr = genreStr[1:]; self.setId(int(genreStr)); return; # ID3 v2 style. # Match (03), (0)Blues, (15) Rap regex = re.compile("\(([0-9][0-9]?[0-9]?)\)(.*)$"); m = regex.match(genreStr); if m: (id, name) = m.groups(); if len(id) != 1 and id[0] == '0': id = id[1:]; if id and name: self.set(int(id), name.strip()); else: self.setId(int(id)); return; # Non standard, but witnessed. # Match genre alone. e.g. Rap, Rock, blues, 'Rock|Punk|Pop-Punk', etc ''' regex = re.compile("^[A-Z 0-9+/\-\|!&'\.]+\00*$", re.IGNORECASE) if regex.match(genreStr): print "boo" self.setName(genreStr); return; ''' # non standard, but witnessed. # Match delimited-separated list of genres alone. # e.g. 'breaks, electronic', 'hardcore|nyhc' regex = re.compile( r"^([A-Z 0-9+/\-\|!&'\.]+)([,;|][A-Z 0-9+/\-\|!&'\.]+)*$", re.IGNORECASE) if regex.match(genreStr): print "boo" self.setName(genreStr); return; raise GenreException("Genre string cannot be parsed with '%s': %s" %\ (regex.pattern, genreStr)); def __str__(self): s = ""; if (self.id != None and ((not utils.itunesCompat()) or (self.id <= GenreMap.ITUNES_GENRE_MAX))): s += "(" + str(self.id) + ")" if self.name: s += self.name; return s; ################################################################################ class InvalidAudioFormatException(Exception): '''Problems with audio format''' ################################################################################ class TagFile: fileName = str(""); fileSize = int(0); tag = None; # Number of seconds required to play the audio file. play_time = int(0); def __init__(self, fileName): self.fileName = fileName; def getTag(self): return self.tag; def getSize(self): if not self.fileSize: self.fileSize = os.stat(self.fileName)[ST_SIZE]; return self.fileSize; def rename(self, name, fsencoding): base = os.path.basename(self.fileName); base_ext = os.path.splitext(base)[1]; dir = os.path.dirname(self.fileName); if not dir: dir = "."; new_name = dir + os.sep + name.encode(fsencoding) + base_ext; if os.path.exists(new_name): raise TagException("File '%s' exists, eyeD3 will not overwrite it" % new_name) try: os.rename(self.fileName, new_name); self.fileName = new_name; except OSError, ex: raise TagException("Error renaming '%s' to '%s'" % (self.fileName, new_name)); def getPlayTime(self): return self.play_time; def getPlayTimeString(self): from eyeD3.utils import format_track_time return format_track_time(self.getPlayTime()) ################################################################################ class Mp3AudioFile(TagFile): def __init__(self, fileName, tagVersion = ID3_ANY_VERSION): TagFile.__init__(self, fileName) self.tag = None self.header = None self.xingHeader = None self.lameTag = None if not isMp3File(fileName): raise InvalidAudioFormatException("File is not mp3"); # Parse ID3 tag. f = file(self.fileName, "rb"); self.tag = Tag(); hasTag = self.tag.link(f, tagVersion); # Find the first mp3 frame. if self.tag.isV1(): framePos = 0; elif not hasTag: framePos = 0; self.tag = None; else: framePos = self.tag.header.SIZE + self.tag.header.tagSize; TRACE_MSG("mp3 header search starting @ %x" % framePos) # Find an mp3 header header_pos, header, header_bytes = mp3.find_header(f, framePos) if header: try: self.header = mp3.Header(header) except mp3.Mp3Exception, ex: self.header = None raise InvalidAudioFormatException(str(ex)); else: TRACE_MSG("mp3 header %x found at position: 0x%x" % (header, header_pos)) else: raise InvalidAudioFormatException("Unable to find a valid mp3 frame") # Check for Xing/Info header information which will always be in the # first "null" frame. f.seek(header_pos) mp3_frame = f.read(self.header.frameLength) if re.compile('Xing|Info').search(mp3_frame): self.xingHeader = mp3.XingHeader(); if not self.xingHeader.decode(mp3_frame): TRACE_MSG("Ignoring corrupt Xing header") self.xingHeader = None # Check for LAME Tag self.lameTag = mp3.LameTag(mp3_frame) # Compute track play time. tpf = mp3.computeTimePerFrame(self.header); if self.xingHeader and self.xingHeader.vbr: self.play_time = int(tpf * self.xingHeader.numFrames); else: length = self.getSize(); if self.tag and self.tag.isV2(): length -= self.tag.header.SIZE + self.tag.header.tagSize; # Handle the case where there is a v2 tag and a v1 tag. f.seek(-128, 2) if f.read(3) == "TAG": length -= 128; elif self.tag and self.tag.isV1(): length -= 128; self.play_time = int((length / self.header.frameLength) * tpf); f.close(); # Returns a tuple. The first value is a boolean which if true means the # bit rate returned in the second value is variable. def getBitRate(self): xHead = self.xingHeader; if xHead and xHead.vbr: tpf = eyeD3.mp3.computeTimePerFrame(self.header); # FIXME: if xHead.numFrames == 0 (Fuoco.mp3), ZeroDivisionError br = int((xHead.numBytes * 8) / (tpf * xHead.numFrames * 1000)); vbr = 1; else: br = self.header.bitRate; vbr = 0; return (vbr, br); def getBitRateString(self): (vbr, bitRate) = self.getBitRate(); brs = "%d kb/s" % bitRate; if vbr: brs = "~" + brs; return brs; def getSampleFreq(self): return self.header.sampleFreq; ################################################################################ def isMp3File(fileName): type = eyeD3.utils.guess_mime_type(fileName); return type == "audio/mpeg"; ################################################################################ class GenreMap(list): # None value are set in the ctor GENRE_MIN = 0; GENRE_MAX = None; ID3_GENRE_MIN = 0; ID3_GENRE_MAX = 79; WINAMP_GENRE_MIN = 80; WINAMP_GENRE_MAX = 147; ITUNES_GENRE_MAX = 125; EYED3_GENRE_MIN = None; EYED3_GENRE_MAX = None; # Accepts both int and string keys. Throws IndexError and TypeError. def __getitem__(self, key): if isinstance(key, int): if key >= 0 and key < len(self): v = list.__getitem__(self, key); if v: return v; else: return None; else: raise IndexError("genre index out of range"); elif isinstance(key, str): if self.reverseDict.has_key(key.lower()): return self.reverseDict[key.lower()]; else: raise IndexError(key + " genre not found"); else: raise TypeError("genre key must be type int or string"); def __init__(self): self.data = [] self.reverseDict = {} # ID3 genres as defined by the v1.1 spec with WinAmp extensions. self.append('Blues'); self.append('Classic Rock'); self.append('Country'); self.append('Dance'); self.append('Disco'); self.append('Funk'); self.append('Grunge'); self.append('Hip-Hop'); self.append('Jazz'); self.append('Metal'); self.append('New Age'); self.append('Oldies'); self.append('Other'); self.append('Pop'); self.append('R&B'); self.append('Rap'); self.append('Reggae'); self.append('Rock'); self.append('Techno'); self.append('Industrial'); self.append('Alternative'); self.append('Ska'); self.append('Death Metal'); self.append('Pranks'); self.append('Soundtrack'); self.append('Euro-Techno'); self.append('Ambient'); self.append('Trip-Hop'); self.append('Vocal'); self.append('Jazz+Funk'); self.append('Fusion'); self.append('Trance'); self.append('Classical'); self.append('Instrumental'); self.append('Acid'); self.append('House'); self.append('Game'); self.append('Sound Clip'); self.append('Gospel'); self.append('Noise'); self.append('AlternRock'); self.append('Bass'); self.append('Soul'); self.append('Punk'); self.append('Space'); self.append('Meditative'); self.append('Instrumental Pop'); self.append('Instrumental Rock'); self.append('Ethnic'); self.append('Gothic'); self.append('Darkwave'); self.append('Techno-Industrial'); self.append('Electronic'); self.append('Pop-Folk'); self.append('Eurodance'); self.append('Dream'); self.append('Southern Rock'); self.append('Comedy'); self.append('Cult'); self.append('Gangsta Rap'); self.append('Top 40'); self.append('Christian Rap'); self.append('Pop / Funk'); self.append('Jungle'); self.append('Native American'); self.append('Cabaret'); self.append('New Wave'); self.append('Psychedelic'); self.append('Rave'); self.append('Showtunes'); self.append('Trailer'); self.append('Lo-Fi'); self.append('Tribal'); self.append('Acid Punk'); self.append('Acid Jazz'); self.append('Polka'); self.append('Retro'); self.append('Musical'); self.append('Rock & Roll'); self.append('Hard Rock'); self.append('Folk'); self.append('Folk-Rock'); self.append('National Folk'); self.append('Swing'); self.append('Fast Fusion'); self.append('Bebob'); self.append('Latin'); self.append('Revival'); self.append('Celtic'); self.append('Bluegrass'); self.append('Avantgarde'); self.append('Gothic Rock'); self.append('Progressive Rock'); self.append('Psychedelic Rock'); self.append('Symphonic Rock'); self.append('Slow Rock'); self.append('Big Band'); self.append('Chorus'); self.append('Easy Listening'); self.append('Acoustic'); self.append('Humour'); self.append('Speech'); self.append('Chanson'); self.append('Opera'); self.append('Chamber Music'); self.append('Sonata'); self.append('Symphony'); self.append('Booty Bass'); self.append('Primus'); self.append('Porn Groove'); self.append('Satire'); self.append('Slow Jam'); self.append('Club'); self.append('Tango'); self.append('Samba'); self.append('Folklore'); self.append('Ballad'); self.append('Power Ballad'); self.append('Rhythmic Soul'); self.append('Freestyle'); self.append('Duet'); self.append('Punk Rock'); self.append('Drum Solo'); self.append('A Cappella'); self.append('Euro-House'); self.append('Dance Hall'); self.append('Goa'); self.append('Drum & Bass'); self.append('Club-House'); self.append('Hardcore'); self.append('Terror'); self.append('Indie'); self.append('BritPop'); self.append('Negerpunk'); self.append('Polsk Punk'); self.append('Beat'); self.append('Christian Gangsta Rap'); self.append('Heavy Metal'); self.append('Black Metal'); self.append('Crossover'); self.append('Contemporary Christian'); self.append('Christian Rock'); self.append('Merengue'); self.append('Salsa'); self.append('Thrash Metal'); self.append('Anime'); self.append('JPop'); self.append('Synthpop'); # The follow genres I've encountered in the wild. self.append('Rock/Pop'); self.EYED3_GENRE_MIN = len(self) - 1; # New genres go here self.EYED3_GENRE_MAX = len(self) - 1; self.GENRE_MAX = len(self) - 1; # Pad up to 255 with "Unknown" count = len(self); while count < 256: self.append("Unknown"); count += 1; for index in range(len(self)): if self[index]: self.reverseDict[string.lower(self[index])] = index class LinkedFile: name = ""; tagPadding = 0; tagSize = 0; # This includes the padding byte count. def __init__(self, fileName): if isinstance(fileName, str): try: self.name = unicode(fileName, sys.getfilesystemencoding()); except: # Work around the local encoding not matching that of a mounted # filesystem self.name = fileName else: self.name = fileName; def tagToUserTune(tag): audio_file = None; if isinstance(tag, Mp3AudioFile): audio_file = tag; tag = audio_file.getTag(); tune = [u"\n"] def add(name, value): if value: value = escape(value) tune.append(' <%s>%s\n' % (name, value, name)) add('artist', tag.getArtist()) add('title', tag.getTitle()) add('source', tag.getAlbum()) add('track', "file://" + unicode(os.path.abspath(tag.linkedFile.name))) if audio_file: add('length', unicode(audio_file.getPlayTime())) tune.append("\n") return ''.join(tune) def tagToRfc822(tag): if isinstance(tag, Mp3AudioFile): tag = tag.getTag(); lines = [] def add(name, value): if value: lines.append(u'%s: %s\n' % (name, value)) add('Filename', tag.linkedFile.name) add('Artist', tag.getArtist()) add('Album', tag.getAlbum()) for comment in tag.getComments(): add('Comment', comment.comment) try: g = tag.getGenre() if g: add('Genre', '%s (%s)' % (g.getName(), g.getId() or 0)) except GenreException: pass add('Title', tag.getTitle()) tn, tt = tag.getTrackNum() add('Track', tn) add('Year', tag.getYear()) return ''.join(lines) # # Module level globals. # genres = GenreMap(); eyeD3-0.6.18/src/eyeD3/mp3.py0000644000175000017500000006721011663615630015007 0ustar travistravis################################################################################ # Copyright (C) 2002-2005,2007 Travis Shirk # # 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 2 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # ################################################################################ from binfuncs import *; from utils import *; from math import log10 ####################################################################### class Mp3Exception(Exception): '''Error reading mp3''' # MPEG1 MPEG2 MPEG2.5 SAMPLE_FREQ_TABLE = ((44100, 22050, 11025), (48000, 24000, 12000), (32000, 16000, 8000), (None, None, None)); # V1/L1 V1/L2 V1/L3 V2/L1 V2/L2&L3 BIT_RATE_TABLE = ((0, 0, 0, 0, 0), (32, 32, 32, 32, 8), (64, 48, 40, 48, 16), (96, 56, 48, 56, 24), (128, 64, 56, 64, 32), (160, 80, 64, 80, 40), (192, 96, 80, 96, 44), (224, 112, 96, 112, 56), (256, 128, 112, 128, 64), (288, 160, 128, 144, 80), (320, 192, 160, 160, 96), (352, 224, 192, 176, 112), (384, 256, 224, 192, 128), (416, 320, 256, 224, 144), (448, 384, 320, 256, 160), (None, None, None, None, None)); # L1 L2 L3 TIME_PER_FRAME_TABLE = (None, 384, 1152, 1152); # Emphasis constants EMPHASIS_NONE = "None"; EMPHASIS_5015 = "50/15 ms"; EMPHASIS_CCIT = "CCIT J.17"; # Mode constants MODE_STEREO = "Stereo"; MODE_JOINT_STEREO = "Joint stereo"; MODE_DUAL_CHANNEL_STEREO = "Dual channel stereo"; MODE_MONO = "Mono"; # Xing flag bits FRAMES_FLAG = 0x0001 BYTES_FLAG = 0x0002 TOC_FLAG = 0x0004 VBR_SCALE_FLAG = 0x0008 ####################################################################### # Pass in a 4 byte integer to determine if it matches a valid mp3 frame # header. def is_valid_mp_header(header): # Test for the mp3 frame sync: 11 set bits. sync = (header >> 16) if sync & 0xFFE0 != 0xFFE0: # ffe0 is 11 sync bits, and supports identifying mpeg v2.5 return False version = (header >> 19) & 0x3 if version == 1: # This is a "reserved" version TRACE_MSG("invalid mpeg version") return False layer = (header >> 17) & 0x3 if layer == 0: # This is a "reserved" layer TRACE_MSG("invalid mpeg layer") return False bitrate = (header >> 12) & 0xf if bitrate in (0, 0xf): # free and bad bitrate values TRACE_MSG("invalid mpeg bitrate") return False sample_rate = (header >> 10) & 0x3 if sample_rate == 0x3: # this is a "reserved" sample rate TRACE_MSG("invalid mpeg sample rate") return False return True def find_header(fp, start_pos=0): def find_sync(fp, start_pos=0): CHUNK_SIZE = 65536 fp.seek(start_pos) data = fp.read(CHUNK_SIZE) data_len = len(data) while data: sync_pos = data.find('\xff', 0) if sync_pos >= 0: header = data[sync_pos:sync_pos + 4] if len(header) == 4: return (start_pos + sync_pos, header) data = fp.read(CHUNK_SIZE) data_len = len(data) return (None, None) sync_pos, header_bytes = find_sync(fp, start_pos) while sync_pos is not None: header = bytes2dec(header_bytes) if is_valid_mp_header(header): return (sync_pos, header, header_bytes) sync_pos, header_bytes = find_sync(fp, start_pos + sync_pos + 2) return (None, None, None) def computeTimePerFrame(frameHeader): return (float(TIME_PER_FRAME_TABLE[frameHeader.layer]) / float(frameHeader.sampleFreq)) ####################################################################### class Header: def __init__(self, header_data=None): self.version = None self.layer = None self.errorProtection = None self.bitRate = None self.playTime = None self.sampleFreq = None self.padding = None self.privateBit = None self.copyright = None self.original = None self.emphasis = None self.mode = None # This value is left as is: 0<=modeExtension<=3. # See http://www.dv.co.yu/mpgscript/mpeghdr.htm for how to interpret self.modeExtension = None if header_data: self.decode(header_data) # This may throw an Mp3Exception if the header is malformed. def decode(self, header): if not is_valid_mp_header(header): raise Mp3Exception("Invalid MPEG header"); # MPEG audio version from bits 19 and 20. version = (header >> 19) & 0x3 self.version = [2.5, None, 2.0, 1.0][version] if self.version is None: raise Mp3Exception("Illegal MPEG version"); # MPEG layer self.layer = 4 - ((header >> 17) & 0x3) if self.layer == 4: raise Mp3Exception("Illegal MPEG layer"); # Decode some simple values. self.errorProtection = not (header >> 16) & 0x1; self.padding = (header >> 9) & 0x1; self.privateBit = (header >> 8) & 0x1; self.copyright = (header >> 3) & 0x1; self.original = (header >> 2) & 0x1; # Obtain sampling frequency. sampleBits = (header >> 10) & 0x3; if self.version == 2.5: freqCol = 2; else: freqCol = int(self.version - 1); self.sampleFreq = SAMPLE_FREQ_TABLE[sampleBits][freqCol]; if not self.sampleFreq: raise Mp3Exception("Illegal MPEG sampling frequency"); # Compute bitrate. bitRateIndex = (header >> 12) & 0xf; if int(self.version) == 1 and self.layer == 1: bitRateCol = 0; elif int(self.version) == 1 and self.layer == 2: bitRateCol = 1; elif int(self.version) == 1 and self.layer == 3: bitRateCol = 2; elif int(self.version) == 2 and self.layer == 1: bitRateCol = 3; elif int(self.version) == 2 and (self.layer == 2 or \ self.layer == 3): bitRateCol = 4; else: raise Mp3Exception("Mp3 version %f and layer %d is an invalid "\ "combination" % (self.version, self.layer)); self.bitRate = BIT_RATE_TABLE[bitRateIndex][bitRateCol]; if self.bitRate == None: raise Mp3Exception("Invalid bit rate"); # We know know the bit rate specified in this frame, but if the file # is VBR we need to obtain the average from the Xing header. # This is done by the caller since right now all we have is the frame # header. # Emphasis; whatever that means?? emph = header & 0x3; if emph == 0: self.emphasis = EMPHASIS_NONE; elif emph == 1: self.emphasis = EMPHASIS_5015; elif emph == 2: self.emphasis = EMPHASIS_CCIT; elif strictID3(): raise Mp3Exception("Illegal mp3 emphasis value: %d" % emph); # Channel mode. modeBits = (header >> 6) & 0x3; if modeBits == 0: self.mode = MODE_STEREO; elif modeBits == 1: self.mode = MODE_JOINT_STEREO; elif modeBits == 2: self.mode = MODE_DUAL_CHANNEL_STEREO; else: self.mode = MODE_MONO; self.modeExtension = (header >> 4) & 0x3; # Layer II has restrictions wrt to mode and bit rate. This code # enforces them. if self.layer == 2: m = self.mode; br = self.bitRate; if (br == 32 or br == 48 or br == 56 or br == 80) and \ (m != MODE_MONO): raise Mp3Exception("Invalid mode/bitrate combination for layer "\ "II"); if (br == 224 or br == 256 or br == 320 or br == 384) and \ (m == MODE_MONO): raise Mp3Exception("Invalid mode/bitrate combination for layer "\ "II"); br = self.bitRate * 1000; sf = self.sampleFreq; p = self.padding; if self.layer == 1: # Layer 1 uses 32 bit slots for padding. p = self.padding * 4; self.frameLength = int((((12 * br) / sf) + p) * 4); else: # Layer 2 and 3 uses 8 bit slots for padding. p = self.padding * 1; self.frameLength = int(((144 * br) / sf) + p); # Dump the state. TRACE_MSG("MPEG audio version: " + str(self.version)); TRACE_MSG("MPEG audio layer: " + ("I" * self.layer)); TRACE_MSG("MPEG sampling frequency: " + str(self.sampleFreq)); TRACE_MSG("MPEG bit rate: " + str(self.bitRate)); TRACE_MSG("MPEG channel mode: " + self.mode); TRACE_MSG("MPEG channel mode extension: " + str(self.modeExtension)); TRACE_MSG("MPEG CRC error protection: " + str(self.errorProtection)); TRACE_MSG("MPEG original: " + str(self.original)); TRACE_MSG("MPEG copyright: " + str(self.copyright)); TRACE_MSG("MPEG private bit: " + str(self.privateBit)); TRACE_MSG("MPEG padding: " + str(self.padding)); TRACE_MSG("MPEG emphasis: " + str(self.emphasis)); TRACE_MSG("MPEG frame length: " + str(self.frameLength)); ####################################################################### class XingHeader: numFrames = int(); numBytes = int(); toc = [0] * 100; vbrScale = int(); # Pass in the first mp3 frame from the file as a byte string. # If an Xing header is present in the file it'll be in the first mp3 # frame. This method returns true if the Xing header is found in the # frame, and false otherwise. def decode(self, frame): # mp3 version version = (ord(frame[1]) >> 3) & 0x1; # channel mode. mode = (ord(frame[3]) >> 6) & 0x3; # Find the start of the Xing header. if version: if mode != 3: pos = 32 + 4; else: pos = 17 + 4; else: if mode != 3: pos = 17 + 4; else: pos = 9 + 4; head = frame[pos:pos+4] self.vbr = (head == 'Xing') and True or False if head not in ['Xing', 'Info']: return 0 TRACE_MSG("%s header detected @ %x" % (head, pos)); pos += 4; # Read Xing flags. headFlags = bin2dec(bytes2bin(frame[pos:pos + 4])); pos += 4; TRACE_MSG("%s header flags: 0x%x" % (head, headFlags)); # Read frames header flag and value if present if headFlags & FRAMES_FLAG: self.numFrames = bin2dec(bytes2bin(frame[pos:pos + 4])); pos += 4; TRACE_MSG("%s numFrames: %d" % (head, self.numFrames)); # Read bytes header flag and value if present if headFlags & BYTES_FLAG: self.numBytes = bin2dec(bytes2bin(frame[pos:pos + 4])); pos += 4; TRACE_MSG("%s numBytes: %d" % (head, self.numBytes)); # Read TOC header flag and value if present if headFlags & TOC_FLAG: i = 0; self.toc = frame[pos:pos + 100]; pos += 100; TRACE_MSG("%s TOC (100 bytes): PRESENT" % head); else: TRACE_MSG("%s TOC (100 bytes): NOT PRESENT" % head); # Read vbr scale header flag and value if present if headFlags & VBR_SCALE_FLAG and head == 'Xing': self.vbrScale = bin2dec(bytes2bin(frame[pos:pos + 4])); pos += 4; TRACE_MSG("%s vbrScale: %d" % (head, self.vbrScale)); return 1; ####################################################################### class LameTag(dict): """Mp3 Info tag (AKA LAME Tag) Lame (and some other encoders) write a tag containing various bits of info about the options used at encode time. If available, the following are parsed and stored in the LameTag dict: encoder_version: short encoder version [str] tag_revision: revision number of the tag [int] vbr_method: VBR method used for encoding [str] lowpass_filter: lowpass filter frequency in Hz [int] replaygain: if available, radio and audiofile gain (see below) [dict] encoding_flags: encoding flags used [list] nogap: location of gaps when --nogap was used [list] ath_type: ATH type [int] bitrate: bitrate and type (Constant, Target, Minimum) [tuple] encoder_delay: samples added at the start of the mp3 [int] encoder_padding: samples added at the end of the mp3 [int] noise_shaping: noise shaping method [int] stereo_mode: stereo mode used [str] unwise_settings: whether unwise settings were used [boolean] sample_freq: source sample frequency [str] mp3_gain: mp3 gain adjustment (rarely used) [float] preset: preset used [str] surround_info: surround information [str] music_length: length in bytes of original mp3 [int] music_crc: CRC-16 of the mp3 music data [int] infotag_crc: CRC-16 of the info tag [int] Prior to ~3.90, Lame simply stored the encoder version in the first frame. If the infotag_crc is invalid, then we try to read this version string. A simple way to tell if the LAME Tag is complete is to check for the infotag_crc key. Replay Gain data is only available since Lame version 3.94b. If set, the replaygain dict has the following structure: peak_amplitude: peak signal amplitude [float] radio: name: name of the gain adjustment [str] adjustment: gain adjustment [float] originator: originator of the gain adjustment [str] audiofile: [same as radio] Note that as of 3.95.1, Lame uses 89dB as a reference level instead of the 83dB that is specified in the Replay Gain spec. This is not automatically compensated for. You can do something like this if you want: import eyeD3 af = eyeD3.Mp3AudioFile('/path/to/some.mp3') lamever = af.lameTag['encoder_version'] name, ver = lamever[:4], lamever[4:] gain = af.lameTag['replaygain']['radio']['adjustment'] if name == 'LAME' and eyeD3.mp3.lamevercmp(ver, '3.95') > 0: gain -= 6 Radio and Audiofile Replay Gain are often referrered to as Track and Album gain, respectively. See http://replaygain.hydrogenaudio.org/ for futher details on Replay Gain. See http://gabriel.mp3-tech.org/mp3infotag.html for the gory details of the LAME Tag. """ # from the LAME source: # http://lame.cvs.sourceforge.net/*checkout*/lame/lame/libmp3lame/VbrTag.c _crc16_table = [ 0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241, 0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440, 0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40, 0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841, 0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40, 0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41, 0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641, 0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040, 0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240, 0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441, 0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41, 0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840, 0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41, 0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40, 0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640, 0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041, 0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240, 0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441, 0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41, 0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840, 0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41, 0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40, 0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640, 0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041, 0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241, 0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440, 0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40, 0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841, 0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40, 0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41, 0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641, 0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040] ENCODER_FLAGS = { 'NSPSYTUNE' : 0x0001, 'NSSAFEJOINT' : 0x0002, 'NOGAP_NEXT' : 0x0004, 'NOGAP_PREV' : 0x0008,} PRESETS = { 0: 'Unknown', # 8 to 320 are reserved for ABR bitrates 410: 'V9', 420: 'V8', 430: 'V7', 440: 'V6', 450: 'V5', 460: 'V4', 470: 'V3', 480: 'V2', 490: 'V1', 500: 'V0', 1000: 'r3mix', 1001: 'standard', 1002: 'extreme', 1003: 'insane', 1004: 'standard/fast', 1005: 'extreme/fast', 1006: 'medium', 1007: 'medium/fast',} REPLAYGAIN_NAME = { 0: 'Not set', 1: 'Radio', 2: 'Audiofile',} REPLAYGAIN_ORIGINATOR = { 0: 'Not set', 1: 'Set by artist', 2: 'Set by user', 3: 'Set automatically', 100: 'Set by simple RMS average',} SAMPLE_FREQUENCIES = { 0: '<= 32 kHz', 1: '44.1 kHz', 2: '48 kHz', 3: '> 48 kHz',} STEREO_MODES = { 0: 'Mono', 1: 'Stereo', 2: 'Dual', 3: 'Joint', 4: 'Force', 5: 'Auto', 6: 'Intensity', 7: 'Undefined',} SURROUND_INFO = { 0: 'None', 1: 'DPL encoding', 2: 'DPL2 encoding', 3: 'Ambisonic encoding', 8: 'Reserved',} VBR_METHODS = { 0: 'Unknown', 1: 'Constant Bitrate', 2: 'Average Bitrate', 3: 'Variable Bitrate method1 (old/rh)', 4: 'Variable Bitrate method2 (mtrh)', 5: 'Variable Bitrate method3 (mt)', 6: 'Variable Bitrate method4', 8: 'Constant Bitrate (2 pass)', 9: 'Average Bitrate (2 pass)', 15: 'Reserved',} def __init__(self, frame): """Read the LAME info tag. frame should be the first frame of an mp3. """ self.decode(frame) def _crc16(self, data, val = 0): """Compute a CRC-16 checksum on a data stream.""" for c in data: val = self._crc16_table[ord(c) ^ (val & 0xff)] ^ (val >> 8) return val def decode(self, frame): """Decode the LAME info tag.""" try: pos = frame.index("LAME") except: return # check the info tag crc. if it's not valid, no point parsing much more. lamecrc = bin2dec(bytes2bin(frame[190:192])) if self._crc16(frame[:190]) != lamecrc: #TRACE_MSG('Lame tag CRC check failed') # read version string from the first 30 bytes, up to any # non-ascii chars, then strip padding chars. # # XXX (How many bytes is proper to read? madplay reads 20, but I've # got files with longer version strings) lamever = [] for c in frame[pos:pos + 30]: if ord(c) not in range(32, 127): break lamever.append(c) self['encoder_version'] = ''.join(lamever).rstrip('\x55') TRACE_MSG('Lame Encoder Version: %s' % self['encoder_version']) return TRACE_MSG('Lame info tag found at position %d' % pos) # Encoder short VersionString, 9 bytes self['encoder_version'] = lamever = frame[pos:pos + 9].rstrip() TRACE_MSG('Lame Encoder Version: %s' % self['encoder_version']) pos += 9 # Info Tag revision + VBR method, 1 byte self['tag_revision'] = bin2dec(bytes2bin(frame[pos:pos + 1])[:5]) vbr_method = bin2dec(bytes2bin(frame[pos:pos + 1])[5:]) self['vbr_method'] = self.VBR_METHODS.get(vbr_method, 'Unknown') TRACE_MSG('Lame info tag version: %s' % self['tag_revision']) TRACE_MSG('Lame VBR method: %s' % self['vbr_method']) pos += 1 # Lowpass filter value, 1 byte self['lowpass_filter'] = bin2dec(bytes2bin(frame[pos:pos + 1])) * 100 TRACE_MSG('Lame Lowpass filter value: %s Hz' % self['lowpass_filter']) pos += 1 # Replay Gain, 8 bytes total replaygain = {} # Peak signal amplitude, 4 bytes peak = bin2dec(bytes2bin(frame[pos:pos + 4])) << 5 if peak > 0: peak /= float(1 << 28) db = 20 * log10(peak) replaygain['peak_amplitude'] = peak TRACE_MSG('Lame Peak signal amplitude: %.8f (%+.1f dB)' % (peak, db)) pos += 4 # Radio and Audiofile Gain, AKA track and album, 2 bytes each for gaintype in ['radio', 'audiofile']: name = bin2dec(bytes2bin(frame[pos:pos + 2])[:3]) orig = bin2dec(bytes2bin(frame[pos:pos + 2])[3:6]) sign = bin2dec(bytes2bin(frame[pos:pos + 2])[6:7]) adj = bin2dec(bytes2bin(frame[pos:pos + 2])[7:]) / 10.0 if sign: adj *= -1 # XXX Lame 3.95.1 and above use 89dB as a reference instead of 83dB # as defined by the Replay Gain spec. Should this be compensated for? #if lamever[:4] == 'LAME' and lamevercmp(lamever[4:], '3.95') > 0: # adj -= 6 if orig: name = self.REPLAYGAIN_NAME.get(name, 'Unknown') orig = self.REPLAYGAIN_ORIGINATOR.get(orig, 'Unknown') replaygain[gaintype] = {'name': name, 'adjustment': adj, 'originator': orig} TRACE_MSG('Lame %s Replay Gain: %s dB (%s)' % (name, adj, orig)) pos += 2 if replaygain: self['replaygain'] = replaygain # Encoding flags + ATH Type, 1 byte encflags = bin2dec(bytes2bin(frame[pos:pos + 1])[:4]) self['encoding_flags'], self['nogap'] = self._parse_encflags(encflags) self['ath_type'] = bin2dec(bytes2bin(frame[pos:pos + 1])[4:]) TRACE_MSG('Lame Encoding flags: %s' % ' '.join(self['encoding_flags'])) if self['nogap']: TRACE_MSG('Lame No gap: %s' % ' and '.join(self['nogap'])) TRACE_MSG('Lame ATH type: %s' % self['ath_type']) pos += 1 # if ABR {specified bitrate} else {minimal bitrate}, 1 byte btype = 'Constant' if 'Average' in self['vbr_method']: btype = 'Target' elif 'Variable' in self['vbr_method']: btype = 'Minimum' # bitrate may be modified below after preset is read self['bitrate'] = (bin2dec(bytes2bin(frame[pos:pos + 1])), btype) TRACE_MSG('Lame Bitrate (%s): %s' % (btype, self['bitrate'][0])) pos += 1 # Encoder delays, 3 bytes self['encoder_delay'] = bin2dec(bytes2bin(frame[pos:pos + 3])[:12]) self['encoder_padding'] = bin2dec(bytes2bin(frame[pos:pos + 3])[12:]) TRACE_MSG('Lame Encoder delay: %s samples' % self['encoder_delay']) TRACE_MSG('Lame Encoder padding: %s samples' % self['encoder_padding']) pos += 3 # Misc, 1 byte sample_freq = bin2dec(bytes2bin(frame[pos:pos + 1])[:2]) unwise_settings = bin2dec(bytes2bin(frame[pos:pos + 1])[2:3]) stereo_mode = bin2dec(bytes2bin(frame[pos:pos + 1])[3:6]) self['noise_shaping'] = bin2dec(bytes2bin(frame[pos:pos + 1])[6:]) self['sample_freq'] = self.SAMPLE_FREQUENCIES.get(sample_freq, 'Unknown') self['unwise_settings'] = bool(unwise_settings) self['stereo_mode'] = self.STEREO_MODES.get(stereo_mode, 'Unknown') TRACE_MSG('Lame Source Sample Frequency: %s' % self['sample_freq']) TRACE_MSG('Lame Unwise settings used: %s' % self['unwise_settings']) TRACE_MSG('Lame Stereo mode: %s' % self['stereo_mode']) TRACE_MSG('Lame Noise Shaping: %s' % self['noise_shaping']) pos += 1 # MP3 Gain, 1 byte sign = bytes2bin(frame[pos:pos + 1])[0] gain = bin2dec(bytes2bin(frame[pos:pos + 1])[1:]) if sign: gain *= -1 self['mp3_gain'] = gain db = gain * 1.5 TRACE_MSG('Lame MP3 Gain: %s (%+.1f dB)' % (self['mp3_gain'], db)) pos += 1 # Preset and surround info, 2 bytes surround = bin2dec(bytes2bin(frame[pos:pos + 2])[2:5]) preset = bin2dec(bytes2bin(frame[pos:pos + 2])[5:]) if preset in range(8, 321): if self['bitrate'] >= 255: # the value from preset is better in this case self['bitrate'] = (preset, btype) TRACE_MSG('Lame Bitrate (%s): %s' % (btype, self['bitrate'][0])) if 'Average' in self['vbr_method']: preset = 'ABR %s' % preset else: preset = 'CBR %s' % preset else: preset = self.PRESETS.get(preset, preset) self['surround_info'] = self.SURROUND_INFO.get(surround, surround) self['preset'] = preset TRACE_MSG('Lame Surround Info: %s' % self['surround_info']) TRACE_MSG('Lame Preset: %s' % self['preset']) pos += 2 # MusicLength, 4 bytes self['music_length'] = bin2dec(bytes2bin(frame[pos:pos + 4])) TRACE_MSG('Lame Music Length: %s bytes' % self['music_length']) pos += 4 # MusicCRC, 2 bytes self['music_crc'] = bin2dec(bytes2bin(frame[pos:pos + 2])) TRACE_MSG('Lame Music CRC: %04X' % self['music_crc']) pos += 2 # CRC-16 of Info Tag, 2 bytes self['infotag_crc'] = lamecrc # we read this earlier TRACE_MSG('Lame Info Tag CRC: %04X' % self['infotag_crc']) pos += 2 def _parse_encflags(self, flags): """Parse encoder flags. Returns a tuple containing lists of encoder flags and nogap data in human readable format. """ encoder_flags, nogap = [], [] if not flags: return encoder_flags, nogap if flags & self.ENCODER_FLAGS['NSPSYTUNE']: encoder_flags.append('--nspsytune') if flags & self.ENCODER_FLAGS['NSSAFEJOINT']: encoder_flags.append('--nssafejoint') NEXT = self.ENCODER_FLAGS['NOGAP_NEXT'] PREV = self.ENCODER_FLAGS['NOGAP_PREV'] if flags & (NEXT | PREV): encoder_flags.append('--nogap') if flags & PREV: nogap.append('before') if flags & NEXT: nogap.append('after') return encoder_flags, nogap def lamevercmp(x, y): """Compare LAME version strings. alpha and beta versions are considered older. versions with sub minor parts or end with 'r' are considered newer. Return negative if xy. """ x = x.ljust(5) y = y.ljust(5) if x[:5] == y[:5]: return 0 ret = cmp(x[:4], y[:4]) if ret: return ret xmaj, xmin = x.split('.')[:2] ymaj, ymin = y.split('.')[:2] minparts = ['.'] # lame 3.96.1 added the use of r in the very short version for post releases if (xmaj == '3' and xmin >= '96') or (ymaj == '3' and ymin >= '96'): minparts.append('r') if x[4] in minparts: return 1 if y[4] in minparts: return -1 if x[4] == ' ': return 1 if y[4] == ' ': return -1 return cmp(x[4], y[4]) eyeD3-0.6.18/src/eyeD3/binfuncs.py0000644000175000017500000000674611663615630016126 0ustar travistravis################################################################################ # # Copyright (C) 2002-2005 Travis Shirk # Copyright (C) 2001 Ryan Finne # # 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 2 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # ################################################################################ # Accepts a string of bytes (chars) and returns an array of bits # representing the bytes in big endian byte (Most significant byte/bit first) # order. Each byte can have it's higher bits ignored by passing an sz arg. def bytes2bin(bytes, sz = 8): if sz < 1 or sz > 8: raise ValueError("Invalid sz value: " + str(sz)); retVal = []; for b in bytes: bits = []; b = ord(b); while b > 0: bits.append(b & 1); b >>= 1; if len(bits) < sz: bits.extend([0] * (sz - len(bits))); elif len(bits) > sz: bits = bits[:sz]; # Big endian byte order. bits.reverse(); retVal.extend(bits); if len(retVal) == 0: retVal = [0]; return retVal; # Convert am array of bits (MSB first) into a string of characters. def bin2bytes(x): bits = []; bits.extend(x); bits.reverse(); i = 0; out = ''; multi = 1; ttl = 0; for b in bits: i += 1; ttl += b * multi; multi *= 2; if i == 8: i = 0; out += chr(ttl); multi = 1; ttl = 0; if multi > 1: out += chr(ttl); out = list(out); out.reverse(); out = ''.join(out); return out; # Convert and array of "bits" (MSB first) to it's decimal value. def bin2dec(x): bits = []; bits.extend(x); bits.reverse(); multi = 1; value = long(0); for b in bits: value += b * multi; multi *= 2; return value; def bytes2dec(bytes, sz = 8): return bin2dec(bytes2bin(bytes, sz)); # Convert a decimal value to an array of bits (MSB first), optionally # padding the overall size to p bits. def dec2bin(n, p = 0): assert(n >= 0) retVal = []; while n > 0: retVal.append(n & 1); n >>= 1; if p > 0: retVal.extend([0] * (p - len(retVal))); retVal.reverse(); return retVal; def dec2bytes(n, p = 0): return bin2bytes(dec2bin(n, p)); # Convert a list of bits (MSB first) to a synch safe list of bits (section 6.2 # of the ID3 2.4 spec). def bin2synchsafe(x): if len(x) > 32 or bin2dec(x) > 268435456: # 2^28 raise ValueError("Invalid value"); elif len(x) < 8: return x; n = bin2dec(x); bites = ""; bites += chr((n >> 21) & 0x7f); bites += chr((n >> 14) & 0x7f); bites += chr((n >> 7) & 0x7f); bites += chr((n >> 0) & 0x7f); bits = bytes2bin(bites); if len(bits) < 32: bits = ([0] * (32 - len(x))) + bits; return bits; def bytes2str(bytes): s = "" for b in bytes: s += ("\\x%02x" % ord(b)) return s eyeD3-0.6.18/src/eyeD3/utils.py0000644000175000017500000001317011663615630015444 0ustar travistravis################################################################################ # Copyright (C) 2003-2005 Travis Shirk # # 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 2 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # $Id: utils.py,v 1.11.2.1 2005/12/02 03:18:42 travis Exp $ ################################################################################ from eyeD3 import *; def versionsToConstant(v): major = v[0]; minor = v[1]; rev = v[2]; if major == 1: if minor == 0: return ID3_V1_0; elif minor == 1: return ID3_V1_1; elif major == 2: if minor == 2: return ID3_V2_2; if minor == 3: return ID3_V2_3; elif minor == 4: return ID3_V2_4; raise str("Invalid ID3 version: %s" % str(v)); def versionToString(v): if v & ID3_V1: if v == ID3_V1_0: return "v1.0"; elif v == ID3_V1_1: return "v1.1"; elif v == ID3_V1: return "v1.x"; elif v & ID3_V2: if v == ID3_V2_2: return "v2.2"; elif v == ID3_V2_3: return "v2.3"; elif v == ID3_V2_4: return "v2.4"; elif v == ID3_V2: return "v2.x"; if v == ID3_ANY_VERSION: return "v1.x/v2.x"; raise str("versionToString - Invalid ID3 version constant: %s" % hex(v)); def constantToVersions(v): if v & ID3_V1: if v == ID3_V1_0: return [1, 0, 0]; elif v == ID3_V1_1: return [1, 1, 0]; elif v == ID3_V1: return [1, 1, 0]; elif v & ID3_V2: if v == ID3_V2_2: return [2, 2, 0]; elif v == ID3_V2_3: return [2, 3, 0]; elif v == ID3_V2_4: return [2, 4, 0]; elif v == ID3_V2: return [2, 4, 0]; raise str("constantToVersions - Invalid ID3 version constant: %s" % hex(v)); ################################################################################ TRACE = 0; prefix = "eyeD3 trace> "; def TRACE_MSG(msg): if TRACE: try: print prefix + msg; except UnicodeEncodeError: pass; STRICT_ID3 = 0 def strictID3(): return STRICT_ID3 ITUNES_COMPAT = 0 def itunesCompat(): return ITUNES_COMPAT ################################################################################ import os; class FileHandler: R_CONT = 0; R_HALT = -1; # MUST return R_CONT or R_HALT def handleFile(self, f): pass # MUST for all files processed return 0 for success and a positive int # for error def handleDone(self): pass class FileWalker: def __init__(self, handler, root, excludes = []): self._handler = handler; self._root = root; self._excludes = excludes; def go(self): for (root, dirs, files) in os.walk(self._root): for f in files: f = os.path.abspath(root + os.sep + f); if not self._isExcluded(f): if self._handler.handleFile(f) == FileHandler.R_HALT: return FileHandler.R_HALT; return self._handler.handleDone(); def _isExcluded(self, path): for ex in self._excludes: match = re.compile(exd).search(path); if match and match.start() == 0: return 1; return 0; ################################################################################ # Time and memory string formatting def format_track_time(curr, total=None): def time_tuple(ts): if ts is None or ts < 0: ts = 0 hours = ts / 3600 mins = (ts % 3600) / 60 secs = (ts % 3600) % 60 tstr = '%02d:%02d' % (mins, secs) if int(hours): tstr = '%02d:%s' % (hours, tstr) return (int(hours), int(mins), int(secs), tstr) hours, mins, secs, curr_str = time_tuple(curr) retval = curr_str if total: hours, mins, secs, total_str = time_tuple(total) retval += ' / %s' % total_str return retval KB_BYTES = 1024 MB_BYTES = 1048576 GB_BYTES = 1073741824 KB_UNIT = 'KB' MB_UNIT = 'MB' GB_UNIT = 'GB' def format_size(sz): unit = 'Bytes' if sz >= GB_BYTES: sz = float(sz) / float(GB_BYTES) unit = GB_UNIT elif sz >= MB_BYTES: sz = float(sz) / float(MB_BYTES) unit = MB_UNIT elif sz >= KB_BYTES: sz = float(sz) / float(KB_BYTES) unit = KB_UNIT return "%.2f %s" % (sz, unit) def format_time_delta(td): days = td.days hours = td.seconds / 3600 mins = (td.seconds % 3600) / 60 secs = (td.seconds % 3600) % 60 tstr = "%02d:%02d:%02d" % (hours, mins, secs) if days: tstr = "%d days %s" % (days, tstr) return tstr ## MIME type guessing import mimetypes try: import magic _magic = magic.open(magic.MAGIC_SYMLINK | magic.MAGIC_MIME) _magic.load() except: _magic = None def guess_mime_type(filename): mime = mimetypes.guess_type(filename)[0] if not mime and _magic and os.path.isfile(filename): mime = _magic.file(filename) if mime: mime = mime.split(";")[0] return mime eyeD3-0.6.18/src/eyeD3/__init__.py.in0000644000175000017500000000326111663615630016450 0ustar travistravis################################################################################ # Copyright (C) 2002-2005,2007 Travis Shirk # # 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 2 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # ################################################################################ eyeD3Version = "@PACKAGE_VERSION@"; eyeD3Maintainer = "@PACKAGE_BUGREPORT@"; # Version constants ID3_CURRENT_VERSION = 0x00; # The version of the linked tag, if any. ID3_V1 = 0x10; ID3_V1_0 = 0x11; ID3_V1_1 = 0x12; ID3_V2 = 0x20; ID3_V2_2 = 0x21; ID3_V2_3 = 0x22; ID3_V2_4 = 0x24; #ID3_V2_5 = 0x28; # This does not seem imminent. ID3_DEFAULT_VERSION = ID3_V2_4; ID3_ANY_VERSION = ID3_V1 | ID3_V2; import locale; LOCAL_ENCODING = locale.getpreferredencoding(do_setlocale=True); if not LOCAL_ENCODING or LOCAL_ENCODING == "ANSI_X3.4-1968": LOCAL_ENCODING = 'latin1'; import eyeD3.frames; import eyeD3.mp3; import eyeD3.tag; from eyeD3.tag import *; import eyeD3.utils; eyeD3-0.6.18/src/eyeD3/frames.py0000644000175000017500000024777211663615630015602 0ustar travistravis################################################################################ # Copyright (C) 2002-2007 Travis Shirk # Copyright (C) 2005 Michael Urman # - Sync-safe encoding/decoding algorithms # # 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 2 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # ################################################################################ import sys, os, os.path, re, zlib, StringIO, time from StringIO import StringIO; from utils import *; from binfuncs import *; # Valid time stamp formats per ISO 8601 and used by time.strptime. timeStampFormats = ["%Y", "%Y-%m", "%Y-%m-%d", "%Y-%m-%dT%H", "%Y-%m-%dT%H:%M", "%Y-%m-%dT%H:%M:%S"] ARTIST_FID = "TPE1" BAND_FID = "TPE2" CONDUCTOR_FID = "TPE3" REMIXER_FID = "TPE4" COMPOSER_FID = "TCOM" ARTIST_FIDS = [ARTIST_FID, BAND_FID, CONDUCTOR_FID, REMIXER_FID, COMPOSER_FID] ALBUM_FID = "TALB" TITLE_FID = "TIT2" SUBTITLE_FID = "TIT3" CONTENT_TITLE_FID = "TIT1" TITLE_FIDS = [TITLE_FID, SUBTITLE_FID, CONTENT_TITLE_FID] COMMENT_FID = "COMM" LYRICS_FID = "USLT" GENRE_FID = "TCON" TRACKNUM_FID = "TRCK" DISCNUM_FID = "TPOS" USERTEXT_FID = "TXXX" CDID_FID = "MCDI" IMAGE_FID = "APIC" OBJECT_FID = "GEOB" URL_COMMERCIAL_FID = "WCOM" URL_COPYRIGHT_FID = "WCOP" URL_AUDIOFILE_FID = "WOAF" URL_ARTIST_FID = "WOAR" URL_AUDIOSRC_FID = "WOAS" URL_INET_RADIO_FID = "WORS" URL_PAYMENT_FID = "WPAY" URL_PUBLISHER_FID = "WPUB" URL_FIDS = [URL_COMMERCIAL_FID, URL_COPYRIGHT_FID, URL_AUDIOFILE_FID, URL_ARTIST_FID, URL_AUDIOSRC_FID, URL_INET_RADIO_FID, URL_PAYMENT_FID, URL_PUBLISHER_FID] USERURL_FID = "WXXX" PLAYCOUNT_FID = "PCNT" UNIQUE_FILE_ID_FID = "UFID" BPM_FID = "TBPM" PUBLISHER_FID = "TPUB" obsoleteFrames = {"EQUA": "Equalisation", "IPLS": "Involved people list", "RVAD": "Relative volume adjustment", "TDAT": "Date", "TORY": "Original release year", "TRDA": "Recording dates", "TYER": "Year"} # Both of these are "coerced" into a v2.4 TDRC frame when read, and # recreated when saving v2.3. OBSOLETE_DATE_FID = "TDAT" OBSOLETE_YEAR_FID = "TYER" OBSOLETE_TIME_FID = "TIME" OBSOLETE_ORIG_RELEASE_FID = "TORY" OBSOLETE_RECORDING_DATE_FID = "TRDA" DATE_FIDS = ["TDRL", "TDOR", "TDRC", OBSOLETE_YEAR_FID, OBSOLETE_DATE_FID] frameDesc = { "AENC": "Audio encryption", "APIC": "Attached picture", "ASPI": "Audio seek point index", "COMM": "Comments", "COMR": "Commercial frame", "ENCR": "Encryption method registration", "EQU2": "Equalisation (2)", "ETCO": "Event timing codes", "GEOB": "General encapsulated object", "GRID": "Group identification registration", "LINK": "Linked information", "MCDI": "Music CD identifier", "MLLT": "MPEG location lookup table", "OWNE": "Ownership frame", "PRIV": "Private frame", "PCNT": "Play counter", "POPM": "Popularimeter", "POSS": "Position synchronisation frame", "RBUF": "Recommended buffer size", "RVA2": "Relative volume adjustment (2)", "RVRB": "Reverb", "SEEK": "Seek frame", "SIGN": "Signature frame", "SYLT": "Synchronised lyric/text", "SYTC": "Synchronised tempo codes", "TALB": "Album/Movie/Show title", "TBPM": "BPM (beats per minute)", "TCOM": "Composer", "TCON": "Content type", "TCOP": "Copyright message", "TDEN": "Encoding time", "TDLY": "Playlist delay", "TDOR": "Original release time", "TDRC": "Recording time", "TDRL": "Release time", "TDTG": "Tagging time", "TENC": "Encoded by", "TEXT": "Lyricist/Text writer", "TFLT": "File type", "TIPL": "Involved people list", "TIT1": "Content group description", "TIT2": "Title/songname/content description", "TIT3": "Subtitle/Description refinement", "TKEY": "Initial key", "TLAN": "Language(s)", "TLEN": "Length", "TMCL": "Musician credits list", "TMED": "Media type", "TMOO": "Mood", "TOAL": "Original album/movie/show title", "TOFN": "Original filename", "TOLY": "Original lyricist(s)/text writer(s)", "TOPE": "Original artist(s)/performer(s)", "TOWN": "File owner/licensee", "TPE1": "Lead performer(s)/Soloist(s)", "TPE2": "Band/orchestra/accompaniment", "TPE3": "Conductor/performer refinement", "TPE4": "Interpreted, remixed, or otherwise modified by", "TPOS": "Part of a set", "TPRO": "Produced notice", "TPUB": "Publisher", "TRCK": "Track number/Position in set", "TRSN": "Internet radio station name", "TRSO": "Internet radio station owner", "TSOA": "Album sort order", "TSOP": "Performer sort order", "TSOT": "Title sort order", "TSRC": "ISRC (international standard recording code)", "TSSE": "Software/Hardware and settings used for encoding", "TSST": "Set subtitle", "TXXX": "User defined text information frame", "UFID": "Unique file identifier", "USER": "Terms of use", "USLT": "Unsynchronised lyric/text transcription", "WCOM": "Commercial information", "WCOP": "Copyright/Legal information", "WOAF": "Official audio file webpage", "WOAR": "Official artist/performer webpage", "WOAS": "Official audio source webpage", "WORS": "Official Internet radio station homepage", "WPAY": "Payment", "WPUB": "Publishers official webpage", "WXXX": "User defined URL link frame" } # mapping of 2.2 frames to 2.3/2.4 TAGS2_2_TO_TAGS_2_3_AND_4 = { "TT1" : "TIT1", # CONTENTGROUP content group description "TT2" : "TIT2", # TITLE title/songname/content description "TT3" : "TIT3", # SUBTITLE subtitle/description refinement "TP1" : "TPE1", # ARTIST lead performer(s)/soloist(s) "TP2" : "TPE2", # BAND band/orchestra/accompaniment "TP3" : "TPE3", # CONDUCTOR conductor/performer refinement "TP4" : "TPE4", # MIXARTIST interpreted, remixed, modified by "TCM" : "TCOM", # COMPOSER composer "TXT" : "TEXT", # LYRICIST lyricist/text writer "TLA" : "TLAN", # LANGUAGE language(s) "TCO" : "TCON", # CONTENTTYPE content type "TAL" : "TALB", # ALBUM album/movie/show title "TRK" : "TRCK", # TRACKNUM track number/position in set "TPA" : "TPOS", # PARTINSET part of set "TRC" : "TSRC", # ISRC international standard recording code "TDA" : "TDAT", # DATE date "TYE" : "TYER", # YEAR year "TIM" : "TIME", # TIME time "TRD" : "TRDA", # RECORDINGDATES recording dates "TOR" : "TORY", # ORIGYEAR original release year "TBP" : "TBPM", # BPM beats per minute "TMT" : "TMED", # MEDIATYPE media type "TFT" : "TFLT", # FILETYPE file type "TCR" : "TCOP", # COPYRIGHT copyright message "TPB" : "TPUB", # PUBLISHER publisher "TEN" : "TENC", # ENCODEDBY encoded by "TSS" : "TSSE", # ENCODERSETTINGS software/hardware + settings for encoding "TLE" : "TLEN", # SONGLEN length (ms) "TSI" : "TSIZ", # SIZE size (bytes) "TDY" : "TDLY", # PLAYLISTDELAY playlist delay "TKE" : "TKEY", # INITIALKEY initial key "TOT" : "TOAL", # ORIGALBUM original album/movie/show title "TOF" : "TOFN", # ORIGFILENAME original filename "TOA" : "TOPE", # ORIGARTIST original artist(s)/performer(s) "TOL" : "TOLY", # ORIGLYRICIST original lyricist(s)/text writer(s) "TXX" : "TXXX", # USERTEXT user defined text information frame "WAF" : "WOAF", # WWWAUDIOFILE official audio file webpage "WAR" : "WOAR", # WWWARTIST official artist/performer webpage "WAS" : "WOAS", # WWWAUDIOSOURCE official audion source webpage "WCM" : "WCOM", # WWWCOMMERCIALINFO commercial information "WCP" : "WCOP", # WWWCOPYRIGHT copyright/legal information "WPB" : "WPUB", # WWWPUBLISHER publishers official webpage "WXX" : "WXXX", # WWWUSER user defined URL link frame "IPL" : "IPLS", # INVOLVEDPEOPLE involved people list "ULT" : "USLT", # UNSYNCEDLYRICS unsynchronised lyrics/text transcription "COM" : "COMM", # COMMENT comments "UFI" : "UFID", # UNIQUEFILEID unique file identifier "MCI" : "MCDI", # CDID music CD identifier "ETC" : "ETCO", # EVENTTIMING event timing codes "MLL" : "MLLT", # MPEGLOOKUP MPEG location lookup table "STC" : "SYTC", # SYNCEDTEMPO synchronised tempo codes "SLT" : "SYLT", # SYNCEDLYRICS synchronised lyrics/text "RVA" : "RVAD", # VOLUMEADJ relative volume adjustment "EQU" : "EQUA", # EQUALIZATION equalization "REV" : "RVRB", # REVERB reverb "PIC" : "APIC", # PICTURE attached picture "GEO" : "GEOB", # GENERALOBJECT general encapsulated object "CNT" : "PCNT", # PLAYCOUNTER play counter "POP" : "POPM", # POPULARIMETER popularimeter "BUF" : "RBUF", # BUFFERSIZE recommended buffer size "CRA" : "AENC", # AUDIOCRYPTO audio encryption "LNK" : "LINK", # LINKEDINFO linked information # Extension workarounds i.e., ignore them "TCP" : "TCP ", # iTunes "extension" for compilation marking "CM1" : "CM1 " # Seems to be some script kiddie tagging the tag. # For example, [rH] join #rH on efnet [rH] } NULL_FRAME_FLAGS = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; TEXT_FRAME_RX = re.compile("^T[A-Z0-9][A-Z0-9][A-Z0-9]$"); USERTEXT_FRAME_RX = re.compile("^" + USERTEXT_FID + "$"); URL_FRAME_RX = re.compile("^W[A-Z0-9][A-Z0-9][A-Z0-9]$"); USERURL_FRAME_RX = re.compile("^" + USERURL_FID + "$"); COMMENT_FRAME_RX = re.compile("^" + COMMENT_FID + "$"); LYRICS_FRAME_RX = re.compile("^" + LYRICS_FID + "$"); CDID_FRAME_RX = re.compile("^" + CDID_FID + "$"); IMAGE_FRAME_RX = re.compile("^" + IMAGE_FID + "$"); OBJECT_FRAME_RX = re.compile("^" + OBJECT_FID + "$"); PLAYCOUNT_FRAME_RX = re.compile("^" + PLAYCOUNT_FID + "$"); UNIQUE_FILE_ID_FRAME_RX = re.compile("^" + UNIQUE_FILE_ID_FID + "$"); # MP3ext causes illegal frames to be inserted, which must be ignored. # Copied from http://shell.lab49.com/~vivake/python/MP3Info.py # Henning Kiel KNOWN_BAD_FRAMES = [ "\x00\x00MP", "\x00MP3", " MP3", "MP3e", "\x00MP", " MP", "MP3", "COM ", "TCP ", # iTunes "CM1 " # Script kiddie ] LATIN1_ENCODING = "\x00"; UTF_16_ENCODING = "\x01"; UTF_16BE_ENCODING = "\x02"; UTF_8_ENCODING = "\x03"; DEFAULT_ENCODING = LATIN1_ENCODING; DEFAULT_ID3_MAJOR_VERSION = 2; DEFAULT_ID3_MINOR_VERSION = 4; DEFAULT_LANG = "eng"; def cleanNulls(s): return "/".join([x for x in s.split('\x00') if x]) def id3EncodingToString(encoding): if encoding == LATIN1_ENCODING: return "latin_1"; elif encoding == UTF_8_ENCODING: return "utf_8"; elif encoding == UTF_16_ENCODING: return "utf_16"; elif encoding == UTF_16BE_ENCODING: return "utf_16_be"; else: if strictID3(): raise ValueError; else: return "latin_1"; ################################################################################ class FrameException(Exception): '''Thrown by invalid frames''' pass; ################################################################################ class FrameHeader: FRAME_HEADER_SIZE = 10; # The tag header majorVersion = DEFAULT_ID3_MAJOR_VERSION; minorVersion = DEFAULT_ID3_MINOR_VERSION; # The 4 character frame ID. id = None; # An array of 16 "bits"... flags = NULL_FRAME_FLAGS; # ...and the info they store. tagAlter = 0; fileAlter = 0; readOnly = 0; compressed = 0; encrypted = 0; grouped = 0; unsync = 0; dataLenIndicator = 0; # The size of the data following this header. dataSize = 0; # 2.4 not only added flag bits, but also reordered the previously defined # flags. So these are mapped once we know the version. TAG_ALTER = None; FILE_ALTER = None; READ_ONLY = None; COMPRESSION = None; ENCRYPTION = None; GROUPING = None; UNSYNC = None; DATA_LEN = None; # Constructor. def __init__(self, tagHeader = None): if tagHeader: self.setVersion(tagHeader); else: self.setVersion([DEFAULT_ID3_MAJOR_VERSION, DEFAULT_ID3_MINOR_VERSION]); def setVersion(self, tagHeader): # A slight hack to make the default ctor work. if isinstance(tagHeader, list): self.majorVersion = tagHeader[0]; self.minorVersion = tagHeader[1]; else: self.majorVersion = tagHeader.majorVersion; self.minorVersion = tagHeader.minorVersion; # Correctly set size of header if self.minorVersion == 2: self.FRAME_HEADER_SIZE = 6; else: self.FRAME_HEADER_SIZE = 10; self.setBitMask(); def setBitMask(self): major = self.majorVersion; minor = self.minorVersion; # 1.x tags are converted to 2.4 frames internally. These frames are # created with frame flags \x00. if (major == 2 and minor == 2): # no flags for 2.2 frames pass; elif (major == 2 and minor == 3): self.TAG_ALTER = 0; self.FILE_ALTER = 1; self.READ_ONLY = 2; self.COMPRESSION = 8; self.ENCRYPTION = 9; self.GROUPING = 10; # This is not really in 2.3 frame header flags, but there is # a "global" unsync bit in the tag header and that is written here # so access to the tag header is not required. self.UNSYNC = 14; # And this is mapped to an used bit, so that 0 is returned. self.DATA_LEN = 4; elif (major == 2 and minor == 4) or \ (major == 1 and (minor == 0 or minor == 1)): self.TAG_ALTER = 1; self.FILE_ALTER = 2; self.READ_ONLY = 3; self.COMPRESSION = 12; self.ENCRYPTION = 13; self.GROUPING = 9; self.UNSYNC = 14; self.DATA_LEN = 15; else: raise ValueError("ID3 v" + str(major) + "." + str(minor) +\ " is not supported."); def render(self, dataSize): data = self.id; if self.minorVersion == 3: data += bin2bytes(dec2bin(dataSize, 32)); else: data += bin2bytes(bin2synchsafe(dec2bin(dataSize, 32))); self.setBitMask(); self.flags = NULL_FRAME_FLAGS; self.flags[self.TAG_ALTER] = self.tagAlter; self.flags[self.FILE_ALTER] = self.fileAlter; self.flags[self.READ_ONLY] = self.readOnly; self.flags[self.COMPRESSION] = self.compressed; self.flags[self.COMPRESSION] = self.compressed; self.flags[self.ENCRYPTION] = self.encrypted; self.flags[self.GROUPING] = self.grouped; self.flags[self.UNSYNC] = self.unsync; self.flags[self.DATA_LEN] = self.dataLenIndicator; data += bin2bytes(self.flags); return data; def parse2_2(self, f): frameId_22 = f.read(3); frameId = map2_2FrameId(frameId_22); if self.isFrameIdValid(frameId): TRACE_MSG("FrameHeader [id]: %s (0x%x%x%x)" % (frameId_22, ord(frameId_22[0]), ord(frameId_22[1]), ord(frameId_22[2]))); self.id = frameId; # dataSize corresponds to the size of the data segment after # encryption, compression, and unsynchronization. sz = f.read(3); self.dataSize = bin2dec(bytes2bin(sz, 8)); TRACE_MSG("FrameHeader [data size]: %d (0x%X)" % (self.dataSize, self.dataSize)); return True elif frameId == '\x00\x00\x00': TRACE_MSG("FrameHeader: Null frame id found at byte " +\ str(f.tell())); elif not strictID3() and frameId in KNOWN_BAD_FRAMES: TRACE_MSG("FrameHeader: Illegal but known frame found; "\ "Happily ignoring" + str(f.tell())); elif strictID3(): raise FrameException("FrameHeader: Illegal Frame ID: " + frameId); return False # Returns 1 on success and 0 when a null tag (marking the beginning of # padding). In the case of an invalid frame header, a FrameException is # thrown. def parse(self, f): TRACE_MSG("FrameHeader [start byte]: %d (0x%X)" % (f.tell(), f.tell())); if self.minorVersion == 2: return self.parse2_2(f) frameId = f.read(4); if self.isFrameIdValid(frameId): TRACE_MSG("FrameHeader [id]: %s (0x%x%x%x%x)" % (frameId, ord(frameId[0]), ord(frameId[1]), ord(frameId[2]), ord(frameId[3]))); self.id = frameId; # dataSize corresponds to the size of the data segment after # encryption, compression, and unsynchronization. sz = f.read(4); # In ID3 v2.4 this value became a synch-safe integer, meaning only # the low 7 bits are used per byte. if self.minorVersion == 3: self.dataSize = bin2dec(bytes2bin(sz, 8)); else: self.dataSize = bin2dec(bytes2bin(sz, 7)); TRACE_MSG("FrameHeader [data size]: %d (0x%X)" % (self.dataSize, self.dataSize)); # Frame flags. flags = f.read(2); self.flags = bytes2bin(flags); self.tagAlter = self.flags[self.TAG_ALTER]; self.fileAlter = self.flags[self.FILE_ALTER]; self.readOnly = self.flags[self.READ_ONLY]; self.compressed = self.flags[self.COMPRESSION]; self.encrypted = self.flags[self.ENCRYPTION]; self.grouped = self.flags[self.GROUPING]; self.unsync = self.flags[self.UNSYNC]; self.dataLenIndicator = self.flags[self.DATA_LEN]; TRACE_MSG("FrameHeader [flags]: ta(%d) fa(%d) ro(%d) co(%d) "\ "en(%d) gr(%d) un(%d) dl(%d)" % (self.tagAlter, self.fileAlter, self.readOnly, self.compressed, self.encrypted, self.grouped, self.unsync, self.dataLenIndicator)); if self.minorVersion >= 4 and self.compressed and \ not self.dataLenIndicator: raise FrameException("Invalid frame; compressed with no data " "length indicator"); return True elif frameId == '\x00\x00\x00\x00': TRACE_MSG("FrameHeader: Null frame id found at byte " +\ str(f.tell())); elif not strictID3() and frameId in KNOWN_BAD_FRAMES: TRACE_MSG("FrameHeader: Illegal but known "\ "(possibly created by the shitty mp3ext) frame found; "\ "Happily ignoring!" + str(f.tell())); elif strictID3(): raise FrameException("FrameHeader: Illegal Frame ID: " + frameId); return False def isFrameIdValid(self, id): return re.compile(r"^[A-Z0-9][A-Z0-9][A-Z0-9][A-Z0-9]$").match(id); def clearFlags(self): flags = [0] * 16; ################################################################################ def unsyncData(data): output = [] safe = True for val in data: if safe: output.append(val) if val == '\xff': safe = False elif val == '\x00' or val >= '\xe0': output.append('\x00') output.append(val) safe = (val != '\xff') else: output.append(val) safe = True if not safe: output.append('\x00') return ''.join(output) def deunsyncData(data): output = [] safe = True for val in data: if safe: output.append(val) safe = (val != '\xff') else: if val != '\x00': output.append(val) safe = True return ''.join(output) ################################################################################ class Frame: def __init__(self, frameHeader, unsync_default): self.header = None self.decompressedSize = 0 self.groupId = 0 self.encryptionMethod = 0 self.dataLen = 0 self.encoding = DEFAULT_ENCODING self.header = frameHeader self.unsync_default = unsync_default def __str__(self): desc = self.getFrameDesc(); return '<%s Frame (%s)>' % (desc, self.header.id); def unsync(self, data): data = unsyncData(data) return data def deunsync(self, data): data = deunsyncData(data) return data def decompress(self, data): TRACE_MSG("before decompression: %d bytes" % len(data)); data = zlib.decompress(data, 15, self.decompressedSize); TRACE_MSG("after decompression: %d bytes" % len(data)); return data; def compress(self, data): TRACE_MSG("before compression: %d bytes" % len(data)); data = zlib.compress(data); TRACE_MSG("after compression: %d bytes" % len(data)); return data; def decrypt(self, data): raise FrameException("Encryption not supported"); def encrypt(self, data): raise FrameException("Encryption not supported"); def disassembleFrame(self, data): # Format flags in the frame header may add extra data to the # beginning of this data. if self.header.minorVersion <= 3: # 2.3: compression(4), encryption(1), group(1) if self.header.compressed: self.decompressedSize = bin2dec(bytes2bin(data[:4])); data = data[4:]; TRACE_MSG("Decompressed Size: %d" % self.decompressedSize); if self.header.encrypted: self.encryptionMethod = bin2dec(bytes2bin(data[0])); data = data[1:]; TRACE_MSG("Encryption Method: %d" % self.encryptionMethod); if self.header.grouped: self.groupId = bin2dec(bytes2bin(data[0])); data = data[1:]; TRACE_MSG("Group ID: %d" % self.groupId); else: # 2.4: group(1), encrypted(1), dataLenIndicator(4,7) if self.header.grouped: self.groupId = bin2dec(bytes2bin(data[0])); data = data[1:]; if self.header.encrypted: self.encryptionMethod = bin2dec(bytes2bin(data[0])); data = data[1:]; TRACE_MSG("Encryption Method: %d" % self.encryptionMethod); TRACE_MSG("Group ID: %d" % self.groupId); if self.header.dataLenIndicator: self.dataLen = bin2dec(bytes2bin(data[:4], 7)); data = data[4:]; TRACE_MSG("Data Length: %d" % self.dataLen); if self.header.compressed: self.decompressedSize = self.dataLen; TRACE_MSG("Decompressed Size: %d" % self.decompressedSize); if self.header.unsync or self.unsync_default: data = self.deunsync(data) if self.header.encrypted: data = self.decrypt(data); if self.header.compressed: data = self.decompress(data); return data; def assembleFrame (self, data): formatFlagData = ""; if self.header.minorVersion == 3: if self.header.compressed: formatFlagData += bin2bytes(dec2bin(len(data), 32)); if self.header.encrypted: formatFlagData += bin2bytes(dec2bin(self.encryptionMethod, 8)); if self.header.grouped: formatFlagData += bin2bytes(dec2bin(self.groupId, 8)); else: if self.header.grouped: formatFlagData += bin2bytes(dec2bin(self.groupId, 8)); if self.header.encrypted: formatFlagData += bin2bytes(dec2bin(self.encryptionMethod, 8)); if self.header.compressed or self.header.dataLenIndicator: # Just in case, not sure about this? self.header.dataLenIndicator = 1; formatFlagData += bin2bytes(dec2bin(len(data), 32)); if self.header.compressed: data = self.compress(data); if self.header.encrypted: data = self.encrypt(data); if self.header.unsync or self.unsync_default: data = self.unsync(data) data = formatFlagData + data; return self.header.render(len(data)) + data; def getFrameDesc(self): try: return frameDesc[self.header.id]; except KeyError: try: return obsoleteFrames[self.header.id]; except KeyError: return "UNKOWN FRAME"; def getTextDelim(self): if self.encoding == UTF_16_ENCODING or \ self.encoding == UTF_16BE_ENCODING: return "\x00\x00"; else: return "\x00"; ################################################################################ class TextFrame(Frame): text = u""; # Data string format: # encoding (one byte) + text def __init__(self, frameHeader, data=None, text=u"", encoding=DEFAULT_ENCODING, unsync_default=False): Frame.__init__(self, frameHeader, unsync_default) if data != None: self._set(data, frameHeader); return; else: assert(text != None and isinstance(text, unicode)); self.encoding = encoding; self.text = text; # Data string format: # encoding (one byte) + text; def _set(self, data, frameHeader): fid = frameHeader.id; if not TEXT_FRAME_RX.match(fid) or USERTEXT_FRAME_RX.match(fid): raise FrameException("Invalid frame id for TextFrame: " + fid); data = self.disassembleFrame(data); self.encoding = data[0]; TRACE_MSG("TextFrame encoding: %s" % id3EncodingToString(self.encoding)); try: data = data[1:] self.text = encodeUnicode(data, id3EncodingToString(self.encoding)) if not strictID3(): self.text = cleanNulls(self.text) except TypeError, excArg: # if data is already unicode, just copy it if excArg.args == ("decoding Unicode is not supported",): self.text = data if not strictID3(): self.text = cleanNulls(self.text) else: raise; TRACE_MSG("TextFrame text: %s" % self.text); def __unicode__(self): return u'<%s (%s): %s>' % (self.getFrameDesc(), self.header.id, self.text); def render(self): if self.header.minorVersion == 4 and self.header.id == "TSIZ": TRACE_MSG("Dropping deprecated frame TSIZ") return "" data = self.encoding +\ self.text.encode(id3EncodingToString(self.encoding)); return self.assembleFrame(data); ################################################################################ class DateFrame(TextFrame): date = None; date_str = u""; def __init__(self, frameHeader, data=None, date_str=None, encoding=DEFAULT_ENCODING, unsync_default=False): if data != None: TextFrame.__init__(self, frameHeader, data=data, encoding=encoding, unsync_default=unsync_default) self._set(data, frameHeader) else: assert(date_str and isinstance(date_str, unicode)) TextFrame.__init__(self, frameHeader, text=date_str, encoding=encoding, unsync_default=unsync_default) self.setDate(self.text) def _set(self, data, frameHeader): TextFrame._set(self, data, frameHeader) if self.header.id[:2] != "TD" and self.header.minorVersion >= 4: raise FrameException("Invalid frame id for DateFrame: " + \ self.header.id) def setDate(self, d): if not d: self.date = None self.date_str = u"" return for fmt in timeStampFormats: try: if isinstance(d, tuple): self.date_str = unicode(time.strftime(fmt, d)) self.date = d else: assert(isinstance(d, unicode)) # Witnessed oddball tags with NULL bytes (ozzy.tag from id3lib) d = d.strip("\x00") try: self.date = time.strptime(d, fmt) except TypeError, ex: continue self.date_str = d break except ValueError: self.date = None self.date_str = u"" continue if strictID3() and not self.date: raise FrameException("Invalid Date: " + str(d)) self.text = self.date_str def getDate(self): return self.date_str def getYear(self): if self.date: return self.__padDateField(self.date[0], 4) else: return None def getMonth(self): if self.date: return self.__padDateField(self.date[1], 2); else: return None; def getDay(self): if self.date: return self.__padDateField(self.date[2], 2); else: return None; def getHour(self): if self.date: return self.__padDateField(self.date[3], 2); else: return None; def getMinute(self): if self.date: return self.__padDateField(self.date[4], 2); else: return None; def getSecond(self): if self.date: return self.__padDateField(self.date[5], 2); else: return None; def __padDateField(self, f, sz): fStr = str(f); if len(fStr) == sz: pass; elif len(fStr) < sz: fStr = ("0" * (sz - len(fStr))) + fStr; else: raise TagException("Invalid date field: " + fStr); return fStr; def render(self): # Conversion crap if self.header.minorVersion == 4 and\ (self.header.id == OBSOLETE_DATE_FID or\ self.header.id == OBSOLETE_YEAR_FID or\ self.header.id == OBSOLETE_TIME_FID or\ self.header.id == OBSOLETE_RECORDING_DATE_FID): self.header.id = "TDRC"; elif self.header.minorVersion == 4 and\ self.header.id == OBSOLETE_ORIG_RELEASE_FID: self.header.id = "TDOR"; elif self.header.minorVersion == 3 and self.header.id == "TDOR": self.header.id = OBSOLETE_ORIG_RELEASE_FID; elif self.header.minorVersion == 3 and self.header.id == "TDEN": TRACE_MSG('Converting TDEN to TXXX(Encoding time) frame') self.header.id = "TXXX"; self.description = "Encoding time"; data = self.encoding +\ self.description.encode(id3EncodingToString(self.encoding)) +\ self.getTextDelim() +\ self.date_str.encode(id3EncodingToString(self.encoding)); return self.assembleFrame(data) elif self.header.minorVersion == 3 and self.header.id[:2] == "TD": if self.header.id not in ['TDEN', 'TDLY', 'TDTG']: self.header.id = OBSOLETE_YEAR_FID; data = self.encoding +\ self.date_str.encode(id3EncodingToString(self.encoding)); data = self.assembleFrame(data); return data; ################################################################################ class UserTextFrame(TextFrame): description = u"" # Data string format: # encoding (one byte) + description + "\x00" + text def __init__(self, frameHeader, data=None, description=u"", text=u"", encoding=DEFAULT_ENCODING, unsync_default=False): if data != None: TextFrame.__init__(self, frameHeader, data=data, unsync_default=unsync_default) self._set(data, frameHeader) else: assert(isinstance(description, unicode) and\ isinstance(text, unicode)) TextFrame.__init__(self, frameHeader, text=text, encoding=encoding, unsync_default=unsync_default) self.description = description # Data string format: # encoding (one byte) + description + "\x00" + text def _set(self, data, frameHeader = None): assert(frameHeader) if not USERTEXT_FRAME_RX.match(frameHeader.id): raise FrameException("Invalid frame id for UserTextFrame: " + frameHeader.id) data = self.disassembleFrame(data) self.encoding = data[0] TRACE_MSG("UserTextFrame encoding: %s" %\ id3EncodingToString(self.encoding)) (d, t) = splitUnicode(data[1:], self.encoding) self.description = encodeUnicode(d, id3EncodingToString(self.encoding)) TRACE_MSG("UserTextFrame description: %s" % self.description) self.text = encodeUnicode(t, id3EncodingToString(self.encoding)) if not strictID3(): self.text = cleanNulls(self.text) TRACE_MSG("UserTextFrame text: %s" % self.text) def render(self): if self.header.minorVersion == 4: if self.description.lower() == 'tagging time': TRACE_MSG("Converting TXXX(%s) to TDTG frame)" % self.description) return "" if self.description.lower() == 'encoding time': TRACE_MSG("Converting TXXX(%s) to TDEN frame" % self.description) self.header.id = 'TDEN' data = self.encoding +\ self.text.encode(id3EncodingToString(self.encoding)) return self.assembleFrame(data) data = self.encoding +\ self.description.encode(id3EncodingToString(self.encoding)) +\ self.getTextDelim() +\ self.text.encode(id3EncodingToString(self.encoding)) return self.assembleFrame(data) def __unicode__(self): return u'<%s (%s): {Desc: %s} %s>' % (self.getFrameDesc(), self.header.id, self.description, self.text) ################################################################################ class URLFrame(Frame): url = "" # Data string format: # url def __init__(self, frameHeader, data=None, url=None, unsync_default=False): Frame.__init__(self, frameHeader, unsync_default) if data != None: self._set(data, frameHeader) else: assert(url) self.url = url # Data string format: # url (ascii) def _set(self, data, frameHeader): fid = frameHeader.id if not URL_FRAME_RX.match(fid) or USERURL_FRAME_RX.match(fid): raise FrameException("Invalid frame id for URLFrame: " + fid) data = self.disassembleFrame(data) self.url = data if not strictID3(): self.url = cleanNulls(self.url) def render(self): data = str(self.url) return self.assembleFrame(data) def __str__(self): return '<%s (%s): %s>' % (self.getFrameDesc(), self.header.id, self.url) ################################################################################ class UserURLFrame(URLFrame): description = u"" url = "" # Data string format: # encoding (one byte) + description + "\x00" + url def __init__(self, frameHeader, data=None, url="", description=u"", encoding=DEFAULT_ENCODING, unsync_default=False): Frame.__init__(self, frameHeader, unsync_default) if data: self._set(data, frameHeader) else: assert(encoding) assert(description and isinstance(description, unicode)) assert(url and isinstance(url, str)) self.encoding = encoding self.url = url self.description = description # Data string format: # encoding (one byte) + description + "\x00" + url; def _set(self, data, frameHeader): assert(data and frameHeader) if not USERURL_FRAME_RX.match(frameHeader.id): raise FrameException("Invalid frame id for UserURLFrame: " +\ frameHeader.id) data = self.disassembleFrame(data); self.encoding = data[0] TRACE_MSG("UserURLFrame encoding: %s" %\ id3EncodingToString(self.encoding)) try: (d, u) = splitUnicode(data[1:], self.encoding) except ValueError, ex: if strictID3(): raise FrameException("Invalid WXXX frame, no null byte") d = data[1:] u = "" self.description = encodeUnicode(d, id3EncodingToString(self.encoding)) TRACE_MSG("UserURLFrame description: %s" % self.description) self.url = u if not strictID3(): self.url = cleanNulls(self.url) TRACE_MSG("UserURLFrame text: %s" % self.url) def render(self): data = self.encoding +\ self.description.encode(id3EncodingToString(self.encoding)) +\ self.getTextDelim() + self.url return self.assembleFrame(data) def __unicode__(self): return u'<%s (%s): %s [Encoding: %s] [Desc: %s]>' %\ (self.getFrameDesc(), self.header.id, self.url, self.encoding, self.description) ################################################################################ class CommentFrame(Frame): lang = "" description = u"" comment = u"" # Data string format: # encoding (one byte) + lang (three byte code) + description + "\x00" + # text def __init__(self, frameHeader, data=None, lang="", description=u"", comment=u"", encoding=DEFAULT_ENCODING, unsync_default=False): Frame.__init__(self, frameHeader, unsync_default) if data != None: self._set(data, frameHeader) else: assert(isinstance(description, unicode)) assert(isinstance(comment, unicode)) assert(isinstance(lang, str)) self.encoding = encoding self.lang = lang self.description = description self.comment = comment # Data string format: # encoding (one byte) + lang (three byte code) + description + "\x00" + # text def _set(self, data, frameHeader = None): assert(frameHeader) if not COMMENT_FRAME_RX.match(frameHeader.id): raise FrameException("Invalid frame id for CommentFrame: " + frameHeader.id) data = self.disassembleFrame(data) self.encoding = data[0] TRACE_MSG("CommentFrame encoding: " + id3EncodingToString(self.encoding)) try: self.lang = str(data[1:4]).strip("\x00") # Test ascii encoding temp_lang = encodeUnicode(self.lang, "ascii") if self.lang and \ not re.compile("[A-Z][A-Z][A-Z]", re.IGNORECASE).match(self.lang): if strictID3(): raise FrameException("[CommentFrame] Invalid language "\ "code: %s" % self.lang) except UnicodeDecodeError, ex: if strictID3(): raise FrameException("[CommentFrame] Invalid language code: " "[%s] %s" % (ex.object, ex.reason)) else: self.lang = "" try: (d, c) = splitUnicode(data[4:], self.encoding) self.description = encodeUnicode(d, id3EncodingToString(self.encoding)) self.comment = encodeUnicode(c, id3EncodingToString(self.encoding)) except ValueError: if strictID3(): raise FrameException("Invalid comment; no description/comment") else: self.description = u"" self.comment = u"" if not strictID3(): self.description = cleanNulls(self.description) self.comment = cleanNulls(self.comment) def render(self): lang = self.lang.encode("ascii") if len(lang) > 3: lang = lang[0:3] elif len(lang) < 3: lang = lang + ('\x00' * (3 - len(lang))) data = self.encoding + lang +\ self.description.encode(id3EncodingToString(self.encoding)) +\ self.getTextDelim() +\ self.comment.encode(id3EncodingToString(self.encoding)); return self.assembleFrame(data); def __unicode__(self): return u"<%s (%s): %s [Lang: %s] [Desc: %s]>" %\ (self.getFrameDesc(), self.header.id, self.comment, self.lang, self.description); ################################################################################ class LyricsFrame(Frame): lang = ""; description = u""; lyrics = u""; # Data string format: # encoding (one byte) + lang (three byte code) + description + "\x00" + # text def __init__(self, frameHeader, data=None, lang="", description=u"", lyrics=u"", encoding=DEFAULT_ENCODING, unsync_default=False): Frame.__init__(self, frameHeader, unsync_default) if data != None: self._set(data, frameHeader) else: assert(isinstance(description, unicode)) assert(isinstance(lyrics, unicode)) assert(isinstance(lang, str)) self.encoding = encoding self.lang = lang self.description = description self.lyrics = lyrics # Data string format: # encoding (one byte) + lang (three byte code) + description + "\x00" + # text def _set(self, data, frameHeader = None): assert(frameHeader) if not LYRICS_FRAME_RX.match(frameHeader.id): raise FrameException("Invalid frame id for LyricsFrame: " +\ frameHeader.id) data = self.disassembleFrame(data) self.encoding = data[0] TRACE_MSG("LyricsFrame encoding: " + id3EncodingToString(self.encoding)) try: self.lang = str(data[1:4]).strip("\x00") # Test ascii encoding temp_lang = encodeUnicode(self.lang, "ascii") if self.lang and \ not re.compile("[A-Z][A-Z][A-Z]", re.IGNORECASE).match(self.lang): if strictID3(): raise FrameException("[LyricsFrame] Invalid language "\ "code: %s" % self.lang) except UnicodeDecodeError, ex: if strictID3(): raise FrameException("[LyricsFrame] Invalid language code: "\ "[%s] %s" % (ex.object, ex.reason)) else: self.lang = ""; try: (d, c) = splitUnicode(data[4:], self.encoding) self.description = encodeUnicode(d, id3EncodingToString(self.encoding)) self.lyrics = encodeUnicode(c, id3EncodingToString(self.encoding)) except ValueError: if strictID3(): raise FrameException("Invalid lyrics; no description/lyrics") else: self.description = u"" self.lyrics = u"" if not strictID3(): self.description = cleanNulls(self.description) self.lyrics = cleanNulls(self.lyrics) def render(self): lang = self.lang.encode("ascii") if len(lang) > 3: lang = lang[0:3] elif len(lang) < 3: lang = lang + ('\x00' * (3 - len(lang))) data = self.encoding + lang +\ self.description.encode(id3EncodingToString(self.encoding)) +\ self.getTextDelim() +\ self.lyrics.encode(id3EncodingToString(self.encoding)) return self.assembleFrame(data) def __unicode__(self): return u"<%s (%s): %s [Lang: %s] [Desc: %s]>" %\ (self.getFrameDesc(), self.header.id, self.lyrics, self.lang, self.description) ################################################################################ # This class refers to the APIC frame, otherwise known as an "attached # picture". class ImageFrame(Frame): mimeType = None pictureType = None description = u"" # Contains the image data when the mimetype is image type. # Otherwise it is None. imageData = None # Contains a URL for the image when the mimetype is "-->" per the spec. # Otherwise it is None. imageURL = None # Declared "picture types". OTHER = 0x00 ICON = 0x01 # 32x32 png only. OTHER_ICON = 0x02 FRONT_COVER = 0x03 BACK_COVER = 0x04 LEAFLET = 0x05 MEDIA = 0x06 # label side of cd, picture disc vinyl, etc. LEAD_ARTIST = 0x07 ARTIST = 0x08 CONDUCTOR = 0x09 BAND = 0x0A COMPOSER = 0x0B LYRICIST = 0x0C RECORDING_LOCATION = 0x0D DURING_RECORDING = 0x0E DURING_PERFORMANCE = 0x0F VIDEO = 0x10 BRIGHT_COLORED_FISH = 0x11 # There's always room for porno. ILLUSTRATION = 0x12 BAND_LOGO = 0x13 PUBLISHER_LOGO = 0x14 MIN_TYPE = OTHER MAX_TYPE = PUBLISHER_LOGO def __init__(self, frameHeader, data=None, description=u"", imageData=None, imageURL=None, pictureType=None, mimeType=None, encoding=DEFAULT_ENCODING, unsync_default=False): Frame.__init__(self, frameHeader, unsync_default) if data != None: self._set(data, frameHeader) else: assert(isinstance(description, unicode)) self.description = description self.encoding = encoding assert(mimeType) self.mimeType = mimeType assert(pictureType != None) self.pictureType = pictureType if imageData: self.imageData = imageData else: self.imageURL = imageURL assert(self.imageData or self.imageURL) # Factory method def create(type, imgFile, desc = u"", encoding = DEFAULT_ENCODING): if not isinstance(desc, unicode) or \ not isinstance(type, int): raise FrameException("Wrong description and/or image-type type."); # Load img fp = file(imgFile, "rb"); imgData = fp.read(); mt = guess_mime_type(imgFile); if not mt: raise FrameException("Unable to guess mime-type for %s" % (imgFile)); frameData = DEFAULT_ENCODING; frameData += mt + "\x00"; frameData += bin2bytes(dec2bin(type, 8)); frameData += desc.encode(id3EncodingToString(encoding)) + "\x00"; frameData += imgData; frameHeader = FrameHeader(); frameHeader.id = IMAGE_FID; return ImageFrame(frameHeader, data = frameData); # Make create a static method. Odd.... create = staticmethod(create); # Data string format: #

# Text encoding $xx # MIME type $00 # Picture type $xx # Description $00 (00) # Picture data def _set(self, data, frameHeader = None): assert(frameHeader); if not IMAGE_FRAME_RX.match(frameHeader.id): raise FrameException("Invalid frame id for ImageFrame: " +\ frameHeader.id); data = self.disassembleFrame(data); input = StringIO(data); TRACE_MSG("APIC frame data size: " + str(len(data))); self.encoding = input.read(1); TRACE_MSG("APIC encoding: " + id3EncodingToString(self.encoding)); # Mime type self.mimeType = ""; if self.header.minorVersion != 2: ch = input.read(1) while ch and ch != "\x00": self.mimeType += ch ch = input.read(1) else: # v2.2 (OBSOLETE) special case self.mimeType = input.read(3); TRACE_MSG("APIC mime type: " + self.mimeType); if strictID3() and not self.mimeType: raise FrameException("APIC frame does not contain a mime type"); if self.mimeType.find("/") == -1: self.mimeType = "image/" + self.mimeType; pt = ord(input.read(1)); TRACE_MSG("Initial APIC picture type: " + str(pt)); if pt < self.MIN_TYPE or pt > self.MAX_TYPE: if strictID3(): raise FrameException("Invalid APIC picture type: %d" % (pt)); # Rather than force this to UNKNOWN, let's assume that they put a # character literal instead of it's byte value. try: pt = int(chr(pt)) except: pt = self.OTHER if pt < self.MIN_TYPE or pt > self.MAX_TYPE: self.pictureType = self.OTHER self.pictureType = pt TRACE_MSG("APIC picture type: " + str(self.pictureType)) self.desciption = u"" # Remaining data is a NULL separated description and image data buffer = input.read() input.close() (desc, img) = splitUnicode(buffer, self.encoding) TRACE_MSG("description len: %d" % len(desc)) TRACE_MSG("image len: %d" % len(img)) self.description = encodeUnicode(desc, id3EncodingToString(self.encoding)) TRACE_MSG("APIC description: " + self.description); if self.mimeType.find("-->") != -1: self.imageData = None self.imageURL = img TRACE_MSG("APIC image data: %d bytes" % 0) else: self.imageData = img self.imageURL = None TRACE_MSG("APIC image data: %d bytes" % len(self.imageData)) if strictID3() and not self.imageData and not self.imageURL: raise FrameException("APIC frame does not contain any image data") def writeFile(self, path = "./", name = None): if not self.imageData: raise IOError("Fetching remote image files is not implemented.") if not name: name = self.getDefaultFileName() imageFile = os.path.join(path, name) f = file(imageFile, "wb"); f.write(self.imageData); f.flush(); f.close(); def getDefaultFileName(self, suffix = ""): nameStr = self.picTypeToString(self.pictureType) if suffix: nameStr += suffix nameStr = nameStr + "." + self.mimeType.split("/")[1] return nameStr def render(self): data = self.encoding + self.mimeType + "\x00" +\ bin2bytes(dec2bin(self.pictureType, 8)) +\ self.description.encode(id3EncodingToString(self.encoding)) +\ self.getTextDelim() if self.imageURL: data += self.imageURL.encode("ascii"); else: data += self.imageData; return self.assembleFrame(data); def stringToPicType(s): if s == "OTHER": return ImageFrame.OTHER; elif s == "ICON": return ImageFrame.ICON; elif s == "OTHER_ICON": return ImageFrame.OTHER_ICON; elif s == "FRONT_COVER": return ImageFrame.FRONT_COVER elif s == "BACK_COVER": return ImageFrame.BACK_COVER; elif s == "LEAFLET": return ImageFrame.LEAFLET; elif s == "MEDIA": return ImageFrame.MEDIA; elif s == "LEAD_ARTIST": return ImageFrame.LEAD_ARTIST; elif s == "ARTIST": return ImageFrame.ARTIST; elif s == "CONDUCTOR": return ImageFrame.CONDUCTOR; elif s == "BAND": return ImageFrame.BAND; elif s == "COMPOSER": return ImageFrame.COMPOSER; elif s == "LYRICIST": return ImageFrame.LYRICIST; elif s == "RECORDING_LOCATION": return ImageFrame.RECORDING_LOCATION; elif s == "DURING_RECORDING": return ImageFrame.DURING_RECORDING; elif s == "DURING_PERFORMANCE": return ImageFrame.DURING_PERFORMANCE; elif s == "VIDEO": return ImageFrame.VIDEO; elif s == "BRIGHT_COLORED_FISH": return ImageFrame.BRIGHT_COLORED_FISH; elif s == "ILLUSTRATION": return ImageFrame.ILLUSTRATION; elif s == "BAND_LOGO": return ImageFrame.BAND_LOGO; elif s == "PUBLISHER_LOGO": return ImageFrame.PUBLISHER_LOGO; else: raise FrameException("Invalid APIC picture type: %s" % s); stringToPicType = staticmethod(stringToPicType); def picTypeToString(t): if t == ImageFrame.OTHER: return "OTHER"; elif t == ImageFrame.ICON: return "ICON"; elif t == ImageFrame.OTHER_ICON: return "OTHER_ICON"; elif t == ImageFrame.FRONT_COVER: return "FRONT_COVER"; elif t == ImageFrame.BACK_COVER: return "BACK_COVER"; elif t == ImageFrame.LEAFLET: return "LEAFLET"; elif t == ImageFrame.MEDIA: return "MEDIA"; elif t == ImageFrame.LEAD_ARTIST: return "LEAD_ARTIST"; elif t == ImageFrame.ARTIST: return "ARTIST"; elif t == ImageFrame.CONDUCTOR: return "CONDUCTOR"; elif t == ImageFrame.BAND: return "BAND"; elif t == ImageFrame.COMPOSER: return "COMPOSER"; elif t == ImageFrame.LYRICIST: return "LYRICIST"; elif t == ImageFrame.RECORDING_LOCATION: return "RECORDING_LOCATION"; elif t == ImageFrame.DURING_RECORDING: return "DURING_RECORDING"; elif t == ImageFrame.DURING_PERFORMANCE: return "DURING_PERFORMANCE"; elif t == ImageFrame.VIDEO: return "VIDEO"; elif t == ImageFrame.BRIGHT_COLORED_FISH: return "BRIGHT_COLORED_FISH"; elif t == ImageFrame.ILLUSTRATION: return "ILLUSTRATION"; elif t == ImageFrame.BAND_LOGO: return "BAND_LOGO"; elif t == ImageFrame.PUBLISHER_LOGO: return "PUBLISHER_LOGO"; else: raise FrameException("Invalid APIC picture type: %d" % t); picTypeToString = staticmethod(picTypeToString); ################################################################################ # This class refers to the GEOB frame class ObjectFrame(Frame): mimeType = None description = u"" filename = u"" objectData = None def __init__(self, frameHeader, data=None, desc=u"", filename=u"", objectData=None, mimeType=None, encoding=DEFAULT_ENCODING, unsync_default=False): Frame.__init__(self, frameHeader, unsync_default) if data != None: self._set(data, frameHeader) else: assert(isinstance(desc, unicode)) self.description = desc assert(isinstance(filename, unicode)) self.filename = filename self.encoding = encoding assert(mimeType) self.mimeType = mimeType assert(objectData) self.objectData = objectData # Factory method def create(objFile, mime = u"", desc = u"", filename = None, encoding = DEFAULT_ENCODING): if filename == None: filename = encodeUnicode(os.path.basename(objFile), sys.getfilesystemencoding()) if not isinstance(desc, unicode) or \ (not isinstance(filename, unicode) and filename != ""): raise FrameException("Wrong description and/or filename type.") # Load file fp = file(objFile, "rb") objData = fp.read() if mime: TRACE_MSG("Using specified mime type %s" % mime) else: mime = guess_mime_type(objFile); if not mime: raise FrameException("Unable to guess mime-type for %s" % objFile) TRACE_MSG("Guessed mime type %s" % mime) frameData = DEFAULT_ENCODING frameData += mime + "\x00" frameData += filename.encode(id3EncodingToString(encoding)) + "\x00" frameData += desc.encode(id3EncodingToString(encoding)) + "\x00" frameData += objData frameHeader = FrameHeader() frameHeader.id = OBJECT_FID return ObjectFrame(frameHeader, data = frameData) # Make create a static method. Odd.... create = staticmethod(create) # Data string format: #
# Text encoding $xx # MIME type $00 # Filename $00 (00) # Content description $00 (00) # Encapsulated object def _set(self, data, frameHeader = None): assert(frameHeader); if not OBJECT_FRAME_RX.match(frameHeader.id): raise FrameException("Invalid frame id for ObjectFrame: " +\ frameHeader.id); data = self.disassembleFrame(data); input = StringIO(data); TRACE_MSG("GEOB frame data size: " + str(len(data))); self.encoding = input.read(1); TRACE_MSG("GEOB encoding: " + id3EncodingToString(self.encoding)); # Mime type self.mimeType = "" if self.header.minorVersion != 2: ch = input.read(1) while ch and ch != "\x00": self.mimeType += ch ch = input.read(1) if not ch: raise FrameException("GEOB frame mime type is not NULL terminated") else: # v2.2 (OBSOLETE) special case self.mimeType = input.read(3) TRACE_MSG("GEOB mime type: " + self.mimeType) if strictID3() and not self.mimeType: raise FrameException("GEOB frame does not contain a mime type") if strictID3() and self.mimeType.find("/") == -1: raise FrameException("GEOB frame does not contain a valid mime type") self.filename = u"" self.description = u"" # Remaining data is a NULL separated filename, description and object data buffer = input.read() input.close() try: (filename, buffer) = splitUnicode(buffer, self.encoding) (desc, obj) = splitUnicode(buffer, self.encoding) except ValueError: raise FrameException("GEOB frame appears to be missing requisite NULL " "terminators"); TRACE_MSG("filename len: %d" % len(filename)) TRACE_MSG("description len: %d" % len(desc)) TRACE_MSG("data len: %d" % len(obj)) self.filename = encodeUnicode(filename, id3EncodingToString(self.encoding)) self.description = encodeUnicode(desc, id3EncodingToString(self.encoding)) TRACE_MSG("GEOB filename: " + self.filename) TRACE_MSG("GEOB description: " + self.description) self.objectData = obj TRACE_MSG("GEOB data: " + str(len(self.objectData)) + " bytes") if strictID3() and not self.objectData: raise FrameException("GEOB frame does not contain any data") def writeFile(self, path = "./", name = None): if not self.objectData: raise IOError("Fetching remote object files is not implemented.") if not name: name = self.getDefaultFileName() objectFile = os.path.join(path, name) f = file(objectFile, "wb") f.write(self.objectData) f.flush() f.close() def getDefaultFileName(self, suffix = ""): nameStr = self.filename if suffix: nameStr += suffix nameStr = nameStr + "." + self.mimeType.split("/")[1] return nameStr def render(self): data = self.encoding + self.mimeType + "\x00" +\ self.filename.encode(id3EncodingToString(self.encoding)) +\ self.getTextDelim() +\ self.description.encode(id3EncodingToString(self.encoding)) +\ self.getTextDelim() +\ self.objectData return self.assembleFrame(data) class PlayCountFrame(Frame): count = None def __init__(self, frameHeader, data=None, count=None, unsync_default=False): Frame.__init__(self, frameHeader, unsync_default) if data != None: self._set(data, frameHeader) else: assert(count != None and count >= 0) self.count = count def _set(self, data, frameHeader): assert(frameHeader) # data of less then 4 bytes is handled with with 'sz' arg self.count = long(bytes2dec(data, sz=(len(data) * 2))) def render(self): data = dec2bytes(self.count, 32) return self.assembleFrame(data) class UniqueFileIDFrame(Frame): owner_id = "" id = "" def __init__(self, frameHeader, data=None, owner_id=None, id=None, unsync_default=False): Frame.__init__(self, frameHeader, unsync_default) if data != None: self._set(data, frameHeader) else: assert(owner_id != None and len(owner_id) > 0) assert(id != None and len(id) > 0 and len(id) <= 64) self.owner_id = owner_id self.id = id def _set(self, data, frameHeader): assert(frameHeader) # Data format # Owner identifier $00 # Identifier up to 64 bytes binary data> (self.owner_id, self.id) = data.split("\x00", 1) TRACE_MSG("UFID owner_id: " + self.owner_id) TRACE_MSG("UFID id: " + self.id) if strictID3() and (len(self.owner_id) == 0 or len(self.id) == 0 or len(self.id) > 64): raise FrameException("Invalid UFID frame") def render(self): data = self.owner_id + "\x00" + self.id return self.assembleFrame(data) ################################################################################ class UnknownFrame(Frame): data = ""; def __init__(self, frameHeader, data, unsync_default=False): assert(frameHeader and data) Frame.__init__(self, frameHeader, unsync_default) self._set(data, frameHeader) def _set(self, data, frameHeader): self.data = self.disassembleFrame(data); def render(self): return self.assembleFrame(self.data) ################################################################################ class MusicCDIdFrame(Frame): toc = ""; def __init__(self, frameHeader, data=None, unsync_default=False): Frame.__init__(self, frameHeader, unsync_default) # XXX: Flesh this class out and add a toc arg assert(data != None); if data != None: self._set(data, frameHeader); # TODO: Parse the TOC and comment the format. def _set(self, data, frameHeader): if not CDID_FRAME_RX.match(frameHeader.id): raise FrameException("Invalid frame id for MusicCDIdFrame: " +\ frameHeader.id); data = self.disassembleFrame(data); self.toc = data; def render(self): data = self.toc; return self.assembleFrame(data); ################################################################################ # A class for containing and managing ID3v2.Frame objects. class FrameSet(list): tagHeader = None; def __init__(self, tagHeader, l = None): self.tagHeader = tagHeader; if l: for f in l: if not isinstance(f, Frame): raise TypeError("Invalid type added to FrameSet: " +\ f.__class__); self.append(f); # Setting a FrameSet instance like this 'fs = []' morphs the instance into # a list object. def clear(self): del self[0:]; # Read frames starting from the current read position of the file object. # Returns the amount of padding which occurs after the tag, but before the # audio content. A return valule of 0 DOES NOT imply an error. def parse(self, f, tagHeader, extendedHeader): self.tagHeader = tagHeader; self.extendedHeader = extendedHeader paddingSize = 0; sizeLeft = tagHeader.tagSize - extendedHeader.size start_size = sizeLeft consumed_size = 0 # Handle a tag-level unsync. Some frames may have their own unsync bit # set instead. tagData = f.read(sizeLeft) # If the tag is 2.3 and the tag header unsync bit is set then all the # frame data is deunsync'd at once, otherwise it will happen on a per # frame basis. from eyeD3 import ID3_V2_3 if tagHeader.unsync and tagHeader.version <= ID3_V2_3: TRACE_MSG("De-unsynching %d bytes at once (<= 2.3 tag)" % len(tagData)) og_size = len(tagData) tagData = deunsyncData(tagData) sizeLeft = len(tagData) TRACE_MSG("De-unsynch'd %d bytes at once (<= 2.3 tag) to %d bytes" % (og_size, sizeLeft)) # Adding bytes to simulate the tag header(s) in the buffer. This keeps # f.tell() values matching the file offsets. prepadding = '\x00' * 10 # Tag header prepadding += '\x00' * extendedHeader.size tagBuffer = StringIO(prepadding + tagData); tagBuffer.seek(len(prepadding)); while sizeLeft > 0: TRACE_MSG("sizeLeft: " + str(sizeLeft)); if sizeLeft < (10 + 1): # The size of the smallest frame. TRACE_MSG("FrameSet: Implied padding (sizeLeft < minFrameSize)"); paddingSize = sizeLeft break; TRACE_MSG("+++++++++++++++++++++++++++++++++++++++++++++++++"); TRACE_MSG("FrameSet: Reading Frame #" + str(len(self) + 1)); frameHeader = FrameHeader(tagHeader); if not frameHeader.parse(tagBuffer): TRACE_MSG("No frame found, implied padding of %d bytes" % sizeLeft) paddingSize = sizeLeft break; # Frame data. if frameHeader.dataSize: TRACE_MSG("FrameSet: Reading %d (0x%X) bytes of data from byte " "pos %d (0x%X)" % (frameHeader.dataSize, frameHeader.dataSize, tagBuffer.tell(), tagBuffer.tell())); data = tagBuffer.read(frameHeader.dataSize); TRACE_MSG("FrameSet: %d bytes of data read" % len(data)); consumed_size += (frameHeader.FRAME_HEADER_SIZE + frameHeader.dataSize) self.addFrame(createFrame(frameHeader, data, tagHeader)) # Each frame contains dataSize + headerSize bytes. sizeLeft -= (frameHeader.FRAME_HEADER_SIZE + frameHeader.dataSize); return paddingSize # Returrns the size of the frame data. def getSize(self): sz = 0; for f in self: sz += len(f.render()); return sz; def setTagHeader(self, tagHeader): self.tagHeader = tagHeader; for f in self: f.header.setVersion(tagHeader); # This methods adds the frame if it is addable per the ID3 spec. def addFrame(self, frame): fid = frame.header.id; # Text frame restrictions. # No multiples except for TXXX which must have unique descriptions. if strictID3() and TEXT_FRAME_RX.match(fid) and self[fid]: if not USERTEXT_FRAME_RX.match(fid): raise FrameException("Multiple %s frames not allowed." % fid); userTextFrames = self[fid]; for frm in userTextFrames: if frm.description == frame.description: raise FrameException("Multiple %s frames with the same\ description not allowed." % fid); # Comment frame restrictions. # Multiples must have a unique description/language combination. if strictID3() and COMMENT_FRAME_RX.match(fid) and self[fid]: commentFrames = self[fid]; for frm in commentFrames: if frm.description == frame.description and\ frm.lang == frame.lang: raise FrameException("Multiple %s frames with the same\ language and description not allowed." %\ fid); # Lyrics frame restrictions. # Multiples must have a unique description/language combination. if strictID3() and LYRICS_FRAME_RX.match(fid) and self[fid]: lyricsFrames = self[fid]; for frm in lyricsFrames: if frm.description == frame.description and\ frm.lang == frame.lang: raise FrameException("Multiple %s frames with the same\ language and description not allowed." %\ fid); # URL frame restrictions. # No multiples except for TXXX which must have unique descriptions. if strictID3() and URL_FRAME_RX.match(fid) and self[fid]: if not USERURL_FRAME_RX.match(fid): raise FrameException("Multiple %s frames not allowed." % fid); userUrlFrames = self[fid]; for frm in userUrlFrames: if frm.description == frame.description: raise FrameException("Multiple %s frames with the same\ description not allowed." % fid); # Music CD ID restrictions. # No multiples. if strictID3() and CDID_FRAME_RX.match(fid) and self[fid]: raise FrameException("Multiple %s frames not allowed." % fid); # Image (attached picture) frame restrictions. # Multiples must have a unique content desciptor. I'm assuming that # the spec means the picture type..... if IMAGE_FRAME_RX.match(fid) and self[fid] and strictID3(): imageFrames = self[fid]; for frm in imageFrames: if frm.pictureType == frame.pictureType: raise FrameException("Multiple %s frames with the same "\ "content descriptor not allowed." % fid); # Object (GEOB) frame restrictions. # Multiples must have a unique content desciptor. if OBJECT_FRAME_RX.match(fid) and self[fid] and strictID3(): objectFrames = self[fid]; for frm in objectFrames: if frm.description == frame.description: raise FrameException("Multiple %s frames with the same "\ "content descriptor not allowed." % fid); # Play count frame (PCNT). There may be only one if PLAYCOUNT_FRAME_RX.match(fid) and self[fid]: raise FrameException("Multiple %s frames not allowed." % fid); # Unique File identifier frame. There may be only one with the same # owner_id if UNIQUE_FILE_ID_FRAME_RX.match(fid) and self[fid]: ufid_frames = self[fid]; for frm in ufid_frames: if frm.owner_id == frame.owner_id: raise FrameException("Multiple %s frames not allowed with "\ "the same owner ID (%s)" %\ (fid, frame.owner_id)); self.append(frame); # Set a text frame value. Text frame IDs must be unique. If a frame with # the same Id is already in the list it's value is changed, otherwise # the frame is added. def setTextFrame(self, frameId, text, encoding=None): assert(type(text) == unicode) if not TEXT_FRAME_RX.match(frameId): raise FrameException("Invalid Frame ID: " + frameId) if self[frameId]: curr = self[frameId][0] if encoding: curr.encoding = encoding if isinstance(curr, DateFrame): curr.setDate(text) else: curr.text = text else: h = FrameHeader(self.tagHeader) h.id = frameId if not encoding: encoding = DEFAULT_ENCODING if frameId in DATE_FIDS: self.addFrame(DateFrame(h, encoding = encoding, date_str = text)) else: self.addFrame(TextFrame(h, encoding = encoding, text = text)) def setURLFrame(self, frame_id, url): assert(type(url) == str) if frame_id not in URL_FIDS: raise FrameException("Invalid URL frame ID: %s" % frame_id) if self[frame_id]: self[frame_id][0].url = url else: h = FrameHeader(self.tagHeader) h.id = frame_id self.addFrame(URLFrame(h, url=url)) # If a user text frame with the same description exists then # the frame text is replaced, otherwise the frame is added. def setCommentFrame(self, comment, description, lang = DEFAULT_LANG, encoding = None): assert(isinstance(comment, unicode) and isinstance(description, unicode)) if self[COMMENT_FID]: found = 0 for f in self[COMMENT_FID]: if f.lang == lang and f.description == description: f.comment = comment if encoding: f.encoding = encoding found = 1 break if not found: h = FrameHeader(self.tagHeader) h.id = COMMENT_FID if not encoding: encoding = DEFAULT_ENCODING self.addFrame(CommentFrame(h, encoding = encoding, lang = lang, description = description, comment = comment)) else: if not encoding: encoding = DEFAULT_ENCODING h = FrameHeader(self.tagHeader) h.id = COMMENT_FID self.addFrame(CommentFrame(h, encoding = encoding, lang = lang, description = description, comment = comment)) # If a user text frame with the same description exists then # the frame text is replaced, otherwise the frame is added. def setLyricsFrame(self, lyrics, description, lang = DEFAULT_LANG, encoding = None): assert(isinstance(lyrics, unicode) and isinstance(description, unicode)); if self[LYRICS_FID]: found = 0; for f in self[LYRICS_FID]: if f.lang == lang and f.description == description: f.lyrics = lyrics; if encoding: f.encoding = encoding; found = 1; break; if not found: h = FrameHeader(self.tagHeader); h.id = LYRICS_FID; if not encoding: encoding = DEFAULT_ENCODING; self.addFrame(LyricsFrame(h, encoding = encoding, lang = lang, description = description, lyrics = lyrics)); else: if not encoding: encoding = DEFAULT_ENCODING; h = FrameHeader(self.tagHeader); h.id = LYRICS_FID; self.addFrame(LyricsFrame(h, encoding = encoding, lang = lang, description = description, lyrics = lyrics)) def setUniqueFileIDFrame(self, owner_id, id): assert(isinstance(owner_id, str) and isinstance(id, str)) if self[UNIQUE_FILE_ID_FID]: found = 0 for f in self[UNIQUE_FILE_ID_FID]: if f.owner_id == owner_id: f.id = id found = 1 break if not found: h = FrameHeader(self.tagHeader) h.id = UNIQUE_FILE_ID_FID self.addFrame(UniqueFileIDFrame(h, owner_id = owner_id, id = id)) else: h = FrameHeader(self.tagHeader) h.id = UNIQUE_FILE_ID_FID self.addFrame(UniqueFileIDFrame(h, owner_id = owner_id, id = id)) # If a comment frame with the same language and description exists then # the comment text is replaced, otherwise the frame is added. def setUserTextFrame(self, txt, description, encoding=None): assert(isinstance(txt, unicode)) assert(isinstance(description, unicode)); if self[USERTEXT_FID]: found = 0; for f in self[USERTEXT_FID]: if f.description == description: f.text = txt; if encoding: f.encoding = encoding; found = 1; break; if not found: if not encoding: encoding = DEFAULT_ENCODING; h = FrameHeader(self.tagHeader); h.id = USERTEXT_FID; self.addFrame(UserTextFrame(h, encoding = encoding, description = description, text = txt)); else: if not encoding: encoding = DEFAULT_ENCODING; h = FrameHeader(self.tagHeader); h.id = USERTEXT_FID; self.addFrame(UserTextFrame(h, encoding = encoding, description = description, text = txt)); def setUserURLFrame(self, url, description, encoding=None): assert(isinstance(url, str)) assert(isinstance(description, unicode)) if self[USERURL_FID]: found = 0 for f in self[USERURL_FID]: if f.description == description: f.url = url if encoding: f.encoding = encoding found = 1 break if not found: if not encoding: encoding = DEFAULT_ENCODING h = FrameHeader(self.tagHeader) h.id = USERURL_FID self.addFrame(UserURLFrame(h, encoding=encoding, description=description, url=url)) else: if not encoding: encoding = DEFAULT_ENCODING h = FrameHeader(self.tagHeader) h.id = USERURL_FID self.addFrame(UserURLFrame(h, encoding=encoding, description=description, url=url)) # This method removes all frames with the matching frame ID. # The number of frames removed is returned. # Note that calling this method with a key like "COMM" may remove more # frames then you really want. def removeFramesByID(self, fid): if not isinstance(fid, str): raise FrameException("removeFramesByID only operates on frame IDs") i = 0 count = 0 while i < len(self): if self[i].header.id == fid: del self[i] count += 1 else: i += 1 return count # Removes the frame at index. True is returned if the element was # removed, and false otherwise. def removeFrameByIndex(self, index): if not isinstance(index, int): raise\ FrameException("removeFrameByIndex only operates on a frame index") try: del self.frames[key] return 1 except: return 0 # Accepts both int (indexed access) and string keys (a valid frame Id). # A list of frames (commonly with only one element) is returned when the # FrameSet is accessed using frame IDs since some frames can appear # multiple times in a tag. To sum it all up htis method returns # string or None when indexed using an integer, and a 0 to N length # list of strings when indexed with a frame ID. # # Throws IndexError and TypeError. def __getitem__(self, key): if isinstance(key, int): if key >= 0 and key < len(self): return list.__getitem__(self, key) else: raise IndexError("FrameSet index out of range") elif isinstance(key, str): retList = list() for f in self: if f.header.id == key: retList.append(f) return retList else: raise TypeError("FrameSet key must be type int or string") ## # Used for splitting user text tags that use null byte(s) to seperate a # description and its text. def splitUnicode(data, encoding): if encoding == LATIN1_ENCODING or encoding == UTF_8_ENCODING: retval = data.split("\x00", 1) elif encoding == UTF_16_ENCODING or encoding == UTF_16BE_ENCODING: # Two null bytes split, but since each utf16 char is also two # bytes we need to ensure we found a proper boundary. (d, t) = data.split("\x00\x00", 1) if (len(d) % 2) != 0: (d, t) = data.split("\x00\x00\x00", 1) d += "\x00" retval = (d, t) if len(retval) != 2: # What we have here is an invalid tag in that contains only piece # of the information. In the spirit of not crashing on crap tags # return it as the ... description retval = (retval[0], "") return retval ####################################################################### # Create and return the appropriate frame. # Exceptions: .... def createFrame(frameHeader, data, tagHeader): f = None # Text Frames if TEXT_FRAME_RX.match(frameHeader.id): if USERTEXT_FRAME_RX.match(frameHeader.id): f = UserTextFrame(frameHeader, data=data, unsync_default=tagHeader.unsync) else: if frameHeader.id[:2] == "TD" or\ frameHeader.id == OBSOLETE_DATE_FID or\ frameHeader.id == OBSOLETE_YEAR_FID or \ frameHeader.id == OBSOLETE_ORIG_RELEASE_FID: f = DateFrame(frameHeader, data=data, unsync_default=tagHeader.unsync) else: f = TextFrame(frameHeader, data=data, unsync_default=tagHeader.unsync) # Comment Frames. elif COMMENT_FRAME_RX.match(frameHeader.id): f = CommentFrame(frameHeader, data=data, unsync_default=tagHeader.unsync) # Lyrics Frames. elif LYRICS_FRAME_RX.match(frameHeader.id): f = LyricsFrame(frameHeader, data=data, unsync_default=tagHeader.unsync) # URL Frames. elif URL_FRAME_RX.match(frameHeader.id): if USERURL_FRAME_RX.match(frameHeader.id): f = UserURLFrame(frameHeader, data=data, unsync_default=tagHeader.unsync) else: f = URLFrame(frameHeader, data=data, unsync_default=tagHeader.unsync) # CD Id frame. elif CDID_FRAME_RX.match(frameHeader.id): f = MusicCDIdFrame(frameHeader, data=data, unsync_default=tagHeader.unsync) # Attached picture elif IMAGE_FRAME_RX.match(frameHeader.id): f = ImageFrame(frameHeader, data=data, unsync_default=tagHeader.unsync) # Encapsulated object elif OBJECT_FRAME_RX.match(frameHeader.id): f = ObjectFrame(frameHeader, data=data, unsync_default=tagHeader.unsync) # Play count elif PLAYCOUNT_FRAME_RX.match(frameHeader.id): f = PlayCountFrame(frameHeader, data=data, unsync_default=tagHeader.unsync) # Unique file identifier elif UNIQUE_FILE_ID_FRAME_RX.match(frameHeader.id): f = UniqueFileIDFrame(frameHeader, data=data, unsync_default=tagHeader.unsync) if f == None: f = UnknownFrame(frameHeader, data=data, unsync_default=tagHeader.unsync) return f def map2_2FrameId(originalId): if not TAGS2_2_TO_TAGS_2_3_AND_4.has_key(originalId): return originalId return TAGS2_2_TO_TAGS_2_3_AND_4[originalId] def dump_data_to_file(s, f): f = file(f, "w") for c in s: f.write("\\x%.2x" % ord(c)) f.write("\n") f.close() ## # A suxtitute for Python's 'unicode' constructor which handles invalid # string encodings (as seen in the real world). def encodeUnicode(bytes, encoding): if (encoding == id3EncodingToString(UTF_16_ENCODING) and len(bytes) % 2 and bytes[-2:] == "\x00\x00"): # Fixes: utf16, odd number of bytes (including 2-byte BOM) where # the final byte is \x00 # Users have sent tags with an invalid utf16 encoding thus # python's unicode type can't decode. Fix this edge case. bytes = bytes[:-1] return unicode(bytes, encoding)