Commits

Travis Shirk committed 5414ac5 Merge

merge eyeD3 0.7.3

  • Participants
  • Parent commits be4c327, e6e74ff

Comments (0)

Files changed (32)

 dcb1afe9d3532fb9c0cb7da33fc0fb6e0b71462e v0.7.0
 e19be93ecd993683630626d4044eccfc34420acc v0.7.1
 9103271cc1db6ecaade20c3e6a523227151645b2 v0.7.2
+b8c6cb2b7ff4608bc2540a7109823d2ddc0c93dd v0.7.3
+2013-07-12  Travis Shirk  <travis@pobox.com>
+
+	* docs/changelog.rst:
+	prep for release
+	[5c7cb224fc9b] [tip] <stable>
+
+	* ChangeLog, docs/changelog.rst:
+	Updated
+	[6e1a16b9eaaf] <stable>
+
+2013-07-11  Travis Shirk  <travis@pobox.com>
+
+	* pavement.py:
+	Workaround for paver bug https://github.com/paver/paver/issues/112
+	[475ea1323115] <stable>
+
+	* src/eyed3/utils/binfuncs.py:
+	Removed unused
+	[3f0b9556a569] <stable>
+
+	* src/eyed3/compat.py, src/eyed3/id3/frames.py,
+	src/eyed3/id3/headers.py, src/eyed3/main.py,
+	src/eyed3/plugins/__init__.py, src/eyed3/plugins/classic.py,
+	src/eyed3/utils/__init__.py, src/eyed3/utils/binfuncs.py,
+	src/test/id3/test_frames.py, src/test/id3/test_id3.py,
+	src/test/id3/test_tag.py, src/test/test_binfuncs.py:
+	More python3'ish
+	[8ca806d3665b] <stable>
+
+2013-07-07  Travis Shirk  <travis@pobox.com>
+
+	* src/eyed3/__init__.py, src/eyed3/compat.py,
+	src/eyed3/id3/__init__.py, src/eyed3/id3/frames.py,
+	src/eyed3/id3/tag.py, src/eyed3/mp3/__init__.py,
+	src/test/test__init__.py:
+	eyed3.Exception -> eyed3.Error, in the name of python3 (which
+	removed exceptions module)
+	[cac2ed5e93dd] <stable>
+
+	* examples/chapters.py, pavement.py, src/eyed3/compat.py,
+	src/eyed3/core.py, src/eyed3/id3/frames.py, src/eyed3/id3/tag.py,
+	src/eyed3/main.py, src/eyed3/plugins/__init__.py,
+	src/eyed3/utils/__init__.py, src/test/__init__.py,
+	src/test/id3/test_frames.py, src/test/id3/test_headers.py,
+	src/test/mp3/test_mp3.py, src/test/test_main.py:
+	python3
+	[42254405a856] <stable>
+
+	* src/eyed3/plugins/statistics.py:
+	python 3
+	[3569727a47a8] <stable>
+
+	* src/eyed3/__init__.py, src/eyed3/id3/frames.py,
+	src/eyed3/plugins/lameinfo.py, src/test/mp3/test_infos.py,
+	src/test/test_plugins.py:
+	python3 fixes
+	[9ab02b198014] <stable>
+
+	* src/eyed3/compat.py:
+	Added compat module for python3 support
+	[a0196b6fa47c] <stable>
+
+	* pavement.py:
+	run2to3 can use python-modernize
+	[7fb3bb59fbc7] <stable>
+
+	* pavement.py:
+	better clean, 2to3 beginnings
+	[06c6cff5d168] <stable>
+
+	* mkenv.sh:
+	Python 3 compat, undo PYTHONPATH in postdeactivate.
+	[9c437dee126b] <stable>
+
+	* src/eyed3/plugins/statistics.py:
+	Honor quiet and print unicode correctly.
+	[b9736680f6c7] <stable>
+
 2013-07-06  Travis Shirk  <travis@pobox.com>
 
+	* src/eyed3/plugins/__init__.py:
+	Stash the ArgumentParser in Plugin
+	[185d003729e6] <stable>
+
+	* pavement.py:
+	bump
+	[1a9475ef1d47] <stable>
+
+	* src/eyed3/plugins/statistics.py:
+	Don't compute stats when no files processed.
+	[018168c2abd6] <stable>
+
+	* src/eyed3/main.py:
+	fix --no-color help info
+	[3b868c7987b2] <stable>
+
+	* pavement.py:
+	Removed checklist
+	[50cff50f9c3c] <stable>
+
+	* .hgtags:
+	Added tag v0.7.2 for changeset 9103271cc1db
+	[3268e7b367d7] <stable>
+
+	* ChangeLog:
+	prep for release
+	[9103271cc1db] [v0.7.2] <stable>
+
 	* ChangeLog, docs/changelog.rst, pavement.py:
 	Prep for release
-	[abdd0a16e839] [tip] <stable>
+	[abdd0a16e839] <stable>
 
 	* docs/changelog.rst:
 	Updated

File docs/changelog.rst

 ChangeLog
 #########
 
