Travis Shirk avatar Travis Shirk committed 93e88df

Import

Comments (0)

Files changed (27)

+		    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.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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.
+
+  <signature of Ty Coon>, 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.
+2011-12-17  Travis Shirk  <travis@pobox.com>
+
+	* TODO, src/eyed3/core.py, src/eyed3/id3/frames-migration.py,
+	src/eyed3/id3/frames2.py, src/eyed3/mp3/__init__.py,
+	src/eyed3/plugins/default.py, src/eyed3/utils/log.py,
+	src/test/test_core.py, src/test/test_mp3.py:
+	Hacking
+	[56a9518f4a8d] [tip]
+
+2011-12-15  Travis Shirk  <travis@pobox.com>
+
+	* Makefile.in, TODO, doc/doxygen.cfg.in:
+	Doxygen tweaks
+	[cde16071d4af]
+
+	* Makefile.in, configure.ac, doc/doxygen.cfg.in:
+	Doxygen
+	[fa3878120b74]
+
+	* src/test/test_binfuncs.py:
+	Added
+	[93a32e7a75e8]
+
+	* src/eyed3/id3/__init__.py, src/eyed3/id3/frames-migration.py,
+	src/eyed3/id3/frames.py, src/eyed3/id3/frames2.py,
+	src/eyed3/id3/headers.py, src/eyed3/id3/tag-migration.py,
+	src/eyed3/id3/tag.py, src/eyed3/mp3/__init__.py,
+	src/eyed3/mp3/headers.py:
+	Brought back old trunk id3 and mp3 packages.
+	[6e08e515abfa]
+
+2011-12-13  Travis Shirk  <travis@pobox.com>
+
+	* src/eyed3/utils/__init__.py:
+	requireUnicode
+	[7fe43922e3b4]
+
+	* src/eyed3/binfuncs.py:
+	Added
+	[80bad2e55cbd]
+
+2011-12-03  Travis Shirk  <travis@pobox.com>
+
+	* setup.py, src/eyed3/__init__.py, src/eyed3/core.py,
+	src/eyed3/id3/__init__.py, src/eyed3/id3/tag.py,
+	src/eyed3/mp3/__init__.py, src/test/test_core.py,
+	src/test/test_tag.py:
+	Tag beginnings
+	[9f5d36a7e44c]
+
+	* src/examples/logs.py:
+	Removed
+	[400a411cb534]
+
+	* src/eyed3/core.py, src/eyed3/mp3/__init__.py:
+	Some clean up and new mimetypes for mp3
+	[9647e88295ee]
+
+2011-12-01  Travis Shirk  <travis@pobox.com>
+
+	* Makefile.in, README, src/eyed3/__init__.py, src/eyed3/core.py,
+	src/eyed3/mp3/__init__.py:
+	Starts on base classes for audio files and tags.
+	[36274250048f]
+
+	* Makefile.in, src/eyeD3, src/eyed3/__init__.py, src/test/__init__.py,
+	src/test/data/.keep, src/test/test__init__.py:
+	eyed3.require(), test stuff, etc.
+	[3920711f3df1]
+
+2011-11-29  Travis Shirk  <travis@pobox.com>
+
+	* Makefile.in, src/eyed3/__init__.py, src/test/test__init__.py:
+	Testing fixes and beginnings
+	[4ab8676968bc]
+
+	* Makefile.in, TODO, env.bash, src/eyed3/__init__.py:
+	Starting work on tests with nose/coverage
+	[5f023154e934]
+
+2011-11-28  Travis Shirk  <travis@pobox.com>
+
+	* src/eyed3/plugins/default.py:
+	missing file
+	[43e5ac45aa8d]
+
+	* TODO, src/eyeD3, src/eyed3/plugins/__init__.py,
+	src/eyed3/plugins/examples.py, src/eyed3/utils/log.py:
+	More work on plugins
+	[3ee1ea7a6e62]
+
+2011-11-27  Travis Shirk  <travis@pobox.com>
+
+	* TODO, src/eyeD3, src/eyed3/plugins/__init__.py:
+	Loading the right plugin
+	[32e70c020456]
+
+	* Makefile.in, TODO, src/eyeD3, src/eyed3/info.py.in,
+	src/eyed3/plugins/__init__.py, src/eyed3/plugins/examples.py,
+	src/eyed3/plugins/mimetype.py, src/eyed3/utils/cli.py:
+	Plugin loading reworked, --plugins, Makefile fixes, etc. etc.
+	[194480374173]
+
+2011-11-26  Travis Shirk  <travis@pobox.com>
+
+	* src/eyeD3, src/eyed3/utils/__init__.py, src/eyed3/utils/cli.py:
+	Got some color up'n here
+	[88a6589186ab]
+
+	* README:
+	Added majic to requiredments list
+	[da8a90d64340]
+
+	* TODO, setup.py, src/eyeD3, src/eyed3/plugins/__init__.py,
+	src/eyed3/plugins/mimetype.py:
+	Fleshed out plugin system and added a simple mimetype plugin
+	[079e4312c79d]
+
+	* src/eyed3/utils/__init__.py:
+	Added mimetype guesser
+	[54e930fc9ad4]
+
+2011-11-25  Travis Shirk  <travis@pobox.com>
+
+	* src/eyeD3, src/eyed3/__init__.py, src/eyed3/utils/__init__.py,
+	src/eyed3/utils/cli.py, src/eyed3/utils/log.py:
+	Traversing files, logging fixes, etc. etc.
+	[0691174a4591]
+
+	* TODO, src/eyeD3, src/eyed3/__init__.py, src/eyed3/info.py.in,
+	src/eyed3/utils/cli.py, src/eyed3/utils/log.py:
+	Command line parsing, profiling, etc.
+	[bb1e9414b55f]
+
+	* Makefile.in, TODO, autogen.sh, configure.ac, setup.py, setup.py.in,
+	src/eyeD3, src/eyed3/info.py.in, version:
+	Got rid of setup.py.in to be more friendly with pip Added a version
+	file where configure and setup.py obtain it. More buildout, etc.
+	[5a56d24e6f54]
+
+	* src/eyeD3:
+	This needs to be executable
+	[d9d44abe3dbf]
+
+	* src/examples/logs.py, src/eyed3/utils/log.py:
+	Better log example/test
+	[7e7081670831]
+
+	* src/examples/logs.py, src/eyed3/__init__.py, src/eyed3/utils/log.py:
+	New log levels
+	[257a0ccd7118]
+
+	* README, env.bash, setup.py.in, src/eyeD3, src/eyed3/__init__.py,
+	src/eyed3/utils/__init__.py, src/eyed3/utils/log.py:
+	Added eyed3.utils.log
+	[512b3eb3efe7]
+
+	* .hgignore, AUTHORS, COPYING, Makefile.in, TODO, acsite.m4,
+	autogen.sh, configure.ac, env.bash, setup.py.in, src/eyeD3,
+	src/eyed3/__init__.py, src/eyed3/info.py.in:
+	Initial import of build system
+	[e17769a982db]
+
+#
+#  Copyright (C) 2002-2011  Travis Shirk <travis@pobox.com>
+#
+#  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
+#
+SHELL:=/bin/bash
+DIST_NAME=@PACKAGE_TARNAME@-@PACKAGE_VERSION@
+DIST_TAR=${DIST_NAME}.tar
+DIST_GZ=${DIST_TAR}.gz
+DIST_WWW=${DIST_NAME}-www.tar.gz
+PYTHON=@PYTHON@
+EBUILD_VERSION=@EBUILD_VERSION@
+
+# FIXME: I think this is an rpm artifact. Ebuilds pass DESTDIR so no translation
+# is necessary. Remve?
+ifdef BUILD_ROOT
+  DESTDIR=${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})
+
+DOCS_BUILD_D=doc/.build
+
+.PHONY: all
+all: module
+
+.PHONY: module
+module:
+	${PYTHON} setup.py build
+
+.PHONY: install
+install:
+	${PYTHON} setup.py install ${SETUP_ARGS} --prefix=${prefix}
+
+	install -m 755 -d ${bindir}
+	install -m 755 bin/eyeD3 ${bindir}
+
+	install -m 755 -d ${docdir}
+	# FIXME
+	#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
+
+	# FIXME
+	# FIXME: conditionalize this with a USE=doc->configure var
+	#install -m 755 -d ${docdir}/api
+
+	# FIXME
+	#install -m 755 -d ${mandir}/man1
+	#install -m 644 doc/eyeD3.1 ${mandir}/man1
+	#gzip -f -9 ${mandir}/man1/eyeD3.1
+
+	# FIXME
+	#-${MAKE} -C po DESTDIR=${DESTDIR} install
+
+.PHONY: clean
+clean:
+	-rm -rf build
+	find . -name \*.pyc -exec rm '{}' \;
+	-rm -rf eyeD3.egg-info
+
+.PHONY: dist-clean
+dist-clean: clean test-clean
+	-rm -rf autom4te*.cache ${DIST_NAME} ${DIST_GZ} ${DIST_WWW} ${DIST_WWW}
+	-rm doc/eyeD3.1.gz
+	-rm -f config.*
+	-rm -rf src/eyed3/info.py
+	-find . -name \*.pyc -exec rm '{}' \;
+	-rm Makefile
+	-rm tags
+	#-${MAKE} -C po distclean
+
+.PHONY: maintainer-clean
+maintainer-clean: docs-clean
+	-rm -f configure src/config.h.in
+	-rm doc/eyeD3.1
+	${MAKE} dist-clean
+
+# FIXME: cp doc/README.rst dist/README
+#        cp doc/history.rst dist/NEWS
+.PHONY: dist
+dist: docs dist-clean
+	mkdir ${DIST_NAME}
+	cp ChangeLog AUTHORS COPYING README TODO INSTALL ${DIST_NAME}
+	# FIXME: not sure if acsite needs to go if configure is processed
+	cp acsite.m4 configure setup.py Makefile.in ${DIST_NAME}
+
+	mkdir ${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
+	# FIXME
+	#mkdir ${DIST_NAME}/doc
+	#cp doc/eyeD3.1.in ${DIST_NAME}/doc
+
+	# FIXME
+	#cp -r ./po ${DIST_NAME}
+
+	find ${DIST_NAME} -type d -name .svn -print | xargs rm -rf
+	tar cf ${DIST_TAR} ${DIST_NAME}
+	gzip ${DIST_TAR}
+	rm -rf ${DIST_NAME}
+	./autogen.sh
+
+.PHONY: release
+release: dist www sloccount
+	# Re-bootstap to undo dist-clean
+	./autogen.sh > /dev/null 2>&1
+
+
+.PHONY: changelog
+changelog:
+	hg log --style=changelog . >| ChangeLog
+	   
+.PHONY: tags
+tags: 
+	@if test -f tags; then \
+	   rm tags; \
+        fi
+	ctags -R --exclude='tmp/*' --exclude='build/*'
+
+.PHONY: docs
+docs:
+	# FIXME
+	#${MAKE} -C ./doc html
+	@echo "Docs: file://`pwd`/${DOCS_BUILD_D}/html/index.html"
+
+.PHONY: docs-clean
+docs-clean:
+	-rm -rf ${DOCS_BUILD_D}/*
+	# FIXME
+	#${MAKE} -C ./doc clean
+
+.PHONY: www
+www:
+	-rm -rf ./www
+	-mkdir -p www/eyeD3/releases
+	-mkdir www/eyeD3/releases/gentoo
+	cp ChangeLog COPYING TODO www/eyeD3
+	cp ${DIST_GZ} www/eyeD3/releases
+	cp etc/gentoo/eyeD3-${EBUILD_VERSION}.ebuild www/eyeD3/releases/gentoo
+	tar czvf ${DIST_WWW} www
+	rm -rf www
+
+.PHONY: push-www
+push-www:
+	scp ${DIST_WWW} nicfit:.
+	ssh nicfit 'tar xzvf ${DIST_WWW}'
+
+.PHONY: sloccount
+sloccount:
+	sloccount ./src
+	sloccount --cached --details ./src
+
+.PHONY: test-dist
+test-dist:
+	-${MAKE} maintainer-clean
+	./autogen.sh
+	${MAKE} dist
+
+	test -d ./tmp || mkdir tmp
+	test -d ./tmp/install || mkdir tmp/install
+	-rm -rf ./tmp/${DIST_NAME}
+	-rm -rf ./tmp/install/*
+	tar xzvf ${DIST_GZ} -C ./tmp
+
+	cd ./tmp/${DIST_NAME} && ./configure --prefix=${CURDIR}/tmp/install
+	${MAKE} -C ./tmp/${DIST_NAME}
+	${MAKE} -C ./tmp/${DIST_NAME} install
+
+.PHONY: test
+test: all
+	nosetests --verbosity=3 --detailed-errors ${NOSE_OPTS} \
+	    --with-coverage --cover-erase --cover-package=eyed3 \
+	    --cover-html --cover-html-dir=build/test/coverage\
+	    src/test
+	@echo "Coverage: file://${CURDIR}/build/test/coverage/index.html"
+
+.PHONY: test-clean
+test-clean:
+	-find ./src/test -name \*.pyc -exec rm '{}' \;
+	-rm -rf build/test/html
+	-rm -rf .coverage
+	-find . -name \*,cover -exec rm '{}' \;
+
+PYLINT_OPTS=
+.PHONY: pylint
+pylint:
+	pylint --rcfile=etc/pylint.conf \
+	       ${PYLINT_OPTS} eyed3 > pylint-report.html
+
+eyeD3 is both a command line tool and Python module for dealing with
+MPEG audio and ID3 (v1.x, v2.x) tags.
+
+.. note::
+  This version of eyeD3 is NOT API compatible with version 0.6.x. The command
+  line tool is mostly compatible but it is highly recommended that you read
+  the updated help to check.
+
+.. code-block:: sh
+
+  $ eyeD3 -a Nobunny -A "Love Visions" -t "I Am a Girlfried" -n 4 example.mp3
+
+or in Python::
+
+  import eyed3
+  audiofile = eyed3.load("example.mp3")
+  audiofile.tag.artist = u"Nobunny"
+  audiofile.tag.album = u"Love Visions"
+  audiofile.tag.title = u"I Am a Girlfried"
+  audiofile.tag.track_num = 4
+  audiofile.tag.save()
+
+
+Requirements:
+=============
+
+* Python 2.7
+* [optional] python-magic (http://www.darwinsys.com/file/) 
+
+Installation
+============
+
+Mercurial
+---------
+.. code-block:: sh
+
+    $ hg clone https://nicfit@bitbucket.org/nicfit/eyed3
+    $ ./autogen.sh
+
+If you want all the required developer tools (nose, sphinx, etc.) and
+have ``virtualenvwrapper`` set up a virtualenv with the ``mkenv.bash`` script.
+
+.. code-block:: sh
+
+    $ cd eyed3
+    $ ./mkenv.bash
+    $ workon eyeD3
+    $ make test
+
+Users of ``virtualenv`` directly should consult ``mkenv.bash`` to setup a
+virtual environment,
+or `download <http://www.doughellmann.com/projects/virtualenvwrapper/>`_
+the wrapper.
+
+Support
+=======
+Join the eyeD3 `mailing list <http://groups.google.com/group/eyed3-users>`_
+or report bugs / feature requests on the bug
+`tracker <https://bitbucket.org/nicfit/eyed3/issues?status=new&status=open`_
+
+
+.. vim: set filetype=rst
+dnl
+dnl  Copyright (C) 2002-2011  Travis Shirk <travis@pobox.com>
+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], [
+   acx_python_major=`echo $1 | cut -f1 -d.`
+   PYTHON=""
+   for python in python python${acx_python_major} 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.%d" % (sys.version_info[[0]], sys.version_info[[1]], sys.version_info[[2]]))'`
+       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
+])
+
+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
+])
+#!/bin/sh
+#
+#  Copyright (C) 2011  Travis Shirk <travis@pobox.com>
+#
+#  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
+
+#
+# Run this to generate all the initial autoconf files, etc.
+#
+
+# XXX: set these to the tools you rely on.
+AUTOCONF="autoconf"
+AUTOHEADER=""
+
+die()
+{
+    test -n "$1" && echo "$1"
+    exit 1
+}
+
+# Check for autoconf
+(${AUTOCONF} --version) < /dev/null > /dev/null 2>&1 || {
+	echo
+        echo "You must have ${AUTOCONF} to build from the source repository."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at ftp://ftp.gnu.org/pub/gnu/"
+        die
+}
+
+if test -n ${AUTOHEADER}; then
+    echo "Running ${AUTOHEADER} to create a template config header file..."
+    ${AUTOHEADER} || die "Running ${AUTOHEADER} failed, exiting..."
+fi
+
+echo ""
+echo "Running ${AUTOCONF} to create 'configure'..."
+${AUTOCONF} || die "Running ${AUTOCONF} failed, exiting..."
+
+# Run confure to start off with a buildable system unless no-config is
+# present on the command line.
+if echo "$*" | grep -v "no-config" > /dev/null 2>&1; then
+    if [ -x config.status -a -z "$*" ]; then
+        ./config.status --recheck
+    else
+        if test -z "$*"; then
+            echo ""
+            echo "I am going to run ./configure with no arguments - if you wish"
+            echo "to pass any, please specify them on the $0 command line."
+            echo "If you do not wish to run ./configure, press Ctrl-C now."
+            echo ""
+            trap 'echo "configure aborted" ; exit 0' 1 2 15
+            sleep 1
+        fi
+        ./configure "$@"
+    fi
+fi
+dnl
+dnl  Copyright (C) 2002-2011  Travis Shirk <travis@pobox.com>
+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
+
+#pkg_version=`cat version | cut -d- -f1`
+#echo "VERSION: ${pkg_version}"
+AC_INIT([eyeD3], m4_esyscmd_s([cat version | cut -d- -f1]),
+        [Travis Shirk <travis@pobox.com>], [eyeD3],
+        [http://eyeD3.nicfit.net/])
+dnl alpha beta final
+PACKAGE_RELEASE_LEVEL=m4_esyscmd_s([cat version | cut -d- -f2])
+AC_SUBST([PACKAGE_RELEASE_LEVEL])
+
+EBUILD_RELEASE= 
+EBUILD_VERSION=${PACKAGE_VERSION}
+if test -n "$EBUILD_RELEASE"; then
+    EBUILD_VERSION=${EBUILD_VERSION}-${EBUILD_RELEASE}
+fi
+AC_SUBST([EBUILD_VERSION])
+
+AC_PREREQ([2.68])
+AC_COPYRIGHT([GNU GPL])
+
+BUILD_DATE=`date`
+AC_SUBST([BUILD_DATE])
+MANPAGE_DATE=`date +'%b. %d, %Y'`
+AC_SUBST([MANPAGE_DATE])
+
+AC_PROG_MAKE_SET
+
+ACX_CHECK_PYTHON([2.7])
+
+AS_AC_EXPAND(DATADIR, $datadir)
+AS_AC_EXPAND(DATADIR, $DATADIR)  # Called a second time to expand ${prefix}
+AC_SUBST([DATADIR])
+
+# FIXME
+#AC_CONFIG_FILES([
+#                 po/Makefile \
+#                 doc/eyeD3.1])
+AC_CONFIG_FILES([Makefile])
+AC_CONFIG_FILES([src/eyed3/info.py])
+AC_OUTPUT
+
+# FIXME
+#if test -f README.t2t.in; then
+#    AC_CONFIG_FILES([README.t2t])
+#    export PYTHONPATH="`pwd`/src"
+#    EYED3_HELP="`./bin/eyeD3 --help`"
+#    AC_SUBST([EYED3_HELP])
+#    AC_OUTPUT
+#fi
+
+#!/bin/bash
+
+_ENV=${1:-eyeD3}
+
+source /usr/bin/virtualenvwrapper.sh
+
+mkvirtualenv -a $(pwd) --python=python2.7 --distribute \
+             -i nose -i coverage -i sphinx -i sphinxcontrib-bitbucket \
+             -i sphinxcontrib-doxylink -i pylint -i pyflakes \
+             ${_ENV}
+workon $_ENV
+
+cat /dev/null >| $VIRTUAL_ENV/bin/postactivate
+echo "alias cd-top=\"cd $PWD\"" >> $VIRTUAL_ENV/bin/postactivate
+echo "export PATH=\"$PWD/bin:$PATH\"" >> $VIRTUAL_ENV/bin/postactivate
+echo "export PYTHONPATH=\"$PWD/src\"" >> $VIRTUAL_ENV/bin/postactivate
+
+cat /dev/null >| $VIRTUAL_ENV/bin/postdeactivate
+echo "unalias cd-top" >> $VIRTUAL_ENV/bin/postdeactivate
+# The changes to PATH are handled by normal deactivate
+# Changes to PYTHONPATH are not undone, yet.
+
+unset _ENV
+# -*- coding: utf-8 -*-
+################################################################################
+#  Copyright (C) 2011  Travis Shirk <travis@pobox.com>
+#
+#  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 setuptools import setup, find_packages
+
+version = open("version", "r").read()
+license = open("COPYING", "r").read()
+
+setup(
+  name="eyeD3",
+  url="http://eyeD3.nicfit.net/",
+  version=version,
+  description="ID3 tools",
+  author="Travis Shirk",
+  author_email="travis@pobox.com",
+  maintainer="Travis Shirk",
+  maintainer_email="travis@pobox.com",
+  license=license,
+  packages=["eyed3",
+            "eyed3.utils",
+            "eyed3.plugins",
+            "eyed3.id3",
+            "eyed3.mp3",
+           ],
+  package_dir={"eyed3": "src/eyed3",
+               "eyed3.utils": "src/eyed3/utils",
+               "eyed3.plugins": "src/eyed3/plugins",
+               "eyed3.id3": "src/eyed3/id3",
+               "eyed3.mp3": "src/eyed3/mp3",
+              },
+  long_description="""
+eyeD3 is a Python module and command line 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.
+  """,
+)

src/eyed3/__init__.py

+################################################################################
+#  Copyright (C) 2002-2011  Travis Shirk <travis@pobox.com>
+#
+#  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, locale, exceptions
+
+
+_DEFAULT_ENCODING = "latin1"
+
+LOCAL_ENCODING = locale.getpreferredencoding(do_setlocale=True)
+'''The local encoding, default is latin1 if it cannot be determined.'''
+if not LOCAL_ENCODING or LOCAL_ENCODING == "ANSI_X3.4-1968":  # pragma: no cover
+    LOCAL_ENCODING = _DEFAULT_ENCODING
+
+LOCAL_FS_ENCODING = sys.getfilesystemencoding()
+'''The local file system encoding, default is latin1 if it cannot be determined.
+'''
+if not LOCAL_FS_ENCODING:  # pragma: no cover
+    LOCAL_FS_ENCODING = _DEFAULT_ENCODING
+
+
+class Exception(exceptions.Exception):
+    '''Base exception type for all eyed3 exceptions.'''
+    def __init__(self, *args):
+        super(Exception, self).__init__(*args)
+        if args:
+            # The base class will do exactly this if len(args) == 1,
+            # but not when > 1.
+            self.message = args[0]
+
+
+def require(version_spec):
+    '''Check for a specific version of eyeD3.
+    Returns ``None`` when the loaded version of ``eyed3`` is <= ``version_spec``
+    and raises a ``eyed3.Exception`` otherwise. ``version_spec`` may be a string
+    or int tuple. In either case at least **2** version values must be
+    specified. For example, "0.7", (0,7,1), etc.
+    '''
+    import types
+    from .info import VERSION_TUPLE as CURRENT_VERSION
+    def t2s(_t):
+        return ".".join([str(v) for v in _t])
+
+    req_version = None
+    if type(version_spec) in types.StringTypes:
+        req_version = tuple((int(v) for v in version_spec.split(".")))
+    else:
+        req_version = tuple(version_spec)
+
+    if len(req_version) < 2:
+        raise ValueError("At least 2 version values are required")
+    elif len(req_version) < 3:
+        # Pad with 0(s)
+        req_version += (tuple([0]) * (3 - len(req_version)))
+
+    # API compatibility is on major minor, so if the current version is greater
+    # than either of these the 'require' will fail.
+    for i in 0, 1:
+        if CURRENT_VERSION[i] > req_version[i]:
+            raise Exception("eyeD3 v%s not compatible with v%s (required)" %
+                            (t2s(CURRENT_VERSION), t2s(req_version)))
+
+    # Is the required version greater than us
+    if req_version > CURRENT_VERSION:
+        raise Exception("eyed3 v%s < v%s (required)" %
+                        (t2s(CURRENT_VERSION), t2s(req_version)))
+
+
+from .utils.log import log
+from .core import load
+
+del sys
+del exceptions
+del locale

src/eyed3/binfuncs.py

+################################################################################
+#  Copyright (C) 2001  Ryan Finne <ryan@finnie.org>
+#  Copyright (C) 2002-2011  Travis Shirk <travis@pobox.com>
+#
+#  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 order.
+#
+# \param bytes String of characters to convert.
+# \param sz An optional max size for each byte (default 8 bits/byte) which can
+#           be used to mask out higher bits.
+def bytes2bin(bytes, sz=8):
+    if sz < 1 or sz > 8:
+       raise ValueError("Invalid sz value: %d" % sz)
+
+    '''
+    # I was willing to bet this implementation was gonna be faster, tis not
+    retval = []
+    for bite in bytes:
+        bits = [int(b) for b in bin(ord(bite))[2:].zfill(8)][-sz:]
+        assert(len(bits) == sz)
+        retval.extend(bits)
+    return retval
+    '''
+
+    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)
+
+    return retVal
+
+
+## Convert an 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()  # MSB
+
+    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):
+    n = bin2dec(x)
+    if len(x) > 32 or n > 268435456:   # 2^28
+        raise ValueError("Invalid value: %s" % str(x))
+    elif len(x) < 8:
+        return 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)
+    assert(len(bits) == 32)
+
+    return bits
+
+def bytes2str(bites):
+    s = bytes("")
+    for b in bites:
+        s += ("\\x%02x" % ord(b))
+    return s
+
+

src/eyed3/core.py

+# -*- coding: utf-8 -*-
+################################################################################
+#  Copyright (C) 2012  Travis Shirk <travis@pobox.com>
+#
+#  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, time
+from . import Exception, LOCAL_FS_ENCODING
+from .utils import guessMimetype
+
+import logging
+log = logging.getLogger(__name__)
+
+
+## Supported audio formats
+AUDIO_TYPES = (AUDIO_NONE, AUDIO_MP3) = range(2)
+
+
+def load(path, tag_version=None):
+    from . import mp3, id3
+    log.info("Loading file: %s" % path)
+
+    if os.path.exists(path):
+        if not os.path.isfile(path):
+            raise IOError("not a file: %s" % path)
+    else:
+        raise IOError("file not found: %s" % path)
+
+    mtype = guessMimetype(path)
+
+    if mtype in mp3.MIME_TYPES:
+        return mp3.Mp3AudioFile(path, tag_version)
+
+    if mtype == "application/x-id3":
+        return id3.TagFile(path, tag_version)
+
+    return None
+
+
+##
+# An abstract container for audio meta data.
+class AudioInfo(object):
+    ## The number of seconds of audio data (i.e., the playtime)
+    time_secs  = 0
+    ## The number of bytes of audio data.
+    size_bytes = 0
+
+
+##
+# An abstract interface around audio tag data (artist, title, etc.)
+class Tag(object):
+
+    def _setArtist(self, val):
+        raise NotImplementedError
+    def _getArtist(self):
+        raise NotImplementedError
+
+    def _setAlbum(self, val):
+        raise NotImplementedError
+    def _getAlbum(self):
+        raise NotImplementedError
+
+    def _setTitle(self, val):
+        raise NotImplementedError
+    def _getTitle(self):
+        raise NotImplementedError
+
+    def _setTrackNum(self, val):
+        raise NotImplementedError
+    def _getTrackNum(self):
+        raise NotImplementedError
+
+    @property
+    def artist(self):
+        return self._getArtist()
+    @artist.setter
+    def artist(self, v):
+        self._setArtist(v)
+
+    @property
+    def album(self):
+        return self._getAlbum()
+    @album.setter
+    def album(self, v):
+        self._setAlbum(v)
+
+    @property
+    def title(self):
+        return self._getTitle()
+    @title.setter
+    def title(self, v):
+        self._setTitle(v)
+
+    @property
+    def track_num(self):
+        return self._getTrackNum()
+    @track_num.setter
+    def track_num(self, v):
+        self._setTrackNum(v)
+
+
+##
+# An abstract base class for audio file (AudioInfo + Tag)
+class AudioFile(object):
+
+    ##
+    # Subclasses MUST override this method and set \c self._info,
+    # \c self._tag and \c self.type. These values are accessed from
+    # the properties \c info, \c tag, and \c type, respectively
+    def _read(self):
+        raise NotImplementedError()
+
+    ##
+    # Rename the file to \a name.
+    # \param name The new file name.
+    # \param fsencoding Optional explicit file name encoding. By default,
+    #                   the detected file system encoding is used.
+    def rename(self, name, fsencoding=LOCAL_FS_ENCODING):
+        import os
+        base = os.path.basename(self.path)
+        base_ext = os.path.splitext(base)[1]
+        dir = os.path.dirname(self.path)
+        if not dir:
+            dir = '.'
+        new_name = "%s%s" % (os.path.join(dir.encode(fsencoding),
+                                          name.encode(fsencoding)),
+                             base_ext)
+        # FIXME: protections against wrecking data
+        os.rename(self.path, new_name)
+        self.path = new_name
+
+    ## AudioFile.path property accessor.
+    @property
+    def path(self):
+        return self._path
+    @path.setter
+    def path(self, t):
+        from os.path import abspath, realpath, normpath
+        self._path = normpath(realpath(abspath(t)))
+    ## AudioFile.info property accessor.
+    @property
+    def info(self):
+        return self._info
+    ## AudioFile.tag property accessor.
+    @property
+    def tag(self):
+        return self._tag
+    @tag.setter
+    def tag(self, t):
+        self._tag = t
+
+    ##
+    # Constructor.
+    # \param path The path to the audio file.
+    def __init__(self, path):
+        self.path = path
+
+        self.type = None
+        self._info = None
+        self._tag = None
+        self._read()
+
+
+class Date(object):
+    ## Valid time stamp formats per ISO 8601 and used by \c strptime.
+    TIME_STAMP_FORMATS = ["%Y",
+                          "%Y-%m",
+                          "%Y-%m-%d",
+                          "%Y-%m-%dT%H",
+                          "%Y-%m-%dT%H:%M",
+                          "%Y-%m-%dT%H:%M:%S",
+                          # The following are wrong per the specs, but ...
+                          "%Y-%m-%d %H:%M:%S",
+                          "%Y-00-00",
+                         ]
+    # TODO: 8601 allows for non-hyphenated versions
+
+    def __init__(self, year, month=None, day=None,
+                 hour=None, minute=None, second=None):
+        # Validate with datetime
+        from datetime import datetime
+        _ = datetime(year, month if month is not None else 1,
+                     day if day is not None else 1,
+                     hour if hour is not None else 0,
+                     minute if minute is not None else 0,
+                     second if second is not None else 0)
+
+        self._year = year
+        self._month = month
+        self._day = day
+        self._hour = hour
+        self._minute = minute
+        self._second = second
+
+        # Python's date classes do a lot more date validation than does not
+        # need to be duplicated here.  Validate it
+        _ = Date._validateFormat(str(self))
+
+    @property
+    def year(self):
+        return self._year
+    @property
+    def month(self):
+        return self._month
+    @property
+    def day(self):
+        return self._day
+    @property
+    def hour(self):
+        return self._hour
+    @property
+    def minute(self):
+        return self._minute
+    @property
+    def second(self):
+        return self._second
+
+    def __eq__(self, rhs):
+        return (self.year == rhs.year and
+                self.month == rhs.month and
+                self.day == rhs.day and
+                self.hour == rhs.hour and
+                self.minute == rhs.minute and
+                self.second == rhs.second)
+
+    @staticmethod
+    def _validateFormat(s):
+        pdate = None
+        for fmt in Date.TIME_STAMP_FORMATS:
+            try:
+                pdate = time.strptime(s, fmt)
+                break
+            except ValueError:
+                # date string did not match format.
+                continue
+
+        if pdate is None:
+            raise ValueError("Invalid date string: %s" % s)
+
+        assert(pdate)
+        return pdate, fmt
+
+    @staticmethod
+    def parse(s):
+        s = s.strip('\x00')
+
+        pdate, fmt = Date._validateFormat(s)
+
+        # Here is the difference with Python date/datetime objects, some
+        # of the members can be None
+        kwargs = {}
+        if "%m" in fmt:
+            kwargs["month"] = pdate.tm_mon
+        if "%d" in fmt:
+            kwargs["day"] = pdate.tm_mday
+        if "%H" in fmt:
+            kwargs["hour"] = pdate.tm_hour
+        if "%M" in fmt:
+            kwargs["minute"] = pdate.tm_min
+        if "%S" in fmt:
+            kwargs["second"] = pdate.tm_sec
+
+        return Date(pdate.tm_year, **kwargs)
+
+    def __str__(self):
+        s = "%d" % self.year
+        if self.month:
+            s += "-%s" % str(self.month).rjust(2, '0')
+            if self.day:
+                s += "-%s" % str(self.day).rjust(2, '0')
+                if self.hour is not None:
+                    s += "T%s" % str(self.hour).rjust(2, '0')
+                    if self.minute is not None:
+                        s += ":%s" % str(self.minute).rjust(2, '0')
+                        if self.second is not None:
+                            s += ":%s" % str(self.second).rjust(2, '0')
+        return s
+
+    def __unicode__(self):
+        return unicode(str(self), "latin1")
+
+
+

src/eyed3/id3/__init__.py

+################################################################################
+#  Copyright (C) 2002-2012  Travis Shirk <travis@pobox.com>
+#
+#  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 string, re, types
+import logging
+log = logging.getLogger(__name__)
+
+from ..utils import requireUnicode
+
+# Version constants and helpers
+ID3_V1              = (1, None, None)
+ID3_V1_0            = (1, 0, 0)
+ID3_V1_1            = (1, 1, 0)
+ID3_V2              = (2, None, None)
+ID3_V2_2            = (2, 2, 0)
+ID3_V2_3            = (2, 3, 0)
+ID3_V2_4            = (2, 4, 0)
+ID3_DEFAULT_VERSION = ID3_V2_4
+ID3_ANY_VERSION     = (ID3_V1[0] | ID3_V2[0], None, None)
+
+LATIN1_ENCODING   = b"\x00"
+UTF_16_ENCODING   = b"\x01"
+UTF_16BE_ENCODING = b"\x02"
+UTF_8_ENCODING    = b"\x03"
+
+DEFAULT_LANG = "eng"
+
+def isValidVersion(v, fully_qualified=False):
+    valid = v in [ID3_V1, ID3_V1_0, ID3_V1_1,
+                  ID3_V2, ID3_V2_2, ID3_V2_3, ID3_V2_4,
+                  ID3_ANY_VERSION]
+    if not valid:
+        return False
+
+    if fully_qualified:
+        return (None not in (v[0], v[1], v[2]))
+    else:
+        return True
+
+
+def normalizeVersion(v):
+    if v == ID3_V1:
+        v = ID3_V1_1
+    elif v == ID3_V2:
+        assert(ID3_DEFAULT_VERSION[0] & ID3_V2[0])
+        v = ID3_DEFAULT_VERSION
+    elif v == ID3_ANY_VERSION:
+        v = ID3_DEFAULT_VERSION
+
+    # Now, correct bogus version as seen in the wild
+    if v[:2] == (2, 2) and v[2] != 0:
+        v = (2, 2, 0)
+
+    return v
+
+
+## Convert an ID3 version constant to a display string
+def versionToString(v):
+    if v == ID3_ANY_VERSION:
+       return "v1.x/v2.x"
+    elif v[0] == 1:
+       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[0] == 2:
+       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"
+    raise ValueError("Invalid ID3 version constant: %s" % str(v))
+
+
+from .. import Exception as BaseException
+class GenreException(BaseException):
+    '''Problem looking up genre'''
+
+##
+# A class containing genre information including the name and/or a numeric ID.
+class Genre(object):
+
+    @requireUnicode("name")
+    def __init__(self, name=None, id=None):
+        self.id, self.name = None, None
+        if not name and id is None:
+            return
+
+        # An ID always takes precedence
+        if id is not None:
+            try:
+                self.id = id
+                # valid id will set name
+                assert(self.name)
+                if name and name != self.name:
+                    log.warning("Genre ID takes precedence and remapped "
+                                "'%s' to '%s'" % (name, self.name))
+            except ValueError:
+                log.warning("Invalid numeric genre ID: %d" % id)
+                if not name:
+                    # Gave an invalid ID and no name to fallback on
+                    raise
+                self.name = name
+                self.id = None
+        else:
+            # All we have is a name
+            self.name = name
+
+        assert(self.id or self.name)
+
+
+    @property
+    def id(self):
+        return self._id
+
+    ##
+    # Sets the genre id. The object'ss name field is set to the corresponding
+    # value obtained from eyeD3.id3.genres.
+    #
+    # \param id The genre ID.
+    # \throws GenreException when \a id does not map to a valid ID3 genre.
+    @id.setter
+    def id(self, val):
+        global genres
+
+        if val is None:
+            self._id = None
+            return
+
+        val = int(val)
+        if val not in genres.keys():
+            raise ValueError("Invalid numeric genre ID: %d" % val)
+
+        name = genres[val]
+        self._id = val
+        self._name = name
+
+    @property
+    def name(self):
+        return self._name
+
+    ##
+    # Sets the genre name. The object'ss id field is set to the corresponding
+    # value obtained from \c eyeD3.id3.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.
+    @name.setter
+    @requireUnicode(1)
+    def name(self, val):
+        global genres
+        if val is None:
+            self._name = None
+            return
+
+        if val.lower() in genres.keys():
+            self._id = genres[val]
+            # normalize the name
+            self._name = genres[self._id]
+        else:
+            log.warning("Non standard genre name: %s" % val)
+            self._id = None
+            self._name = val
+
+    ##
+    # Parses genre information from \a genre_str. 
+    # The following formats are supported:
+    # 01, 2, 23, 125 - ID3 v1.x style.
+    # (01), (2), (129)Hardcore, (9)Metal, Indie - ID3 v2 style with and without
+    #                                             refinement.
+    #
+    # \throws GenreException when an invalid string is passed.
+    @staticmethod
+    @requireUnicode(1)
+    def parse(g_str):
+        g_str = g_str.strip()
+        if not g_str:
+            return None
+
+        def strip0Padding(s):
+            if len(s) > 1:
+                return s.lstrip("0")
+            else:
+                return s
+
+        # ID3 v1 style.
+        # Match 03, 34, 129.
+        regex = re.compile("[0-9][0-9]*$")
+        if regex.match(g_str):
+            return Genre(id=int(strip0Padding(g_str)))
+
+        # ID3 v2 style.
+        # Match (03), (0)Blues, (15) Rap
+        regex = re.compile("\(([0-9][0-9]*)\)(.*)$")
+        m = regex.match(g_str)
+        if m:
+            (id, name) = m.groups()
+
+            id = int(strip0Padding(id))
+            if id and name:
+                id = id
+                name = name.strip()
+            else:
+                id = id
+                name = None
+
+            return Genre(id=id, name=name)
+
+        # Let everything else slide, genres suck anyway
+        return Genre(id=None, name=g_str)
+
+    def __unicode__(self):
+        s = u""
+        if self.id != None:
+           s += u"(%d)" % self.id
+        if self.name:
+           s += self.name
+        return s
+
+    def __eq__(self, rhs):
+        return self.id == rhs.id and self.name == rhs.name
+
+    def __ne__(self, rhs):
+        return not self.__eq__(rhs)
+
+
+class GenreMap(dict):
+    '''Classic genres defined around ID3 v1 but suitable anywhere.  This class
+    is used primarily as a way to map numeric genre values to a string name.
+    Genre strings on the other hand are not required to exist in this list.
+    '''
+    GENRE_MIN = 0
+    GENRE_MAX = None
+    ID3_GENRE_MIN = 0
+    ID3_GENRE_MAX = 79
+    WINAMP_GENRE_MIN = 80
+    WINAMP_GENRE_MAX = 147
+
+    def __init__(self, *args):
+        global ID3_GENRES
+        super(GenreMap, self).__init__(*args)
+
+        # ID3 genres as defined by the v1.1 spec with WinAmp extensions.
+        for i, g in enumerate(ID3_GENRES):
+            self[i] = g
+            self[g.lower()] = i
+
+        GenreMap.GENRE_MAX = len(ID3_GENRES) - 1
+        # Pad up to 255
+        for i in range(GenreMap.GENRE_MAX + 1, 255 + 1):
+            self[i] = u"<not-set>"
+        self[u"<not-set>".lower()] = 255
+
+
+    def __getitem__(self, key):
+        if type(key) is not int:
+            key = key.lower()
+        return super(GenreMap, self).__getitem__(key)
+
+STRICT_ID3 = False
+def strict_id3():
+   return STRICT_ID3
+
+
+from .. import core
+class TagFile(core.AudioFile):
+    '''
+    A shim class for dealing with files that contain only ID3 data, no audio.
+    '''
+    def __init__(self, path, version=ID3_ANY_VERSION):
+        self._tag_version = version
+        core.AudioFile.__init__(self, path)
+        assert(self.type == core.AUDIO_NONE)
+
+    def _read(self):
+        from .tag import Tag
+
+        with file(self.path, 'rb') as file_obj:
+            tag = Tag()
+            tag_found = tag.parse(file_obj, self._tag_version)
+            self._tag = tag if tag_found else None
+
+        self.type = core.AUDIO_NONE
+
+
+ID3_GENRES = [
+u'Blues',
+u'Classic Rock',
+u'Country',
+u'Dance',
+u'Disco',
+u'Funk',
+u'Grunge',
+u'Hip-Hop',
+u'Jazz',
+u'Metal',
+u'New Age',
+u'Oldies',
+u'Other',
+u'Pop',
+u'R&B',
+u'Rap',
+u'Reggae',
+u'Rock',
+u'Techno',
+u'Industrial',
+u'Alternative',
+u'Ska',
+u'Death Metal',
+u'Pranks',
+u'Soundtrack',
+u'Euro-Techno',
+u'Ambient',
+u'Trip-Hop',
+u'Vocal',
+u'Jazz+Funk',
+u'Fusion',
+u'Trance',
+u'Classical',
+u'Instrumental',
+u'Acid',
+u'House',
+u'Game',
+u'Sound Clip',
+u'Gospel',
+u'Noise',
+u'AlternRock',
+u'Bass',
+u'Soul',
+u'Punk',
+u'Space',
+u'Meditative',
+u'Instrumental Pop',
+u'Instrumental Rock',
+u'Ethnic',
+u'Gothic',
+u'Darkwave',
+u'Techno-Industrial',
+u'Electronic',
+u'Pop-Folk',
+u'Eurodance',
+u'Dream',
+u'Southern Rock',
+u'Comedy',
+u'Cult',
+u'Gangsta Rap',
+u'Top 40',
+u'Christian Rap',
+u'Pop / Funk',
+u'Jungle',
+u'Native American',
+u'Cabaret',
+u'New Wave',
+u'Psychedelic',
+u'Rave',
+u'Showtunes',
+u'Trailer',
+u'Lo-Fi',
+u'Tribal',
+u'Acid Punk',
+u'Acid Jazz',
+u'Polka',
+u'Retro',
+u'Musical',
+u'Rock & Roll',
+u'Hard Rock',
+u'Folk',
+u'Folk-Rock',
+u'National Folk',
+u'Swing',
+u'Fast  Fusion',
+u'Bebob',
+u'Latin',
+u'Revival',
+u'Celtic',
+u'Bluegrass',
+u'Avantgarde',
+u'Gothic Rock',
+u'Progressive Rock',
+u'Psychedelic Rock',
+u'Symphonic Rock',
+u'Slow Rock',
+u'Big Band',
+u'Chorus',
+u'Easy Listening',
+u'Acoustic',
+u'Humour',
+u'Speech',
+u'Chanson',
+u'Opera',
+u'Chamber Music',
+u'Sonata',
+u'Symphony',
+u'Booty Bass',
+u'Primus',
+u'Porn Groove',
+u'Satire',
+u'Slow Jam',
+u'Club',
+u'Tango',
+u'Samba',
+u'Folklore',
+u'Ballad',
+u'Power Ballad',
+u'Rhythmic Soul',
+u'Freestyle',
+u'Duet',
+u'Punk Rock',
+u'Drum Solo',
+u'A Cappella',
+u'Euro-House',
+u'Dance Hall',
+u'Goa',
+u'Drum & Bass',
+u'Club-House',
+u'Hardcore',
+u'Terror',
+u'Indie',
+u'BritPop',
+u'Negerpunk',
+u'Polsk Punk',
+u'Beat',
+u'Christian Gangsta Rap',
+u'Heavy Metal',
+u'Black Metal',
+u'Crossover',
+u'Contemporary Christian',
+u'Christian Rock',
+u'Merengue',
+u'Salsa',
+u'Thrash Metal',
+u'Anime',
+u'JPop',
+u'Synthpop',
+u'Rock/Pop',
+]
+
+from .tag import Tag, FileInfo, TagException, TagTemplate
+genres = GenreMap()
+
+import frames

src/eyed3/id3/frames.py

+# -*- coding: utf-8 -*-
+################################################################################
+#  Copyright (C) 2012  Travis Shirk <travis@pobox.com>
+#
+#  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
+from cStringIO import StringIO
+
+from .. import core
+from ..utils import requireUnicode
+from ..binfuncs import (bytes2dec, dec2bytes, bin2bytes, dec2bin, bytes2bin,
+                        bin2dec)
+from . import ID3_V2, ID3_V2_3, ID3_V2_4
+from . import (LATIN1_ENCODING, UTF_8_ENCODING, UTF_16BE_ENCODING,
+               UTF_16_ENCODING, DEFAULT_LANG)
+from . import strict_id3
+from .headers import FrameHeader
+
+import logging
+log = logging.getLogger(__name__)
+
+from .. import Exception as BaseException
+class FrameException(BaseException):
+    pass
+
+
+TITLE_FID          = "TIT2"
+ARTIST_FID         = "TPE1"
+ALBUM_FID          = "TALB"
+TRACKNUM_FID       = "TRCK"
+GENRE_FID          = "TCON"
+COMMENT_FID        = "COMM"
+USERTEXT_FID       = "TXXX"
+OBJECT_FID         = "GEOB"
+UNIQUE_FILE_ID_FID = "UFID"
+LYRICS_FID         = "USLT"
+DISCNUM_FID        = "TPOS"
+IMAGE_FID          = "APIC"
+USERURL_FID        = "WXXX"
+PLAYCOUNT_FID      = "PCNT"
+BPM_FID            = "TBPM"
+PUBLISHER_FID      = "TPUB"
+CDID_FID           = "MCDI"
+PRIVATE_FID        = "PRIV"
+TOS_FID            = "USER"
+
+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]
+
+DEPRECATED_DATE_FIDS = ["TDAT", "TYER", "TIME", "TORY", "TRDA",
+                        # Nonstandard v2.3 only
+                        "XDOR",
+                       ]
+DATE_FIDS = ["TDEN", "TDOR", "TDRC", "TDRL", "TDTG"]
+
+
+class Frame(object):
+    def __init__(self, id, unsync_default=False):
+        self.id = id
+        self.header = None
+        self.unsync_default = unsync_default
+
+        self.decompressed_size = 0
+        self.group_id = None
+        self.encrypt_method = None
+        self.data = None
+        self.data_len = 0
+        self.encoding = None
+
+    def parse(self, data, frame_header):
+        self.id = frame_header.id
+        self.header = frame_header
+        self.data = self._disassembleFrame(data)
+
+    def render(self):
+        return self._assembleFrame(self.data)
+
+    @staticmethod
+    def unsync(data):
+        return unsyncData(data)
+
+    @staticmethod
+    def deunsync(data):
+        return deunsyncData(data)
+
+    @staticmethod
+    def decompress(data):
+        import zlib
+        log.debug("before decompression: %d bytes" % len(data))
+        data = zlib.decompress(data, 15)
+        log.debug("after decompression: %d bytes" % len(data))