1 | # ----------------------------------------------------------------------- |
---|
2 | # discinfo.py - basic class for any discs containing collections of |
---|
3 | # media. |
---|
4 | # ----------------------------------------------------------------------- |
---|
5 | # $Id: discinfo.py,v 1.23 2004/09/14 20:12:25 dischi Exp $ |
---|
6 | # |
---|
7 | # $Log: discinfo.py,v $ |
---|
8 | # Revision 1.23 2004/09/14 20:12:25 dischi |
---|
9 | # fix future warning |
---|
10 | # |
---|
11 | # Revision 1.22 2004/08/28 17:27:16 dischi |
---|
12 | # handle empty discs |
---|
13 | # |
---|
14 | # Revision 1.21 2004/06/25 13:20:35 dischi |
---|
15 | # FreeBSD patches |
---|
16 | # |
---|
17 | # Revision 1.20 2004/02/08 17:44:05 dischi |
---|
18 | # close fd |
---|
19 | # |
---|
20 | # Revision 1.19 2004/01/24 18:40:44 dischi |
---|
21 | # add md5 as possible way to generate the id |
---|
22 | # |
---|
23 | # Revision 1.18 2003/11/07 09:43:40 dischi |
---|
24 | # make interface compatible to old one |
---|
25 | # |
---|
26 | # Revision 1.17 2003/11/05 20:58:26 dischi |
---|
27 | # detect mixed audio cds |
---|
28 | # |
---|
29 | # Revision 1.16 2003/10/18 11:13:03 dischi |
---|
30 | # patch from Cyril Lacoux to detect blank discs |
---|
31 | # |
---|
32 | # Revision 1.15 2003/09/23 13:51:27 outlyer |
---|
33 | # More *BSD fixes from Lars; repairs a problem in his older patch. |
---|
34 | # |
---|
35 | # Revision 1.14 2003/09/14 13:32:12 dischi |
---|
36 | # set self.id for audio discs in discinfo.py to make it possible to use the cache for cddb |
---|
37 | # |
---|
38 | # Revision 1.13 2003/09/14 01:41:37 outlyer |
---|
39 | # FreeBSD support |
---|
40 | # |
---|
41 | # Revision 1.12 2003/09/10 18:50:31 dischi |
---|
42 | # error handling |
---|
43 | # |
---|
44 | # Revision 1.11 2003/08/26 21:21:18 outlyer |
---|
45 | # Fix two more Python 2.3 warnings. |
---|
46 | # |
---|
47 | # Revision 1.10 2003/08/26 18:01:26 outlyer |
---|
48 | # Patch from Lars Eggert for FreeBSD support |
---|
49 | # |
---|
50 | # Revision 1.9 2003/08/23 17:54:14 dischi |
---|
51 | # move id translation of bad chars directly after parsing |
---|
52 | # |
---|
53 | # Revision 1.8 2003/07/04 15:32:52 outlyer |
---|
54 | # Fix the label so we don't try to cache into a directory instead of a file. |
---|
55 | # |
---|
56 | # Revision 1.7 2003/07/02 22:04:26 dischi |
---|
57 | # just to be save |
---|
58 | # |
---|
59 | # Revision 1.6 2003/07/02 22:03:13 dischi |
---|
60 | # cache the disc id, it cannot change in 1 sec |
---|
61 | # |
---|
62 | # Revision 1.5 2003/06/23 19:26:16 dischi |
---|
63 | # Fixed bug in the cdrommodule that the file was not closed after usage. |
---|
64 | # The result was a drive you can't eject while the program (e.g. Freevo) |
---|
65 | # is running. Added cvs log for DiscID and cdrommodule to keep track of |
---|
66 | # all changes we did for mmpython. |
---|
67 | # |
---|
68 | # Revision 1.4 2003/06/23 09:22:54 the_krow |
---|
69 | # Typo and Indentation fixes. |
---|
70 | # |
---|
71 | # Revision 1.3 2003/06/10 22:11:36 dischi |
---|
72 | # some fixes |
---|
73 | # |
---|
74 | # Revision 1.2 2003/06/10 11:50:52 dischi |
---|
75 | # Moved all ioctl calls for discs to discinfo.cdrom_disc_status. This function |
---|
76 | # uses try catch around ioctl so it will return 0 (== no disc) for systems |
---|
77 | # without ioctl (e.g. Windows) |
---|
78 | # |
---|
79 | # Revision 1.1 2003/06/10 10:56:54 the_krow |
---|
80 | # - Build try-except blocks around disc imports to make it run on platforms |
---|
81 | # not compiling / running the C extensions. |
---|
82 | # - moved DiscInfo class to disc module |
---|
83 | # - changed video.VcdInfo to be derived from CollectionInfo instead of |
---|
84 | # DiskInfo so it can be used without the cdrom extensions which are |
---|
85 | # hopefully not needed for bin-files. |
---|
86 | # |
---|
87 | # |
---|
88 | # ----------------------------------------------------------------------- |
---|
89 | # Copyright (C) 2003 Thomas Schueppel, Dirk Meyer |
---|
90 | # |
---|
91 | # This program is free software; you can redistribute it and/or modify |
---|
92 | # it under the terms of the GNU General Public License as published by |
---|
93 | # the Free Software Foundation; either version 2 of the License, or |
---|
94 | # (at your option) any later version. |
---|
95 | # |
---|
96 | # This program is distributed in the hope that it will be useful, but |
---|
97 | # WITHOUT ANY WARRANTY; without even the implied warranty of MER- |
---|
98 | # CHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General |
---|
99 | # Public License for more details. |
---|
100 | # |
---|
101 | # You should have received a copy of the GNU General Public License along |
---|
102 | # with this program; if not, write to the Free Software Foundation, Inc., |
---|
103 | # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
---|
104 | # |
---|
105 | # ----------------------------------------------------------------------- */ |
---|
106 | #endif |
---|
107 | |
---|
108 | from mmpython import mediainfo |
---|
109 | import os |
---|
110 | import re |
---|
111 | import time |
---|
112 | import array |
---|
113 | import md5 |
---|
114 | from struct import * |
---|
115 | |
---|
116 | CREATE_MD5_ID = 0 |
---|
117 | |
---|
118 | try: |
---|
119 | from fcntl import ioctl |
---|
120 | import DiscID |
---|
121 | except: |
---|
122 | print 'WARNING: failed to import ioctl, discinfo won\'t work' |
---|
123 | |
---|
124 | |
---|
125 | def cdrom_disc_status(device, handle_mix = 0): |
---|
126 | """ |
---|
127 | check the current disc in device |
---|
128 | return: no disc (0), audio cd (1), data cd (2), blank cd (3) |
---|
129 | """ |
---|
130 | CDROM_DRIVE_STATUS=0x5326 |
---|
131 | CDSL_CURRENT=( (int ) ( ~ 0 >> 1 ) ) |
---|
132 | CDROM_DISC_STATUS=0x5327 |
---|
133 | CDS_AUDIO=100 |
---|
134 | CDS_MIXED=105 |
---|
135 | CDS_DISC_OK=4 |
---|
136 | |
---|
137 | # FreeBSD ioctls - there is no CDROM.py |
---|
138 | # XXX 0xc0086305 below creates a warning, but 0xc0086305L |
---|
139 | # doesn't work. Suppress that warning for Linux users, |
---|
140 | # until a better solution can be found. |
---|
141 | if os.uname()[0] == 'FreeBSD': |
---|
142 | CDIOREADTOCENTRYS = 0xc0086305L |
---|
143 | CD_MSF_FORMAT = 2 |
---|
144 | |
---|
145 | try: |
---|
146 | fd = os.open(device, os.O_RDONLY | os.O_NONBLOCK) |
---|
147 | if os.uname()[0] == 'FreeBSD': |
---|
148 | try: |
---|
149 | cd_toc_entry = array.array('c', '\000'*4096) |
---|
150 | (address, length) = cd_toc_entry.buffer_info() |
---|
151 | buf = pack('BBHP', CD_MSF_FORMAT, 0, length, address) |
---|
152 | s = ioctl(fd, CDIOREADTOCENTRYS, buf) |
---|
153 | s = CDS_DISC_OK |
---|
154 | except: |
---|
155 | s = CDS_NO_DISC |
---|
156 | else: |
---|
157 | s = ioctl(fd, CDROM_DRIVE_STATUS, CDSL_CURRENT) |
---|
158 | except: |
---|
159 | print 'ERROR: no permission to read %s' % device |
---|
160 | print 'Media detection not possible, set drive to \'empty\'' |
---|
161 | |
---|
162 | # maybe we need to close the fd if ioctl fails, maybe |
---|
163 | # open fails and there is no fd, maye we aren't running |
---|
164 | # linux and don't have ioctl |
---|
165 | try: |
---|
166 | os.close(fd) |
---|
167 | except: |
---|
168 | pass |
---|
169 | return 0 |
---|
170 | |
---|
171 | if not s == CDS_DISC_OK: |
---|
172 | # no disc, error, whatever |
---|
173 | return 0 |
---|
174 | |
---|
175 | if os.uname()[0] == 'FreeBSD': |
---|
176 | s = 0 |
---|
177 | # We already have the TOC from above |
---|
178 | for i in range(0, 4096, 8): |
---|
179 | control = unpack('B', cd_toc_entry[i+1])[0] & 4 |
---|
180 | track = unpack('B', cd_toc_entry[i+2])[0] |
---|
181 | if track == 0: |
---|
182 | break |
---|
183 | if control == 0 and s != CDS_MIXED: |
---|
184 | s = CDS_AUDIO |
---|
185 | elif control != 0: |
---|
186 | if s == CDS_AUDIO: |
---|
187 | s = CDS_MIXED |
---|
188 | else: |
---|
189 | s = 100 + control # ugly, but encodes Linux ioctl returns |
---|
190 | elif control == 5: |
---|
191 | s = CDS_MIXED |
---|
192 | |
---|
193 | else: |
---|
194 | s = ioctl(fd, CDROM_DISC_STATUS) |
---|
195 | os.close(fd) |
---|
196 | if s == CDS_MIXED and handle_mix: |
---|
197 | return 4 |
---|
198 | if s == CDS_AUDIO or s == CDS_MIXED: |
---|
199 | return 1 |
---|
200 | |
---|
201 | try: |
---|
202 | fd = open(device, 'rb') |
---|
203 | # try to read from the disc to get information if the disc |
---|
204 | # is a rw medium not written yet |
---|
205 | |
---|
206 | fd.seek(32768) # 2048 multiple boundary for FreeBSD |
---|
207 | # FreeBSD doesn't return IOError unless we try and read: |
---|
208 | fd.read(1) |
---|
209 | except IOError: |
---|
210 | # not readable |
---|
211 | fd.close() |
---|
212 | return 3 |
---|
213 | else: |
---|
214 | # disc ok |
---|
215 | fd.close() |
---|
216 | return 2 |
---|
217 | |
---|
218 | |
---|
219 | id_cache = {} |
---|
220 | |
---|
221 | def cdrom_disc_id(device, handle_mix=0): |
---|
222 | """ |
---|
223 | return the disc id of the device or None if no disc is there |
---|
224 | """ |
---|
225 | global id_cache |
---|
226 | try: |
---|
227 | if id_cache[device][0] + 0.9 > time.time(): |
---|
228 | return id_cache[device][1:] |
---|
229 | except: |
---|
230 | pass |
---|
231 | |
---|
232 | disc_type = cdrom_disc_status(device, handle_mix=handle_mix) |
---|
233 | if disc_type == 0 or disc_type == 3: |
---|
234 | return 0, None |
---|
235 | |
---|
236 | elif disc_type == 1 or disc_type == 4: |
---|
237 | disc_id = DiscID.disc_id(device) |
---|
238 | id = '%08lx_%d' % (disc_id[0], disc_id[1]) |
---|
239 | else: |
---|
240 | f = open(device,'rb') |
---|
241 | |
---|
242 | if os.uname()[0] == 'FreeBSD': |
---|
243 | # FreeBSD can only seek to 2048 multiple boundaries. |
---|
244 | # Below works on Linux and FreeBSD: |
---|
245 | f.seek(32768) |
---|
246 | id = f.read(829) |
---|
247 | label = id[40:72] |
---|
248 | id = id[813:829] |
---|
249 | else: |
---|
250 | f.seek(0x0000832d) |
---|
251 | id = f.read(16) |
---|
252 | f.seek(32808, 0) |
---|
253 | label = f.read(32) |
---|
254 | |
---|
255 | if CREATE_MD5_ID: |
---|
256 | id_md5 = md5.new() |
---|
257 | id_md5.update(f.read(51200)) |
---|
258 | id = id_md5.hexdigest() |
---|
259 | |
---|
260 | f.close() |
---|
261 | |
---|
262 | m = re.match("^(.*[^ ]) *$", label) |
---|
263 | if m: |
---|
264 | id = '%s%s' % (id, m.group(1)) |
---|
265 | id = re.compile('[^a-zA-Z0-9()_-]').sub('_', id) |
---|
266 | |
---|
267 | |
---|
268 | id_cache[device] = time.time(), disc_type, id |
---|
269 | id = id.replace('/','_') |
---|
270 | return disc_type, id |
---|
271 | |
---|
272 | |
---|
273 | class DiscInfo(mediainfo.CollectionInfo): |
---|
274 | def isDisc(self, device): |
---|
275 | (type, self.id) = cdrom_disc_id(device, handle_mix=1) |
---|
276 | if type != 2: |
---|
277 | if type == 4: |
---|
278 | self.keys.append('mixed') |
---|
279 | self.mixed = 1 |
---|
280 | type = 1 |
---|
281 | return type |
---|
282 | |
---|
283 | if CREATE_MD5_ID: |
---|
284 | if len(self.id) == 32: |
---|
285 | self.label = '' |
---|
286 | else: |
---|
287 | self.label = self.id[32:] |
---|
288 | else: |
---|
289 | if len(self.id) == 16: |
---|
290 | self.label = '' |
---|
291 | else: |
---|
292 | self.label = self.id[16:] |
---|
293 | |
---|
294 | self.keys.append('label') |
---|
295 | return type |
---|