+.. _release-0.7.3:
+
+**0.7.3** - 07.12.2013 (Harder They Fall)
+  Bug fixes:
+    * Allow setup.py to run with having ``paver`` installed.
+    * [statistics plgin] Don't crash when 0 files are processed.
+
 .. _release-0.7.2:
 
 **0.7.2** - 07.06.2013 (Nevertheless)

File examples/chapters.py

     # Start and end offset - tuple. None is used to set to "no offset"
     print("-- Start offset: %s; End offset: %s" %
           tuple((str(o) for o in chapter.offsets)))
-    print("-- Sub frames:", str(chapter.sub_frames.keys()))
+    print("-- Sub frames:", str(list(chapter.sub_frames.keys())))
 
 tag = Tag()
 if len(sys.argv) > 1:
 #!/bin/bash
 
 _PYTHON=${2:-python2.7}
-_PYTHON_VERSION=$($_PYTHON -c 'import sys; print ".".join([str(v) for v in sys.version_info[:2]])')
+_PYTHON_VERSION=$($_PYTHON -c 'import sys; print(".".join([str(v) for v in sys.version_info[:2]]))')
 _ENV=${1:-eyeD3-${_PYTHON_VERSION}}
 
 source /usr/bin/virtualenvwrapper.sh
 mkvirtualenv -a $(pwd) --python=${_PYTHON} --distribute ${_ENV}
 workon $_ENV
 
-# FIXME
 PKGS_OPTS=
 if test -d ./.pip-download-cache; then
     export PIP_DOWNLOAD_CACHE=./.pip-download-cache    
 
 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.
+echo "unset PYTHONPATH" >> $VIRTUAL_ENV/bin/postdeactivate
     paverutils = None
 
 PROJECT = u"eyeD3"
