[4] | 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 |
---|