See the changelog. :)
[rox-ripper.git] / CDLow.py
bloba0baed56554a4eaaddae3b7941f4ab9264ba680a
1 """
2 CDLow - Low level CDROM interface
3 Written to replace CDAudio, CDDB and PyGAME.CD and
4 add functionality (skipping)
5 Originally from the Gryphon CD player (http://gryphon.sourceforge.net/)
7 Copyright 2001 (c) Christian Storgaard.
8 Changes made by Ron Kuslak <rds@rdsarts.com>, 2003-2004
10 This program is free software; you can redistribute it and/or modify
11 it under the terms of the GNU General Public License as published by
12 the Free Software Foundation; either version 2 of the License, or
13 (at your option) any later version.
15 This program is distributed in the hope that it will be useful
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License for more details.
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 """
25 from fcntl import ioctl
26 from CDROM import *
27 import os, struct
29 debug = 0
31 trayopen = 0
33 NOSTATUS = 0
34 PAUSED = 1
35 STOPPED = 2
36 NODISC = 3
37 PLAYING = 4
38 ERROR = 5
40 tochdr_fmt = "BB"
41 tocentry_fmt = "BBBix"
42 addr_fmt = "BBB"+"x"*(struct.calcsize('i')-3)
43 region_fmt = "BBBBBB"
44 subchnl_fmt = "BBxBBiBBB"
46 class cd:
47 path = "/dev/cdrom"
48 start = end = None
49 open = cd = 0
50 last = (0,0,0)
51 skip = 3 # In seconds
52 data = 0 # Display Data-tracks
54 """ #### Internal functions ####
55 #### -- start -- ####"""
56 def cd_open():
57 if cd.cd > 0:
58 cd.open = 1
59 return 0
60 else:
61 try:
62 cd.cd = os.open(cd.path, os.O_RDONLY|os.O_NONBLOCK)
63 trayopen = 0
64 return 0
65 except:
66 cd.status = NODISC
67 trayopen = 1
68 return 1
70 def cd_close():
71 if cd.cd < 1:
72 cd.open = 0
73 return 0
74 os.close(cd.cd)
75 cd.cd = 0
76 cd.open = 0
77 return 0
79 """exit function"""
80 import atexit
81 atexit.register(cd_close)
83 """ #### Internal functions ####
84 #### -- end -- ####"""
86 def stop():
87 # cd_open()
88 """ We don't care if there's a CD in or not, we're just stopping ;)"""
89 if get_status() == PAUSED:
90 try:
91 ioctl(cd.cd, CDROMRESUME)
92 except:
93 pass
94 # cd_open()
95 try:
96 ioctl(cd.cd, CDROMSTOP)
97 except:
98 pass
99 # cd_close()
101 def pause():
102 status = get_status()
103 # cd_open()
104 if status == PLAYING:
105 ioctl(cd.cd, CDROMPAUSE)
106 elif status == PAUSED:
107 ioctl(cd.cd, CDROMRESUME)
108 # cd_close()
110 def forward(val=None):
111 if val!=None: skip(0,val,1)
112 else: skip(0)
114 def rewind(val=None):
115 if val!=None: skip(1,val,1)
116 skip(1)
118 def goto(min, sec, frm=0):
119 """See if we have the last track to play in cache, else set to last on disc"""
120 if cd.last == (0,0,0):
121 if cd.end == None: get_header()
122 cd.last = get_track_time(cd.end)
123 try: play_msf(min, sec, frm, cd.last[0], cd.last[1], cd.last[2])
124 except: pass
126 def skip(direction, amount=None, frames=0):
127 if amount == None: amount = cd.skip
128 if not frames: amount = amount*CD_FRAMES
129 curmin, cursec, curfrm = get_current_timing()['abs']
130 start = msf2frm(curmin,cursec,curfrm)
131 """Apply according to which direction we are skipping"""
132 if direction == 1:start=start-amount
133 else:start=start+amount
134 startmin,startsec,startfrm=frm2msf(start)
135 """See if we have the last track to play in cache, else set to last on disc"""
136 if cd.last == (0,0,0):
137 if cd.end == None: get_header()
138 cd.last = get_track_time(cd.end)
139 try: play_msf(startmin, startsec, startfrm, cd.last[0], cd.last[1], cd.last[2])
140 except: pass
142 def play(first,last=None):
143 if last == None:last=first
144 i=get_track_time(first)
145 cd.last = get_track_time(last+1)
146 play_msf(i[0],i[1],i[2],cd.last[0],cd.last[1],cd.last[2])
148 def play_msf(startmin, startsec, startfrm, endmin, endsec, endfrm):
149 # cd_open()
150 cd.last=(endmin,endsec,endfrm)
151 region = struct.pack(region_fmt,startmin,startsec,startfrm,endmin,endsec,endfrm)
152 ioctl(cd.cd, CDROMPLAYMSF, region)
153 # cd_close()
155 def eject():
156 cd_open()
157 try:
158 ioctl(cd.cd, CDROMSTOP)
159 # Always unlock door before eject, in case something else locked it
160 ioctl(cd.cd, CDROM_LOCKDOOR, 0)
161 except:
162 # ioctl(cd.cd, CDROMRESET)
163 ioctl(cd.cd, CDROMSTOP)
164 # Always unlock door before eject, in case something else locked it
165 ioctl(cd.cd, CDROM_LOCKDOOR, 0)
166 try:
167 ioctl(cd.cd, CDROMEJECT_SW, 0)
168 ioctl(cd.cd, CDROMEJECT)
169 except:
170 ioctl(cd.cd, CDROMEJECT_SW, 0)
171 ioctl(cd.cd, CDROMEJECT)
172 # cd_close()
174 def close():
175 # cd_open()
176 try:
177 ioctl(cd.cd, CDROMCLOSETRAY)
178 except:
179 pass
180 # cd_close()
182 def do_eject():
183 if debug: print "Activated:\tCDLow.do_eject"
184 cd_close()
185 cd_open()
186 global trayopen
187 if debug: print "\tStatus:",trayopen
188 ioctl(cd.cd, CDROMEJECT_SW, 0)
189 if not trayopen:
190 try:
191 stop()
192 if debug: print "\tStopped"
193 except:
194 pass
195 eject()
196 if debug: print "\tEjected Tray"
197 cd.cd = 0
198 cd.status = NODISC
199 trayopen = 1
200 else:
201 close()
202 if debug: print "\tClosed Tray"
203 trayopen = 0
204 cd_close()
205 if debug: print "\tClosed CD"
206 cd_open()
207 if debug: print "\tOpened CD"
208 return trayopen
210 def get_volume():
211 # cd_open()
212 try:
213 return struct.unpack("BBBB",ioctl(cd.cd, CDROMVOLREAD, struct.pack("BBBB",0,0,0,0)))
215 except:
216 return (0,0,0,0)
217 # cd_close()
219 def set_volume(frontleft,frontright=None,backleft=0,backright=0):
220 # cd_open()
221 if frontright == None: frontright = frontleft
222 try:
223 return struct.unpack("BBBB",ioctl(cd.cd, CDROMVOLCTRL, struct.pack("BBBB",frontleft,frontright,backleft,backright)))
225 except:
226 return 1
227 # cd_close()
229 def get_status(time=0, all=0):
230 # cd_open()
231 result = 0
232 i = 1
233 while result == 0 and i < 5:
234 try: # If this fails then it's because it's happening too fast, just retry
235 info = ioctl(cd.cd, CDROMSUBCHNL, struct.pack(subchnl_fmt, CDROM_MSF,0,0,0,0,0,0,0))
236 drvstatus = get_drive_status()
237 result = 1
238 except:
239 i = i +1
240 # cd_close()
241 format, status, track, something, absaddr, relmin, relsec, relfrm = struct.unpack(subchnl_fmt, info)
242 absmin, abssec, absfrm = struct.unpack(addr_fmt, struct.pack("i", absaddr))
243 zero = (1, 0,0,0, 0,0,0)
244 if status == CDROM_AUDIO_PLAY : cd.status = PLAYING
245 elif status == CDROM_AUDIO_PAUSED : cd.status = PAUSED
246 elif status == CDROM_AUDIO_COMPLETED :
247 cd.status = STOPPED
248 track,relmin,relsec,relfrm,absmin,abssec,absfrm = zero
249 elif status == CDROM_AUDIO_ERROR :
250 cd.status = ERROR
251 track,relmin,relsec,relfrm,absmin,abssec,absfrm = zero
252 elif status == CDROM_AUDIO_INVALID :
253 cd.status = ERROR
254 track,relmin,relsec,relfrm,absmin,abssec,absfrm = zero
255 elif status == CDROM_AUDIO_NO_STATUS :
256 cd.status = NOSTATUS
257 track,relmin,relsec,relfrm,absmin,abssec,absfrm = zero
258 if drvstatus == CDS_NO_DISC :
259 cd.status = NODISC
260 track,relmin,relsec,relfrm,absmin,abssec,absfrm = zero
261 if all: return {'status': drvstatus, 'format': format, 'status': status, 'track': track, 'something': something, 'absaddr': absaddr, 'relmin': relmin, 'relsec': relsec, 'relfrm': relfrm}
262 elif time: return {'cur_t': track,'rel':(relmin,relsec,relfrm),'abs':(absmin,abssec,absfrm)}
263 else: return cd.status
265 def get_disc_type():
266 # cd_open()
267 ret = ioctl(cd.cd, CDROM_DRIVE_STATUS)
268 # cd_close()
269 return ret
271 def get_drive_status():
272 # cd_open()
273 ret = 999
274 ret = ioctl(cd.cd, CDROM_DRIVE_STATUS)
275 # cd_close()
276 return ret
278 def get_header():
279 # cd_open()
280 tochdr = struct.pack(tochdr_fmt, 0, 0)
281 tochdr = ioctl(cd.cd, CDROMREADTOCHDR, tochdr)
282 cd.start, cd.end = struct.unpack(tochdr_fmt, tochdr)
283 # cd_close()
285 def get_track_time(trk):
286 if cd.start == None:
287 get_header()
288 if trk < 1: trk = 1
289 elif trk > cd.end: trk = CDROM_LEADOUT
290 # cd_open()
291 toc = struct.pack(tocentry_fmt, trk, 0, CDROM_MSF, 0)
292 toc = ioctl(cd.cd, CDROMREADTOCENTRY, toc)
294 track, adrctrl, format, addr = struct.unpack(tocentry_fmt, toc)
295 m, s, f = struct.unpack(addr_fmt, struct.pack("i", addr))
297 adr = adrctrl & 0xf
298 ctrl = (adrctrl & 0xf0) >> 4
300 start = (m * CD_SECS + s) * CD_FRAMES + f # 60 and 75
301 data = 0
302 if ctrl & CDROM_DATA_TRACK:
303 data = 1
304 # cd_close()
305 return m,s,f,start,data
307 def get_track_length(first,last=None):
308 if last==None: last=first+1
309 off1min, off1sec, off1frm, off1, data1 = get_track_time(first)
310 off2min, off2sec, off2frm, off2, data2 = get_track_time(last)
312 lenmin,lensec,lenfrm=frm2msf(off2-off1)
314 return off1min,off1sec,off1frm,lenmin,lensec,lenfrm,off1,data1
316 def get_tracks_id():
317 get_header()
318 offset = []
319 data = []
320 length = []
321 tracklist = range(cd.start,cd.end+1)
322 tracklist.append(CDROM_LEADOUT)
323 for i in tracklist:
324 m,s,f,lenm,lens,lenf,start,tdata = get_track_length(i)
325 offset.append((m, s, f))
326 data.append(tdata)
327 if i <= cd.end: # We don't want length of CDROM_LEADOUT ;)
328 length.append((lenm, lens, lenf))#0)) # 0 is CDAudio's way
329 tracks = {'start_t':cd.start, 'end_t':cd.end, 'offset': offset, 'length': length, 'id': disc_id(offset, cd.start, cd.end), 'data': data}
330 tracks['cddb'] = get_cddb_id(tracks['id'][2:-1], tracks['id'][-1])
331 return tracks
333 def get_current_timing(): # Wrapper for get_status(1)
334 return get_status(1)
336 def disc_id(offset, first, last):
337 track_frames = []
338 checksum = long(0)
339 for i in range(first-1, last):
340 (min, sec, frame) = offset[i]
341 checksum = checksum + id_sum(min*CD_SECS + sec)
342 track_frames.append(min*CD_SECS*CD_FRAMES + sec*CD_FRAMES + frame)
343 (min, sec, frame) = offset[last]
344 track_frames.append(min*CD_SECS*CD_FRAMES + sec*CD_FRAMES + frame)
346 total_time = (track_frames[-1] / CD_FRAMES) - (track_frames[0] / CD_FRAMES)
348 discid = (int((checksum % 0xff) << 24) | total_time << 8 | last)
350 return [discid,last]+track_frames[:-1]+[track_frames[-1]/CD_FRAMES]
352 def id_sum(s):
353 sum = 0
354 while s > 0:
355 sum = sum + (s % 10)
356 s = s / 10
357 return sum
359 def frm2msf(frm):
360 min = (frm / CD_FRAMES) / CD_SECS
361 sec = (frm / CD_FRAMES) - (min * CD_SECS)
362 frm = frm - ((min * CD_SECS) * CD_FRAMES + sec * CD_FRAMES)
363 return min,sec,frm
365 def msf2frm(min,sec,frm):
366 return min*CD_SECS*CD_FRAMES+sec*CD_FRAMES+frm
368 def get_cddb_id(tracks, disclen):
369 checksum = long(0)
370 for i in tracks:
371 sum = 0
372 mfs = (i / CD_FRAMES)
373 while mfs > 0:
374 sum = sum + (mfs % 10)
375 mfs = mfs / 10
376 checksum = checksum + sum
377 tot_time = disclen - tracks[0] / CD_FRAMES
378 return "%08x" % ((checksum % 0xff) << 24 | tot_time << 8 | len(tracks))
381 """ #### Startup code ####
382 #### ####"""
384 cd_open()