-VERSION = "0.7.2"
+VERSION = "0.7.3"
 
 LICENSE = open("COPYING", "r").read().strip('\n')
 DESCRIPTION = "Python audio data toolkit (ID3 and MP3)"
 
 options(
     minilib=Bunch(
-        extra_files=['doctools']
+        # XXX: the explicit inclusion of 'version' is a workaround for:
+        # https://github.com/paver/paver/issues/112
+        extra_files=['doctools', "version"]
     ),
     setup=Bunch(
         name=PROJECT, version=VERSION,
     release=Bunch(
         test=False,
     ),
+
+    run2to3=Bunch(
+        modernize=False,
+    ),
 )
 
 
     '''Cleans mostly everything'''
     path("build").rmtree()
 
-    for d in [path("./src")]:
+    for p in path(".").glob("*.pyc"):
+        p.remove()
+    for d in [path("./src"), path("./examples")]:
         for f in d.walk(pattern="*.pyc"):
             f.remove()
     try:
 
 
 @task
-def checklist():
-    '''Show release procedure'''
-    print("""
-Release TODO
-=============
-
-# Publish
-- Update eyeD3.nicfit.net
-  - fab -H melvins deploy
-- Update Python package index (PKG-INFO)
-- Announce to mailing list
-- Announce to FreshMeat
-
-# Merge to default
-- hg up default
-- hg merge stable
-
-- ebuild
-""" % globals())
-
-
-@task
 @cmdopts([("test", "",
            u"Run in a mode where commits, pushes, etc. are performed"),
          ])
     if prompt("Push for release?") and not testing:
         sh("hg push --rev .")
 
-    checklist()
-
 
 def prompt(prompt):
     print(prompt + ' ', end='')
 def cog(options):
     '''Run cog on all docs'''
     _runcog(options)
+
+
+@task
+@cmdopts([("modernize", "",
+           u"Run with 'python-modernize' instead of 2to3"),
+         ])
+def run2to3(options):
+    cmd = "2to3-3.3" if not options.run2to3.modernize else "python-modernize"
+    common_opts = "-p -x unicode -x future"
+    cmd_opts = "" if not options.run2to3.modernize else "--no-six"
+    paths = "./src ./examples"
+
+    sh("%(cmd)s %(common_opts)s %(cmd_opts)s %(paths)s >| %(cmd)s.patch" %
+       locals())

File src/eyed3/__init__.py

 '''Top-level module.'''
 import sys
 import locale
-import exceptions
+from .compat import StringTypes
 
 
 _DEFAULT_ENCODING = "latin1"
     LOCAL_FS_ENCODING = _DEFAULT_ENCODING
 
 
-class Exception(exceptions.Exception):
-    '''Base exception type for all eyed3 exceptions.'''
+class Error(Exception):
+    '''Base exception type for all eyed3 errors.'''
     def __init__(self, *args):
-        super(Exception, self).__init__(*args)
+        super(Error, self).__init__(*args)
         if args:
             # The base class will do exactly this if len(args) == 1,
-            # but not when > 1.
+            # but not when > 1. Note, the 2.7 base class will, 3 will not.
+            # Make it so.
             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
+    and raises a ``eyed3.Error`` 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.
 
     API compatibility is currently based on major and minor version values,
     therefore neither version 0.6 or 0.8 is compatible for version 0.7.
     '''
-    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:
+    if type(version_spec) in StringTypes:
         req_version = tuple((int(v) for v in version_spec.split(".")))
     else:
         req_version = tuple(version_spec)
     # 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)))
+            raise Error("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)))
+        raise Error("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
 
 import warnings

File src/eyed3/compat.py

+# -*- coding: utf-8 -*-
+################################################################################
+#  Copyright (C) 2013  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
+#
+################################################################################
+'''
+Compatibility for various versions of Python (e.g. 2.6, 2.7, and 3.3)
+'''
+import sys
+import types
+
+
+PY2 = sys.version_info[0] == 2
+
+if PY2:
+    StringTypes = types.StringTypes
+    UnicodeType = unicode
+    BytesType = str
+    unicode = unicode
+
+    from ConfigParser import SafeConfigParser as ConfigParser
+    from ConfigParser import Error as ConfigParserError
+
+    from StringIO import StringIO
+else:
+    StringTypes = (str,)
+    UnicodeType = str
+    BytesType = bytes
+    unicode = str
+
+    from configparser import ConfigParser
+    from configparser import Error as ConfigParserError
+
+    from io import StringIO
+
+
+def toByteString(n):
+    if PY2:
+        return chr(n)
+    else:
+        return bytes((n,))
+
+
+def byteiter(bites):
+    assert(isinstance(bites, str if PY2 else bytes))
+    for b in bites:
+        yield b if PY2 else bytes((b,))

File src/eyed3/core.py

 '''Basic core types and utilities.'''
 import os
 import time
-from . import Exception, LOCAL_FS_ENCODING
+from . import LOCAL_FS_ENCODING
 from .utils import guessMimetype
 
 import logging
         The encoding used for the file name is :attr:`eyed3.LOCAL_FS_ENCODING`
         unless overridden by ``fsencoding``. Note, if the target file already
         exists, or the full path contains non-existent directories the
-        operation will fail with :class:`eyed3.Exception`.'''
+        operation will fail with :class:`IOError`.'''
         base = os.path.basename(self.path)
         base_ext = os.path.splitext(base)[1]
         dir = os.path.dirname(self.path)

File src/eyed3/id3/__init__.py

     raise ValueError("Invalid ID3 version constant: %s" % str(v))
 
 
-from .. import Exception as BaseException
-class GenreException(BaseException):
+from .. import Error
+class GenreException(Error):
     '''Excpetion type for exceptions related to genres.'''
 
 class Genre(object):
             self._name = val
 
     ##
-    # Parses genre information from \a genre_str. 
+    # 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

File src/eyed3/id3/frames.py

 #
 ################################################################################
 import re
-from cStringIO import StringIO
 from collections import namedtuple
 import logging
 
 from .. import core
 from ..utils import requireUnicode
 from ..utils.binfuncs import *
-from .. import Exception as BaseException
+from ..compat import StringIO, unicode, BytesType
+from .. import Error
 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)
 log = logging.getLogger(__name__)
 
 
-class FrameException(BaseException):
+class FrameException(Error):
     pass
 
 
             lang = DEFAULT_LANG
 
         # Test it at least looks like a valid code
-        if (lang and not re.compile("[A-Z][A-Z][A-Z]",
+        if (lang and not re.compile(b"[A-Z][A-Z][A-Z]",
                                     re.IGNORECASE).match(lang)):
             log.warning("Fixing invalid lyrics language code: %s" % lang)
             lang = DEFAULT_LANG
     @property
     def text_delim(self):
         assert(self.encoding is not None)
-        return "\x00\x00" if self.encoding in (UTF_16_ENCODING,
-                                               UTF_16BE_ENCODING) else "\x00"
+        return b"\x00\x00" if self.encoding in (UTF_16_ENCODING,
+                                                UTF_16BE_ENCODING) else b"\x00"
 
     def _initEncoding(self):
         assert(self.header.version and len(self.header.version) == 3)
 
     def render(self):
         self._initEncoding()
-        self.data = b"%s%s" % \
-                    (self.encoding,
+        self.data = (self.encoding +
                      self.text.encode(id3EncodingToString(self.encoding)))
+        assert(type(self.data) == BytesType)
         return super(TextFrame, self).render()
 
 
         self.mime_type = ""
         if frame_header.minor_version != 2:
             ch = input.read(1)
-            while ch and ch != "\x00":
+            while ch and ch != b"\x00":
                 self.mime_type += ch
                 ch = input.read(1)
         else:
         if not self.image_data and self.image_url:
             self.mime_type = self.URL_MIME_TYPE
 
-        data = (self.encoding + self.mime_type + "\x00" +
+        data = (self.encoding + self.mime_type + b"\x00" +
                 bin2bytes(dec2bin(self.picture_type, 8)) +
                 self.description.encode(id3EncodingToString(self.encoding)) +
                 self.text_delim)
         self.mime_type = ""
         if self.header.minor_version != 2:
             ch = input.read(1)
-            while ch != "\x00":
+            while ch != b"\x00":
                 self.mime_type += ch
                 ch = input.read(1)
         else:
 
     def render(self):
         self._initEncoding()
-        data = (self.encoding + self.mime_type + "\x00" +
+        data = (self.encoding + self.mime_type + b"\x00" +
                 self.filename.encode(id3EncodingToString(self.encoding)) +
                 self.text_delim +
                 self.description.encode(id3EncodingToString(self.encoding)) +
 
 
     def render(self):
-        self.data = self.owner_id + "\x00" + self.owner_data
+        self.data = self.owner_id + b"\x00" + self.owner_data
         return super(PrivateFrame, self).render()
 
 
                                            "long: %s" % self.uniq_id))
 
     def render(self):
-        self.data = self.owner_id + "\x00" + self.uniq_id
+        self.data = self.owner_id + b"\x00" + self.uniq_id
         return super(UniqueFileIDFrame, self).render()
 
 
         super(DescriptionLangTextFrame, self).parse(data, frame_header)
 
         self.encoding = encoding = self.data[0]
-        self.lang = Frame._processLang(self.data[1:4].strip("\x00"))
+        self.lang = Frame._processLang(self.data[1:4].strip(b"\x00"))
         log.debug("%s lang: %s" % (self.id, self.lang))
 
         try:
         super(TermsOfUseFrame, self).parse(data, frame_header)
 
         self.encoding = encoding = self.data[0]
-        self.lang = Frame._processLang(self.data[1:4].strip("\x00"))
+        self.lang = Frame._processLang(self.data[1:4]).strip(b"\x00")
         log.debug("%s lang: %s" % (self.id, self.lang))
         self.text = decodeUnicode(self.data[4:], encoding)
         log.debug("%s text: %s" % (self.id, self.text))
 
         # Any data remaining must be a TIT2 frame
         self.description = None
-        if data and data[:4] != "TIT2":
+        if data and data[:4] != b"TIT2":
             log.warning("Invalid toc data, TIT2 frame expected")
             return
         elif data:
     return frame
 
 
-def decodeUnicode(bytes, encoding):
+def decodeUnicode(bites, encoding):
     codec = id3EncodingToString(encoding)
     log.debug("Unicode encoding: %s" % codec)
-    return unicode(bytes, codec).rstrip("\x00")
+    return unicode(bites, codec).rstrip(b"\x00")
 
 
 def splitUnicode(data, encoding):
     try:
         if encoding == LATIN1_ENCODING or encoding == UTF_8_ENCODING:
-            (d, t) = data.split("\x00", 1)
+            (d, t) = data.split(b"\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)
+            (d, t) = data.split(b"\x00\x00", 1)
             if (len(d) % 2) != 0:
-                (d, t) = data.split("\x00\x00\x00", 1)
-                d += "\x00"
+                (d, t) = data.split(b"\x00\x00\x00", 1)
+                d += b"\x00"
     except ValueError as ex:
         log.warning("Invalid 2-tuple ID3 frame data: %s", ex)
         d, t = data, b""
     "PCS" : "PCST", # iTunes extension for podcast marking.
 }
 
-import apple
+from . import apple
 NONSTANDARD_ID3_FRAMES = {
     "NCON": ("Undefined MusicMatch extension", ID3_V2, Frame),
     "TCMP": ("iTunes complilation flag extension", ID3_V2, TextFrame),

File src/eyed3/id3/headers.py

             if self.update_bit:
                 data += b"\x00"
             if self.crc_bit:
-                data += "\x05"
+                data += b"\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(frame_data +
-                                                        ("\x00" * padding))))
+                                                        (b"\x00" * padding))))
                 crc_data = self._syncsafeCRC()
                 if len(crc_data) < 5:
                     # pad if necessary
-                    crc_data = ("\x00" * (5 - len(crc_data))) + crc_data
+                    crc_data = (b"\x00" * (5 - len(crc_data))) + crc_data
                 assert(len(crc_data) == 5)
                 data += crc_data
             if self.restrictions_bit:
-                data += "\x01"
+                data += b"\x01"
                 data += chr(self._restrictions)
             log.debug("Rendered extended header data (%d bytes)" % len(data))
 
             size = bin2bytes(bin2synchsafe(dec2bin(len(data) + 6, 32)))
             assert(len(size) == 4)
 
-            data = size + "\x01" + bin2bytes(dec2bin(self._flags)) + data
+            data = size + b"\x01" + bin2bytes(dec2bin(self._flags)) + data
             log.debug("Rendered extended header of size %d" % len(data))
         else:
             # Version 2.3
                 # 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(frame_data +
-                                                        ("\x00" * padding))))
+                                                        (b"\x00" * padding))))
                 crc = bin2bytes(dec2bin(self.crc))
                 assert(len(crc) == 4)
                 size += 4
                              " is not supported.")
 
     def render(self, data_size):
-        data = bytes(self.id)
+        from ..compat import BytesType
+        if type(self.id) is BytesType:
+            data = self.id
+        else:
+            data = self.id.encode("ascii")
+
         self.data_size = data_size
 
         if self.minor_version == 3:

File src/eyed3/id3/tag.py

 
 from ..utils import requireUnicode, chunkCopy
 from .. import core
-from .. import Exception as BaseException
+from .. import Error
 from . import (ID3_ANY_VERSION, ID3_V1, ID3_V1_0, ID3_V1_1,
                ID3_V2, ID3_V2_2, ID3_V2_3, ID3_V2_4, versionToString)
 from . import DEFAULT_LANG
 from . import Genre
 from . import frames
 from .headers import TagHeader, ExtendedTagHeader
+from ..compat import StringTypes
 
 import logging
 log = logging.getLogger(__name__)
 
 
-class TagException(BaseException):
+class TagException(Error):
     pass
 
 
         close_file = False
         if isinstance(fileobj, types.FileType):
             filename = fileobj.name
-        elif type(fileobj) in types.StringTypes:
+        elif type(fileobj) in StringTypes:
             filename = fileobj
             fileobj = file(filename, "rb")
             close_file = True
             if date_type is int:
                 # The integer year
                 date = core.Date(date)
-            elif date_type in types.StringTypes:
+            elif date_type in StringTypes:
                 date = core.Date.parse(date)
             elif not isinstance(date, core.Date):
                 raise TypeError("Invalid type: %s" % str(type(date)))

File src/eyed3/main.py

 ################################################################################
 from __future__ import print_function
 import sys
-import exceptions
 import os.path
 import textwrap
 import eyed3
 import eyed3.utils.cli
 import eyed3.plugins
 import eyed3.info
+from eyed3.compat import ConfigParser, ConfigParserError, StringIO
 
 
 DEFAULT_PLUGIN = "classic"
 
 def _loadConfig(args):
     import os
-    import ConfigParser
 
     config = None
     config_file = None
 
     if os.path.isfile(config_file):
         try:
-            config = ConfigParser.SafeConfigParser()
+            config = ConfigParser()
             config.read(config_file)
-        except ConfigParser.Error as ex:
+        except ConfigParserError as ex:
             eyed3.log.warning("User config error: " + str(ex))
             return None
     elif config_file != DEFAULT_CONFIG:
     '''
     import cProfile
     import pstats
-    import StringIO
 
     eyed3.log.debug("driver profileMain")
     prof = cProfile.Profile()
     prof = prof.runctx("main(args)", globals(), locals())
 
-    stream = StringIO.StringIO()
+    stream = StringIO()
     stats = pstats.Stats(prof, stream=stream)
     stats.sort_stats("time")  # Or cumulative
     stats.print_stats(100)  # 80 = how many to print
                             "The -c/--config options are still honored if "
                             "present." % DEFAULT_CONFIG)
         p.add_argument("--no-color", action="store_true", dest="no_color",
-                       help="Do not load the default user config '%s'. "
-                            "Suppress color codes in console output.")
+                       help="Suppress color codes in console output. "
+                            "This will happen automatically if the output is "
+                            "not a TTY (e.g. when redirecting to a file)")
 
         # Debugging options
         group = p.debug_arg_group
         retval = 0
     except IOError as ex:
         eyed3.utils.cli.printError(ex)
