Package musicbrainz2 :: Module disc

[frames] | no frames]

# Source Code for Module musicbrainz2.disc

      1  """Utilities for working with Audio CDs.
      2
      3  This module contains utilities for working with Audio CDs.
      4
      5  The functions in this module need both a working ctypes package (already
      6  included in python-2.5) and an installed libdiscid. If you don't have
      7  libdiscid, it can't be loaded, or your platform isn't supported by either
      8  ctypes or this module, a C{NotImplementedError} is raised when using the
      9  L{readDisc()} function.
     10
     11  @author: Matthias Friedrich <matt@mafr.de>
     12  """
     13  __revision__ = '$Id: disc.py 11987 2009-08-22 11:57:51Z matt $'
     14
     15  import sys
     16  import urllib
     17  import urlparse
     18  import ctypes
     19  import ctypes.util
     20  from musicbrainz2.model import Disc
     21
     22  __all__ = [ 'DiscError', 'readDisc', 'getSubmissionUrl' ]
     23
     24
     25 -class DiscError(IOError):
     26          """The Audio CD could not be read.
     27
     28          This may be simply because no disc was in the drive, the device name
     29          was wrong or the disc can't be read. Reading errors can occur in case
     30          of a damaged disc or a copy protection mechanism, for example.
     31          """
     32          pass
     33
     34
     35 -def _openLibrary():
     36          """Tries to open libdiscid.
     37
     38          @return: a C{ctypes.CDLL} object, representing the opened library
     39
     40          @raise NotImplementedError: if the library can't be opened
     41          """
     42          # This only works for ctypes >= 0.9.9.3. Any libdiscid is found,
     43          # no matter how it's called on this platform.
     44          try:
     45                  if hasattr(ctypes.cdll, 'find'):
     46                          libDiscId = ctypes.cdll.find('discid')
     47                          _setPrototypes(libDiscId)
     48                          return libDiscId
     49          except OSError, e:
     50                  raise NotImplementedError('Error opening library: ' + str(e))
     51
     52          # Try to find the library using ctypes.util
     53          libName = ctypes.util.find_library('discid')
     54          if libName != None:
     55                  try:
     56                          libDiscId = ctypes.cdll.LoadLibrary(libName)
     57                          _setPrototypes(libDiscId)
     58                          return libDiscId
     59                  except OSError, e:
     60                          raise NotImplementedError('Error opening library: ' +
     61                                  str(e))
     62
     63          # For compatibility with ctypes < 0.9.9.3 try to figure out the library
     64          # name without the help of ctypes. We use cdll.LoadLibrary() below,
     65          # which isn't available for ctypes == 0.9.9.3.
     66          #
     67          if sys.platform == 'linux2':
     68                  libName = 'libdiscid.so.0'
     69          elif sys.platform == 'darwin':
     70                  libName = 'libdiscid.0.dylib'
     71          elif sys.platform == 'win32':
     72                  libName = 'discid.dll'
     73          else:
     74                  # This should at least work for Un*x-style operating systems
     75                  libName = 'libdiscid.so.0'
     76
     77          try:
     78                  libDiscId = ctypes.cdll.LoadLibrary(libName)
     79                  _setPrototypes(libDiscId)
     80                  return libDiscId
     81          except OSError, e:
     82                  raise NotImplementedError('Error opening library: ' + str(e))
     83
     84          assert False # not reached
     85
     86
     87 -def _setPrototypes(libDiscId):
     88          ct = ctypes
     89          libDiscId.discid_new.argtypes = ( )
     90          libDiscId.discid_new.restype = ct.c_void_p
     91
     92          libDiscId.discid_free.argtypes = (ct.c_void_p, )
     93
     94          libDiscId.discid_read.argtypes = (ct.c_void_p, ct.c_char_p)
     95
     96          libDiscId.discid_get_error_msg.argtypes = (ct.c_void_p, )
     97          libDiscId.discid_get_error_msg.restype = ct.c_char_p
     98
     99          libDiscId.discid_get_id.argtypes = (ct.c_void_p, )
    100          libDiscId.discid_get_id.restype = ct.c_char_p
    101
    102          libDiscId.discid_get_first_track_num.argtypes = (ct.c_void_p, )
    103          libDiscId.discid_get_first_track_num.restype = ct.c_int
    104
    105          libDiscId.discid_get_last_track_num.argtypes = (ct.c_void_p, )
    106          libDiscId.discid_get_last_track_num.restype = ct.c_int
    107
    108          libDiscId.discid_get_sectors.argtypes = (ct.c_void_p, )
    109          libDiscId.discid_get_sectors.restype = ct.c_int
    110
    111          libDiscId.discid_get_track_offset.argtypes = (ct.c_void_p, ct.c_int)
    112          libDiscId.discid_get_track_offset.restype = ct.c_int
    113
    114          libDiscId.discid_get_track_length.argtypes = (ct.c_void_p, ct.c_int)
    115          libDiscId.discid_get_track_length.restype = ct.c_int
    116
    117
    118 -def getSubmissionUrl(disc, host='mm.musicbrainz.org', port=80):
    119          """Returns a URL for adding a disc to the MusicBrainz database.
    120
    121          A fully initialized L{musicbrainz2.model.Disc} object is needed, as
    122          returned by L{readDisc}. A disc object returned by the web service
    123          doesn't provide the necessary information.
    124
    125          Note that the created URL is intended for interactive use and points
    126          to the MusicBrainz disc submission wizard by default. This method
    127          just returns a URL, no network connection is needed. The disc drive
    128          isn't used.
    129
    130          @param disc: a fully initialized L{musicbrainz2.model.Disc} object
    131          @param host: a string containing a host name
    132          @param port: an integer containing a port number
    133
    134          @return: a string containing the submission URL
    135
    136          @see: L{readDisc}
    137          """
    138          assert isinstance(disc, Disc), 'musicbrainz2.model.Disc expected'
    139          discid = disc.getId()
    140          first = disc.getFirstTrackNum()
    141          last = disc.getLastTrackNum()
    142          sectors = disc.getSectors()
    143          assert None not in (discid, first, last, sectors)
    144
    145          tracks = last - first + 1
    146          toc = "%d %d %d " % (first, last, sectors)
    147          toc = toc + ' '.join( map(lambda x: str(x[0]), disc.getTracks()) )
    148
    149          query = urllib.urlencode({ 'id': discid, 'toc': toc, 'tracks': tracks })
    150
    151          if port == 80:
    152                  netloc = host
    153          else:
    154                  netloc = host + ':' + str(port)
    155
    156          url = ('http', netloc, '/bare/cdlookup.html', '', query, '')
    157
    158          return urlparse.urlunparse(url)
    159
    160
    161 -def readDisc(deviceName=None):
    162          """Reads an Audio CD in the disc drive.
    163
    164          This reads a CD's table of contents (TOC) and calculates the MusicBrainz
    165          DiscID, which is a 28 character ASCII string. This DiscID can be used
    166          to retrieve a list of matching releases from the web service (see
    167          L{musicbrainz2.webservice.Query}).
    168
    169          Note that an Audio CD has to be in drive for this to work. The
    170          C{deviceName} argument may be used to set the device. The default
    171          depends on the operating system (on linux, it's C{'/dev/cdrom'}).
    172          No network connection is needed for this function.
    173
    174          If the device doesn't exist or there's no valid Audio CD in the drive,
    175          a L{DiscError} exception is raised.
    176
    177          @param deviceName: a string containing the CD drive's device name
    178
    179          @return: a L{musicbrainz2.model.Disc} object
    180
    181          @raise DiscError: if there was a problem reading the disc
    182          @raise NotImplementedError: if DiscID generation isn't supported
    183          """
    184          libDiscId = _openLibrary()
    185
    186          handle = libDiscId.discid_new()
    187          assert handle != 0, "libdiscid: discid_new() returned NULL"
    188
    189          # Access the CD drive. This also works if deviceName is None because
    190          # ctypes passes a NULL pointer in this case.
    191          #
    192          res = libDiscId.discid_read(handle, deviceName)
    193          if res == 0:
    194                  raise DiscError(libDiscId.discid_get_error_msg(handle))
    195
    196
    197          # Now extract the data from the result.
    198          #
    199          disc = Disc()
    200
    201          disc.setId( libDiscId.discid_get_id(handle) )
    202
    203          firstTrackNum = libDiscId.discid_get_first_track_num(handle)
    204          lastTrackNum = libDiscId.discid_get_last_track_num(handle)
    205
    206          disc.setSectors(libDiscId.discid_get_sectors(handle))
    207
    208          for i in range(firstTrackNum, lastTrackNum+1):
    209                  trackOffset = libDiscId.discid_get_track_offset(handle, i)
    210                  trackSectors = libDiscId.discid_get_track_length(handle, i)
    211
    212                  disc.addTrack( (trackOffset, trackSectors) )
    213
    214          disc.setFirstTrackNum(firstTrackNum)
    215          disc.setLastTrackNum(lastTrackNum)
    216
    217          libDiscId.discid_free(handle)
    218
    219          return disc
    220
    221  # EOF
    222