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 8507 2006-09-30 19:04:27Z luks $'
14
15 import sys
16 import urllib
17 import urlparse
18 import ctypes
19 from musicbrainz2.model import Disc
20
21 __all__ = [ 'DiscError', 'readDisc', 'getSubmissionUrl' ]
22
23
25 """The Audio CD could not be read.
26
27 This may be simply because no disc was in the drive, the device name
28 was wrong or the disc can't be read. Reading errors can occur in case
29 of a damaged disc or a copy protection mechanism, for example.
30 """
31 pass
32
33
35 """Tries to open libdiscid.
36
37 @return: a C{ctypes.CDLL} object, representing the opened library
38
39 @raise NotImplementedError: if the library can't be opened
40 """
41
42
43 try:
44 if hasattr(ctypes.cdll, 'find'):
45 libDiscId = ctypes.cdll.find('discid')
46 _setPrototypes(libDiscId)
47 return libDiscId
48 except OSError, e:
49 raise NotImplementedError('Error opening library: ' + str(e))
50
51
52
53
54
55 if sys.platform == 'linux2':
56 libName = 'libdiscid.so.0'
57 elif sys.platform == 'darwin':
58 libName = 'libdiscid.0.dylib'
59 elif sys.platform == 'win32':
60 libName = 'discid.dll'
61 else:
62
63 libName = 'libdiscid.so.0'
64
65 try:
66 libDiscId = ctypes.cdll.LoadLibrary(libName)
67 _setPrototypes(libDiscId)
68 return libDiscId
69 except OSError, e:
70 raise NotImplementedError('Error opening library: ' + str(e))
71
72 assert False
73
74
76 ct = ctypes
77 libDiscId.discid_new.argtypes = ( )
78
79 libDiscId.discid_free.argtypes = (ct.c_int, )
80
81 libDiscId.discid_read.argtypes = (ct.c_int, ct.c_char_p)
82
83 libDiscId.discid_get_error_msg.argtypes = (ct.c_int, )
84 libDiscId.discid_get_error_msg.restype = ct.c_char_p
85
86 libDiscId.discid_get_id.argtypes = (ct.c_int, )
87 libDiscId.discid_get_id.restype = ct.c_char_p
88
89 libDiscId.discid_get_first_track_num.argtypes = (ct.c_int, )
90 libDiscId.discid_get_first_track_num.restype = ct.c_int
91
92 libDiscId.discid_get_last_track_num.argtypes = (ct.c_int, )
93 libDiscId.discid_get_last_track_num.restype = ct.c_int
94
95 libDiscId.discid_get_sectors.argtypes = (ct.c_int, )
96 libDiscId.discid_get_sectors.restype = ct.c_int
97
98 libDiscId.discid_get_track_offset.argtypes = (ct.c_int, ct.c_int)
99 libDiscId.discid_get_track_offset.restype = ct.c_int
100
101 libDiscId.discid_get_track_length.argtypes = (ct.c_int, ct.c_int)
102 libDiscId.discid_get_track_length.restype = ct.c_int
103
104
106 """Returns a URL for adding a disc to the MusicBrainz database.
107
108 A fully initialized L{musicbrainz2.model.Disc} object is needed, as
109 returned by L{readDisc}. A disc object returned by the web service
110 doesn't provide the necessary information.
111
112 Note that the created URL is intended for interactive use and points
113 to the MusicBrainz disc submission wizard by default. This method
114 just returns a URL, no network connection is needed. The disc drive
115 isn't used.
116
117 @param disc: a fully initialized L{musicbrainz2.model.Disc} object
118 @param host: a string containing a host name
119 @param port: an integer containing a port number
120
121 @return: a string containing the submission URL
122
123 @see: L{readDisc}
124 """
125 assert isinstance(disc, Disc), 'musicbrainz2.model.Disc expected'
126 discid = disc.getId()
127 first = disc.getFirstTrackNum()
128 last = disc.getLastTrackNum()
129 sectors = disc.getSectors()
130 assert None not in (discid, first, last, sectors)
131
132 tracks = last - first + 1
133 toc = "%d %d %d " % (first, last, sectors)
134 toc = toc + ' '.join( map(lambda x: str(x[0]), disc.getTracks()) )
135
136 query = urllib.urlencode({ 'id': discid, 'toc': toc, 'tracks': tracks })
137
138 if port == 80:
139 netloc = host
140 else:
141 netloc = host + ':' + str(port)
142
143 url = ('http', netloc, '/bare/cdlookup.html', '', query, '')
144
145 return urlparse.urlunparse(url)
146
147
149 """Reads an Audio CD in the disc drive.
150
151 This reads a CD's table of contents (TOC) and calculates the MusicBrainz
152 DiscID, which is a 28 character ASCII string. This DiscID can be used
153 to retrieve a list of matching releases from the web service (see
154 L{musicbrainz2.webservice.Query}).
155
156 Note that an Audio CD has to be in drive for this to work. The
157 C{deviceName} argument may be used to set the device. The default
158 depends on the operating system (on linux, it's C{'/dev/cdrom'}).
159 No network connection is needed for this function.
160
161 If the device doesn't exist or there's no valid Audio CD in the drive,
162 a L{DiscError} exception is raised.
163
164 @param deviceName: a string containing the CD drive's device name
165
166 @return: a L{musicbrainz2.model.Disc} object
167
168 @raise DiscError: if there was a problem reading the disc
169 @raise NotImplementedError: if DiscID generation isn't supported
170 """
171 libDiscId = _openLibrary()
172
173 handle = libDiscId.discid_new()
174 assert handle != 0, "libdiscid: discid_new() returned NULL"
175
176
177
178
179 res = libDiscId.discid_read(handle, deviceName)
180 if res == 0:
181 raise DiscError(libDiscId.discid_get_error_msg(handle))
182
183
184
185
186 disc = Disc()
187
188 disc.setId( libDiscId.discid_get_id(handle) )
189
190 firstTrackNum = libDiscId.discid_get_first_track_num(handle)
191 lastTrackNum = libDiscId.discid_get_last_track_num(handle)
192
193 disc.setSectors(libDiscId.discid_get_sectors(handle))
194
195 for i in range(firstTrackNum, lastTrackNum+1):
196 trackOffset = libDiscId.discid_get_track_offset(handle, i)
197 trackSectors = libDiscId.discid_get_track_length(handle, i)
198
199 disc.addTrack( (trackOffset, trackSectors) )
200
201 disc.setFirstTrackNum(firstTrackNum)
202 disc.setLastTrackNum(lastTrackNum)
203
204 libDiscId.discid_free(handle)
205
206 return disc
207
208
209