-    except exceptions.Exception as ex:
+    except Exception as ex:
         eyed3.utils.cli.printError("Uncaught exception: %s\n" % str(ex))
         eyed3.log.exception(ex)
 

File src/eyed3/mp3/__init__.py

 ################################################################################
 import os, re
 
-from .. import Exception as BaseException
+from .. import Error
 from .. import id3
 from .. import core, utils
 
 
 ##
 # \brief used to signal mp3-related errors.
-class Mp3Exception(BaseException):
+class Mp3Exception(Error):
     pass
 
 

File src/eyed3/plugins/__init__.py

 #
 ################################################################################
 from __future__ import print_function
-import os, sys, logging, exceptions, types
+import os, sys, logging, types
 
 try:
     from collections import OrderedDict
     refresh the cache.'''
     global _PLUGINS
 
-    if len(_PLUGINS.keys()) and reload == False:
+    if len(list(_PLUGINS.keys())) and not reload:
         # Return from the cache if possible
         try:
             return _PLUGINS[name] if name else _PLUGINS
                     log.warning("Plugin '%s' requires packages that are not "
                                 "installed: %s" % ((f, d), ex))
                     continue
-                except exceptions.Exception as ex:
+                except Exception as ex:
                     log.exception("Bad plugin '%s'", (f, d))
                     continue
 
                 for attr in [getattr(mod, a) for a in dir(mod)]:
-                    if (type(attr) == types.TypeType and
-                            issubclass(attr, Plugin)):
+                    if (type(attr) == type and issubclass(attr, Plugin)):
                         # This is a eyed3.plugins.Plugin
                         PluginClass = attr
                         if (PluginClass not in list(_PLUGINS.values()) and
     are treated as alias'''
 
     def __init__(self, arg_parser):
