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
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
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
43
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
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
64
65
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
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
85
86
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
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
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
190
191
192 res = libDiscId.discid_read(handle, deviceName)
193 if res == 0:
194 raise DiscError(libDiscId.discid_get_error_msg(handle))
195
196
197
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
222