# -----------------------------------------------------------------------
# discinfo.py - basic class for any discs containing collections of
# media.
# -----------------------------------------------------------------------
# $Id: discinfo.py 378 2004-09-14 20:12:25Z dischi $
#
# $Log$
# Revision 1.23 2004/09/14 20:12:25 dischi
# fix future warning
#
# Revision 1.22 2004/08/28 17:27:16 dischi
# handle empty discs
#
# Revision 1.21 2004/06/25 13:20:35 dischi
# FreeBSD patches
#
# Revision 1.20 2004/02/08 17:44:05 dischi
# close fd
#
# Revision 1.19 2004/01/24 18:40:44 dischi
# add md5 as possible way to generate the id
#
# Revision 1.18 2003/11/07 09:43:40 dischi
# make interface compatible to old one
#
# Revision 1.17 2003/11/05 20:58:26 dischi
# detect mixed audio cds
#
# Revision 1.16 2003/10/18 11:13:03 dischi
# patch from Cyril Lacoux to detect blank discs
#
# Revision 1.15 2003/09/23 13:51:27 outlyer
# More *BSD fixes from Lars; repairs a problem in his older patch.
#
# Revision 1.14 2003/09/14 13:32:12 dischi
# set self.id for audio discs in discinfo.py to make it possible to use the cache for cddb
#
# Revision 1.13 2003/09/14 01:41:37 outlyer
# FreeBSD support
#
# Revision 1.12 2003/09/10 18:50:31 dischi
# error handling
#
# Revision 1.11 2003/08/26 21:21:18 outlyer
# Fix two more Python 2.3 warnings.
#
# Revision 1.10 2003/08/26 18:01:26 outlyer
# Patch from Lars Eggert for FreeBSD support
#
# Revision 1.9 2003/08/23 17:54:14 dischi
# move id translation of bad chars directly after parsing
#
# Revision 1.8 2003/07/04 15:32:52 outlyer
# Fix the label so we don't try to cache into a directory instead of a file.
#
# Revision 1.7 2003/07/02 22:04:26 dischi
# just to be save
#
# Revision 1.6 2003/07/02 22:03:13 dischi
# cache the disc id, it cannot change in 1 sec
#
# Revision 1.5 2003/06/23 19:26:16 dischi
# Fixed bug in the cdrommodule that the file was not closed after usage.
# The result was a drive you can't eject while the program (e.g. Freevo)
# is running. Added cvs log for DiscID and cdrommodule to keep track of
# all changes we did for mmpython.
#
# Revision 1.4 2003/06/23 09:22:54 the_krow
# Typo and Indentation fixes.
#
# Revision 1.3 2003/06/10 22:11:36 dischi
# some fixes
#
# Revision 1.2 2003/06/10 11:50:52 dischi
# Moved all ioctl calls for discs to discinfo.cdrom_disc_status. This function
# uses try catch around ioctl so it will return 0 (== no disc) for systems
# without ioctl (e.g. Windows)
#
# Revision 1.1 2003/06/10 10:56:54 the_krow
# - Build try-except blocks around disc imports to make it run on platforms
# not compiling / running the C extensions.
# - moved DiscInfo class to disc module
# - changed video.VcdInfo to be derived from CollectionInfo instead of
# DiskInfo so it can be used without the cdrom extensions which are
# hopefully not needed for bin-files.
#
#
# -----------------------------------------------------------------------
# Copyright (C) 2003 Thomas Schueppel, Dirk Meyer
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MER-
# CHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
# Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# ----------------------------------------------------------------------- */
#endif
from mmpython import mediainfo
import os
import re
import time
import array
import md5
from struct import *
CREATE_MD5_ID = 0
try:
from fcntl import ioctl
import DiscID
except:
print 'WARNING: failed to import ioctl, discinfo won\'t work'
def cdrom_disc_status(device, handle_mix = 0):
"""
check the current disc in device
return: no disc (0), audio cd (1), data cd (2), blank cd (3)
"""
CDROM_DRIVE_STATUS=0x5326
CDSL_CURRENT=( (int ) ( ~ 0 >> 1 ) )
CDROM_DISC_STATUS=0x5327
CDS_AUDIO=100
CDS_MIXED=105
CDS_DISC_OK=4
# FreeBSD ioctls - there is no CDROM.py
# XXX 0xc0086305 below creates a warning, but 0xc0086305L
# doesn't work. Suppress that warning for Linux users,
# until a better solution can be found.
if os.uname()[0] == 'FreeBSD':
CDIOREADTOCENTRYS = 0xc0086305L
CD_MSF_FORMAT = 2
try:
fd = os.open(device, os.O_RDONLY | os.O_NONBLOCK)
if os.uname()[0] == 'FreeBSD':
try:
cd_toc_entry = array.array('c', '\000'*4096)
(address, length) = cd_toc_entry.buffer_info()
buf = pack('BBHP', CD_MSF_FORMAT, 0, length, address)
s = ioctl(fd, CDIOREADTOCENTRYS, buf)
s = CDS_DISC_OK
except:
s = CDS_NO_DISC
else:
s = ioctl(fd, CDROM_DRIVE_STATUS, CDSL_CURRENT)
except:
print 'ERROR: no permission to read %s' % device
print 'Media detection not possible, set drive to \'empty\''
# maybe we need to close the fd if ioctl fails, maybe
# open fails and there is no fd, maye we aren't running
# linux and don't have ioctl
try:
os.close(fd)
except:
pass
return 0
if not s == CDS_DISC_OK:
# no disc, error, whatever
return 0
if os.uname()[0] == 'FreeBSD':
s = 0
# We already have the TOC from above
for i in range(0, 4096, 8):
control = unpack('B', cd_toc_entry[i+1])[0] & 4
track = unpack('B', cd_toc_entry[i+2])[0]
if track == 0:
break
if control == 0 and s != CDS_MIXED:
s = CDS_AUDIO
elif control != 0:
if s == CDS_AUDIO:
s = CDS_MIXED
else:
s = 100 + control # ugly, but encodes Linux ioctl returns
elif control == 5:
s = CDS_MIXED
else:
s = ioctl(fd, CDROM_DISC_STATUS)
os.close(fd)
if s == CDS_MIXED and handle_mix:
return 4
if s == CDS_AUDIO or s == CDS_MIXED:
return 1
try:
fd = open(device, 'rb')
# try to read from the disc to get information if the disc
# is a rw medium not written yet
fd.seek(32768) # 2048 multiple boundary for FreeBSD
# FreeBSD doesn't return IOError unless we try and read:
fd.read(1)
except IOError:
# not readable
fd.close()
return 3
else:
# disc ok
fd.close()
return 2
id_cache = {}
def cdrom_disc_id(device, handle_mix=0):
"""
return the disc id of the device or None if no disc is there
"""
global id_cache
try:
if id_cache[device][0] + 0.9 > time.time():
return id_cache[device][1:]
except:
pass
disc_type = cdrom_disc_status(device, handle_mix=handle_mix)
if disc_type == 0 or disc_type == 3:
return 0, None
elif disc_type == 1 or disc_type == 4:
disc_id = DiscID.disc_id(device)
id = '%08lx_%d' % (disc_id[0], disc_id[1])
else:
f = open(device,'rb')
if os.uname()[0] == 'FreeBSD':
# FreeBSD can only seek to 2048 multiple boundaries.
# Below works on Linux and FreeBSD:
f.seek(32768)
id = f.read(829)
label = id[40:72]
id = id[813:829]
else:
f.seek(0x0000832d)
id = f.read(16)
f.seek(32808, 0)
label = f.read(32)
if CREATE_MD5_ID:
id_md5 = md5.new()
id_md5.update(f.read(51200))
id = id_md5.hexdigest()
f.close()
m = re.match("^(.*[^ ]) *$", label)
if m:
id = '%s%s' % (id, m.group(1))
id = re.compile('[^a-zA-Z0-9()_-]').sub('_', id)
id_cache[device] = time.time(), disc_type, id
id = id.replace('/','_')
return disc_type, id
class DiscInfo(mediainfo.CollectionInfo):
def isDisc(self, device):
(type, self.id) = cdrom_disc_id(device, handle_mix=1)
if type != 2:
if type == 4:
self.keys.append('mixed')
self.mixed = 1
type = 1
return type
if CREATE_MD5_ID:
if len(self.id) == 32:
self.label = ''
else:
self.label = self.id[32:]
else:
if len(self.id) == 16:
self.label = ''
else:
self.label = self.id[16:]
self.keys.append('label')
return type
|