+        self.arg_parser = arg_parser
         self.arg_group = arg_parser.add_argument_group("Plugin options",
                                                   "%s\n%s" % (self.SUMMARY,
                                                               self.DESCRIPTION))

File src/eyed3/plugins/classic.py

 ################################################################################
 from __future__ import print_function
 
-import os, stat, exceptions, re
+import os, stat, re
 from argparse import ArgumentTypeError
 from eyed3 import LOCAL_ENCODING
 from eyed3.plugins import LoaderPlugin

File src/eyed3/plugins/lameinfo.py

 
         format = '%-20s: %s'
         lt = self.audio_file.info.lame_tag
-        if not lt.has_key('infotag_crc'):
+        if "infotag_crc" not in lt:
             try:
                 printMsg('%s: %s' % ('Encoder Version', lt['encoder_version']))
             except KeyError:
         values.append(('VBR Method', lt['vbr_method']))
         values.append(('Lowpass Filter', lt['lowpass_filter']))
 
-        if lt.has_key('replaygain'):
+        if "replaygain" in lt:
            try:
                peak = lt['replaygain']['peak_amplitude']
                db = 20 * math.log10(peak)

File src/eyed3/plugins/statistics.py

             return self._key_names[k] if k in self._key_names else k
 
         key_map = {}
-        for k in self.keys():
+        for k in list(self.keys()):
             key_map[keyDisplayName(k)] = k
 
         if not most_common:
 
     def handleFile(self, path):
         super(StatisticsPlugin, self).handleFile(path)
-        sys.stdout.write('.')
-        sys.stdout.flush()
+        if not self.args.quiet:
+            sys.stdout.write('.')
+            sys.stdout.flush()
 
         for stat in self._stats:
             if isinstance(stat, AudioStat):
         self._score_sum += total_score
 
     def handleDone(self):
+        if self._num_loaded == 0:
+            super(StatisticsPlugin, self).handleDone()
+            return
 
         print()
         for stat in self._stats + [self._rules_stat]:
         # Detailed rule violations
         if self.args.verbose:
             for path in self._rules_log:
-                print(path)
+                cli.printMsg(path) # does the right thing for unicode
                 for score, text in self._rules_log[path]:
                     print("\t%s%s%s (%s)" % (cli.RED, str(score).center(3),
                                              cli.RESET, text))

File src/eyed3/utils/__init__.py

 from __future__ import print_function
 import os
 import re
+from ..compat import unicode
 
 ID3_MIME_TYPE = "application/x-id3"
 ID3_MIME_TYPE_EXTENSIONS = (".id3", ".tag")
 
-import StringIO
+from ..compat import StringIO
 import mimetypes
 _mime_types = mimetypes.MimeTypes()
-_mime_types.readfp(StringIO.StringIO("%s %s" %
+_mime_types.readfp(StringIO("%s %s" %
                    (ID3_MIME_TYPE,
                     " ".join((e[1:] for e in ID3_MIME_TYPE_EXTENSIONS)))))
 del mimetypes

File src/eyed3/utils/binfuncs.py

 #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 #
 ################################################################################
-
+from ..compat import toByteString, BytesType, byteiter
 
 def bytes2bin(bytes, sz=8):
     '''Accepts a string of ``bytes`` (chars) and returns an array of bits
     '''
 
     retVal = []
-    for b in bytes:
+    for b in byteiter(bytes):
         bits = []
         b = ord(b)
         while b > 0:
     bits.reverse()
 
     i = 0
-    out = ''
+    out = b''
     multi = 1
     ttl = 0
     for b in bits:
         multi *= 2
         if i == 8:
             i = 0
-            out += chr(ttl)
+            out += toByteString(ttl)
             multi = 1
             ttl = 0
 
     if multi > 1:
-        out += chr(ttl)
+        out += toByteString(ttl)
 
-    out = list(out)
+    out = bytearray(out)
     out.reverse()
-    out = ''.join(out)
+    out = BytesType(out)
     return out
 
 
     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)
+    bites = b""
+    bites += toByteString((n >> 21) & 0x7f)
+    bites += toByteString((n >> 14) & 0x7f)
+    bites += toByteString((n >>  7) & 0x7f)
+    bites += toByteString((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
-
-

File src/test/__init__.py

 #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 #
 ################################################################################
-from StringIO import StringIO
+from eyed3.compat import StringIO
 import os
 import sys
 import logging

File src/test/id3/test_frames.py

 #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 #
 ################################################################################
-import unittest, cStringIO
+import unittest
 from nose.tools import *
 from eyed3.id3 import (LATIN1_ENCODING, UTF_8_ENCODING, UTF_16_ENCODING,
                        UTF_16BE_ENCODING)
 from eyed3.id3 import ID3_V1_0, ID3_V1_1, ID3_V2_2, ID3_V2_3, ID3_V2_4
 from eyed3.id3.frames import *
+from eyed3.compat import unicode
 from ..compat import *
 
 

File src/test/id3/test_headers.py

 #
 ################################################################################
 import unittest
-from cStringIO import StringIO
 from nose.tools import *
 from eyed3.utils.binfuncs import dec2bin, bin2bytes, bin2synchsafe
 from eyed3.id3.headers import *
 from eyed3.id3 import ID3_DEFAULT_VERSION, TagException
+from eyed3.compat import StringIO
 from ..compat import *
 
 

File src/test/id3/test_id3.py

 import unittest
 from nose.tools import *
 from eyed3.id3 import *
+from eyed3.compat import unicode
 
 class GenreTests(unittest.TestCase):
     def testEmptyGenre(self):

File src/test/id3/test_tag.py

 from eyed3.core import Date
 from eyed3.id3 import Tag, ID3_DEFAULT_VERSION, ID3_V2_3, ID3_V2_4
 from eyed3.id3 import frames
+from eyed3.compat import unicode
 from ..compat import *
 from .. import ExternalDataTestCase, DATA_D
 

File src/test/mp3/test_infos.py

 
     python -m test.mp3.test_infos <file>
 '''
+from __future__ import print_function
 import eyed3
 import sys
 import os

File src/test/mp3/test_mp3.py

 else:
     import unittest
 import os
-import StringIO
+from eyed3.compat import StringIO
 from nose.tools import *
 from .. import DATA_D
 
     from eyed3.mp3.headers import findHeader
 
     # No header
-    buffer = StringIO.StringIO('\x00' * 1024)
+    buffer = StringIO(b'\x00' * 1024)
     (offset, header_int, header_bytes) = findHeader(buffer, 0)
     assert_equal(header_int, None)
 
     # Valid header
-    buffer = StringIO.StringIO('\x11\x12\x23' * 1024 + "\xff\xfb\x90\x64" +
-                               "\x00" * 1024)
+    buffer = StringIO(b'\x11\x12\x23' * 1024 + b"\xff\xfb\x90\x64" +
+                      b"\x00" * 1024)
     (offset, header_int, header_bytes) = findHeader(buffer, 0)
     assert_equal(header_int, 0xfffb9064)
 
     # Same thing with a false sync in the mix
-    buffer = StringIO.StringIO('\x11\x12\x23' * 1024 +
-                               "\x11" * 100 +
-                               "\xff\xea\x00\x00" + # false sync
-                               "\x22" * 100 +
-                               "\xff\xe2\x1c\x34" + # false sync
-                               "\xee" * 100 +
-                               "\xff\xfb\x90\x64" +
-                               "\x00" * 1024)
+    buffer = StringIO(b'\x11\x12\x23' * 1024 +
+                      b"\x11" * 100 +
+                      b"\xff\xea\x00\x00" + # false sync
+                      b"\x22" * 100 +
+                      b"\xff\xe2\x1c\x34" + # false sync
+                      b"\xee" * 100 +
+                      b"\xff\xfb\x90\x64" +
+                      b"\x00" * 1024)
     (offset, header_int, header_bytes) = findHeader(buffer, 0)
     assert_equal(header_int, 0xfffb9064)
 

File src/test/test__init__.py

     assert_true(eyed3.LOCAL_FS_ENCODING)
 
 def testException():
-    import exceptions
 
-    ex = eyed3.Exception()
-    assert_true(isinstance(ex, exceptions.Exception))
+    ex = eyed3.Error()
+    assert_true(isinstance(ex, Exception))
 
     msg = "this is a test"
-    ex = eyed3.Exception(msg)
+    ex = eyed3.Error(msg)
     assert_equal(ex.message, msg)
     assert_equal(ex.args, (msg,))
 
-    ex = eyed3.Exception(msg, 1, 2)
+    ex = eyed3.Error(msg, 1, 2)
     assert_equal(ex.message, msg)
     assert_equal(ex.args, (msg, 1, 2))
 
     assert_raises(ValueError, eyed3.require, str(MAJOR))
 
     # test this version++, FAIL
-    assert_raises(eyed3.Exception, eyed3.require, (MAJOR + 1, MINOR, MAINT))
-    assert_raises(eyed3.Exception, eyed3.require, (MAJOR + 1, MINOR))
-    assert_raises(eyed3.Exception, eyed3.require, (MAJOR, MINOR + 1, MAINT))
-    assert_raises(eyed3.Exception, eyed3.require, (MAJOR, MINOR + 1))
-    assert_raises(eyed3.Exception, eyed3.require, (MAJOR, MINOR, MAINT + 1))
-    assert_raises(eyed3.Exception, eyed3.require,
+    assert_raises(eyed3.Error, eyed3.require, (MAJOR + 1, MINOR, MAINT))
+    assert_raises(eyed3.Error, eyed3.require, (MAJOR + 1, MINOR))
+    assert_raises(eyed3.Error, eyed3.require, (MAJOR, MINOR + 1, MAINT))
+    assert_raises(eyed3.Error, eyed3.require, (MAJOR, MINOR + 1))
+    assert_raises(eyed3.Error, eyed3.require, (MAJOR, MINOR, MAINT + 1))
+    assert_raises(eyed3.Error, eyed3.require,
                   t2s((MAJOR + 1, MINOR, MAINT)))
-    assert_raises(eyed3.Exception, eyed3.require,
+    assert_raises(eyed3.Error, eyed3.require,
                   t2s((MAJOR, MINOR + 1, MAINT)))
-    assert_raises(eyed3.Exception, eyed3.require,
+    assert_raises(eyed3.Error, eyed3.require,
                   t2s((MAJOR, MINOR, MAINT + 1)))
 
     # test x, y--, z (0.6.x, but 0.7 installed) FAIL
-    assert_raises(eyed3.Exception, eyed3.require, (MAJOR, MINOR - 1, MAINT))
-    assert_raises(eyed3.Exception, eyed3.require, (MAJOR, MINOR - 1))
+    assert_raises(eyed3.Error, eyed3.require, (MAJOR, MINOR - 1, MAINT))
+    assert_raises(eyed3.Error, eyed3.require, (MAJOR, MINOR - 1))
     # test -x, y, z (0.6.x, but 1.0 installed) FAIL
-    assert_raises(eyed3.Exception, eyed3.require, (MAJOR - 1, MINOR, MAINT))
-    assert_raises(eyed3.Exception, eyed3.require, (MAJOR - 1, MINOR))
+    assert_raises(eyed3.Error, eyed3.require, (MAJOR - 1, MINOR, MAINT))
+    assert_raises(eyed3.Error, eyed3.require, (MAJOR - 1, MINOR))
 
 def test_log():
     from eyed3 import log

File src/test/test_binfuncs.py

 def test_bytes2bin():
     # test ones and zeros, sz==8
     for i in range(1, 11):
-        zeros = bytes2bin(bytes("\x00") * i)
-        ones = bytes2bin(bytes("\xFF") * i)
+        zeros = bytes2bin(b"\x00" * i)
+        ones = bytes2bin(b"\xFF" * i)
         assert_true(len(zeros) == (8 * i) and len(zeros) == len(ones))
         for i in range(len(zeros)):
             assert_true(zeros[i] == 0)
             assert_true(ones[i] == 1)
 
     # test 'sz' bounds checking
-    assert_raises(ValueError, bytes2bin, bytes("a"), -1)
-    assert_raises(ValueError, bytes2bin, bytes("a"), 0)
-    assert_raises(ValueError, bytes2bin, bytes("a"), 9)
+    assert_raises(ValueError, bytes2bin, b"a", -1)
+    assert_raises(ValueError, bytes2bin, b"a", 0)
+    assert_raises(ValueError, bytes2bin, b"a", 9)
 
     # Test 'sz'
     for sz in range(1, 9):
-        res = bytes2bin(bytes("\x00\xFF"), sz=sz)
+        res = bytes2bin(b"\x00\xFF", sz=sz)
         assert_true(len(res) == 2 * sz)
         assert_true(res[:sz] == [0] * sz)
         assert_true(res[sz:] == [1] * sz)
     assert_equal(bin2dec([1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0]), 2730)
 
 def test_bytes2dec():
-    assert_equal(bytes2dec(bytes("\x00\x11\x22\x33")), 1122867)
+    assert_equal(bytes2dec(b"\x00\x11\x22\x33"), 1122867)
 
 def test_dec2bin():
     assert_equal(dec2bin(3036790792), [1, 0, 1, 1, 0, 1, 0, 1,
     assert_equal(dec2bin(1, p=8), [0, 0, 0, 0, 0, 0, 0, 1])
 
 def test_dec2bytes():
-    assert_equal(dec2bytes(ord("a")), "\x61")
+    assert_equal(dec2bytes(ord(b"a")), b"\x61")
 
 def test_bin2syncsafe():
-    assert_raises(ValueError, bin2synchsafe, bytes2bin("\xff\xff\xff\xff"))
+    assert_raises(ValueError, bin2synchsafe, bytes2bin(b"\xff\xff\xff\xff"))
     assert_raises(ValueError, bin2synchsafe, [0] * 33)
     assert_equal(bin2synchsafe([1] * 7), [1] * 7)
     assert_equal(bin2synchsafe(dec2bin(255)), [0, 0, 0, 0, 0, 0, 0, 0,
                                                0, 0, 0, 0, 0, 0, 0, 0,
                                                0, 0, 0, 0, 0, 0, 0, 1,
                                                0, 1, 1, 1, 1, 1, 1, 1])
-
-def test_bytes2str():
-    assert_equal(bytes2str("\xfe"), "\\xfe")

File src/test/test_main.py

 #
 ################################################################################
 import unittest
-from StringIO import StringIO
+from eyed3.compat import StringIO
 from nose.tools import *
 from eyed3 import main, info
 from eyed3.utils import cli

File src/test/test_plugins.py

 
 def test_load():
     plugins = load()
-    assert_in("classic", plugins.keys())
-    assert_in("genres", plugins.keys())
+    assert_in("classic", list(plugins.keys()))
+    assert_in("genres", list(plugins.keys()))
 
     assert_equal(load("classic"), plugins["classic"])
     assert_equal(load("genres"), plugins["genres"])