From 934d53057d1399d5760e005b12beebfd86bb84a7 Mon Sep 17 00:00:00 2001 From: "R. Bernstein" Date: Sun, 23 Nov 2008 23:17:02 -0500 Subject: [PATCH] First semblance of distutils setuptools. Not complete yet though. --- .gitignore | 2 + MANIFEST.in | 9 + README.txt | 52 +++ cdio.py | 887 ++++++++++++++++++++++++++++++++++++++++++ data/copying.iso | Bin 0 -> 131072 bytes data/isofs-m1.bin | Bin 0 -> 710304 bytes data/isofs-m1.cue | 3 + example/audio.py | 147 +++++++ example/cd-read.py | 121 ++++++ example/cdchange.py | 60 +++ example/device.py | 68 ++++ example/drives.py | 57 +++ example/eject.py | 59 +++ example/iso1.py | 83 ++++ example/iso2.py | 100 +++++ example/iso3.py | 100 +++++ example/tracks.py | 75 ++++ iso9660.py | 502 ++++++++++++++++++++++++ pycdio.py | 803 ++++++++++++++++++++++++++++++++++++++ pyiso9660.py | 532 +++++++++++++++++++++++++ setup.py | 34 ++ swig/audio.swg | 62 +++ swig/compat.swg | 103 +++++ swig/device.swg | 531 +++++++++++++++++++++++++ swig/device_const.swg | 143 +++++++ swig/disc.swg | 96 +++++ swig/pycdio.swg | 85 ++++ swig/pyiso9660.swg | 823 +++++++++++++++++++++++++++++++++++++++ swig/read.swg | 167 ++++++++ swig/track.swg | 206 ++++++++++ swig/types.swg | 56 +++ test/cdda.bin | Bin 0 -> 710304 bytes test/cdda.cue | 7 + test/cdda.toc | 14 + test/test-cdio.py | 209 ++++++++++ test/test-iso.py | 167 ++++++++ test/test-isocopy.py | 399 +++++++++++++++++++ 37 files changed, 6762 insertions(+) create mode 100644 .gitignore create mode 100644 MANIFEST.in create mode 100644 README.txt create mode 100644 cdio.py create mode 100644 data/copying.iso create mode 100644 data/isofs-m1.bin create mode 100644 data/isofs-m1.cue create mode 100755 example/audio.py create mode 100755 example/cd-read.py create mode 100755 example/cdchange.py create mode 100755 example/device.py create mode 100755 example/drives.py create mode 100755 example/eject.py create mode 100755 example/iso1.py create mode 100755 example/iso2.py create mode 100755 example/iso3.py create mode 100755 example/tracks.py create mode 100644 iso9660.py create mode 100644 pycdio.py create mode 100644 pyiso9660.py create mode 100755 setup.py create mode 100644 swig/audio.swg create mode 100644 swig/compat.swg create mode 100644 swig/device.swg create mode 100644 swig/device_const.swg create mode 100644 swig/disc.swg create mode 100644 swig/pycdio.swg create mode 100644 swig/pyiso9660.swg create mode 100644 swig/read.swg create mode 100644 swig/track.swg create mode 100644 swig/types.swg create mode 100644 test/cdda.bin create mode 100644 test/cdda.cue create mode 100644 test/cdda.toc create mode 100755 test/test-cdio.py create mode 100755 test/test-iso.py create mode 100755 test/test-isocopy.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..bf5d622b --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/pycdio.pyc +/pyiso9660_wrap.c diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 00000000..e437a99a --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,9 @@ +include data/copying.iso +include data/isofs-m1.bin +include data/isofs-m1.cue +include example/README +include example/*.py +include swig/*.swg +include test/cdda.bin +include test/cdda.cue +include test/cdda.toc diff --git a/README.txt b/README.txt new file mode 100644 index 00000000..881d2682 --- /dev/null +++ b/README.txt @@ -0,0 +1,52 @@ +pycdio is a Python interface to the CD Input and Control library +(libcdio). You can get the source at the same place as libcdio: + ftp://ftp.gnu.org:/pub/gnu/libcdio/pycdio-* + +The pycdio (and libcdio) libraries encapsulate CD-ROM reading and +control. Python programs wishing to be oblivious of the OS- and +device-dependent properties of a CD-ROM can use this library. + +libcdio is rather large and yet may still grow a bit. (UDF support in +libcdio may be on the horizon.) + +What is in pycdio is incomplete; over time it may grow to completion +depending on various factors: e.g. interest, whether others help +out. + +Sections of libcdio that are currently missing are the (SCSI) MMC +commands, the cdparanoia library, CD-Text handling. Of the audio +controls, I put in those things that didn't require any thought. The +ISO 9660 library is pretty complete, except file "stat" information +which is at present is pretty minimal. + +That said, what's in there is very usable (It contains probably more +access capabilities than what most media players that don't use +libcdio have.) + +The encapsulation by SWIG is done in two parts. The lower-level python +interface is called pycdio and is generated by SWIG. + +The more object-oriented module is cdio; it is a Python class that +uses pycdio. Although pycdio is perfectly usable on its own, it is +expected that cdio is what most people will use. As pycdio more +closely models the C interface, it is conceivable (if unlikely) that +diehard libcdio C users who are very familiar with that interface +could prefer that. + +It is probably possible to change the SWIG in such a way to combine +these pieces. However there are the problems. First, I'm not that much +of a SWIG expert. Second it looks as though the resulting SWIG code +would be more complex. Third the separation makes translation very +straight forward to understand and maintain: first get what's in C +into Python as a one-to-one translation. Then we implement some nice +abstraction off of that. The abstraction can be modified without +having to redo the underlying translation. (But the reverse is +generally not true: usually changes to the C-to-python translation, +pycdio, do result in small, but obvious and straightforward changes to +the abstraction layer cdio.) + +There is much to be done - you want to help out, please do so! + +Standalone documentation is missing although many of the methods, +classes and functions have some document strings. See also the +programs in the example directory. diff --git a/cdio.py b/cdio.py new file mode 100644 index 00000000..c4238f3c --- /dev/null +++ b/cdio.py @@ -0,0 +1,887 @@ +#!/usr/bin/python +# $Id: cdio.py,v 1.6 2008/05/01 16:55:03 karl Exp $ +# +# Copyright (C) 2006, 2008 Rocky Bernstein +# +# 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 3 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 +# MERCHANTABILITY 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, see . + +"""The CD Input and Control library (pycdio) encapsulates CD-ROM +reading and control. Applications wishing to be oblivious of the OS- +and device-dependent properties of a CD-ROM can use this library.""" + +import pycdio +import types + +class DeviceException(Exception): + """General device or driver exceptions""" + +class DriverError(DeviceException): pass +class DriverUnsupportedError(DeviceException): pass +class DriverUninitError(DeviceException): pass +class DriverNotPermittedError(DeviceException): pass +class DriverBadParameterError(DeviceException): pass +class DriverBadPointerError(DeviceException): pass +class NoDriverError(DeviceException): pass + +class TrackError(DeviceException): pass + + +# Note: the keys below match those the names returned by +# cdio_get_driver_name() + +drivers = { + 'Unknown' : pycdio.DRIVER_UNKNOWN, + 'AIX' : pycdio.DRIVER_AIX, + 'aix' : pycdio.DRIVER_AIX, + 'BSDI' : pycdio.DRIVER_BSDI, + 'bsdi' : pycdio.DRIVER_BSDI, + 'FreeBSD' : pycdio.DRIVER_FREEBSD, + 'freebsd' : pycdio.DRIVER_FREEBSD, + 'GNU/Linux': pycdio.DRIVER_LINUX, + 'Solaris' : pycdio.DRIVER_SOLARIS, + 'solaris' : pycdio.DRIVER_SOLARIS, + 'OS X' : pycdio.DRIVER_OSX, + 'WIN32' : pycdio.DRIVER_WIN32, + 'CDRDAO' : pycdio.DRIVER_CDRDAO, + 'cdrdao' : pycdio.DRIVER_CDRDAO, + 'BIN/CUE' : pycdio.DRIVER_BINCUE, + 'NRG' : pycdio.DRIVER_NRG, + 'Nero' : pycdio.DRIVER_NRG, + 'device' : pycdio.DRIVER_DEVICE + } + +read_mode2blocksize = { + pycdio.READ_MODE_AUDIO: pycdio.CD_FRAMESIZE_RAW, + pycdio.READ_MODE_M1F1: pycdio.M2RAW_SECTOR_SIZE, + pycdio.READ_MODE_M1F2: pycdio.CD_FRAMESIZE, + pycdio.READ_MODE_M2F1: pycdio.M2RAW_SECTOR_SIZE, + pycdio.READ_MODE_M2F2: pycdio.CD_FRAMESIZE + } + +def __possibly_raise_exception__(drc, msg=None): + """Raise a Driver Error exception on error as determined by drc""" + if drc==pycdio.DRIVER_OP_SUCCESS: + return + if drc==pycdio.DRIVER_OP_ERROR: + raise DriverError + if drc==pycdio.DRIVER_OP_UNINIT: + raise DriverUninitError + if drc==pycdio.DRIVER_OP_UNSUPPORTED: + raise DriverUnsupportedError + if drc==pycdio.DRIVER_OP_NOT_PERMITTED: + raise DriverUnsupportedError + if drc==pycdio.DRIVER_OP_BAD_PARAMETER: + raise DriverBadParameterError + if drc==pycdio.DRIVER_OP_BAD_POINTER: + raise DriverBadPointerError + if drc==pycdio.DRIVER_OP_NO_DRIVER: + raise NoDriverError + raise DeviceException('unknown exception %d' % drc) + +def close_tray(drive=None, driver_id=pycdio.DRIVER_UNKNOWN): + """close_tray(drive=None, driver_id=DRIVER_UNKNOWN) -> driver_id + + close media tray in CD drive if there is a routine to do so. + The driver id is returned. A DeviceException is thrown on error.""" + drc, found_driver_id = pycdio.close_tray(drive, driver_id) + __possibly_raise_exception__(drc) + return found_driver_id + +def get_default_device_driver(driver_id=pycdio.DRIVER_DEVICE): + """get_default_device_driver(self, driver_id=pycdio.DRIVER_DEVICE) + ->[device, driver] + + Return a string containing the default CD device if none is + specified. if driver_id is DRIVER_UNKNOWN or DRIVER_DEVICE + then one set the default device for that. + + None is returned as the device if we couldn't get a default + device.""" + result = pycdio.get_default_device_driver(driver_id) + if type(result) == type([1,2]): + return result + return None + +def get_devices(driver_id=pycdio.DRIVER_UNKNOWN): + """ + get_devices(driver_id)->[device1, device2, ...] + + Get an list of device names. + """ + result = pycdio.get_devices(driver_id) + if type(result) == types.StringType: + return [result] + else: + return result + +def get_devices_ret(driver_id=pycdio.DRIVER_UNKNOWN): + """ + get_devices_ret(driver_id)->[device1, device2, ... driver_id] + + Like get_devices, but return the p_driver_id which may be different + from the passed-in driver_id if it was pycdio.DRIVER_DEVICE or + pycdio.DRIVER_UNKNOWN. The return driver_id may be useful because + often one wants to get a drive name and then *open* it + afterwards. Giving the driver back facilitates this, and speeds things + up for libcdio as well. + """ + # FIXME: SWIG code is not removing a parameter properly, hence the [1:] + # at the end + return pycdio.get_devices_ret(driver_id)[1:] + +def get_devices_with_cap(capabilities, any=False): + """ + get_devices_with_cap(capabilities, any=False)->[device1, device2...] + Get an array of device names in search_devices that have at least + the capabilities listed by the capabities parameter. + + If any is False then every capability listed in the + extended portion of capabilities (i.e. not the basic filesystem) + must be satisified. If any is True, then if any of the + capabilities matches, we call that a success. + + To find a CD-drive of any type, use the mask pycdio.CDIO_FS_MATCH_ALL. + + The array of device names is returned or NULL if we couldn't get a + default device. It is also possible to return a non NULL but after + dereferencing the the value is NULL. This also means nothing was + found. + """ + # FIXME: SWIG code is not removing a parameter properly, hence the [1:] + # at the end + return pycdio.get_devices_with_cap(capabilities, any)[1:] + +def get_devices_with_cap_ret(capabilities, any=False): + """ + get_devices_with_cap(capabilities, any=False) + [device1, device2..., driver_id] + + Like cdio_get_devices_with_cap but we return the driver we found + as well. This is because often one wants to search for kind of drive + and then *open* it afterwards. Giving the driver back facilitates this, + and speeds things up for libcdio as well. + """ + # FIXME: SWIG code is not removing a parameter properly, hence the [1:] + # at the end + return pycdio.get_devices_with_cap_ret(capabilities, any)[1:] + +def have_driver(driver_id): + """ + have_driver(driver_id) -> bool + + Return True if we have driver driver_id. + """ + if type(driver_id)==types.IntType: + return pycdio.have_driver(driver_id) + elif type(driver_id)==types.StringType and driver_id in drivers: + ret = pycdio.have_driver(drivers[driver_id]) + if ret == 0: return False + if ret == 1: return True + raise ValueError('internal error: driver id came back %d' % ret) + else: + raise ValueError('need either a number or string driver id') + +def is_binfile(binfile_name): + """ + is_binfile(binfile_name)->cue_name + + Determine if binfile_name is the BIN file part of a CDRWIN CD + disk image. + + Return the corresponding CUE file if bin_name is a BIN file or + None if not a BIN file. + """ + return pycdio.is_binfile(binfile_name) + +def is_cuefile(cuefile_name): + """ + is_cuefile(cuefile_name)->bin_name + + Determine if cuefile_name is the CUE file part of a CDRWIN CD + disk image. + + Return the corresponding BIN file if bin_name is a CUE file or + None if not a CUE file. + """ + return pycdio.is_cuefile(cuefile_name) + +def is_device(source, driver_id=pycdio.DRIVER_UNKNOWN): + """ + is_device(source, driver_id=pycdio.DRIVER_UNKNOWN)->bool + Return True if source refers to a real hardware CD-ROM. + """ + if driver_id is None: driver_id=pycdio.DRIVER_UNKNOWN + return pycdio.is_device(source, driver_id) + +def is_nrg(nrgfile_name): + """ + is_nrg(nrgfile_name)->bool + + Determine if nrgfile_name is a Nero CD disc image + """ + return pycdio.is_nrg(nrgfile_name) + +def is_tocfile(tocfile_name): + """ + is_tocfile(tocfile_name)->bool + + Determine if tocfile_name is a cdrdao CD disc image + """ + return pycdio.is_tocfile(tocfile_name) + +def convert_drive_cap_misc(bitmask): + """Convert bit mask for miscellaneous drive properties + into a dictionary of drive capabilities""" + result={} + if bitmask & pycdio.DRIVE_CAP_ERROR: + result['DRIVE_CAP_ERROR'] = True + if bitmask & pycdio.DRIVE_CAP_UNKNOWN: + result['DRIVE_CAP_UNKNOWN'] = True + if bitmask & pycdio.DRIVE_CAP_MISC_CLOSE_TRAY: + result['DRIVE_CAP_MISC_CLOSE_TRAY'] = True + if bitmask & pycdio.DRIVE_CAP_MISC_EJECT: + result['DRIVE_CAP_MISC_EJECT'] = True + if bitmask & pycdio.DRIVE_CAP_MISC_LOCK: + result['DRIVE_CAP_MISC_LOCK'] = True + if bitmask & pycdio.DRIVE_CAP_MISC_SELECT_SPEED: + result['DRIVE_CAP_MISC_SELECT_SPEED'] = True + if bitmask & pycdio.DRIVE_CAP_MISC_SELECT_DISC: + result['DRIVE_CAP_MISC_SELECT_DISC'] = True + if bitmask & pycdio.DRIVE_CAP_MISC_MULTI_SESSION: + result['DRIVE_CAP_MISC_MULTI_SESSION'] = True + if bitmask & pycdio.DRIVE_CAP_MISC_MEDIA_CHANGED: + result['DRIVE_CAP_MISC_MEDIA_CHANGED'] = True + if bitmask & pycdio.DRIVE_CAP_MISC_RESET: + result['DRIVE_CAP_MISC_RESET'] = True + if bitmask & pycdio.DRIVE_CAP_MISC_FILE: + result['DRIVE_CAP_MISC_FILE'] = True + return result + +def convert_drive_cap_read(bitmask): + """Convert bit mask for drive read properties + into a dictionary of drive capabilities""" + result={} + if bitmask & pycdio.DRIVE_CAP_READ_AUDIO: + result['DRIVE_CAP_READ_AUDIO'] = True + if bitmask & pycdio.DRIVE_CAP_READ_CD_DA: + result['DRIVE_CAP_READ_CD_DA'] = True + if bitmask & pycdio.DRIVE_CAP_READ_CD_G: + result['DRIVE_CAP_READ_CD_G'] = True + if bitmask & pycdio.DRIVE_CAP_READ_CD_R: + result['DRIVE_CAP_READ_CD_R'] = True + if bitmask & pycdio.DRIVE_CAP_READ_CD_RW: + result['DRIVE_CAP_READ_CD_RW'] = True + if bitmask & pycdio.DRIVE_CAP_READ_DVD_R: + result['DRIVE_CAP_READ_DVD_R'] = True + if bitmask & pycdio.DRIVE_CAP_READ_DVD_PR: + result['DRIVE_CAP_READ_DVD_PR'] = True + if bitmask & pycdio.DRIVE_CAP_READ_DVD_RAM: + result['DRIVE_CAP_READ_DVD_RAM'] = True + if bitmask & pycdio.DRIVE_CAP_READ_DVD_ROM: + result['DRIVE_CAP_READ_DVD_ROM'] = True + if bitmask & pycdio.DRIVE_CAP_READ_DVD_RW: + result['DRIVE_CAP_READ_DVD_RW'] = True + if bitmask & pycdio.DRIVE_CAP_READ_DVD_RPW: + result['DRIVE_CAP_READ_DVD_RPW'] = True + if bitmask & pycdio.DRIVE_CAP_READ_C2_ERRS: + result['DRIVE_CAP_READ_C2_ERRS'] = True + if bitmask & pycdio.DRIVE_CAP_READ_MODE2_FORM1: + result['DRIVE_CAP_READ_MODE2_FORM1'] = True + if bitmask & pycdio.DRIVE_CAP_READ_MODE2_FORM2: + result['DRIVE_CAP_READ_MODE2_FORM2'] = True + if bitmask & pycdio.DRIVE_CAP_READ_MCN: + result['DRIVE_CAP_READ_MCN'] = True + if bitmask & pycdio.DRIVE_CAP_READ_ISRC: + result['DRIVE_CAP_READ_ISRC'] = True + return result + +def convert_drive_cap_write(bitmask): + """Convert bit mask for drive write properties + into a dictionary of drive capabilities""" + result={} + if bitmask & pycdio.DRIVE_CAP_WRITE_CD_R: + result['DRIVE_CAP_WRITE_CD_R'] = True + if bitmask & pycdio.DRIVE_CAP_WRITE_CD_RW: + result['DRIVE_CAP_WRITE_CD_RW'] = True + if bitmask & pycdio.DRIVE_CAP_WRITE_DVD_R: + result['DRIVE_CAP_WRITE_DVD_R'] = True + if bitmask & pycdio.DRIVE_CAP_WRITE_DVD_PR: + result['DRIVE_CAP_WRITE_DVD_PR'] = True + if bitmask & pycdio.DRIVE_CAP_WRITE_DVD_RAM: + result['DRIVE_CAP_WRITE_DVD_RAM'] = True + if bitmask & pycdio.DRIVE_CAP_WRITE_DVD_RW: + result['DRIVE_CAP_WRITE_DVD_RW'] = True + if bitmask & pycdio.DRIVE_CAP_WRITE_DVD_RPW: + result['DRIVE_CAP_WRITE_DVD_RPW'] = True + if bitmask & pycdio.DRIVE_CAP_WRITE_MT_RAINIER: + result['DRIVE_CAP_WRITE_MT_RAINIER'] = True + if bitmask & pycdio.DRIVE_CAP_WRITE_BURN_PROOF: + result['DRIVE_CAP_WRITE_BURN_PROOF'] = True + return result + +class Device: + """CD Input and control class for discs/devices""" + + def __init__(self, source=None, driver_id=None, + access_mode=None): + self.cd = None + if source is not None or driver_id is not None: + self.open(source, driver_id, access_mode) + + def audio_pause(self): + """ + audio_pause(cdio)->status + Pause playing CD through analog output. + A DeviceError exception may be raised. + """ + drc=pycdio.audio_pause(self.cd) + __possibly_raise_exception__(drc) + + def audio_play_lsn(self, start_lsn, end_lsn): + """ + auto_play_lsn(cdio, start_lsn, end_lsn)->status + + Playing CD through analog output at the given lsn to the ending lsn + A DeviceError exception may be raised. + """ + drc=pycdio.audio_play_lsn(self.cd, start_lsn, end_lsn) + __possibly_raise_exception__(drc) + + def audio_resume(self): + """ + audio_resume(cdio)->status + Resume playing an audio CD through the analog interface. + A DeviceError exception may be raised. + """ + drc=pycdio.audio_resume(self.cd) + __possibly_raise_exception__(drc) + + def audio_stop(self): + """ + audio_stop(cdio)->status + Stop playing an audio CD through the analog interface. + A DeviceError exception may be raised. + """ + drc=pycdio.audio_stop(self.cd) + __possibly_raise_exception__(drc) + + def close(self): + """close(self) + Free resources associated with p_cdio. Call this when done using + using CD reading/control operations for the current device. + """ + if self.cd is not None: + pycdio.close(self.cd) + else: + print "***No object to close" + self.cd=None + + def eject_media(self): + """eject_media(self) + Eject media in CD drive if there is a routine to do so. + A DeviceError exception may be raised. + """ + drc=pycdio.eject_media(self.cd) + self.cd = None + __possibly_raise_exception__(drc) + + ### FIXME: combine into above by testing if drive is the string + ### None versus drive = pycdio.DRIVER_UNKNOWN + def eject_media_drive(self, drive=None): + """eject_media_drive(self, drive=None) + Eject media in CD drive if there is a routine to do so. + An exception is thrown on error.""" + pycdio.eject_media_drive(drive) + + def get_arg(self, key): + """get_arg(self, key)->string + Get the value associatied with key.""" + return pycdio.get_arg(self.cd, key) + + def get_device(self): + """get_device(self)->str + Get the default CD device. + If we haven't initialized a specific device driver), + then find a suitable one and return the default device for that. + In some situations of drivers or OS's we can't find a CD device if + there is no media in it and it is possible for this routine to return + None even though there may be a hardware CD-ROM.""" + if self.cd is not None: + return pycdio.get_arg(self.cd, "source") + return pycdio.get_device(self.cd) + + def get_disc_last_lsn(self): + """ + get_disc_last_lsn(self)->int + Get the LSN of the end of the CD + + DriverError and IOError may raised on error. + """ + lsn = pycdio.get_disc_last_lsn(self.cd) + if lsn == pycdio.INVALID_LSN: + raise DriverError('Invalid LSN returned') + return lsn + + def get_disc_mode(self): + """ + get_disc_mode(p_cdio) -> str + + Get disc mode - the kind of CD (CD-DA, CD-ROM mode 1, CD-MIXED, etc. + that we've got. The notion of 'CD' is extended a little to include + DVD's. + """ + return pycdio.get_disc_mode(self.cd) + + def get_drive_cap(self): + """ + get_drive_cap(self)->(read_cap, write_cap, misc_cap) + + Get drive capabilities of device. + + In some situations of drivers or OS's we can't find a CD + device if there is no media in it. In this situation + capabilities will show up as empty even though there is a + hardware CD-ROM. get_drive_cap_dev()->(read_cap, write_cap, + misc_cap) + + Get drive capabilities of device. + + In some situations of drivers or OS's we can't find a CD + device if there is no media in it. In this situation + capabilities will show up as empty even though there is a + hardware CD-ROM.""" + + b_read_cap, b_write_cap, b_misc_cap = pycdio.get_drive_cap(self.cd) + return (convert_drive_cap_read(b_read_cap), \ + convert_drive_cap_write(b_write_cap), \ + convert_drive_cap_misc(b_misc_cap)) + + ### FIXME: combine into above by testing on the type of device. + def get_drive_cap_dev(self, device=None): + b_read_cap, b_write_cap, b_misc_cap = \ + pycdio.get_drive_cap_dev(device) + return (convert_drive_cap_read(b_read_cap), \ + convert_drive_cap_write(b_write_cap), \ + convert_drive_cap_misc(b_misc_cap)) + + def get_driver_name(self): + """ + get_driver_name(self)-> string + + return a string containing the name of the driver in use. + + An IOError exception is raised on error. + """ + return pycdio.get_driver_name(self.cd) + + def get_driver_id(self): + """ + get_driver_id(self)-> int + + Return the driver id of the driver in use. + if object has not been initialized or is None, + return pycdio.DRIVER_UNKNOWN. + """ + return pycdio.get_driver_id(self.cd) + + def get_first_track(self): + """ + get_first_track(self)->Track + + return a Track object of the first track. None is returned + if there was a problem. + """ + track = pycdio.get_first_track_num(self.cd) + if track == pycdio.INVALID_TRACK: + return None + return Track(self.cd, track) + + def get_hwinfo(self): + """ + get_hwinfo(self)->[vendor, model, release] + Get the CD-ROM hardware info via a SCSI MMC INQUIRY command. + """ + return pycdio.get_hwinfo(self.cd) + + def get_joliet_level(self): + """ + get_joliet_level(self)->int + + Return the Joliet level recognized for cdio. + This only makes sense for something that has an ISO-9660 + filesystem. + """ + return pycdio.get_joliet_level(self.cd) + + def get_last_session(self): + """get_last_session(self) -> int + Get the LSN of the first track of the last session of on the CD. + An exception is thrown on error.""" + drc, session = pycdio.get_last_session(self.cd) + __possibly_raise_exception__(drc) + return session + + def get_last_track(self): + """ + get_last_track(self)->Track + + return a Track object of the first track. None is returned + if there was a problem. + """ + track = pycdio.get_last_track_num(self.cd) + if track == pycdio.INVALID_TRACK: + return None + return Track(self.cd, track) + + def get_mcn(self): + """ + get_mcn(self) -> str + + Get the media catalog number (MCN) from the CD. + """ + return pycdio.get_mcn(self.cd) + + def get_media_changed(self): + """ + get_media_changed(self) -> bool + + Find out if media has changed since the last call. + Return True if media has changed since last call. An exception + Error is given on error. + """ + drc = pycdio.get_media_changed(self.cd) + if drc == 0: return False + if drc == 1: return True + __possibly_raise_exception__(drc) + raise DeviceException('Unknown return value %d' % drc) + + def get_num_tracks(self): + """ + get_num_tracks(self)->int + + Return the number of tracks on the CD. + A TrackError or IOError exception may be raised on error. + """ + track = pycdio.get_num_tracks(self.cd) + if track == pycdio.INVALID_TRACK: + raise TrackError('Invalid track returned') + return track + + def get_track(self, track_num): + """ + get_track(self, track_num)->track + + Return a track object for the given track number. + """ + return Track(self.cd, track_num) + + def get_track_for_lsn(self, lsn): + """ + get_track_for_lsn(self, lsn)->Track + + Find the track which contains lsn. + None is returned if the lsn outside of the CD or + if there was some error. + + If the lsn is before the pregap of the first track, + A track object with a 0 track is returned. + Otherwise we return the track that spans the lsn. + """ + track = pycdio.get_last_track_num(self.cd) + if track == pycdio.INVALID_TRACK: + return None + return Track(self.cd, track) + + def have_ATAPI(self): + """have_ATAPI(self)->bool + return True if CD-ROM understand ATAPI commands.""" + return pycdio.have_ATAPI(self.cd) + + def lseek(self, offset, whence): + """ + lseek(self, offset, whence)->int + Reposition read offset + Similar to (if not the same as) libc's fseek() + + cdio is object to get adjested, offset is amount to seek and + whence is like corresponding parameter in libc's lseek, e.g. + it should be SEEK_SET or SEEK_END. + + the offset is returned or -1 on error. + """ + return pycdio.lseek(self.cd, offset, whence) + + def open(self, source=None, driver_id=pycdio.DRIVER_UNKNOWN, + access_mode=None): + """ + open(self, source=None, driver_id=pycdio.DRIVER_UNKNOWN, + access_mode=None) + + Sets up to read from place specified by source, driver_id and + access mode. This should be called before using any other routine + except those that act on a CD-ROM drive by name. + + If None is given as the source, we'll use the default driver device. + If None is given as the driver_id, we'll find a suitable device driver. + + If device object was, previously opened it is closed first. + + Device is opened so that subsequent operations can be performed. + + """ + if driver_id is None: driver_id=pycdio.DRIVER_UNKNOWN + if self.cd is not None: + self.close() + self.cd = pycdio.open_cd(source, driver_id, access_mode) + + def read(self, size): + """ + read(self, size)->[size, data] + + Reads the next size bytes. + Similar to (if not the same as) libc's read() + + The number of bytes read and the data is returned. + A DeviceError exception may be raised. + """ + size, data = pycdio.read_cd(self.cd, size) + __possibly_raise_exception__(size) + return [size, data] + + def read_data_blocks(self, lsn, blocks=1): + """ + read_data_blocks(blocks, lsn, blocks=1)->[size, data] + + Reads a number of data sectors (AKA blocks). + + lsn is sector to read, bytes is the number of bytes. + A DeviceError exception may be raised. + """ + size = pycdio.ISO_BLOCKSIZE*blocks + size, data = pycdio.read_data_bytes(self.cd, size, lsn, + pycdio.ISO_BLOCKSIZE) + if size < 0: + __possibly_raise_exception__(size) + return [size, data] + + def read_sectors(self, lsn, read_mode, blocks=1): + """ + read_sectors(self, lsn, read_mode, blocks=1)->[blocks, data] + Reads a number of sectors (AKA blocks). + + lsn is sector to read, bytes is the number of bytes. + + If read_mode is pycdio.MODE_AUDIO, the return buffer size will be + truncated to multiple of pycdio.CDIO_FRAMESIZE_RAW i_blocks bytes. + + If read_mode is pycdio.MODE_DATA, buffer will be truncated to a + multiple of pycdio.ISO_BLOCKSIZE, pycdio.M1RAW_SECTOR_SIZE or + pycdio.M2F2_SECTOR_SIZE bytes depending on what mode the data is in. + + If read_mode is pycdio.MODE_M2F1, buffer will be truncated to a + multiple of pycdio.M2RAW_SECTOR_SIZE bytes. + + If read_mode is pycdio.MODE_M2F2, the return buffer size will be + truncated to a multiple of pycdio.CD_FRAMESIZE bytes. + + The number of bytes read and the data is returned. + A DeviceError exception may be raised. + """ + try: + blocksize = read_mode2blocksize[read_mode] + size = blocks * blocksize + except KeyError: + raise DriverBadParameterError ('Bad read mode %d' % read_mode) + size, data = pycdio.read_sectors(self.cd, size, lsn, read_mode) + if size < 0: + __possibly_raise_exception__(size) + blocks = size / blocksize + return [blocks, data] + + def set_blocksize(self, blocksize): + """set_blocksize(self, blocksize) + Set the blocksize for subsequent reads. + An exception is thrown on error. + """ + drc = pycdio.set_blocksize(self.cd, blocksize) + __possibly_raise_exception__(drc) + + def set_speed(self, speed): + """set_speed(self, speed) + Set the drive speed. An exception is thrown on error.""" + drc = pycdio.set_speed(self.cd, speed) + __possibly_raise_exception__(drc) + +class Track: + """CD Input and control track class""" + + def __init__(self, device, track_num): + + if type(track_num) != types.IntType: + raise TrackError('track number parameter is not an integer') + self.track = track_num + + # See if the device parameter is a string or + # a device object. + if type(device) == types.StringType: + self.device = Device(device) + else: + test_device=Device() + ## FIXME: would like a way to test if device + ## is a PySwigObject + self.device = device + + def get_audio_channels(self): + """ + get_audio_channels(self, track)->int + + Return number of channels in track: 2 or 4 + Not meaningful if track is not an audio track. + An exception can be raised on error. + """ + channels = pycdio.get_track_channels(self.device, self.track) + if -2 == channels: + raise DriverUnsupportedError + elif -1 == channels: + raise TrackError + else: + return channels + + def get_copy_permit(self): + """ + get_copy_permit(self, track)->int + + Return copy protection status on a track. Is this meaningful + not an audio track? + + """ + if pycdio.get_track_copy_permit(self.device, self.track): + return "OK" + else: + return "no" + + def get_format(self): + """ + get_format(self)->format + + Get the format (e.g. 'audio', 'mode2', 'mode1') of track. + """ + return pycdio.get_track_format(self.device, self.track) + + def get_last_lsn(self): + """ + get_last_lsn(self)->lsn + + Return the ending LSN for a track + A TrackError or IOError exception may be raised on error. + """ + lsn = pycdio.get_track_last_lsn(self.device, self.track) + if lsn == pycdio.INVALID_LSN: + raise TrackError('Invalid LSN returned') + return lsn + + def get_lba(self): + """ + get_lsn(self)->lba + + Return the starting LBA for a track + A TrackError exception is raised on error. + """ + lba = pycdio.get_track_lba(self.device, self.track) + if lba == pycdio.INVALID_LBA: + raise TrackError('Invalid LBA returned') + return lba + + def get_lsn(self): + """ + get_lsn(self)->lsn + + Return the starting LSN for a track + A TrackError exception is raised on error. + """ + lsn = pycdio.get_track_lsn(self.device, self.track) + if lsn == pycdio.INVALID_LSN: + raise TrackError('Invalid LSN returned') + return lsn + + def get_msf(self): + """ + get_msf(self)->str + + Return the starting MSF (minutes/secs/frames) for track number track. + Track numbers usually start at something greater than 0, usually 1. + + Returns string of the form mm:ss:ff if all good, or string None on + error. + """ + return pycdio.get_track_msf(self.device, self.track) + + def get_preemphasis(self): + """ + get_preemphaisis(self)->result + + Get linear preemphasis status on an audio track. + This is not meaningful if not an audio track? + A TrackError exception is raised on error. + """ + rc = pycdio.get_track_preemphasis(self.device, self.track) + if rc == pycdio.TRACK_FLAG_FALSE: + return 'none' + elif rc == pycdio.TRACK_FLAG_TRUE: + return 'preemphasis' + elif rc == pycdio.TRACK_FLAG_UNKNOWN: + return 'unknown' + else: + raise TrackError('Invalid return value %d' % d) + + def get_track_sec_count(self): + """ + get_track_sec_count(self)->int + + Get the number of sectors between this track an the next. This + includes any pregap sectors before the start of the next track. + Track numbers usually start at something + greater than 0, usually 1. + + A TrackError exception is raised on error. + """ + sec_count = pycdio.get_track_sec_count(self.device, self.track) + if sec_count == 0: + raise TrackError + return sec_count + + def is_green(self): + """ + is_track_green(self, track) -> bool + + Return True if we have XA data (green, mode2 form1) or + XA data (green, mode2 form2). That is track begins: + sync - header - subheader + 12 4 - 8 + """ + return pycdio.is_track_green(self.device, self.track) + + def set_track(self, track_num): + """ + set_track(self, track_num) + + Set a new track number. + """ + self.track = track_num + + +# +# Local variables: +# mode: Python +# End: diff --git a/data/copying.iso b/data/copying.iso new file mode 100644 index 0000000000000000000000000000000000000000..b519e7fa1a27acc119a50a4fcee6eb687bc8a441 GIT binary patch literal 131072 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 z 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�(950HF(GaBbFhpU(ikk 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

J{N>n|(y!_#XOR7wJ4{~n} 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 Xw3mKWzcBo%{~Du>HDQ9sxf` +# +# 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 3 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 +# MERCHANTABILITY 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, see . + +import sys, os +from optparse import OptionParser + +libdir = os.path.join(os.path.dirname(__file__), '..') +if libdir[-1] != os.path.sep: + libdir += os.path.sep +sys.path.insert(0, libdir) +import pycdio +import cdio + +def process_options(): + program = os.path.basename(sys.argv[0]) + usage_str="""%s [options] [device] + Issue analog audio CD controls - like playing""" % program + + optparser = OptionParser(usage=usage_str) + + optparser.add_option("-c", "--close", dest="close", + action="store", type='string', + help="close CD tray") + optparser.add_option("-p", "--play", dest="play", + action="store_true", default=False, + help="Play entire CD") + optparser.add_option("-P", "--pause", dest="pause", + action="store_true", default=False, + help="pause playing") + optparser.add_option("-E", "--eject", dest="eject", + action="store_true", default=False, + help="Eject CD") + optparser.add_option("-r", "--resume", dest="resume", + action="store_true", default=False, + help="resume playing") + optparser.add_option("-s", "--stop", dest="stop", + action="store_true", default=False, + help="stop playing") + optparser.add_option("-t", "--track", dest="track", + action="store", type='int', + help="play a single track") + return optparser.parse_args() + +opts, argv = process_options() + +# Handle closing the CD-ROM tray if that was specified. +if opts.close: + try: + device_name = opts.close + cdio.close_tray(device_name) + except cdio.DeviceException: + print "Closing tray of CD-ROM drive %s failed" % device_name + + +# While sys.argv[0] is a program name and sys.argv[1] the first +# option, argv[0] is the first unprocessed option -- roughly +# the equivalent of sys.argv[1]. +if argv[0:]: + try: + d = cdio.Device(argv[0]) + except IOError: + print "Problem opening CD-ROM: %s" % device_name + sys.exit(1) +else: + try: + d = cdio.Device(driver_id=pycdio.DRIVER_UNKNOWN) + except IOError: + print "Problem finding a CD-ROM" + sys.exit(1) + +device_name=d.get_device() +if opts.play: + if d.get_disc_mode() != 'CD-DA': + print "The disc on %s I found was not CD-DA, but %s" \ + % (device_name, d.get_disc_mode()) + print "I have bad feeling about trying to play this..." + + try: + start_lsn = d.get_first_track().get_lsn() + end_lsn=d.get_disc_last_lsn() + print "Playing entire CD on %s" % device_name + d.audio_play_lsn(start_lsn, end_lsn) + except cdio.TrackError: + pass + +elif opts.track is not None: + try: + if opts.track > d.get_last_track().track: + print "Requested track %d but CD only has %d tracks" \ + % (opts.track, d.get_last_track().track) + sys.exit(2) + if opts.track < d.get_first_track().track: + print "Requested track %d but first track on CD is %d" \ + % (opts.track, d.get_first_track().track) + sys.exit(2) + print "Playing track %d on %s" % (opts.track, device_name) + start_lsn = d.get_track(opts.track).get_lsn() + end_lsn = d.get_track(opts.track+1).get_lsn() + d.audio_play_lsn(start_lsn, end_lsn) + except cdio.TrackError: + pass + +elif opts.pause: + try: + print "Pausing playing in drive %s" % device_name + d.audio_pause() + except cdio.DeviceException: + print "Pause failed on drive %s" % device_name +elif opts.resume: + try: + print "Resuming playing in drive %s" % device_name + d.audio_resume() + except cdio.DeviceException: + print "Resume failed on drive %s" % device_name +elif opts.stop: + try: + print "Stopping playing in drive %s" % device_name + d.audio_stop() + except cdio.DeviceException: + print "Stopping failed on drive %s" % device_name +elif opts.eject: + try: + print "Ejecting CD in drive %s" % device_name + d.eject_media() + except cdio.DriverUnsupportedError: + print "Eject not supported for %s" % device_name + except cdio.DeviceException: + print "Eject of CD-ROM drive %s failed" % device_name + +d.close() diff --git a/example/cd-read.py b/example/cd-read.py new file mode 100755 index 00000000..c195f5e1 --- /dev/null +++ b/example/cd-read.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python +"""Program to read CD blocks. See read-cd from the libcdio distribution +for a more complete program. +""" +# +# Copyright (C) 2006, 2008 Rocky Bernstein +# +# 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 3 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 +# MERCHANTABILITY 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, see . + +import sys, os +from optparse import OptionParser + +libdir = os.path.join(os.path.dirname(__file__), '..') +if libdir[-1] != os.path.sep: + libdir += os.path.sep +sys.path.insert(0, libdir) +import pycdio +import cdio + +read_modes = { + 'audio': pycdio.READ_MODE_AUDIO, + 'm1f1' : pycdio.READ_MODE_M1F1, + 'm1f2' : pycdio.READ_MODE_M1F2, + 'm2f1' : pycdio.READ_MODE_M2F1, + 'm2f2' : pycdio.READ_MODE_M2F2, + 'data' : None + } + +def process_options(): + program = os.path.basename(sys.argv[0]) + usage_str="""%s --mode *mode* [options] [device] + Read blocks of a CD""" % program + + optparser = OptionParser(usage=usage_str) + + optparser.add_option("-m", "--mode", dest="mode", + action="store", type='string', + help="CD Reading mode: audio, m1f1, m1f2, " + + "m2f1 or m2f2") + optparser.add_option("-s", "--start", dest="start", + action="store", type='int', default=1, + help="Starting block") + optparser.add_option("-n", "--number", dest="number", + action="store", type='int', default=1, + help="Number of blocks") + (opts, argv) = optparser.parse_args() + if opts.mode is None: + print "Mode option must given " + \ + "(and one of audio, m1f1, m1f2, m1f2 or m1f2)." + sys.exit(1) + try: + read_mode = read_modes[opts.mode] + except KeyError: + print "Need to use the --mode option with one of" + \ + "audio, m1f1, m1f2, m1f2 or m1f2" + sys.exit(2) + + return opts, argv, read_mode + +import re +PRINTABLE = r'[ -~]' +pat = re.compile(PRINTABLE) +def isprint(c): + global pat + return pat.match(c) + +def hexdump (buffer, just_hex=False): + i = 0 + while i < len(buffer): + if (i % 16) == 0: + print ("0x%04x: " % i), + print "%02x%02x" % (ord(buffer[i]), ord(buffer[i+1])), + if (i % 16) == 14: + if not just_hex: + s = " " + for j in range(i-14, i+2): + if isprint(buffer[j]): + s += buffer[j] + else: + s += '.' + print s, + print + i += 2 + print + return + +opts, argv, read_mode = process_options() +# While sys.argv[0] is a program name and sys.argv[1] the first +# option, argv[0] is the first unprocessed option -- roughly +# the equivalent of sys.argv[1]. +if argv[0:]: + try: + d = cdio.Device(argv[0]) + except IOError: + print "Problem opening CD-ROM: %s" % argv[0] + sys.exit(1) +else: + try: + d = cdio.Device(driver_id=pycdio.DRIVER_UNKNOWN) + except IOError: + print "Problem finding a CD-ROM" + sys.exit(1) + +## All this setup just to issue this one of these commands. +if read_mode == None: + blocks, data=d.read_data_blocks(opts.start, opts.number) +else: + blocks, data=d.read_sectors(opts.start, read_mode, opts.number) +hexdump(data) + diff --git a/example/cdchange.py b/example/cdchange.py new file mode 100755 index 00000000..0fcfee9d --- /dev/null +++ b/example/cdchange.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python +"""Program to show CD media changing""" +# +# Copyright (C) 2006, 2008 Rocky Bernstein +# +# 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 +# MERCHANTABILITY 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 +# +import sys, time +libdir = os.path.join(os.path.dirname(__file__), '..') +if libdir[-1] != os.path.sep: + libdir += os.path.sep +sys.path.insert(0, libdir) +import pycdio +import cdio +from time import sleep + +sleep_time = 15 +if sys.argv[1:]: + try: + d = cdio.Device(sys.argv[1]) + except IOError: + print "Problem opening CD-ROM: %s" % sys.argv[1] + sys.exit(1) + if sys.argv[2:]: + try: + sleep_time = int(sys.argv[2]) + except ValueError, msg: + print "Invalid sleep parameter %s" % sys.argv[2] + sys.exit(2) +else: + try: + d = cdio.Device(driver_id=pycdio.DRIVER_UNKNOWN) + except IOError: + print "Problem finding a CD-ROM" + sys.exit(1) + +if d.get_media_changed(): + print "Initial media status: changed" +else: + print "Initial media status: not changed" + +print "Giving you %lu seconds to change CD if you want to do so." % sleep_time +sleep(sleep_time) +if d.get_media_changed(): + print "Media status: changed" +else: + print "Media status: not changed" +d.close() diff --git a/example/device.py b/example/device.py new file mode 100755 index 00000000..5e89c972 --- /dev/null +++ b/example/device.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python +"""Program to show CD-ROM device information""" +# +# Copyright (C) 2006, 2008 Rocky Bernstein +# +# 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 3 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 +# MERCHANTABILITY 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, see . + +import os, sys +libdir = os.path.join(os.path.dirname(__file__), '..') +if libdir[-1] != os.path.sep: + libdir += os.path.sep +sys.path.insert(0, libdir) +import pycdio +import cdio + +def sort_dict_keys(dict): + """Return sorted keys of a dictionary. + There's probably an easier way to do this that I'm not aware of.""" + keys=dict.keys() + keys.sort() + return keys + +if sys.argv[1:]: + try: + drive_name = sys.argv[1] + d = cdio.Device(sys.argv[1]) + except IOError: + print "Problem opening CD-ROM: %s" % drive_name + sys.exit(1) +else: + try: + d = cdio.Device(driver_id=pycdio.DRIVER_UNKNOWN) + drive_name = d.get_device() + except IOError: + print "Problem finding a CD-ROM" + sys.exit(1) + +# Should there should be no "ok"? +ok, vendor, model, release = d.get_hwinfo() + +print "drive: %s, vendor: %s, model: %s, release: %s" \ + % (drive_name, vendor, model, release) + +read_cap, write_cap, misc_cap = d.get_drive_cap() +print "Drive Capabilities for %s..." % drive_name + +print "\n".join(cap for cap in sort_dict_keys(read_cap) + + sort_dict_keys(write_cap) + sort_dict_keys(misc_cap)) + +print "\nDriver Availabiliity..." +for driver_name in sort_dict_keys(cdio.drivers): + try: + if cdio.have_driver(driver_name): + print "Driver %s is installed." % driver_name + except ValueError: + pass +d.close() diff --git a/example/drives.py b/example/drives.py new file mode 100755 index 00000000..294e6264 --- /dev/null +++ b/example/drives.py @@ -0,0 +1,57 @@ +#!/usr/bin/python +# $Id: drives.py,v 1.2 2008/11/24 00:53:59 rocky Exp $ +"""Program to read CD blocks. See read-cd from the libcdio distribution +for a more complete program.""" + +# +# Copyright (C) 2006, 2008 Rocky Bernstein +# +# 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 +# MERCHANTABILITY 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., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA. +# + +import os, sys +libdir = os.path.join(os.path.dirname(__file__), '..') +if libdir[-1] != os.path.sep: + libdir += os.path.sep +sys.path.insert(0, libdir) +import pycdio +import cdio + +def print_drive_class(msg, bitmask, any): + cd_drives = cdio.get_devices_with_cap(bitmask, any) + + print "%s..." % msg + for drive in cd_drives: + print "Drive %s" % drive + print "-----" + +cd_drives = cdio.get_devices(pycdio.DRIVER_DEVICE) +for drive in cd_drives: + print "Drive %s" % drive + +print "-----" + +sys.exit(0) +# FIXME: there's a bug in get_drive_with_cap that corrupts memory. +print_drive_class("All CD-ROM drives (again)", pycdio.FS_MATCH_ALL, False); +print_drive_class("All CD-DA drives...", pycdio.FS_AUDIO, False); +print_drive_class("All drives with ISO 9660...", pycdio.FS_ISO_9660, False); +print_drive_class("VCD drives...", + (pycdio.FS_ANAL_SVCD|pycdio.FS_ANAL_CVD| + pycdio.FS_ANAL_VIDEOCD|pycdio.FS_UNKNOWN), True); + + + diff --git a/example/eject.py b/example/eject.py new file mode 100755 index 00000000..85e1d65f --- /dev/null +++ b/example/eject.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python +"""Program to Eject and close CD-ROM drive""" +# +# Copyright (C) 2006, 2008 Rocky Bernstein +# +# 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 3 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 +# MERCHANTABILITY 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, see . + +import os, sys +libdir = os.path.join(os.path.dirname(__file__), '..') +if libdir[-1] != os.path.sep: + libdir += os.path.sep +sys.path.insert(0, libdir) +import pycdio +import cdio + +if sys.argv[1:]: + try: + drive_name=sys.argv[1] + d = cdio.Device(drive_name) + except IOError: + print "Problem opening CD-ROM: %s" % drive_name + sys.exit(1) +else: + try: + d = cdio.Device(driver_id=pycdio.DRIVER_UNKNOWN) + drive_name = d.get_device() + except IOError: + print "Problem finding a CD-ROM" + sys.exit(1) + +try: + + print "Ejecting CD in drive %s" % drive_name + d.eject_media() + try: + cdio.close_tray(drive_name) + print "Closed tray of CD-ROM drive %s" % drive_name + except cdio.DeviceException: + print "Closing tray of CD-ROM drive %s failed" % drive_name + +except cdio.DriverUnsupportedError: + print "Eject not supported for %s" % drive_name +except cdio.DeviceException: + print "Eject of CD-ROM drive %s failed" % drive_name + + + + diff --git a/example/iso1.py b/example/iso1.py new file mode 100755 index 00000000..d2846987 --- /dev/null +++ b/example/iso1.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python + +""" A simple program to show using libiso9660 to list files in a directory of + an ISO-9660 image. + + If a single argument is given, it is used as the ISO 9660 image to + use in the listing. Otherwise a compiled-in default ISO 9660 image + name (that comes with the libcdio distribution) will be used.""" + +# Copyright (C) 2006, 2008 Rocky Bernstein +# +# 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 3 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 +# MERCHANTABILITY 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, see . + +import os, sys +libdir = os.path.join(os.path.dirname(__file__), '..') +if libdir[-1] != os.path.sep: + libdir += os.path.sep +sys.path.insert(0, libdir) +import iso9660 + +# The default ISO 9660 image if none given +ISO9660_IMAGE_PATH="../data" +ISO9660_IMAGE=os.path.join(ISO9660_IMAGE_PATH, "copying.iso") + +if len(sys.argv) > 1: + iso_image_fname = sys.argv[1] +else: + iso_image_fname = ISO9660_IMAGE + +iso = iso9660.ISO9660.IFS(source=iso_image_fname) + +if not iso.is_open(): + print "Sorry, couldn't open %s as an ISO-9660 image." % iso_image_fname + sys.exit(1) + +path = '/' + +file_stats = iso.readdir(path) + +id = iso.get_application_id() +if id is not None: print "Application ID: %s" % id + +id = iso.get_preparer_id() +if id is not None: print "Preparer ID: %s" % id + +id = iso.get_publisher_id() +if id is not None: print "Publisher ID: %s" % id + +id = iso.get_system_id() +if id is not None: print "System ID: %s" % id + +id = iso.get_volume_id() +if id is not None: print "Volume ID: %s" % id + +id = iso.get_volumeset_id() +if id is not None: print "Volumeset ID: %s" % id + +dir_tr=['-', 'd'] + +for stat in file_stats: + # FIXME + filename = stat[0] + LSN = stat[1] + size = stat[2] + sec_size = stat[3] + is_dir = stat[4] == 2 + print "%s [LSN %6d] %8d %s%s" % (dir_tr[is_dir], LSN, size, path, + iso9660.name_translate(filename)) +iso.close() +sys.exit(0) + + diff --git a/example/iso2.py b/example/iso2.py new file mode 100755 index 00000000..3fa65856 --- /dev/null +++ b/example/iso2.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python +"""A program to show using iso9660 to extract a file from an ISO-9660 +image. + +If a single argument is given, it is used as the ISO 9660 image to use +in the extraction. Otherwise a compiled in default ISO 9660 image name +(that comes with the libcdio distribution) will be used. A program to +show using iso9660 to extract a file from an ISO-9660 image.""" +# +# Copyright (C) 2006, 2008 Rocky Bernstein +# +# 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 3 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 +# MERCHANTABILITY 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, see . + +import os, sys +libdir = os.path.join(os.path.dirname(__file__), '..') +if libdir[-1] != os.path.sep: + libdir += os.path.sep +sys.path.insert(0, libdir) +import pycdio +import iso9660 + +# Python has rounding (round) and trucation (int), but what about an integer +# ceiling function? Until I learn what it is... +def ceil(x): + return int(round(x+0.5)) + +# The default CD image if none given +cd_image_path="../data" +cd_image_fname=os.path.join(cd_image_path, "isofs-m1.cue") + +# File to extract if none given. +iso9660_path="/" +local_filename="COPYING" + +if len(sys.argv) > 1: + cd_image_fname = sys.argv[1] + if len(sys.argv) > 2: + local_filename = sys.argv[1] + if len(sys.argv) > 3: + print """ +usage: %s [CD-ROM-or-image [filename]] +Extracts filename from CD-ROM-or-image. +""" % sys.argv[0] + sys.exit(1) + +try: + cd = iso9660.ISO9660.FS(source=cd_image_fname) +except: + print "Sorry, couldn't open %s as a CD image." % cd_image_fname + sys.exit(1) + +statbuf = cd.stat (local_filename, False) + +if statbuf is None: + print "Could not get ISO-9660 file information for file %s in %s" \ + % (local_filename, cd_image_fname) + cd.close() + sys.exit(2) + +try: + OUTPUT=os.open(local_filename, os.O_CREAT|os.O_WRONLY, 0664) +except: + print "Can't open %s for writing" % local_filename + +# Copy the blocks from the ISO-9660 filesystem to the local filesystem. +blocks = ceil(statbuf['size'] / pycdio.ISO_BLOCKSIZE) +for i in range(blocks): + lsn = statbuf['LSN'] + i + size, buf = cd.read_data_blocks(lsn) + + if size < 0: + print "Error reading ISO 9660 file %s at LSN %d" % ( + local_filename, lsn) + sys.exit(4) + + os.write(OUTPUT, buf) + + +# Make sure the file size has the exact same byte size. Without the +# truncate below, the file will a multiple of ISO_BLOCKSIZE. + +os.ftruncate(OUTPUT, statbuf['size']) + +print "Extraction of file '%s' from %s successful." % ( + local_filename, cd_image_fname) + +os.close(OUTPUT) +cd.close() +sys.exit(0) diff --git a/example/iso3.py b/example/iso3.py new file mode 100755 index 00000000..b57baaad --- /dev/null +++ b/example/iso3.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python +"""A program to show using iso9660 to extract a file from an ISO-9660 +image. If a single argument is given, it is used as the ISO 9660 +image to use in the extraction. Otherwise a compiled in default ISO +9660 image name (that comes with the libcdio distribution) will be +used.""" +# +# Copyright (C) 2006, 2008 Rocky Bernstein +# +# 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 3 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 +# MERCHANTABILITY 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, see . + +import os, sys +libdir = os.path.join(os.path.dirname(__file__), '..') +if libdir[-1] != os.path.sep: + libdir += os.path.sep +sys.path.insert(0, libdir) +import pycdio +import iso9660 + +# Python has rounding (round) and truncation (int), but what about an integer +# ceiling function? Until I learn what it is... +def ceil(x): + return int(round(x+0.5)) + +# The default ISO 9660 image if none given +ISO9660_IMAGE_PATH="../data" +ISO9660_IMAGE=os.path.join(ISO9660_IMAGE_PATH, "copying.iso") + +# File to extract if none given. +local_filename="copying" + +iso_image_fname = ISO9660_IMAGE + +if len(sys.argv) > 1: + iso_image_fname = sys.argv[1] + if len(sys.argv) > 2: + local_filename = sys.argv[1] + if len(sys.argv) > 3: + print """ +usage: %s [ISO9660-image.ISO [filename]] +Extracts filename from ISO9660-image.ISO. +""" % sys.argv[0] + sys.exit(1) + +iso = iso9660.ISO9660.IFS(source=iso_image_fname) + +if not iso.is_open(): + print "Sorry, couldn't open %s as an ISO-9660 image." % iso_image_fname + sys.exit(1) + + +statbuf = iso.stat (local_filename, True) + +if statbuf is None: + print "Could not get ISO-9660 file information for file %s" \ + % local_filename + iso.close() + sys.exit(2) + +try: + OUTPUT=os.open(local_filename, os.O_CREAT|os.O_WRONLY, 0664) +except: + print "Can't open %s for writing" % local_filename + +# Copy the blocks from the ISO-9660 filesystem to the local filesystem. +blocks = ceil(statbuf['size'] / pycdio.ISO_BLOCKSIZE) +for i in range(blocks): + lsn = statbuf['LSN'] + i + size, buf = iso.seek_read (lsn) + + if size <= 0: + print "Error reading ISO 9660 file %s at LSN %d" % ( + local_filename, lsn) + sys.exit(4) + + os.write(OUTPUT, buf) + + +# Make sure the file size has the exact same byte size. Without the +# truncate below, the file will a multiple of ISO_BLOCKSIZE. + +os.ftruncate(OUTPUT, statbuf['size']) + +print "Extraction of file '%s' from %s successful." % ( + local_filename, iso_image_fname) + +os.close(OUTPUT) +iso.close() +sys.exit(0) diff --git a/example/tracks.py b/example/tracks.py new file mode 100755 index 00000000..c4aaea3d --- /dev/null +++ b/example/tracks.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python +"""A program to show CD information.""" +# +# Copyright (C) 2006, 2008 Rocky Bernstein +# +# 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 3 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 +# MERCHANTABILITY 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, see . + +import os, sys +libdir = os.path.join(os.path.dirname(__file__), '..') +if libdir[-1] != os.path.sep: + libdir += os.path.sep +sys.path.insert(0, libdir) +import pycdio +import cdio + +if sys.argv[1:]: + try: + d = cdio.Device(sys.argv[1]) + except IOError: + print "Problem opening CD-ROM: %s" % sys.argv[1] + sys.exit(1) +else: + try: + d = cdio.Device(driver_id=pycdio.DRIVER_UNKNOWN) + except IOError: + print "Problem finding a CD-ROM" + sys.exit(1) + +t = d.get_first_track() +if t is None: + print "Problem getting first track" + sys.exit(2) + +first_track = t.track +num_tracks = d.get_num_tracks() +last_track = first_track+num_tracks-1 + +try: + last_session = d.get_last_session() + print "CD-ROM %s has %d track(s) and %d session(s)." % \ + (d.get_device(), d.get_num_tracks(), last_session) +except cdio.DriverUnsupportedError: + print "CD-ROM %s has %d track(s). " % (d.get_device(), d.get_num_tracks()) + +print "Track format is %s." % d.get_disc_mode() + +mcn = d.get_mcn() +if mcn: + print "Media Catalog Number: %s" % mcn + +print "%3s: %-6s %s" % ("#", "LSN", "Format") +i=first_track +while (i <= last_track): + try: + t = d.get_track(i) + print "%3d: %06lu %-6s %s" % (t.track, t.get_lsn(), + t.get_msf(), t.get_format()) + except cdio.TrackError: + pass + i += 1 + +print "%3X: %06lu leadout" \ + % (pycdio.CDROM_LEADOUT_TRACK, d.get_disc_last_lsn()) +d.close() diff --git a/iso9660.py b/iso9660.py new file mode 100644 index 00000000..d3f436ba --- /dev/null +++ b/iso9660.py @@ -0,0 +1,502 @@ +#!/usr/bin/python +# +# $Id: iso9660.py,v 1.12 2008/05/01 16:55:03 karl Exp $ +# +# Copyright (C) 2006, 2008 Rocky Bernstein +# +# 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 3 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 +# MERCHANTABILITY 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, see . + +import pyiso9660 +import cdio +import types + +check_types = { + 'nocheck' : pyiso9660.NOCHECK, + '7bit' : pyiso9660.SEVEN_BIT, + 'achars' : pyiso9660.ACHARS, + 'dchars' : pyiso9660.DCHARS + } + +def dirname_valid_p(path): + """dirname_valid_p(path)->bool + +Check that path is a valid ISO-9660 directory name. + +A valid directory name should not start out with a slash (/), +dot (.) or null byte, should be less than 37 characters long, +have no more than 8 characters in a directory component +which is separated by a /, and consist of only DCHARs. + +True is returned if path is valid.""" + return pyiso9660.dirname_valid(path) + + +def is_achar(achar): + """is_achar(achar)->bool + +Return 1 if achar is an ACHAR. achar should either be a string of +length one or the ord() of a string of length 1. + +These are the DCHAR's plus some ASCII symbols including the space +symbol.""" + if type(achar) != types.IntType: + # Not integer. Should be a string of length one then. + # We'll turn it into an integer. + try: + achar = ord(achar) + except: + return 0 + else: + # Is an integer. Is it too large? + if achar > 255: return 0 + return pyiso9660.is_achar(achar) + + +def is_dchar(dchar): + """is_dchar(dchar)->bool + +Return 1 if dchar is a DCHAR - a character that can appear in an an +ISO-9600 level 1 directory name. These are the ASCII capital +letters A-Z, the digits 0-9 and an underscore. + +dchar should either be a string of length one or the ord() of a string +of length 1.""" + + if type(dchar) != types.IntType: + # Not integer. Should be a string of length one then. + # We'll turn it into an integer. + try: + dchar = ord(dchar) + except: + return 0 + else: + # Is an integer. Is it too large? + if dchar > 255: return 0 + return pyiso9660.is_dchar(dchar) + + +def pathname_valid_p(path): + """pathname_valid_p(path)->bool + +Check that path is a valid ISO-9660 pathname. + +A valid pathname contains a valid directory name, if one appears and +the filename portion should be no more than 8 characters for the +file prefix and 3 characters in the extension (or portion after a +dot). There should be exactly one dot somewhere in the filename +portion and the filename should be composed of only DCHARs. + +True is returned if path is valid.""" + return pyiso9660.pathame_valid(path) + + +def pathname_isofy(path, version=1): + """pathname_valid_p(path, version=1)->string + +Take path and a version number and turn that into a ISO-9660 pathname. +(That's just the pathname followed by ';' and the version number. For +example, mydir/file.ext -> MYDIR/FILE.EXT;1 for version 1. The +resulting ISO-9660 pathname is returned.""" + return pyiso9660.pathname_isofy(path, version) + +def name_translate(filename, joliet_level=0): + """name_translate(name, joliet_level=0)->str + +Convert an ISO-9660 file name of the kind that is that stored in a ISO +9660 directory entry into what's usually listed as the file name in a +listing. Lowercase name if no Joliet Extension interpretation. Remove +trailing ;1's or .;1's and turn the other ;'s into version numbers. + +If joliet_level is not given it is 0 which means use no Joliet +Extensions. Otherwise use the specified the Joliet level. + +The translated string is returned and it will be larger than the input +filename.""" + return pyiso9660.name_translate_ext(filename, joliet_level) + + +# FIXME: should be +# def stat_array_to_dict(*args): +# Probably have a SWIG error. +def stat_array_to_dict(filename, LSN, size, sec_size, is_dir): + """stat_array_to_href(filename, LSN, size, sec_size, is_dir)->stat + +Convert a ISO 9660 array to an hash reference of the values. + +Used internally in convert from C code.""" + + stat = {} + stat['filename'] = filename + stat['LSN'] = LSN + stat['size'] = size + stat['sec_size'] = sec_size + stat['is_dir'] = is_dir == 2 + return stat + +def strncpy_pad(name, len, check): + """strncpy_pad(src, len, check='nocheck')->str + +Pad string 'name' with spaces to size len and return this. If 'len' is +less than the length of 'src', the return value will be truncated to +the first len characters of 'name'. + +'name' can also be scanned to see if it contains only ACHARs, DCHARs, +or 7-bit ASCII chars, and this is specified via the 'check' parameter. +If the I parameter is given it must be one of the 'nocheck', +'7bit', 'achars' or 'dchars'. Case is not significant.""" + if check not in check_types: + print "*** A CHECK parameter must be one of %s\n" % \ + ', '.join(check_types.keys()) + return None + return pyiso9660.strncpy_pad(name, len, check_types[check]) + +class ISO9660: + """ """ + class IFS: + """ISO 9660 Filesystem image reading""" + + def __init__(self, source=None, iso_mask=pyiso9660.EXTENSION_NONE): + + """Create a new ISO 9660 object. If source is given, open() + is called using that and the optional iso_mask parameter; + iso_mask is used only if source is specified. If source is + given but opening fails, undef is returned. If source is not + given, an object is always returned.""" + + self.iso9660 = None + if source is not None: + self.open(source, iso_mask) + + def close(self): + """close(self)->bool + + Close previously opened ISO 9660 image and free resources + associated with ISO9660. Call this when done using using + an ISO 9660 image.""" + + if self.iso9660 is not None: + pyiso9660.close(self.iso9660) + else: + print "***No object to close" + self.iso9660 = None + + def find_lsn(self, lsn): + """find_lsn(self, lsn)->[stat_href] + + Find the filesystem entry that contains LSN and return + file stat information about it. None is returned on + error.""" + + if pycdio.VERSION_NUM <= 76: + print "*** Routine available only in libcdio versions >= 0.76" + return None + + filename, LSN, size, sec_size, is_dir = \ + pyiso9660.ifs_find_lsn(self.iso9660, lsn) + return stat_array_to_href(filename, LSN, size, sec_size, is_dir) + + + def get_application_id(self): + """get_application_id(self)->id + + Get the application ID stored in the Primary Volume Descriptor. + None is returned if there is some problem.""" + + return pyiso9660.ifs_get_application_id(self.iso9660) + + def get_preparer_id(self): + """get_preparer_id(self)->id + + Get the preparer ID stored in the Primary Volume Descriptor. + None is returned if there is some problem.""" + + return pyiso9660.ifs_get_preparer_id(self.iso9660) + + + def get_publisher_id(self): + """get_publisher_id()->id + + Get the publisher ID stored in the Primary Volume Descriptor. + undef is returned if there is some problem.""" + return pyiso9660.ifs_get_publisher_id(self.iso9660) + + def get_root_lsn(self): + """get_root_lsn(self)->lsn + + Get the Root LSN stored in the Primary Volume Descriptor. + undef is returned if there is some problem.""" + + return pyiso9660.ifs_get_root_lsn(self.iso9660) + + def get_system_id(self): + """get_system_id(self)->id + + Get the Volume ID stored in the Primary Volume Descriptor. + undef is returned if there is some problem.""" + + return pyiso9660.ifs_get_system_id(self.iso9660) + + def get_volume_id(self): + """get_volume_id()->id + + Get the Volume ID stored in the Primary Volume Descriptor. + undef is returned if there is some problem.""" + + return pyiso9660.ifs_get_volume_id(self.iso9660) + + def get_volumeset_id(self): + """get_volume_id(self)->id + + Get the Volume ID stored in the Primary Volume Descriptor. + undef is returned if there is some problem.""" + + return pyiso9660.ifs_get_volumeset_id(self.iso9660) + + def is_open(self): + """is_open(self)->bool + Return true if we have an ISO9660 image open. + """ + return self.iso9660 is not None + + def open(self, source, iso_mask=pyiso9660.EXTENSION_NONE): + """open(source, iso_mask=pyiso9660.EXTENSION_NONE)->bool + + Open an ISO 9660 image for reading. Subsequent operations + will read from this ISO 9660 image. + + This should be called before using any other routine + except possibly new. It is implicitly called when a new is + done specifying a source. + + If device object was previously opened it is closed first. + + See also open_fuzzy.""" + + if self.iso9660 is not None: self.close() + + self.iso9660 = pyiso9660.open_ext(source, iso_mask) + return self.iso9660 is not None + + + def open_fuzzy(self, source, iso_mask=pyiso9660.EXTENSION_NONE, + fuzz=20): + """open_fuzzy(source, iso_mask=pyiso9660.EXTENSION_NONE, + fuzz=20)->bool + + Open an ISO 9660 image for reading. Subsequent operations + will read from this ISO 9660 image. Some tolerence allowed + for positioning the ISO9660 image. We scan for + pyiso9660.STANDARD_ID and use that to set the eventual + offset to adjust by (as long as that is <= fuzz). + + This should be called before using any other routine + except possibly new (which must be called first. It is + implicitly called when a new is done specifying a source. + + See also open.""" + + if self.iso9660 is not None: self.close() + + if type(fuzz) != types.IntType: + print "*** Expecting fuzz to be an integer; got 'fuzz'" + return False + + self.iso9660 = pyiso9660.open_fuzzy_ext(source, iso_mask, fuzz) + return self.iso9660 is not None + + def read_fuzzy_superblock(self, iso_mask=pyiso9660.EXTENSION_NONE, + fuzz=20): + """read_fuzzy_superblock(iso_mask=pyiso9660.EXTENSION_NONE, + fuzz=20)->bool + + Read the Super block of an ISO 9660 image but determine + framesize and datastart and a possible additional + offset. Generally here we are not reading an ISO 9660 image + but a CD-Image which contains an ISO 9660 filesystem.""" + + if type(fuzz) != types.IntType: + print "*** Expecting fuzz to be an integer; got 'fuzz'" + return False + + return pyiso9660.ifs_fuzzy_read_superblock(self.iso9660, iso_mask, + fuzz) + + def readdir(self, dirname): + """readdir(dirname)->[LSN, size, sec_size, filename, XA, is_dir] + + Read path (a directory) and return a list of iso9660 stat + references + + Each item of @iso_stat is a hash reference which contains + + * LSN - the Logical sector number (an integer) + * size - the total size of the file in bytes + * sec_size - the number of sectors allocated + * filename - the file name of the statbuf entry + * XA - if the file has XA attributes; 0 if not + * is_dir - 1 if a directory; 0 if a not; + + FIXME: If you look at iso9660.h you'll see more fields, such as for + Rock-Ridge specific fields or XA specific fields. Eventually these + will be added. Volunteers?""" + + return pyiso9660.ifs_readdir(self.iso9660, dirname) + + + def read_pvd(self): + """read_pvd()->pvd + + Read the Super block of an ISO 9660 image. This is the + Primary Volume Descriptor (PVD) and perhaps a Supplemental + Volume Descriptor if (Joliet) extensions are + acceptable.""" + + return pyiso9660.ifs_read_pvd(self.iso9660) + + def read_superblock(self, iso_mask=pyiso9660.EXTENSION_NONE): + """read_superblock(iso_mask=pyiso9660.EXTENSION_NONE)->bool + + Read the Super block of an ISO 9660 image. This is the + Primary Volume Descriptor (PVD) and perhaps a Supplemental + Volume Descriptor if (Joliet) extensions are + acceptable.""" + + return pyiso9660.ifs_read_superblock(self.iso9660, iso_mask) + + def seek_read(self, start, size=1): + """seek_read(self, start, size=1) + ->(size, str) + + Seek to a position and then read n blocks. A block is + pycdio.ISO_BLOCKSIZE (2048) bytes. The Size in BYTES (not blocks) + is returned.""" + size *= pyiso9660.ISO_BLOCKSIZE + return pyiso9660.seek_read(self.iso9660, start, size) + + def stat(self, path, translate=False): + """stat(self, path, translate=False)->{stat} + + Return file status for path name path. None is returned on + error. If translate is True, version numbers in the ISO 9660 + name are dropped, i.e. ;1 is removed and if level 1 ISO-9660 + names are lowercased. + + Each item of the return is a hash reference which contains: + + * LSN - the Logical sector number (an integer) + * size - the total size of the file in bytes + * sec_size - the number of sectors allocated + * filename - the file name of the statbuf entry + * XA - if the file has XA attributes; False if not + * is_dir - True if a directory; False if a not.""" + + if translate: + values = pyiso9660.ifs_stat_translate(self.iso9660, path) + else: + values = pyiso9660.ifs_stat(self.iso9660, path) + return stat_array_to_dict(values[0], values[1], values[2], + values[3], values[4]) + + class FS(cdio.Device): + """ISO 9660 CD reading""" + + def __init__(self, source=None, driver_id=None, + access_mode=None): + + """Create a new ISO 9660 object. If source is given, open() + is called using that and the optional iso_mask parameter; + iso_mask is used only if source is specified. If source is + given but opening fails, undef is returned. If source is not + given, an object is always returned.""" + + cdio.Device.__init__(self, source, driver_id, access_mode) + + + def find_lsn(self, lsn): + """find_lsn(self, lsn)->[stat_href] + + Find the filesystem entry that contains LSN and return + file stat information about it. None is returned on + error.""" + + filename, LSN, size, sec_size, is_dir = \ + pyiso9660.fs_find_lsn(self.cd, lsn) + return stat_array_to_href(filename, LSN, size, sec_size, is_dir) + + + def readdir(self, dirname): + """readdir(dirname)->[LSN, size, sec_size, filename, XA, is_dir] + + Read path (a directory) and return a list of iso9660 stat + references + + Each item of @iso_stat is a hash reference which contains + + * LSN - the Logical sector number (an integer) + * size - the total size of the file in bytes + * sec_size - the number of sectors allocated + * filename - the file name of the statbuf entry + * XA - if the file has XA attributes; 0 if not + * is_dir - 1 if a directory; 0 if a not; + + FIXME: If you look at iso9660.h you'll see more fields, such as for + Rock-Ridge specific fields or XA specific fields. Eventually these + will be added. Volunteers?""" + + return pyiso9660.fs_readdir(self.cd, dirname) + + + def read_pvd(self): + """read_pvd()->pvd + + Read the Super block of an ISO 9660 image. This is the + Primary Volume Descriptor (PVD) and perhaps a Supplemental + Volume Descriptor if (Joliet) extensions are + acceptable.""" + + return pyiso9660.fs_read_pvd(self.cd) + + def read_superblock(self, iso_mask=pyiso9660.EXTENSION_NONE): + """read_superblock(iso_mask=pyiso9660.EXTENSION_NONE)->bool + + Read the Super block of an ISO 9660 image. This is the + Primary Volume Descriptor (PVD) and perhaps a Supplemental + Volume Descriptor if (Joliet) extensions are + acceptable.""" + + return pyiso9660.fs_read_superblock(self.cd, iso_mask) + + def stat(self, path, translate=False): + """stat(self, path, translate=False)->{stat} + + Return file status for path name path. None is returned on + error. If translate is True, version numbers in the ISO 9660 + name are dropped, i.e. ;1 is removed and if level 1 ISO-9660 + names are lowercased. + + Each item of the return is a hash reference which contains: + + * LSN - the Logical sector number (an integer) + * size - the total size of the file in bytes + * sec_size - the number of sectors allocated + * filename - the file name of the statbuf entry + * XA - if the file has XA attributes; False if not + * is_dir - True if a directory; False if a not.""" + + if translate: + values = pyiso9660.fs_stat_translate(self.cd, path) + else: + values = pyiso9660.fs_stat(self.cd, path) + return stat_array_to_dict(values[0], values[1], values[2], + values[3], values[4]) diff --git a/pycdio.py b/pycdio.py new file mode 100644 index 00000000..61eec0de --- /dev/null +++ b/pycdio.py @@ -0,0 +1,803 @@ +# This file was automatically generated by SWIG (http://www.swig.org). +# Version 1.3.31 +# +# Don't modify this file, modify the SWIG interface instead. +# This file is compatible with both classic and new-style classes. + +""" +This is a wrapper for The CD Input and Control library (libcdio) which +encapsulates CD-ROM reading and control. Python programs wishing to be +oblivious of the OS- and device-dependent properties of a CD-ROM can +use this library. +""" + +import _pycdio +import new +new_instancemethod = new.instancemethod +try: + _swig_property = property +except NameError: + pass # Python < 2.2 doesn't have 'property'. +def _swig_setattr_nondynamic(self,class_type,name,value,static=1): + if (name == "thisown"): return self.this.own(value) + if (name == "this"): + if type(value).__name__ == 'PySwigObject': + self.__dict__[name] = value + return + method = class_type.__swig_setmethods__.get(name,None) + if method: return method(self,value) + if (not static) or hasattr(self,name): + self.__dict__[name] = value + else: + raise AttributeError("You cannot add attributes to %s" % self) + +def _swig_setattr(self,class_type,name,value): + return _swig_setattr_nondynamic(self,class_type,name,value,0) + +def _swig_getattr(self,class_type,name): + if (name == "thisown"): return self.this.own() + method = class_type.__swig_getmethods__.get(name,None) + if method: return method(self) + raise AttributeError,name + +def _swig_repr(self): + try: strthis = "proxy of " + self.this.__repr__() + except: strthis = "" + return "<%s.%s; %s >" % (self.__class__.__module__, self.__class__.__name__, strthis,) + +import types +try: + _object = types.ObjectType + _newclass = 1 +except AttributeError: + class _object : pass + _newclass = 0 +del types + + +CDIO_READ_MODE_AUDIO = _pycdio.CDIO_READ_MODE_AUDIO +CDIO_READ_MODE_M1F1 = _pycdio.CDIO_READ_MODE_M1F1 +CDIO_READ_MODE_M1F2 = _pycdio.CDIO_READ_MODE_M1F2 +CDIO_READ_MODE_M2F1 = _pycdio.CDIO_READ_MODE_M2F1 +CDIO_READ_MODE_M2F2 = _pycdio.CDIO_READ_MODE_M2F2 +cdio_read_sectors = _pycdio.cdio_read_sectors +cdio_eject_media_drive = _pycdio.cdio_eject_media_drive +VERSION_NUM = _pycdio.VERSION_NUM +INVALID_LBA = _pycdio.INVALID_LBA +INVALID_LSN = _pycdio.INVALID_LSN +CD_FRAMESIZE = _pycdio.CD_FRAMESIZE +CD_FRAMESIZE_RAW = _pycdio.CD_FRAMESIZE_RAW +ISO_BLOCKSIZE = _pycdio.ISO_BLOCKSIZE +M2F2_SECTOR_SIZE = _pycdio.M2F2_SECTOR_SIZE +M2RAW_SECTOR_SIZE = _pycdio.M2RAW_SECTOR_SIZE + +def audio_pause(*args): + """ + audio_pause(cdio)->status + + Pause playing CD through analog output. + """ + return _pycdio.audio_pause(*args) + +def audio_play_lsn(*args): + """ + auto_play_lsn(cdio, start_lsn, end_lsn)->status + + Playing CD through analog output at the given lsn to the ending lsn + """ + return _pycdio.audio_play_lsn(*args) + +def audio_resume(*args): + """ + audio_resume(cdio)->status + Resume playing an audio CD. + """ + return _pycdio.audio_resume(*args) + +def audio_stop(*args): + """ + audio_stop(cdio)->status + Stop playing an audio CD. + """ + return _pycdio.audio_stop(*args) +READ_MODE_AUDIO = _pycdio.READ_MODE_AUDIO +READ_MODE_M1F1 = _pycdio.READ_MODE_M1F1 +READ_MODE_M1F2 = _pycdio.READ_MODE_M1F2 +READ_MODE_M2F1 = _pycdio.READ_MODE_M2F1 +READ_MODE_M2F2 = _pycdio.READ_MODE_M2F2 + +def lseek(*args): + """ + lseek(cdio, offset, whence)->int + Reposition read offset + Similar to (if not the same as) libc's fseek() + + cdio is object to get adjested, offset is amount to seek and + whence is like corresponding parameter in libc's lseek, e.g. + it should be SEEK_SET or SEEK_END. + + the offset is returned or -1 on error. + """ + return _pycdio.lseek(*args) + +def read_cd(*args): + """ + read_cd(cdio, size)->[size, data] + + Reads into buf the next size bytes. + Similar to (if not the same as) libc's read() + + The number of reads read is returned. -1 is returned on error. + """ + return _pycdio.read_cd(*args) + +def read_sectors(*args): + """ + read_sectors(bytes, lsn, read_mode)->[size, data] + Reads a number of sectors (AKA blocks). + + lsn is sector to read, bytes is the number of bytes. + + If read_mode is pycdio.MODE_AUDIO, the return buffer size will be + truncated to multiple of pycdio.CDIO_FRAMESIZE_RAW i_blocks bytes. + + If read_mode is pycdio.MODE_DATA, buffer will be truncated to a + multiple of pycdio.ISO_BLOCKSIZE, pycdio.M1RAW_SECTOR_SIZE or + pycdio.M2F2_SECTOR_SIZE bytes depending on what mode the data is in. + If read_mode is pycdio.CDIO_MODE_M2F1, buffer will be truncated to a + multiple of pycdio.M2RAW_SECTOR_SIZE bytes. + + If read_mode is CDIO_MODE_M2F2, the return buffer size will be + truncated to a multiple of pycdio.CD_FRAMESIZE bytes. + + If size <= 0 an error has occurred. + """ + return _pycdio.read_sectors(*args) + +def read_data_bytes(*args): + """ + read_data_bytes(lsn, bytes) + + Reads a number of data sectors (AKA blocks). + + lsn is sector to read, bytes is the number of bytes. + If you don't know whether you have a Mode 1/2, + Form 1/ Form 2/Formless sector best to reserve space for the maximum + which is pycdio.M2RAW_SECTOR_SIZE. + + If size <= 0 an error has occurred. + """ + return _pycdio.read_data_bytes(*args) +TRACK_FORMAT_AUDIO = _pycdio.TRACK_FORMAT_AUDIO +TRACK_FORMAT_CDI = _pycdio.TRACK_FORMAT_CDI +TRACK_FORMAT_XA = _pycdio.TRACK_FORMAT_XA +TRACK_FORMAT_DATA = _pycdio.TRACK_FORMAT_DATA +TRACK_FORMAT_PSX = _pycdio.TRACK_FORMAT_PSX +CDIO_TRACK_FLAG_FALSE = _pycdio.CDIO_TRACK_FLAG_FALSE +CDIO_TRACK_FLAG_TRUE = _pycdio.CDIO_TRACK_FLAG_TRUE +CDIO_TRACK_FLAG_ERROR = _pycdio.CDIO_TRACK_FLAG_ERROR +CDIO_TRACK_FLAG_UNKNOWN = _pycdio.CDIO_TRACK_FLAG_UNKNOWN +CDIO_CDROM_LBA = _pycdio.CDIO_CDROM_LBA +CDIO_CDROM_MSF = _pycdio.CDIO_CDROM_MSF +CDIO_CDROM_DATA_TRACK = _pycdio.CDIO_CDROM_DATA_TRACK +CDIO_CDROM_CDI_TRACK = _pycdio.CDIO_CDROM_CDI_TRACK +CDIO_CDROM_XA_TRACK = _pycdio.CDIO_CDROM_XA_TRACK +AUDIO = _pycdio.AUDIO +MODE1 = _pycdio.MODE1 +MODE1_RAW = _pycdio.MODE1_RAW +MODE2 = _pycdio.MODE2 +MODE2_FORM1 = _pycdio.MODE2_FORM1 +MODE2_FORM2 = _pycdio.MODE2_FORM2 +MODE2_FORM_MIX = _pycdio.MODE2_FORM_MIX +MODE2_RAW = _pycdio.MODE2_RAW +INVALID_TRACK = _pycdio.INVALID_TRACK +CDROM_LEADOUT_TRACK = _pycdio.CDROM_LEADOUT_TRACK + +def get_first_track_num(*args): + """ + get_first_track_num(p_cdio) -> int + Get the number of the first track. + + return the track number or pycdio.INVALID_TRACK if there was + a problem. + """ + return _pycdio.get_first_track_num(*args) + +def get_last_track_num(*args): + """ + get_last_track_num + Return the last track number. + pycdio.INVALID_TRACK is if there was a problem. + """ + return _pycdio.get_last_track_num(*args) + +def get_track(*args): + """ + cdio_get_track(lsn)->int + + Find the track which contains lsn. + pycdio.INVALID_TRACK is returned if the lsn outside of the CD or + if there was some error. + + If the lsn is before the pregap of the first track, 0 is returned. + Otherwise we return the track that spans the lsn. + """ + return _pycdio.get_track(*args) + +def get_track_channels(*args): + """ + get_track_channels(cdio, track)->int + + Return number of channels in track: 2 or 4; -2 if implemented or -1 + for error. Not meaningful if track is not an audio track. + """ + return _pycdio.get_track_channels(*args) + +def get_track_copy_permit(*args): + """ + get_copy_permit(cdio, track)->int + + Return copy protection status on a track. Is this meaningful + not an audio track? + + """ + return _pycdio.get_track_copy_permit(*args) + +def get_track_format(*args): + """ + get_track_format(cdio, track)->format + + Get the format (audio, mode2, mode1) of track. + """ + return _pycdio.get_track_format(*args) + +def is_track_green(*args): + """ + is_track_green(cdio, track) -> bool + + Return True if we have XA data (green, mode2 form1) or + XA data (green, mode2 form2). That is track begins: + sync - header - subheader + 12 4 - 8 + + FIXME: there's gotta be a better design for this and get_track_format? + """ + return _pycdio.is_track_green(*args) + +def get_track_last_lsn(*args): + """ + cdio_get_track_last_lsn(cdio, track)->lsn + + Return the ending LSN for track number + track in cdio. CDIO_INVALID_LSN is returned on error. + """ + return _pycdio.get_track_last_lsn(*args) + +def get_track_lba(*args): + """ + cdio_get_track_lba + Get the starting LBA for track number + i_track in p_cdio. Track numbers usually start at something + greater than 0, usually 1. + + The 'leadout' track is specified either by + using i_track CDIO_CDROM_LEADOUT_TRACK or the total tracks+1. + + @param p_cdio object to get information from + @param i_track the track number we want the LSN for + @return the starting LBA or CDIO_INVALID_LBA on error. + """ + return _pycdio.get_track_lba(*args) + +def get_track_lsn(*args): + """ + cdio_get_track_lsn (cdio, track)->int + + Return the starting LSN for track number. + Track numbers usually start at something greater than 0, usually 1. + + The 'leadout' track is specified either by + using i_track pycdio.CDROM_LEADOUT_TRACK or the total tracks+1. + + pycdio.INVALID_LSN is returned on error. + """ + return _pycdio.get_track_lsn(*args) + +def get_track_msf(*args): + """ + get_track_msf(cdio,track)->string + + Return the starting MSF (minutes/secs/frames) for track number + track. Track numbers usually start at something + greater than 0, usually 1. + + The 'leadout' track is specified either by + using i_track CDIO_CDROM_LEADOUT_TRACK or the total tracks+1. + + @return string mm:ss:ff if all good, or string 'error' on error. + """ + return _pycdio.get_track_msf(*args) + +def get_track_preemphasis(*args): + """ + cdio_get_track_preemphasis(cdio, track) + + Get linear preemphasis status on an audio track. + This is not meaningful if not an audio track? + """ + return _pycdio.get_track_preemphasis(*args) + +def get_track_sec_count(*args): + """ + get_track_sec_count(cdio, track)->int + + Get the number of sectors between this track an the next. This + includes any pregap sectors before the start of the next track. + Track numbers usually start at something + greater than 0, usually 1. + + 0 is returned if there is an error. + """ + return _pycdio.get_track_sec_count(*args) +DRIVE_CAP_ERROR = _pycdio.DRIVE_CAP_ERROR +DRIVE_CAP_UNKNOWN = _pycdio.DRIVE_CAP_UNKNOWN +DRIVE_CAP_MISC_CLOSE_TRAY = _pycdio.DRIVE_CAP_MISC_CLOSE_TRAY +DRIVE_CAP_MISC_EJECT = _pycdio.DRIVE_CAP_MISC_EJECT +DRIVE_CAP_MISC_LOCK = _pycdio.DRIVE_CAP_MISC_LOCK +DRIVE_CAP_MISC_SELECT_SPEED = _pycdio.DRIVE_CAP_MISC_SELECT_SPEED +DRIVE_CAP_MISC_SELECT_DISC = _pycdio.DRIVE_CAP_MISC_SELECT_DISC +DRIVE_CAP_MISC_MULTI_SESSION = _pycdio.DRIVE_CAP_MISC_MULTI_SESSION +DRIVE_CAP_MISC_MEDIA_CHANGED = _pycdio.DRIVE_CAP_MISC_MEDIA_CHANGED +DRIVE_CAP_MISC_RESET = _pycdio.DRIVE_CAP_MISC_RESET +DRIVE_CAP_MISC_FILE = _pycdio.DRIVE_CAP_MISC_FILE +DRIVE_CAP_READ_AUDIO = _pycdio.DRIVE_CAP_READ_AUDIO +DRIVE_CAP_READ_CD_DA = _pycdio.DRIVE_CAP_READ_CD_DA +DRIVE_CAP_READ_CD_G = _pycdio.DRIVE_CAP_READ_CD_G +DRIVE_CAP_READ_CD_R = _pycdio.DRIVE_CAP_READ_CD_R +DRIVE_CAP_READ_CD_RW = _pycdio.DRIVE_CAP_READ_CD_RW +DRIVE_CAP_READ_DVD_R = _pycdio.DRIVE_CAP_READ_DVD_R +DRIVE_CAP_READ_DVD_PR = _pycdio.DRIVE_CAP_READ_DVD_PR +DRIVE_CAP_READ_DVD_RAM = _pycdio.DRIVE_CAP_READ_DVD_RAM +DRIVE_CAP_READ_DVD_ROM = _pycdio.DRIVE_CAP_READ_DVD_ROM +DRIVE_CAP_READ_DVD_RW = _pycdio.DRIVE_CAP_READ_DVD_RW +DRIVE_CAP_READ_DVD_RPW = _pycdio.DRIVE_CAP_READ_DVD_RPW +DRIVE_CAP_READ_C2_ERRS = _pycdio.DRIVE_CAP_READ_C2_ERRS +DRIVE_CAP_READ_MODE2_FORM1 = _pycdio.DRIVE_CAP_READ_MODE2_FORM1 +DRIVE_CAP_READ_MODE2_FORM2 = _pycdio.DRIVE_CAP_READ_MODE2_FORM2 +DRIVE_CAP_READ_MCN = _pycdio.DRIVE_CAP_READ_MCN +DRIVE_CAP_READ_ISRC = _pycdio.DRIVE_CAP_READ_ISRC +DRIVE_CAP_WRITE_CD_R = _pycdio.DRIVE_CAP_WRITE_CD_R +DRIVE_CAP_WRITE_CD_RW = _pycdio.DRIVE_CAP_WRITE_CD_RW +DRIVE_CAP_WRITE_DVD_R = _pycdio.DRIVE_CAP_WRITE_DVD_R +DRIVE_CAP_WRITE_DVD_PR = _pycdio.DRIVE_CAP_WRITE_DVD_PR +DRIVE_CAP_WRITE_DVD_RAM = _pycdio.DRIVE_CAP_WRITE_DVD_RAM +DRIVE_CAP_WRITE_DVD_RW = _pycdio.DRIVE_CAP_WRITE_DVD_RW +DRIVE_CAP_WRITE_DVD_RPW = _pycdio.DRIVE_CAP_WRITE_DVD_RPW +DRIVE_CAP_WRITE_MT_RAINIER = _pycdio.DRIVE_CAP_WRITE_MT_RAINIER +DRIVE_CAP_WRITE_BURN_PROOF = _pycdio.DRIVE_CAP_WRITE_BURN_PROOF +DRIVE_CAP_WRITE_CD = _pycdio.DRIVE_CAP_WRITE_CD +DRIVE_CAP_WRITE_DVD = _pycdio.DRIVE_CAP_WRITE_DVD +DRIVE_CAP_WRITE = _pycdio.DRIVE_CAP_WRITE +MMC_HW_VENDOR_LEN = _pycdio.MMC_HW_VENDOR_LEN +MMC_HW_MODEL_LEN = _pycdio.MMC_HW_MODEL_LEN +MMC_HW_REVISION_LEN = _pycdio.MMC_HW_REVISION_LEN +SRC_IS_DISK_IMAGE_MASK = _pycdio.SRC_IS_DISK_IMAGE_MASK +SRC_IS_DEVICE_MASK = _pycdio.SRC_IS_DEVICE_MASK +SRC_IS_SCSI_MASK = _pycdio.SRC_IS_SCSI_MASK +SRC_IS_NATIVE_MASK = _pycdio.SRC_IS_NATIVE_MASK +DRIVER_UNKNOWN = _pycdio.DRIVER_UNKNOWN +DRIVER_AIX = _pycdio.DRIVER_AIX +DRIVER_BSDI = _pycdio.DRIVER_BSDI +DRIVER_FREEBSD = _pycdio.DRIVER_FREEBSD +DRIVER_LINUX = _pycdio.DRIVER_LINUX +DRIVER_SOLARIS = _pycdio.DRIVER_SOLARIS +DRIVER_OSX = _pycdio.DRIVER_OSX +DRIVER_WIN32 = _pycdio.DRIVER_WIN32 +DRIVER_CDRDAO = _pycdio.DRIVER_CDRDAO +DRIVER_BINCUE = _pycdio.DRIVER_BINCUE +DRIVER_NRG = _pycdio.DRIVER_NRG +DRIVER_DEVICE = _pycdio.DRIVER_DEVICE +MIN_DRIVER = _pycdio.MIN_DRIVER +MIN_DEVICE_DRIVER = _pycdio.MIN_DEVICE_DRIVER +MAX_DRIVER = _pycdio.MAX_DRIVER +MAX_DEVICE_DRIVER = _pycdio.MAX_DEVICE_DRIVER +DRIVER_OP_SUCCESS = _pycdio.DRIVER_OP_SUCCESS +DRIVER_OP_ERROR = _pycdio.DRIVER_OP_ERROR +DRIVER_OP_UNSUPPORTED = _pycdio.DRIVER_OP_UNSUPPORTED +DRIVER_OP_UNINIT = _pycdio.DRIVER_OP_UNINIT +DRIVER_OP_NOT_PERMITTED = _pycdio.DRIVER_OP_NOT_PERMITTED +DRIVER_OP_BAD_PARAMETER = _pycdio.DRIVER_OP_BAD_PARAMETER +DRIVER_OP_BAD_POINTER = _pycdio.DRIVER_OP_BAD_POINTER +DRIVER_OP_NO_DRIVER = _pycdio.DRIVER_OP_NO_DRIVER +FS_AUDIO = _pycdio.FS_AUDIO +FS_HIGH_SIERRA = _pycdio.FS_HIGH_SIERRA +FS_ISO_9660 = _pycdio.FS_ISO_9660 +FS_INTERACTIVE = _pycdio.FS_INTERACTIVE +FS_HFS = _pycdio.FS_HFS +FS_UFS = _pycdio.FS_UFS +FS_EXT2 = _pycdio.FS_EXT2 +FS_ISO_HFS = _pycdio.FS_ISO_HFS +FS_ISO_9660_INTERACTIVE = _pycdio.FS_ISO_9660_INTERACTIVE +FS_3DO = _pycdio.FS_3DO +FS_XISO = _pycdio.FS_XISO +FS_UDFX = _pycdio.FS_UDFX +FS_UDF = _pycdio.FS_UDF +FS_ISO_UDF = _pycdio.FS_ISO_UDF +FS_ANAL_XA = _pycdio.FS_ANAL_XA +FS_ANAL_MULTISESSION = _pycdio.FS_ANAL_MULTISESSION +FS_ANAL_PHOTO_CD = _pycdio.FS_ANAL_PHOTO_CD +FS_ANAL_HIDDEN_TRACK = _pycdio.FS_ANAL_HIDDEN_TRACK +FS_ANAL_CDTV = _pycdio.FS_ANAL_CDTV +FS_ANAL_BOOTABLE = _pycdio.FS_ANAL_BOOTABLE +FS_ANAL_VIDEOCD = _pycdio.FS_ANAL_VIDEOCD +FS_ANAL_ROCKRIDGE = _pycdio.FS_ANAL_ROCKRIDGE +FS_ANAL_JOLIET = _pycdio.FS_ANAL_JOLIET +FS_ANAL_SVCD = _pycdio.FS_ANAL_SVCD +FS_ANAL_CVD = _pycdio.FS_ANAL_CVD +FS_ANAL_XISO = _pycdio.FS_ANAL_XISO +FS_MATCH_ALL = _pycdio.FS_MATCH_ALL +FS_UNKNOWN = _pycdio.FS_UNKNOWN + +def close_tray(*args): + """ + close_tray(drive=None, driver_id=None) -> [status, driver_id] + + close media tray in CD drive if there is a routine to do so. + The driver id is returned. An exception is thrown on error. + """ + return _pycdio.close_tray(*args) + +def close(*args): + """ + destroy(p_cdio) + Free resources associated with p_cdio. Call this when done using + using CD reading/control operations for the current device. + + """ + return _pycdio.close(*args) + +def eject_media(*args): + """ + eject_media(cdio)->return_code + + Eject media in CD drive if there is a routine to do so. + + """ + return _pycdio.eject_media(*args) + +def eject_media_drive(*args): + """ + eject_media_drive(drive=None)->return_code + Eject media in CD drive if there is a routine to do so. + + psz_drive: the name of the device to be acted upon. + The operation status is returned. + """ + return _pycdio.eject_media_drive(*args) + +def get_arg(*args): + """ + get_arg(p_cdio, key)->string + + Get the value associatied with key. + """ + return _pycdio.get_arg(*args) + +def get_device(*args): + """ + get_device(cdio)->str + + Get the CD device associated with cdio. + If cdio is NULL (we haven't initialized a specific device driver), + then find a suitable one and return the default device for that. + + In some situations of drivers or OS's we can't find a CD device if + there is no media in it and it is possible for this routine to return + None even though there may be a hardware CD-ROM. + """ + return _pycdio.get_device(*args) + +def get_default_device_driver(*args): + """ + get_default_device_driver(driver_id=None)->[device, driver] + Return a string containing the default CD device if none is specified. + if p_driver_id is DRIVER_UNKNOWN or DRIVER_DEVICE then find a suitable + one set the default device for that. + + None is returned as the device if we couldn't get a default device. + """ + return _pycdio.get_default_device_driver(*args) + +def get_devices(*args): + """ + get_devices(driver_id)->[device1, device2, ...] + + Get an list of device names. + """ + return _pycdio.get_devices(*args) + +def get_devices_ret(*args): + """ + get_devices_ret(driver_id)->[device1, device2, ... driver_id] + + Like get_devices, but return the p_driver_id which may be different + from the passed-in driver_id if it was pycdio.DRIVER_DEVICE or + pycdio.DRIVER_UNKNOWN. The return driver_id may be useful because + often one wants to get a drive name and then *open* it + afterwards. Giving the driver back facilitates this, and speeds things + up for libcdio as well. + """ + return _pycdio.get_devices_ret(*args) + +def get_devices_with_cap(*args): + """ + get_devices_with_cap(capabilities, any)->[device1, device2...] + Get an array of device names in search_devices that have at least + the capabilities listed by the capabities parameter. + + If any is False then every capability listed in the + extended portion of capabilities (i.e. not the basic filesystem) + must be satisified. If any is True, then if any of the + capabilities matches, we call that a success. + + To find a CD-drive of any type, use the mask pycdio.CDIO_FS_MATCH_ALL. + + The array of device names is returned or NULL if we couldn't get a + default device. It is also possible to return a non NULL but after + dereferencing the the value is NULL. This also means nothing was + found. + """ + return _pycdio.get_devices_with_cap(*args) + +def get_devices_with_cap_ret(*args): + """ + Like cdio_get_devices_with_cap but we return the driver we found + as well. This is because often one wants to search for kind of drive + and then *open* it afterwards. Giving the driver back facilitates this, + and speeds things up for libcdio as well. + """ + return _pycdio.get_devices_with_cap_ret(*args) + +def get_driver_name(*args): + """ + get_driver_name(cdio)-> string + + return a string containing the name of the driver in use. + + An IOError exception is raised on error. + + """ + return _pycdio.get_driver_name(*args) + +def get_driver_id(*args): + """ + get_driver_id(cdio)-> int + + Return the driver id of the driver in use. + if cdio has not been initialized or is None, + return pycdio.DRIVER_UNKNOWN. + """ + return _pycdio.get_driver_id(*args) + +def get_last_session(*args): + """ + get_last_session(p_cdio) -> int + Get the LSN of the first track of the last session of on the CD. + An exception is thrown on error. + """ + return _pycdio.get_last_session(*args) + +def have_driver(*args): + """ + have_driver(driver_id) -> int + + Return 1 if we have driver driver_id, 0 if not and -1 + if driver id is out of range. + """ + return _pycdio.have_driver(*args) + +def have_ATAPI(*args): + """ + have_ATAPI(CdIo_t *p_cdio)->bool + return True if CD-ROM understand ATAPI commands. + """ + return _pycdio.have_ATAPI(*args) + +def is_binfile(*args): + """ + is_binfile(binfile_name)->cue_name + + Determine if binfile_name is the BIN file part of a CDRWIN CD disk + image. + + Return the corresponding CUE file if bin_name is a BIN file or + None if not a BIN file. + """ + return _pycdio.is_binfile(*args) + +def is_cuefile(*args): + """ + is_cuefile(cuefile_name)->bin_name + + Determine if cuefile_name is the CUE file part of a CDRWIN CD disk + image. + + Return the corresponding BIN file if bin_name is a CUE file or + None if not a CUE file. + """ + return _pycdio.is_cuefile(*args) + +def is_device(*args): + """ + is_cuefile(cuefile_name)->bin_name + + Determine if cuefile_name is the CUE file part of a CDRWIN CD disk + image. + + Return the corresponding BIN file if bin_name is a CUE file or + None if not a CUE file. + """ + return _pycdio.is_device(*args) + +def is_nrg(*args): + """ + is_nrg(cue_name)->bool + + Determine if nrg_name is a Nero CD disc image + """ + return _pycdio.is_nrg(*args) + +def is_tocfile(*args): + """ + is_tocfile(tocfile_name)->bool + + Determine if tocfile_name is a cdrdao CD disc image + """ + return _pycdio.is_tocfile(*args) + +def get_media_changed(*args): + """ + get_media_changed(cdio) -> int + + Find out if media has changed since the last call. + Return 1 if media has changed since last call, 0 if not. Error + return codes are the same as driver_return_code_t + """ + return _pycdio.get_media_changed(*args) + +def get_hwinfo(*args): + """ + get_hwinfo(p_cdio)->[drc, vendor, model, release] + Get the CD-ROM hardware info via a SCSI MMC INQUIRY command. + """ + return _pycdio.get_hwinfo(*args) + +def set_blocksize(*args): + """ + set_blocksize(cdio, blocksize)->return_status + + Set the blocksize for subsequent reads. + """ + return _pycdio.set_blocksize(*args) + +def set_speed(*args): + """ + cdio_set_speed(cdio, speed)->return_status + Set the drive speed. + """ + return _pycdio.set_speed(*args) + +def open_cd(*args): + """ + open_cd(source=NULL, driver_id=None, access_mode=None) + + Sets up to read from place specified by source, driver_id and + access mode. This should be called before using any other routine + except those that act on a CD-ROM drive by name. + + If None is given as the source, we'll use the default driver device. + If None is given as the driver_id, we'll find a suitable device driver. + + Return the a pointer than can be used in subsequent operations or + None on error or no device. + """ + return _pycdio.open_cd(*args) + +def set_python_errstring(*args): + """ + open_cd(source=NULL, driver_id=None, access_mode=None) + + Sets up to read from place specified by source, driver_id and + access mode. This should be called before using any other routine + except those that act on a CD-ROM drive by name. + + If None is given as the source, we'll use the default driver device. + If None is given as the driver_id, we'll find a suitable device driver. + + Return the a pointer than can be used in subsequent operations or + None on error or no device. + """ + return _pycdio.set_python_errstring(*args) +DISC_MODE_CD_DA = _pycdio.DISC_MODE_CD_DA +DISC_MODE_CD_DATA = _pycdio.DISC_MODE_CD_DATA +DISC_MODE_CD_XA = _pycdio.DISC_MODE_CD_XA +DISC_MODE_CD_MIXED = _pycdio.DISC_MODE_CD_MIXED +DISC_MODE_DVD_ROM = _pycdio.DISC_MODE_DVD_ROM +DISC_MODE_DVD_RAM = _pycdio.DISC_MODE_DVD_RAM +DISC_MODE_DVD_R = _pycdio.DISC_MODE_DVD_R +DISC_MODE_DVD_RW = _pycdio.DISC_MODE_DVD_RW +DISC_MODE_DVD_PR = _pycdio.DISC_MODE_DVD_PR +DISC_MODE_DVD_PRW = _pycdio.DISC_MODE_DVD_PRW +DISC_MODE_DVD_OTHER = _pycdio.DISC_MODE_DVD_OTHER +DISC_MODE_NO_INFO = _pycdio.DISC_MODE_NO_INFO +DISC_MODE_ERROR = _pycdio.DISC_MODE_ERROR +DISC_MODE_CD_I = _pycdio.DISC_MODE_CD_I + +def get_disc_last_lsn(*args): + """ + get_disc_last_lsn(cdio)->lsn + Get the LSN of the end of the CD. + + pycdio.INVALID_LSN is returned on error. + """ + return _pycdio.get_disc_last_lsn(*args) + +def get_disc_mode(*args): + """ + get_disc_mode(p_cdio) -> str + + Get disc mode - the kind of CD (CD-DA, CD-ROM mode 1, CD-MIXED, ...) + that we've got. The notion of 'CD' is extended a little to include + DVD's. + """ + return _pycdio.get_disc_mode(*args) + +def get_joliet_level(*args): + """ + get_joliet_level(cdio)->int + + Return the Joliet level recognized for cdio. + This only makes sense for something that has an ISO-9660 + filesystem. + """ + return _pycdio.get_joliet_level(*args) + +def get_mcn(*args): + """ + get_mcn(cdio) -> str + + Get the media catalog number (MCN) from the CD. + """ + return _pycdio.get_mcn(*args) + +def get_num_tracks(*args): + """ + get_num_tracks(p_cdio)->int + + Return the number of tracks on the CD. + On error pycdio.INVALID_TRACK is returned. + """ + return _pycdio.get_num_tracks(*args) +INCLUDE_CLASS = _pycdio.INCLUDE_CLASS + + +def get_drive_cap(*args): + """ + get_drive_cap()->(read_cap, write_cap, misc_cap) + + Get drive capabilities of device. + + In some situations of drivers or OS's we can't find a CD device if + there is no media in it. In this situation capabilities will show up as + empty even though there is a hardware CD-ROM. + get_drive_cap_dev()->(read_cap, write_cap, misc_cap) + + Get drive capabilities of device. + + In some situations of drivers or OS's we can't find a CD device if + there is no media in it. In this situation capabilities will show up as + empty even though there is a hardware CD-ROM. + """ + return _pycdio.get_drive_cap(*args) +cvar = _pycdio.cvar + diff --git a/pyiso9660.py b/pyiso9660.py new file mode 100644 index 00000000..01244856 --- /dev/null +++ b/pyiso9660.py @@ -0,0 +1,532 @@ +# This file was automatically generated by SWIG (http://www.swig.org). +# Version 1.3.31 +# +# Don't modify this file, modify the SWIG interface instead. +# This file is compatible with both classic and new-style classes. + +""" +This is a wrapper for The CD Input and Control library's ISO-9660 library +See also the ISO-9660 specification. The freely available European +equivalant standard is called ECMA-119. +""" + +import _pyiso9660 +import new +new_instancemethod = new.instancemethod +try: + _swig_property = property +except NameError: + pass # Python < 2.2 doesn't have 'property'. +def _swig_setattr_nondynamic(self,class_type,name,value,static=1): + if (name == "thisown"): return self.this.own(value) + if (name == "this"): + if type(value).__name__ == 'PySwigObject': + self.__dict__[name] = value + return + method = class_type.__swig_setmethods__.get(name,None) + if method: return method(self,value) + if (not static) or hasattr(self,name): + self.__dict__[name] = value + else: + raise AttributeError("You cannot add attributes to %s" % self) + +def _swig_setattr(self,class_type,name,value): + return _swig_setattr_nondynamic(self,class_type,name,value,0) + +def _swig_getattr(self,class_type,name): + if (name == "thisown"): return self.this.own() + method = class_type.__swig_getmethods__.get(name,None) + if method: return method(self) + raise AttributeError,name + +def _swig_repr(self): + try: strthis = "proxy of " + self.this.__repr__() + except: strthis = "" + return "<%s.%s; %s >" % (self.__class__.__module__, self.__class__.__name__, strthis,) + +import types +try: + _object = types.ObjectType + _newclass = 1 +except AttributeError: + class _object : pass + _newclass = 0 +del types + + +VERSION_NUM = _pyiso9660.VERSION_NUM +INVALID_LBA = _pyiso9660.INVALID_LBA +INVALID_LSN = _pyiso9660.INVALID_LSN +CD_FRAMESIZE = _pyiso9660.CD_FRAMESIZE +CD_FRAMESIZE_RAW = _pyiso9660.CD_FRAMESIZE_RAW +ISO_BLOCKSIZE = _pyiso9660.ISO_BLOCKSIZE +M2F2_SECTOR_SIZE = _pyiso9660.M2F2_SECTOR_SIZE +M2RAW_SECTOR_SIZE = _pyiso9660.M2RAW_SECTOR_SIZE +CDIO_READ_MODE_AUDIO = _pyiso9660.CDIO_READ_MODE_AUDIO +CDIO_READ_MODE_M1F1 = _pyiso9660.CDIO_READ_MODE_M1F1 +CDIO_READ_MODE_M1F2 = _pyiso9660.CDIO_READ_MODE_M1F2 +CDIO_READ_MODE_M2F1 = _pyiso9660.CDIO_READ_MODE_M2F1 +CDIO_READ_MODE_M2F2 = _pyiso9660.CDIO_READ_MODE_M2F2 +cdio_read_sectors = _pyiso9660.cdio_read_sectors +cdio_eject_media_drive = _pyiso9660.cdio_eject_media_drive +PVD_SECTOR = _pyiso9660.PVD_SECTOR +EVD_SECTOR = _pyiso9660.EVD_SECTOR +LEN_ISONAME = _pyiso9660.LEN_ISONAME +MAX_SYSTEM_ID = _pyiso9660.MAX_SYSTEM_ID +MAX_ISONAME = _pyiso9660.MAX_ISONAME +MAX_PREPARER_ID = _pyiso9660.MAX_PREPARER_ID +MAX_ISOPATHNAME = _pyiso9660.MAX_ISOPATHNAME +FILE = _pyiso9660.FILE +EXISTENCE = _pyiso9660.EXISTENCE +DIRECTORY = _pyiso9660.DIRECTORY +ASSOCIATED = _pyiso9660.ASSOCIATED +RECORD = _pyiso9660.RECORD +PROTECTION = _pyiso9660.PROTECTION +DRESERVED1 = _pyiso9660.DRESERVED1 +DRESERVED2 = _pyiso9660.DRESERVED2 +MULTIEXTENT = _pyiso9660.MULTIEXTENT +VD_BOOT_RECORD = _pyiso9660.VD_BOOT_RECORD +VD_PRIMARY = _pyiso9660.VD_PRIMARY +VD_SUPPLEMENTARY = _pyiso9660.VD_SUPPLEMENTARY +VD_PARITION = _pyiso9660.VD_PARITION +VD_END = _pyiso9660.VD_END +MAX_PUBLISHER_ID = _pyiso9660.MAX_PUBLISHER_ID +MAX_APPLICATION_ID = _pyiso9660.MAX_APPLICATION_ID +MAX_VOLUME_ID = _pyiso9660.MAX_VOLUME_ID +MAX_VOLUMESET_ID = _pyiso9660.MAX_VOLUMESET_ID +STANDARD_ID = _pyiso9660.STANDARD_ID +NOCHECK = _pyiso9660.NOCHECK +SEVEN_BIT = _pyiso9660.SEVEN_BIT +ACHARS = _pyiso9660.ACHARS +DCHARS = _pyiso9660.DCHARS +EXTENSION_JOLIET_LEVEL1 = _pyiso9660.EXTENSION_JOLIET_LEVEL1 +EXTENSION_JOLIET_LEVEL2 = _pyiso9660.EXTENSION_JOLIET_LEVEL2 +EXTENSION_JOLIET_LEVEL3 = _pyiso9660.EXTENSION_JOLIET_LEVEL3 +EXTENSION_ROCK_RIDGE = _pyiso9660.EXTENSION_ROCK_RIDGE +EXTENSION_HIGH_SIERRA = _pyiso9660.EXTENSION_HIGH_SIERRA +EXTENSION_ALL = _pyiso9660.EXTENSION_ALL +EXTENSION_NONE = _pyiso9660.EXTENSION_NONE +EXTENSION_JOLIET = _pyiso9660.EXTENSION_JOLIET + +def open_iso(*args): + """ + open_iso(path) + Open an ISO 9660 image for reading. Maybe in the future we will have + mode. None is returned on error. + + """ + return _pyiso9660.open_iso(*args) + +def open_ext(*args): + """ + Open an ISO 9660 image for reading allowing various ISO 9660 + extensions. Maybe in the future we will have a mode. None is + returned on error. + """ + return _pyiso9660.open_ext(*args) + +def open_fuzzy(*args): + """ + Open an ISO 9660 image for reading with some tolerence for positioning + of the ISO9660 image. We scan for ISO_STANDARD_ID and use that to set + the eventual offset to adjust by (as long as that is <= i_fuzz). + + Maybe in the future we will have a mode. None is returned on error. + + see iso9660_open + """ + return _pyiso9660.open_fuzzy(*args) + +def iso9660_open_fuzzy_ext(*args): + """ + Open an ISO 9660 image for reading with some tolerence for positioning + of the ISO9660 image. We scan for ISO_STANDARD_ID and use that to set + the eventual offset to adjust by (as long as that is <= i_fuzz). + + Maybe in the future we will have a mode. None is returned on error. + + see open_iso + + """ + return _pyiso9660.iso9660_open_fuzzy_ext(*args) + +def ifs_fuzzy_read_superblock(*args): + """ + Read the Super block of an ISO 9660 image but determine framesize + and datastart and a possible additional offset. Generally here we are + not reading an ISO 9660 image but a CD-Image which contains an ISO 9660 + filesystem. + + """ + return _pyiso9660.ifs_fuzzy_read_superblock(*args) + +def close(*args): + """ + Close previously opened ISO 9660 image. + True is unconditionally returned. If there was an error false would + be returned. + """ + return _pyiso9660.close(*args) + +def seek_read(*args): + """ + Seek to a position and then read n bytes. (buffer, size) are + returned. + """ + return _pyiso9660.seek_read(*args) + +def fs_read_pvd(*args): + """ + Read the Primary Volume Descriptor for a CD. + None is returned if there was an error. + """ + return _pyiso9660.fs_read_pvd(*args) + +def ifs_read_pvd(*args): + """ + Read the Primary Volume Descriptor for an ISO 9660 image. + None is returned if there was an error. + """ + return _pyiso9660.ifs_read_pvd(*args) + +def fs_read_superblock(*args): + """ + Read the Super block of an ISO 9660 image. This is the + Primary Volume Descriptor (PVD) and perhaps a Supplemental Volume + Descriptor if (Joliet) extensions are acceptable. + """ + return _pyiso9660.fs_read_superblock(*args) + +def ifs_read_superblock(*args): + """ + Read the Super block of an ISO 9660 image. This is the + Primary Volume Descriptor (PVD) and perhaps a Supplemental Volume + Descriptor if (Joliet) extensions are acceptable. + """ + return _pyiso9660.ifs_read_superblock(*args) + +def set_dtime(*args): + """Set time in format used in ISO 9660 directory index record""" + return _pyiso9660.set_dtime(*args) + +def set_ltime(*args): + """Set 'long' time in format used in ISO 9660 primary volume descriptor""" + return _pyiso9660.set_ltime(*args) + +def get_dtime(*args): + """ + Get Unix time structure from format use in an ISO 9660 directory index + record. Even though tm_wday and tm_yday fields are not explicitly in + idr_date, they are calculated from the other fields. + + If tm is to reflect the localtime, set 'use_localtime' true, otherwise + tm will reported in GMT. + """ + return _pyiso9660.get_dtime(*args) + +def get_ltime(*args): + """ + Get 'long' time in format used in ISO 9660 primary volume descriptor + from a Unix time structure. + """ + return _pyiso9660.get_ltime(*args) + +def is_dchar(*args): + """ + Return true if c is a DCHAR - a character that can appear in an an + ISO-9600 level 1 directory name. These are the ASCII capital + letters A-Z, the digits 0-9 and an underscore. + """ + return _pyiso9660.is_dchar(*args) + +def is_achar(*args): + """ + Return true if c is an ACHAR - + These are the DCHAR's plus some ASCII symbols including the space + symbol. + """ + return _pyiso9660.is_achar(*args) + +def name_translate(*args): + """ + Convert an ISO-9660 file name that stored in a directory entry into + what's usually listed as the file name in a listing. + Lowercase name, and remove trailing ;1's or .;1's and + turn the other ;'s into version numbers. + + @param psz_oldname the ISO-9660 filename to be translated. + @param psz_newname returned string. The caller allocates this and + it should be at least the size of psz_oldname. + @return length of the translated string is returned. + """ + return _pyiso9660.name_translate(*args) + +def name_translate_ext(*args): + """ + Convert an ISO-9660 file name that stored in a directory entry into + what's usually listed as the file name in a listing. Lowercase + name if no Joliet Extension interpretation. Remove trailing ;1's or + .;1's and turn the other ;'s into version numbers. + + @param psz_oldname the ISO-9660 filename to be translated. + @param psz_newname returned string. The caller allocates this and + it should be at least the size of psz_oldname. + @param i_joliet_level 0 if not using Joliet Extension. Otherwise the + Joliet level. + @return length of the translated string is returned. It will be no greater + than the length of psz_oldname. + """ + return _pyiso9660.name_translate_ext(*args) + +def strncpy_pad(*args): + """ + Pad string src with spaces to size len and copy this to dst. If + en is less than the length of src, dst will be truncated to the + first len characters of src. + + src can also be scanned to see if it contains only ACHARs, DCHARs, + 7-bit ASCII chars depending on the enumeration _check. + + In addition to getting changed, dst is the return value. + Note: this string might not be NULL terminated. + """ + return _pyiso9660.strncpy_pad(*args) + +def dirname_valid_p(*args): + """ + Check that psz_path is a valid ISO-9660 directory name. + + A valid directory name should not start out with a slash (/), + dot (.) or null byte, should be less than 37 characters long, + have no more than 8 characters in a directory component + which is separated by a /, and consist of only DCHARs. + + True is returned if psz_path is valid. + """ + return _pyiso9660.dirname_valid_p(*args) + +def pathname_isofy(*args): + """ + Take psz_path and a version number and turn that into a ISO-9660 + pathname. (That's just the pathname followed by ';' and the version + number. For example, mydir/file.ext -> MYDIR/FILE.EXT;1 for version + 1. The resulting ISO-9660 pathname is returned. + """ + return _pyiso9660.pathname_isofy(*args) + +def pathname_valid_p(*args): + """ + Check that psz_path is a valid ISO-9660 pathname. + + A valid pathname contains a valid directory name, if one appears and + the filename portion should be no more than 8 characters for the + file prefix and 3 characters in the extension (or portion after a + dot). There should be exactly one dot somewhere in the filename + portion and the filename should be composed of only DCHARs. + + True is returned if psz_path is valid. + """ + return _pyiso9660.pathname_valid_p(*args) + +def fs_stat(*args): + """Return file status for psz_path. None is returned on error.""" + return _pyiso9660.fs_stat(*args) + +def fs_stat_translate(*args): + """ + Return file status for path name psz_path. None is returned on error. + pathname version numbers in the ISO 9660 name are dropped, i.e. ;1 + is removed and if level 1 ISO-9660 names are lowercased. + + The b_mode2 parameter is not used. + """ + return _pyiso9660.fs_stat_translate(*args) + +def ifs_stat(*args): + """Return file status for pathname. None is returned on error.""" + return _pyiso9660.ifs_stat(*args) + +def ifs_stat_translate(*args): + """ + Return file status for path name psz_path. undef is returned on error. + pathname version numbers in the ISO 9660 name are dropped, i.e. ;1 is + removed and if level 1 ISO-9660 names are lowercased. + """ + return _pyiso9660.ifs_stat_translate(*args) + +def fs_readdir(*args): + """ + Read psz_path (a directory) and return a list of iso9660_stat_t + pointers for the files inside that directory. + """ + return _pyiso9660.fs_readdir(*args) + +def ifs_readdir(*args): + """ + Read psz_path (a directory) and return a list of iso9660_stat_t + pointers for the files inside that directory. + """ + return _pyiso9660.ifs_readdir(*args) + +def get_application_id(*args): + """ + Return the PVD's application ID. + None is returned if there is some problem in getting this. + """ + return _pyiso9660.get_application_id(*args) + +def ifs_get_application_id(*args): + """ + Get the application ID. Return None if there + is some problem in getting this. + """ + return _pyiso9660.ifs_get_application_id(*args) + +def get_joliet_level(*args): + """Return the Joliet level recognized for p_iso.""" + return _pyiso9660.get_joliet_level(*args) + +def get_dir_len(*args): + """Return the Joliet level recognized for p_iso.""" + return _pyiso9660.get_dir_len(*args) + +def iso9660_dir_to_name(*args): + """Return the directory name stored in the iso9660_dir_t.""" + return _pyiso9660.iso9660_dir_to_name(*args) + +def get_preparer_id(*args): + """ + Return a string containing the preparer id with trailing + blanks removed. + """ + return _pyiso9660.get_preparer_id(*args) + +def ifs_get_preparer_id(*args): + """ + Get the preparer ID. Return None if there is some problem in + getting this. + """ + return _pyiso9660.ifs_get_preparer_id(*args) + +def get_publisher_id(*args): + """ + Return a string containing the PVD's publisher id with trailing + blanks removed. + """ + return _pyiso9660.get_publisher_id(*args) + +def ifs_get_publisher_id(*args): + """ + Get the publisher ID. Return None if there + is some problem in getting this. + """ + return _pyiso9660.ifs_get_publisher_id(*args) + +def get_pvd_type(*args): + """ + Get the publisher ID. Return None if there + is some problem in getting this. + """ + return _pyiso9660.get_pvd_type(*args) + +def get_pvd_id(*args): + """ + Get the publisher ID. Return None if there + is some problem in getting this. + """ + return _pyiso9660.get_pvd_id(*args) + +def get_pvd_space_size(*args): + """ + Get the publisher ID. Return None if there + is some problem in getting this. + """ + return _pyiso9660.get_pvd_space_size(*args) + +def get_pvd_block_size(*args): + """ + Get the publisher ID. Return None if there + is some problem in getting this. + """ + return _pyiso9660.get_pvd_block_size(*args) + +def get_pvd_version(*args): + """ + Return the primary volume id version number (of pvd). + If there is an error 0 is returned. + """ + return _pyiso9660.get_pvd_version(*args) + +def get_system_id(*args): + """ + Return a string containing the PVD's system id with trailing + blanks removed. + """ + return _pyiso9660.get_system_id(*args) + +def ifs_get_system_id(*args): + """ + Get the system ID. None is returned if there + is some problem in getting this. + """ + return _pyiso9660.ifs_get_system_id(*args) + +def get_root_lsn(*args): + """ + Return the LSN of the root directory for pvd. If there is an error + INVALID_LSN is returned. + + """ + return _pyiso9660.get_root_lsn(*args) + +def get_volume_id(*args): + """Return the PVD's volume ID.""" + return _pyiso9660.get_volume_id(*args) + +def ifs_get_volume_id(*args): + """ + Get the system ID. None is returned if there + is some problem in getting this. + """ + return _pyiso9660.ifs_get_volume_id(*args) + +def get_volumeset_id(*args): + """ + Return the PVD's volumeset ID. + None is returned if there is some problem in getting this. + + """ + return _pyiso9660.get_volumeset_id(*args) + +def ifs_get_volumeset_id(*args): + """ + Get the volumeset ID. None is returned if there + is some problem in getting this. + """ + return _pyiso9660.ifs_get_volumeset_id(*args) + +def pathtable_init(*args): + """Zero's out pathable. Do this first.""" + return _pyiso9660.pathtable_init(*args) + +def pathtable_get_size(*args): + """Zero's out pathable. Do this first.""" + return _pyiso9660.pathtable_get_size(*args) + +def pathtable_l_add_entry(*args): + """Zero's out pathable. Do this first.""" + return _pyiso9660.pathtable_l_add_entry(*args) + +def pathtable_m_add_entry(*args): + """Zero's out pathable. Do this first.""" + return _pyiso9660.pathtable_m_add_entry(*args) + +def set_evd(*args): + """Zero's out pathable. Do this first.""" + return _pyiso9660.set_evd(*args) + +def is_xa(*args): + """Return true if ISO 9660 image has extended attrributes (XA).""" + return _pyiso9660.is_xa(*args) + + diff --git a/setup.py b/setup.py new file mode 100755 index 00000000..537d6f46 --- /dev/null +++ b/setup.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python + +""" +setup.py file for pycdio +""" + +from setuptools import setup +from distutils.core import Extension + +version = '0.14vc' + +import os +README = os.path.join(os.path.dirname(__file__), 'README.txt') +long_description = open(README).read() + '\n\n' + +pycdio_module = Extension('_pycdio', + sources=['pycdio_wrap.c', 'swig/pycdio.swg'], + ) +pyiso9660_module = Extension('_pyiso9660', + sources=['iso9660_wrap.c', 'swig/pyiso9660.swg'], + ) +setup (name = 'pycdio', + author = 'Rocky Bernstein', + author_email = 'rocky@gnu.org', + description = 'Python OO interface to libcdio (CD Input and Control library)', + py_ext_modules = [pycdio_module, pyiso9660_module], + license = 'GPL', + long_description = long_description, + name = 'pytracer', + py_modules = ['pycdio'], + test_suite = 'nose.collector', + url = 'http://freshmeat.net/projects/libcdio/?branch_id=62870', + version = version, + ) diff --git a/swig/audio.swg b/swig/audio.swg new file mode 100644 index 00000000..11fe88e9 --- /dev/null +++ b/swig/audio.swg @@ -0,0 +1,62 @@ +/* -*- c -*- + $Id: audio.swg,v 1.3 2008/05/01 16:55:05 karl Exp $ + + Copyright (C) 2006, 2008 Rocky Bernstein + + 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 3 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 + MERCHANTABILITY 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, see . +*/ + +/** NOTE: THIS IS THE EASILY CONVERTED SUBSET OF LIBCDIO WE CAN DO. **/ + +/* See for more extensive documentation. */ + +%rename cdio_audio_pause audio_pause; +%feature("autodoc", +"audio_pause(cdio)->status + +Pause playing CD through analog output."); +driver_return_code_t cdio_audio_pause (CdIo_t *p_cdio); + + +%feature("autodoc", +"auto_play_lsn(cdio, start_lsn, end_lsn)->status + +Playing CD through analog output at the given lsn to the ending lsn"); +driver_return_code_t audio_play_lsn (CdIo_t *p_cdio, lsn_t start_lsn, + lsn_t end_lsn); +%inline %{ +driver_return_code_t audio_play_lsn (CdIo_t *p_cdio, lsn_t start_lsn, + lsn_t end_lsn) +{ + msf_t start_msf; + msf_t end_msf; + cdio_lsn_to_msf (start_lsn, &start_msf); + cdio_lsn_to_msf (end_lsn, &end_msf); + return cdio_audio_play_msf(p_cdio, &start_msf, &end_msf); +} +%} + +%rename cdio_audio_resume audio_resume; +%feature("autodoc", +"audio_resume(cdio)->status +Resume playing an audio CD."); +driver_return_code_t cdio_audio_resume (CdIo_t *p_cdio); + + +%rename cdio_audio_stop audio_stop; +%feature("autodoc", +"audio_stop(cdio)->status +Stop playing an audio CD."); +driver_return_code_t cdio_audio_stop (CdIo_t *p_cdio); + diff --git a/swig/compat.swg b/swig/compat.swg new file mode 100644 index 00000000..c3fa24dc --- /dev/null +++ b/swig/compat.swg @@ -0,0 +1,103 @@ +/* -*- c -*- + $Id: compat.swg,v 1.3 2008/05/01 16:55:05 karl Exp $ + + Copyright (C) 2006, 2008 Rocky Bernstein + + 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 3 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 + MERCHANTABILITY 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, see . +*/ +%inline %{ +/* When libcdio version > 0.76 comes out this won't be needed. */ +#include +#if LIBCDIO_VERSION_NUM <= 76 + +/**< Masks derived from above... */ +#undef CDIO_DRIVE_CAP_WRITE_DVD +#define CDIO_DRIVE_CAP_WRITE_DVD ( \ + CDIO_DRIVE_CAP_WRITE_DVD_R \ + | CDIO_DRIVE_CAP_WRITE_DVD_PR \ + | CDIO_DRIVE_CAP_WRITE_DVD_RAM \ + | CDIO_DRIVE_CAP_WRITE_DVD_RW \ + | CDIO_DRIVE_CAP_WRITE_DVD_RPW \ + ) + +/** All the different ways a block/sector can be read. */ +typedef enum { + CDIO_READ_MODE_AUDIO, /**< CD-DA, audio, Red Book */ + CDIO_READ_MODE_M1F1, /**< Mode 1 Form 1 */ + CDIO_READ_MODE_M1F2, /**< Mode 1 Form 2 */ + CDIO_READ_MODE_M2F1, /**< Mode 2 Form 1 */ + CDIO_READ_MODE_M2F2, /**< Mode 2 Form 2 */ +} cdio_read_mode_t; + +/*! + Reads a number of sectors (AKA blocks). + + @param p_buf place to read data into. The caller should make sure + this location is large enough. See below for size information. + @param read_mode the kind of "mode" to use in reading. + @param i_lsn sector to read + @param i_blocks number of sectors to read + @return DRIVER_OP_SUCCESS (0) if no error, other (negative) enumerations + are returned on error. + + If read_mode is CDIO_MODE_AUDIO, + *p_buf should hold at least CDIO_FRAMESIZE_RAW * i_blocks bytes. + + If read_mode is CDIO_MODE_DATA, + *p_buf should hold at least i_blocks times either ISO_BLOCKSIZE, + M1RAW_SECTOR_SIZE or M2F2_SECTOR_SIZE depending on the kind of + sector getting read. If you don't know whether you have a Mode 1/2, + Form 1/ Form 2/Formless sector best to reserve space for the maximum + which is M2RAW_SECTOR_SIZE. + + If read_mode is CDIO_MODE_M2F1, + *p_buf should hold at least M2RAW_SECTOR_SIZE * i_blocks bytes. + + If read_mode is CDIO_MODE_M2F2, + *p_buf should hold at least CDIO_CD_FRAMESIZE * i_blocks bytes. + + +*/ +driver_return_code_t +cdio_read_sectors(const CdIo_t *p_cdio, void *p_buf, lsn_t i_lsn, + cdio_read_mode_t read_mode, uint32_t i_blocks) +{ + switch(read_mode) { + case CDIO_READ_MODE_AUDIO: + return cdio_read_audio_sectors (p_cdio, p_buf, i_lsn, i_blocks); + case CDIO_READ_MODE_M1F1: + return cdio_read_mode1_sectors (p_cdio, p_buf, i_lsn, false, i_blocks); + case CDIO_READ_MODE_M1F2: + return cdio_read_mode1_sectors (p_cdio, p_buf, i_lsn, true, i_blocks); + case CDIO_READ_MODE_M2F1: + return cdio_read_mode2_sectors (p_cdio, p_buf, i_lsn, false, i_blocks); + case CDIO_READ_MODE_M2F2: + return cdio_read_mode2_sectors (p_cdio, p_buf, i_lsn, true, i_blocks); + } + /* Can't happen. Just to shut up gcc. */ + return DRIVER_OP_ERROR; +} + +driver_return_code_t +cdio_eject_media_drive (const char *psz_drive) +{ + CdIo_t *p_cdio = cdio_open (psz_drive, DRIVER_DEVICE); + if (p_cdio) { + return cdio_eject_media(&p_cdio); + } else { + return DRIVER_OP_UNINIT; + } +} +#endif /* LIBCDIO_VERSION_NUM <= 76 */ +%} diff --git a/swig/device.swg b/swig/device.swg new file mode 100644 index 00000000..a934f4d3 --- /dev/null +++ b/swig/device.swg @@ -0,0 +1,531 @@ +/* -*- c -*- + $Id: device.swg,v 1.16 2008/05/01 16:55:05 karl Exp $ + + Copyright (C) 2006, 2008 Rocky Bernstein + + 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 3 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 + MERCHANTABILITY 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, see . +*/ +/* See for more extensive documentation. */ + +%include "device_const.swg" + +/* Set up to allow functions returning device lists of type "char + **". We'll use a typedef so we can make sure to isolate this. I + don't think we need to in this program, but it I think it makes + thing clearer. +*/ +%inline %{ +typedef char ** DeviceList_t; +%} + +%typemap(newfree) DeviceList_t "cdio_free_device_list($1);"; +%typemap(out,fragment="t_output_helper") DeviceList_t { + // result is of type DeviceList_t + char **p = result; + if (!result) goto fail; + + /* For each element in the array of strings, push that + * onto the result object. */ + for (p = result; *p; p++) { + PyObject *o = PyString_FromString(*p); + resultobj = t_output_helper(resultobj,o); + } +} + +%rename cdio_close_tray close_tray; +%feature("autodoc", +"close_tray(drive=None, driver_id=None) -> [status, driver_id] + +close media tray in CD drive if there is a routine to do so. +The driver id is returned. An exception is thrown on error."); +%apply driver_id_t *OUTPUT { driver_id_t *p_out_driver_id }; +driver_return_code_t close_tray(const char *psz_drive, + driver_id_t p_driver_id=DRIVER_UNKNOWN, + driver_id_t *p_out_driver_id); +%inline %{ +driver_return_code_t +close_tray(const char *psz_drive, driver_id_t p_driver_id, + driver_id_t *p_out_driver_id) +{ + *p_out_driver_id = p_driver_id; + return cdio_close_tray(psz_drive, p_out_driver_id); +} +%} + +%rename cdio_destroy close; +%feature("autodoc", +"destroy(p_cdio) +Free resources associated with p_cdio. Call this when done using +using CD reading/control operations for the current device. +"); +void cdio_destroy(CdIo_t *p_cdio); + +%feature("autodoc", +"eject_media(cdio)->return_code + +Eject media in CD drive if there is a routine to do so. +"); +driver_return_code_t eject_media (CdIo_t *p_cdio); +%inline %{ +driver_return_code_t +eject_media (CdIo_t *p_cdio) +{ + /* libcdio routines uses a Cdio_t **p_cdio, so we have to pass in + something it can clobber. + */ + CdIo_t **pp_cdio = &p_cdio; + return cdio_eject_media (pp_cdio); +} +%} + + +%rename cdio_eject_media_drive eject_media_drive; +%feature("autodoc", +"eject_media_drive(drive=None)->return_code +Eject media in CD drive if there is a routine to do so. + +psz_drive: the name of the device to be acted upon. +The operation status is returned."); +driver_return_code_t cdio_eject_media_drive (const char *psz_drive=NULL); + +%rename cdio_get_arg get_arg; +%feature("autodoc", +"get_arg(p_cdio, key)->string + +Get the value associatied with key."); +const char *cdio_get_arg (const CdIo_t *p_cdio, const char key[]); + +%newobject cdio_get_default_device; // free malloc'd return value +%rename cdio_get_default_device get_device; +%feature("autodoc", +"get_device(cdio)->str + +Get the CD device associated with cdio. +If cdio is NULL (we haven't initialized a specific device driver), +then find a suitable one and return the default device for that. + +In some situations of drivers or OS's we can't find a CD device if +there is no media in it and it is possible for this routine to return +None even though there may be a hardware CD-ROM."); +char *cdio_get_default_device (const CdIo_t *p_cdio=NULL); + +%newobject get_default_device_driver; // free malloc'd return value +%feature("autodoc", +"get_default_device_driver(driver_id=None)->[device, driver] +Return a string containing the default CD device if none is specified. +if p_driver_id is DRIVER_UNKNOWN or DRIVER_DEVICE then find a suitable +one set the default device for that. + +None is returned as the device if we couldn't get a default device."); +%apply driver_id_t *OUTPUT { driver_id_t *p_out_driver_id }; +char *get_default_device_driver (driver_id_t p_driver_id, + driver_id_t *p_out_driver_id); +%inline %{ +char * +get_default_device_driver(driver_id_t driver_id, driver_id_t *p_out_driver_id) +{ + *p_out_driver_id = driver_id; + return cdio_get_default_device_driver(p_out_driver_id); +} +%} + +%rename cdio_get_devices get_devices; +%newobject cdio_get_devices; +%feature("autodoc", +"get_devices(driver_id)->[device1, device2, ...] + +Get an list of device names."); +DeviceList_t cdio_get_devices (driver_id_t driver_id); + +%newobject get_devices_ret; +%feature("autodoc", +"get_devices_ret(driver_id)->[device1, device2, ... driver_id] + +Like get_devices, but return the p_driver_id which may be different +from the passed-in driver_id if it was pycdio.DRIVER_DEVICE or +pycdio.DRIVER_UNKNOWN. The return driver_id may be useful because +often one wants to get a drive name and then *open* it +afterwards. Giving the driver back facilitates this, and speeds things +up for libcdio as well."); + +DeviceList_t get_devices_ret (driver_id_t driver_id, + driver_id_t *p_out_driver_id); +%inline %{ +DeviceList_t get_devices_ret (driver_id_t driver_id, + driver_id_t *p_out_driver_id) { + *p_out_driver_id = driver_id; + return cdio_get_devices_ret (p_out_driver_id); + } +%} + +%feature("autodoc", +"get_devices_with_cap(capabilities, any)->[device1, device2...] +Get an array of device names in search_devices that have at least +the capabilities listed by the capabities parameter. + +If any is False then every capability listed in the +extended portion of capabilities (i.e. not the basic filesystem) +must be satisified. If any is True, then if any of the +capabilities matches, we call that a success. + +To find a CD-drive of any type, use the mask pycdio.CDIO_FS_MATCH_ALL. + +The array of device names is returned or NULL if we couldn't get a +default device. It is also possible to return a non NULL but after +dereferencing the the value is NULL. This also means nothing was +found."); +DeviceList_t get_devices_with_cap (unsigned int capabilities, bool b_any); +%inline %{ +DeviceList_t +get_devices_with_cap (unsigned int capabilities, bool b_any) { + /* FIXME: ? libcdio allows one to specify a list (char **) of devices + to search. Don't know how to do that via SWIG though. */ + return cdio_get_devices_with_cap (NULL, (cdio_fs_anal_t) capabilities, + b_any); + } +%} + +%apply driver_id_t *OUTPUT { driver_id_t *p_out_driver_id }; +%newobject get_devices_with_cap_ret; +%feature("autodoc", +"Like cdio_get_devices_with_cap but we return the driver we found +as well. This is because often one wants to search for kind of drive +and then *open* it afterwards. Giving the driver back facilitates this, + and speeds things up for libcdio as well."); +DeviceList_t get_devices_with_cap_ret (unsigned int capabilities, bool b_any, + driver_id_t *p_out_driver_id); +%inline %{ +DeviceList_t +get_devices_with_cap_ret (unsigned int capabilities, bool b_any, + driver_id_t *p_out_driver_id) { + /* FIXME: ? libcdio allows one to specify a list (char **) of devices + to search. Don't know how to do that via SWIG though. */ + return cdio_get_devices_with_cap_ret (NULL, + (cdio_fs_anal_t) capabilities, b_any, + p_out_driver_id); + } +%} + +%rename cdio_get_drive_cap get_drive_cap; +%feature("autodoc", +"get_drive_cap()->(read_cap, write_cap, misc_cap) + +Get drive capabilities of device. + +In some situations of drivers or OS's we can't find a CD device if +there is no media in it. In this situation capabilities will show up as +empty even though there is a hardware CD-ROM."); +%apply uint32_t *OUTPUT { uint32_t *p_read_cap, + uint32_t *p_write_cap, + uint32_t *p_misc_cap }; +void cdio_get_drive_cap (const CdIo_t *p_cdio, + uint32_t *p_read_cap, + uint32_t *p_write_cap, + uint32_t *p_misc_cap); + +%rename cdio_get_drive_cap_dev get_drive_cap; +%feature("autodoc", +"get_drive_cap_dev()->(read_cap, write_cap, misc_cap) + +Get drive capabilities of device. + +In some situations of drivers or OS's we can't find a CD device if +there is no media in it. In this situation capabilities will show up as +empty even though there is a hardware CD-ROM."); + +void cdio_get_drive_cap_dev(const char *device=NULL, + uint32_t *p_read_cap, + uint32_t *p_write_cap, + uint32_t *p_misc_cap); + +%rename cdio_get_driver_name get_driver_name; +%feature("autodoc", +"get_driver_name(cdio)-> string + +return a string containing the name of the driver in use. + +An IOError exception is raised on error. +"); +%exception cdio_get_driver_name { + $action + if (NULL == result) { + PyErr_SetString(PyExc_IOError, "Error getting driver name."); + return NULL; + } +} +const char *cdio_get_driver_name (const CdIo_t *p_cdio); + +%rename cdio_get_driver_id get_driver_id; +%feature("autodoc", +"get_driver_id(cdio)-> int + +Return the driver id of the driver in use. +if cdio has not been initialized or is None, +return pycdio.DRIVER_UNKNOWN."); +driver_id_t cdio_get_driver_id (const CdIo_t *p_cdio); + +%rename cdio_get_last_session get_last_session; +%feature("autodoc", +"get_last_session(p_cdio) -> int +Get the LSN of the first track of the last session of on the CD. +An exception is thrown on error."); +%apply int *OUTPUT { lsn_t *i_last_session }; +driver_return_code_t cdio_get_last_session (CdIo_t *p_cdio, + lsn_t *i_last_session); + +%feature("autodoc", +"have_driver(driver_id) -> int + +Return 1 if we have driver driver_id, 0 if not and -1 +if driver id is out of range."); +%inline %{ +int +have_driver (unsigned int driver_id) +{ + if (driver_id < CDIO_MIN_DRIVER || driver_id > CDIO_MAX_DRIVER) + return -1; + if (cdio_have_driver(driver_id)) return 1; + return 0; +} +%} + +%feature("autodoc", +"have_ATAPI(CdIo_t *p_cdio)->bool +return True if CD-ROM understand ATAPI commands."); +%inline %{ +/*! True if CD-ROM understand ATAPI commands. */ +bool +have_ATAPI (CdIo_t *p_cdio) +{ + return cdio_have_atapi(p_cdio) == yep; +} +%} + +%newobject cdio_is_binfile; // free malloc'd return value +%rename cdio_is_binfile is_binfile; +%feature("autodoc", +"is_binfile(binfile_name)->cue_name + +Determine if binfile_name is the BIN file part of a CDRWIN CD disk +image. + +Return the corresponding CUE file if bin_name is a BIN file or +None if not a BIN file."); +char *cdio_is_binfile(const char *bin_name); + +%newobject cdio_is_cuefile; // free malloc'd return value +%rename cdio_is_cuefile is_cuefile; +%feature("autodoc", +"is_cuefile(cuefile_name)->bin_name + +Determine if cuefile_name is the CUE file part of a CDRWIN CD disk +image. + +Return the corresponding BIN file if bin_name is a CUE file or +None if not a CUE file."); +char *cdio_is_cuefile(const char *cue_name); + +#if LIBCDIO_VERSION_NUM <= 76 +/* There is a bug in the 0.76 code when driver_id==DRIVER_UNKNOWN + or DRIVER_DEVICE, so here we'll use code from compat.swg. +*/ +bool is_device(const char *psz_source, + driver_id_t driver_id=DRIVER_UNKNOWN); + +%inline %{ +bool +is_device(const char *psz_source, driver_id_t driver_id) +{ + if (DRIVER_UNKNOWN == driver_id || DRIVER_DEVICE == driver_id) { + char *psz_drive = cdio_get_default_device_driver(&driver_id); + /* We don't need the psz_drive, just the driver_id */ + free(psz_drive); + } + return cdio_is_device(psz_source, driver_id); +} +%} +#else +%rename cdio_is_device is_device; +bool cdio_is_device(const char *psz_source, + driver_id_t driver_id=DRIVER_UNKNOWN); +#endif /* LIBCDIO_VERSION_NUM */ + +%rename cdio_is_nrg is_nrg; +%feature("autodoc", +"is_nrg(cue_name)->bool + +Determine if nrg_name is a Nero CD disc image"); +bool cdio_is_nrg(const char *nrg_name); + +%rename cdio_is_tocfile is_tocfile; +%feature("autodoc", +"is_tocfile(tocfile_name)->bool + +Determine if tocfile_name is a cdrdao CD disc image"); +bool cdio_is_tocfile(const char *tocfile_name); + +%rename cdio_get_media_changed get_media_changed; +%feature("autodoc", +"get_media_changed(cdio) -> int + +Find out if media has changed since the last call. +Return 1 if media has changed since last call, 0 if not. Error +return codes are the same as driver_return_code_t"); +int cdio_get_media_changed(CdIo_t *p_cdio); + +%feature("autodoc", +"get_hwinfo(p_cdio)->[drc, vendor, model, release] +Get the CD-ROM hardware info via a SCSI MMC INQUIRY command."); + +%cstring_bounded_output(char *vendor, CDIO_MMC_HW_VENDOR_LEN); +%cstring_bounded_output(char *model, CDIO_MMC_HW_MODEL_LEN); +%cstring_bounded_output(char *revision, CDIO_MMC_HW_REVISION_LEN); +int get_hwinfo ( const CdIo_t *p_cdio, + char *vendor, char *model, char *revision ); +%inline %{ +int get_hwinfo ( const CdIo_t *p_cdio, + char *vendor, char *model, char *revision ) +{ + /** There's a bug somewhere here. If we take off the static, + we clobber our parameters. So get_hwinfo sizes must not + agree. + **/ + static cdio_hwinfo_t hw_info; + bool b_got_hwinfo = cdio_get_hwinfo(p_cdio, &hw_info); + + if (b_got_hwinfo) { + strncpy(vendor, hw_info.psz_vendor, CDIO_MMC_HW_VENDOR_LEN); + strncpy(model, hw_info.psz_model, CDIO_MMC_HW_MODEL_LEN); + strncpy(revision, hw_info.psz_revision, CDIO_MMC_HW_REVISION_LEN); + } + + return b_got_hwinfo; +} +%} + +%rename cdio_set_blocksize set_blocksize; +%feature("autodoc", +"set_blocksize(cdio, blocksize)->return_status + +Set the blocksize for subsequent reads."); +driver_return_code_t cdio_set_blocksize ( const CdIo_t *p_cdio, + int i_blocksize ); + +%rename cdio_set_speed set_speed; +%feature("autodoc", +"cdio_set_speed(cdio, speed)->return_status +Set the drive speed."); +driver_return_code_t cdio_set_speed ( const CdIo_t *p_cdio, int i_speed ); + + +/** FIXME: the below works, but is clunky. + + In the next release of libcdio (or CVS right now) we have a way to + get the strings from the exception code so we don't need the switch + statement. +*/ +%exception { + $action + if (DRIVER_OP_SUCCESS == drc) goto out; + set_python_errstring(drc); + return NULL; + out: ; +} + + +/*=================================================================* + NOTE: ALL ROUTINES DEFINED BELOW CAN GIVE A DRIVER_RETURN_CODE_T + EXCEPTION. +*=================================================================*/ + +/**** Using the name open() conflicts with some C routine. + So we use open_cd() instead. +***/ +%feature("autodoc", +"open_cd(source=NULL, driver_id=None, access_mode=None) + +Sets up to read from place specified by source, driver_id and +access mode. This should be called before using any other routine +except those that act on a CD-ROM drive by name. + +If None is given as the source, we'll use the default driver device. +If None is given as the driver_id, we'll find a suitable device driver. + +Return the a pointer than can be used in subsequent operations or +None on error or no device."); +CdIo_t *open_cd(const char *psz_source=NULL, + driver_id_t driver_id=DRIVER_UNKNOWN, + const char *psz_access_mode=NULL); + +%inline %{ + +/* FIXME: Instead of a static variable drc which doesn't allow for + multiple threads, we should change the C code to return the status + parameter and have the exception handling code work off of this. + Although this is easily done in C and the SWIG-generated C wrapper + code, I don't the SWIG lingo to make the generated C wrapper do + this. + + Basically, we need a way to tell SWIG not to translate the %inline + C code return value into a Python return value, but save the value + anyway to pick it value up in the SWIG %exception. +*/ +static driver_return_code_t drc = DRIVER_OP_SUCCESS; + +static void +set_python_errstring(driver_return_code_t drc) +{ + switch(drc) { + case DRIVER_OP_SUCCESS: + break; + case DRIVER_OP_ERROR: + PyErr_SetString(PyExc_IOError, "driver I/O error."); + break; + case DRIVER_OP_UNINIT: + PyErr_SetString(PyExc_IOError, "driver not initialized."); + break; + case DRIVER_OP_UNSUPPORTED: + PyErr_SetString(PyExc_IOError, "driver operatation not supported."); + break; + case DRIVER_OP_NOT_PERMITTED: + PyErr_SetString(PyExc_IOError, "driver operatation not permitted."); + break; + case DRIVER_OP_BAD_PARAMETER: + PyErr_SetString(PyExc_IOError, "bad parameter passed."); + break; + case DRIVER_OP_BAD_POINTER: + PyErr_SetString(PyExc_IOError, "bad pointer to memory area."); + break; + case DRIVER_OP_NO_DRIVER: + PyErr_SetString(PyExc_IOError, "driver not available."); + break; + default: + PyErr_SetString(PyExc_IOError, "unknown error."); + break; + } + } + +CdIo_t *open_cd(const char *psz_source, driver_id_t driver_id, + const char *psz_access_mode) { + /* FIXME: On error we return a funny "NULL" Object. + */ + CdIo_t *p_cdio = cdio_open_am(psz_source, driver_id, psz_access_mode); + if (NULL == p_cdio) { + drc = DRIVER_OP_ERROR; + } else { + drc = DRIVER_OP_SUCCESS; + } + return p_cdio; + } +%} diff --git a/swig/device_const.swg b/swig/device_const.swg new file mode 100644 index 00000000..c3275d01 --- /dev/null +++ b/swig/device_const.swg @@ -0,0 +1,143 @@ +/* -*- c -*- + $Id: device_const.swg,v 1.5 2008/05/01 16:55:05 karl Exp $ + + Copyright (C) 2006, 2008 Rocky Bernstein + + 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 3 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 + MERCHANTABILITY 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, see . +*/ +/* See for more extensive documentation. */ + +/**** ALL OF THESE ARE CONSTANTS *** */ +%immutable; + +/* Drive types returned by cdio_get_drive_cap() */ +%constant long int DRIVE_CAP_ERROR = CDIO_DRIVE_CAP_ERROR; +%constant long int DRIVE_CAP_UNKNOWN = CDIO_DRIVE_CAP_UNKNOWN; +%constant long int DRIVE_CAP_MISC_CLOSE_TRAY = CDIO_DRIVE_CAP_MISC_CLOSE_TRAY; +%constant long int DRIVE_CAP_MISC_EJECT = CDIO_DRIVE_CAP_MISC_EJECT; +%constant long int DRIVE_CAP_MISC_LOCK = CDIO_DRIVE_CAP_MISC_LOCK ; +%constant long int DRIVE_CAP_MISC_SELECT_SPEED = CDIO_DRIVE_CAP_MISC_SELECT_SPEED; +%constant long int DRIVE_CAP_MISC_SELECT_DISC = CDIO_DRIVE_CAP_MISC_SELECT_DISC; +%constant long int DRIVE_CAP_MISC_MULTI_SESSION = CDIO_DRIVE_CAP_MISC_MULTI_SESSION; +%constant long int DRIVE_CAP_MISC_MEDIA_CHANGED = CDIO_DRIVE_CAP_MISC_MEDIA_CHANGED; +%constant long int DRIVE_CAP_MISC_RESET = CDIO_DRIVE_CAP_MISC_RESET; +%constant long int DRIVE_CAP_MISC_FILE = CDIO_DRIVE_CAP_MISC_FILE; + +/* Reading masks.. */ +%constant long int DRIVE_CAP_READ_AUDIO = CDIO_DRIVE_CAP_READ_AUDIO; +%constant long int DRIVE_CAP_READ_CD_DA = CDIO_DRIVE_CAP_READ_CD_DA; +%constant long int DRIVE_CAP_READ_CD_G = CDIO_DRIVE_CAP_READ_CD_G; +%constant long int DRIVE_CAP_READ_CD_R = CDIO_DRIVE_CAP_READ_CD_R; +%constant long int DRIVE_CAP_READ_CD_RW = CDIO_DRIVE_CAP_READ_CD_RW; +%constant long int DRIVE_CAP_READ_DVD_R = CDIO_DRIVE_CAP_READ_DVD_R; +%constant long int DRIVE_CAP_READ_DVD_PR = CDIO_DRIVE_CAP_READ_DVD_PR; +%constant long int DRIVE_CAP_READ_DVD_RAM = CDIO_DRIVE_CAP_READ_DVD_RAM; +%constant long int DRIVE_CAP_READ_DVD_ROM = CDIO_DRIVE_CAP_READ_DVD_ROM; +%constant long int DRIVE_CAP_READ_DVD_RW = CDIO_DRIVE_CAP_READ_DVD_RW; +%constant long int DRIVE_CAP_READ_DVD_RPW = CDIO_DRIVE_CAP_READ_DVD_RPW; +%constant long int DRIVE_CAP_READ_C2_ERRS = CDIO_DRIVE_CAP_READ_C2_ERRS; +%constant long int DRIVE_CAP_READ_MODE2_FORM1 = CDIO_DRIVE_CAP_READ_MODE2_FORM1; +%constant long int DRIVE_CAP_READ_MODE2_FORM2 = CDIO_DRIVE_CAP_READ_MODE2_FORM2; +%constant long int DRIVE_CAP_READ_MCN = CDIO_DRIVE_CAP_READ_MCN; +%constant long int DRIVE_CAP_READ_ISRC = CDIO_DRIVE_CAP_READ_ISRC; + +/* Writing masks.. */ +%constant long int DRIVE_CAP_WRITE_CD_R = CDIO_DRIVE_CAP_WRITE_CD_R; +%constant long int DRIVE_CAP_WRITE_CD_RW = CDIO_DRIVE_CAP_WRITE_CD_RW; +%constant long int DRIVE_CAP_WRITE_DVD_R = CDIO_DRIVE_CAP_WRITE_DVD_R; +%constant long int DRIVE_CAP_WRITE_DVD_PR = CDIO_DRIVE_CAP_WRITE_DVD_PR; +%constant long int DRIVE_CAP_WRITE_DVD_RAM = CDIO_DRIVE_CAP_WRITE_DVD_RAM; +%constant long int DRIVE_CAP_WRITE_DVD_RW = CDIO_DRIVE_CAP_WRITE_DVD_RW ; +%constant long int DRIVE_CAP_WRITE_DVD_RPW = CDIO_DRIVE_CAP_WRITE_DVD_RPW; +%constant long int DRIVE_CAP_WRITE_MT_RAINIER = CDIO_DRIVE_CAP_WRITE_MT_RAINIER; +%constant long int DRIVE_CAP_WRITE_BURN_PROOF = CDIO_DRIVE_CAP_WRITE_BURN_PROOF; + +/*** Masks derived from above... ***/ +/* Has some sort of CD writer ability. */ +%constant long int DRIVE_CAP_WRITE_CD = CDIO_DRIVE_CAP_WRITE_CD; +/* Has some sort of DVD writer ability */ +%constant long int DRIVE_CAP_WRITE_DVD = CDIO_DRIVE_CAP_WRITE_DVD; +%constant long int DRIVE_CAP_WRITE = CDIO_DRIVE_CAP_WRITE; + +/*! Size of fields returned by an INQUIRY command */ +%constant long int MMC_HW_VENDOR_LEN = CDIO_MMC_HW_VENDOR_LEN; +%constant long int MMC_HW_MODEL_LEN = CDIO_MMC_HW_MODEL_LEN; +%constant long int MMC_HW_REVISION_LEN = CDIO_MMC_HW_REVISION_LEN; + +/**! Flags specifying the category of device to open or is opened. */ +%constant long int SRC_IS_DISK_IMAGE_MASK = CDIO_SRC_IS_DISK_IMAGE_MASK; +%constant long int SRC_IS_DEVICE_MASK = CDIO_SRC_IS_DEVICE_MASK; +%constant long int SRC_IS_SCSI_MASK = CDIO_SRC_IS_SCSI_MASK; +%constant long int SRC_IS_NATIVE_MASK = CDIO_SRC_IS_NATIVE_MASK; + +/* driver_id_t enums. */ +%constant long int DRIVER_UNKNOWN = DRIVER_UNKNOWN; +%constant long int DRIVER_AIX = DRIVER_AIX; +%constant long int DRIVER_BSDI = DRIVER_BSDI; +%constant long int DRIVER_FREEBSD = DRIVER_FREEBSD; +%constant long int DRIVER_LINUX = DRIVER_LINUX; +%constant long int DRIVER_SOLARIS = DRIVER_SOLARIS; +%constant long int DRIVER_OSX = DRIVER_OSX; +%constant long int DRIVER_WIN32 = DRIVER_WIN32; +%constant long int DRIVER_CDRDAO = DRIVER_CDRDAO; +%constant long int DRIVER_BINCUE = DRIVER_BINCUE; +%constant long int DRIVER_NRG = DRIVER_NRG; +%constant long int DRIVER_DEVICE = DRIVER_DEVICE; + +%constant long int MIN_DRIVER = CDIO_MIN_DRIVER; +%constant long int MIN_DEVICE_DRIVER = CDIO_MIN_DEVICE_DRIVER; +%constant long int MAX_DRIVER = CDIO_MAX_DRIVER; +%constant long int MAX_DEVICE_DRIVER = CDIO_MAX_DEVICE_DRIVER; + + +%constant long int DRIVER_OP_SUCCESS = DRIVER_OP_SUCCESS; +%constant long int DRIVER_OP_ERROR = DRIVER_OP_ERROR; +%constant long int DRIVER_OP_UNSUPPORTED = DRIVER_OP_UNSUPPORTED; +%constant long int DRIVER_OP_UNINIT = DRIVER_OP_UNINIT; +%constant long int DRIVER_OP_NOT_PERMITTED = DRIVER_OP_NOT_PERMITTED; +%constant long int DRIVER_OP_BAD_PARAMETER = DRIVER_OP_BAD_PARAMETER; +%constant long int DRIVER_OP_BAD_POINTER = DRIVER_OP_BAD_POINTER; +%constant long int DRIVER_OP_NO_DRIVER = DRIVER_OP_NO_DRIVER; + +%constant unsigned int FS_AUDIO = CDIO_FS_AUDIO; +%constant unsigned int FS_HIGH_SIERRA = CDIO_FS_HIGH_SIERRA; +%constant unsigned int FS_ISO_9660 = CDIO_FS_ISO_9660; +%constant unsigned int FS_INTERACTIVE = CDIO_FS_INTERACTIVE; +%constant unsigned int FS_HFS = CDIO_FS_HFS; +%constant unsigned int FS_UFS = CDIO_FS_UFS; +%constant unsigned int FS_EXT2 = CDIO_FS_EXT2; +%constant unsigned int FS_ISO_HFS = CDIO_FS_ISO_HFS; +%constant unsigned int FS_ISO_9660_INTERACTIVE = CDIO_FS_ISO_9660_INTERACTIVE; +%constant unsigned int FS_3DO = CDIO_FS_3DO; +%constant unsigned int FS_XISO = CDIO_FS_XISO; +%constant unsigned int FS_UDFX = CDIO_FS_UDFX; +%constant unsigned int FS_UDF = CDIO_FS_UDF; +%constant unsigned int FS_ISO_UDF = CDIO_FS_ISO_UDF; + +%constant unsigned int FS_ANAL_XA = CDIO_FS_ANAL_XA; +%constant unsigned int FS_ANAL_MULTISESSION = CDIO_FS_ANAL_MULTISESSION; +%constant unsigned int FS_ANAL_PHOTO_CD = CDIO_FS_ANAL_PHOTO_CD; +%constant unsigned int FS_ANAL_HIDDEN_TRACK = CDIO_FS_ANAL_HIDDEN_TRACK; +%constant unsigned int FS_ANAL_CDTV = CDIO_FS_ANAL_CDTV; +%constant unsigned int FS_ANAL_BOOTABLE = CDIO_FS_ANAL_BOOTABLE; +%constant unsigned int FS_ANAL_VIDEOCD = CDIO_FS_ANAL_VIDEOCD; +%constant unsigned int FS_ANAL_ROCKRIDGE = CDIO_FS_ANAL_ROCKRIDGE; +%constant unsigned int FS_ANAL_JOLIET = CDIO_FS_ANAL_JOLIET; +%constant unsigned int FS_ANAL_SVCD = CDIO_FS_ANAL_SVCD; +%constant unsigned int FS_ANAL_CVD = CDIO_FS_ANAL_CVD; +%constant unsigned int FS_ANAL_XISO = CDIO_FS_ANAL_XISO; +%constant unsigned int FS_MATCH_ALL = CDIO_FS_MATCH_ALL; +%constant unsigned int FS_UNKNOWN = CDIO_FS_UNKNOWN; + +%mutable; diff --git a/swig/disc.swg b/swig/disc.swg new file mode 100644 index 00000000..93ebca7f --- /dev/null +++ b/swig/disc.swg @@ -0,0 +1,96 @@ +/* -*- c -*- + $Id: disc.swg,v 1.5 2008/05/01 16:55:05 karl Exp $ + + Copyright (C) 2006, 2008 Rocky Bernstein + + 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 3 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 + MERCHANTABILITY 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, see . +*/ +/* See for more extensive documentation. */ + +%constant long int DISC_MODE_CD_DA = CDIO_DISC_MODE_CD_DA; +%constant long int DISC_MODE_CD_DATA = CDIO_DISC_MODE_CD_DATA; +%constant long int DISC_MODE_CD_XA = CDIO_DISC_MODE_CD_XA; +%constant long int DISC_MODE_CD_MIXED = CDIO_DISC_MODE_CD_MIXED; +%constant long int DISC_MODE_DVD_ROM = CDIO_DISC_MODE_DVD_ROM; +%constant long int DISC_MODE_DVD_RAM = CDIO_DISC_MODE_DVD_RAM; +%constant long int DISC_MODE_DVD_R = CDIO_DISC_MODE_DVD_R; +%constant long int DISC_MODE_DVD_RW = CDIO_DISC_MODE_DVD_RW; +%constant long int DISC_MODE_DVD_PR = CDIO_DISC_MODE_DVD_PR; +%constant long int DISC_MODE_DVD_PRW = CDIO_DISC_MODE_DVD_PRW; +%constant long int DISC_MODE_DVD_OTHER = CDIO_DISC_MODE_DVD_OTHER; +%constant long int DISC_MODE_NO_INFO = CDIO_DISC_MODE_NO_INFO; +%constant long int DISC_MODE_ERROR = CDIO_DISC_MODE_ERROR; +%constant long int DISC_MODE_CD_I = CDIO_DISC_MODE_CD_I; + +%rename cdio_get_disc_last_lsn get_disc_last_lsn; +%feature("autodoc", +"get_disc_last_lsn(cdio)->lsn +Get the LSN of the end of the CD. + +pycdio.INVALID_LSN is returned on error."); +lsn_t cdio_get_disc_last_lsn(const CdIo_t *p_cdio); + + +%feature("autodoc", +"get_disc_mode(p_cdio) -> str + +Get disc mode - the kind of CD (CD-DA, CD-ROM mode 1, CD-MIXED, ...) +that we've got. The notion of 'CD' is extended a little to include +DVD's."); +%exception get_disc_mode { + $action + if (NULL == result) { + PyErr_SetString(PyExc_IOError, "Error getting disc mode."); + return NULL; + } +} +const char *get_disc_mode (CdIo_t *p_cdio); + +%rename cdio_get_joliet_level get_joliet_level; +%feature("autodoc", +"get_joliet_level(cdio)->int + +Return the Joliet level recognized for cdio. +This only makes sense for something that has an ISO-9660 +filesystem."); +int cdio_get_joliet_level(const CdIo_t *p_cdio); + +%newobject cdio_get_mcn; // free malloc'd return value +%rename cdio_get_mcn get_mcn; +%feature("autodoc", +"get_mcn(cdio) -> str + +Get the media catalog number (MCN) from the CD."); +char * cdio_get_mcn(CdIo_t *p_cdio); + +%rename cdio_get_num_tracks get_num_tracks; +%feature("autodoc", +"get_num_tracks(p_cdio)->int + +Return the number of tracks on the CD. +On error pycdio.INVALID_TRACK is returned."); +track_t cdio_get_num_tracks (const CdIo_t *p_cdio); + +/*========================INLINE======================================*/ + +%inline %{ +const char * +get_disc_mode (CdIo_t *p_cdio) +{ + discmode_t discmode = cdio_get_discmode(p_cdio); + if (CDIO_DISC_MODE_ERROR == discmode) return NULL; + return discmode2str[discmode]; +} + +%} diff --git a/swig/pycdio.swg b/swig/pycdio.swg new file mode 100644 index 00000000..1ee0cca6 --- /dev/null +++ b/swig/pycdio.swg @@ -0,0 +1,85 @@ +/* -*- c -*- + $Id: pycdio.swg,v 1.6 2008/05/01 16:55:05 karl Exp $ + + Copyright (C) 2006, 2008 Rocky Bernstein + + 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 3 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 + MERCHANTABILITY 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, see . +*/ +%define DOCSTRING +"This is a wrapper for The CD Input and Control library (libcdio) which +encapsulates CD-ROM reading and control. Python programs wishing to be +oblivious of the OS- and device-dependent properties of a CD-ROM can +use this library." +%enddef +%module(docstring=DOCSTRING) pycdio +%{ +/* Includes the header in the wrapper code */ +#include +#include +#include +%} + +#include +#include + +%include "compat.swg" + +%include "typemaps.i" +%include "cstring.i" + +/* Various libcdio constants and typedefs */ +%include "types.swg" + +%feature("autodoc", 1); + +%include "audio.swg" +%include "read.swg" +%include "track.swg" + +/* In the presence of the exception handling, the order is important. + The below use %exception. +*/ +%include "device.swg" +%include "disc.swg" + +/* Encapsulation is done in two parts. The lower-level python +interface is called pycdio and is generated by SWIG. + +The more object-oriented module is cdio; it is a Python class that +uses pycdio. Although pycdio is perfectly usable on its own, it is +expected that cdio is what most people will use. As pycdio more +closely models the C interface, it is conceivable (if unlikely) that +diehard libcdio C users who are very familiar with that interface +could prefer that. + +It is probably possible to change the SWIG in such a way to combine +these pieces. However there are the problems. First, I'm not that much +of a SWIG expert. Second it looks as though the resulting SWIG code +would be more complex. Third the separation makes translation very +straight forward to understand and maintain: first get what's in C +into Python as a one-to-one translation. Then we implement some nice +abstraction off of that. The abstraction can be modified without +having to redo the underlying translation. (But the reverse is +generally not true: usually changes to the C-to-python translation, +pycdio, do result in small, but obvious and straightforward changes to +the abstraction layer cdio.) + */ +#define INCLUDE_CLASS 0 +#if INCLUDE_CLASS +/* In spite of the above, if we were to do things as a one-step approach, + this might be how it would be done. */ +%pythoncode %{ +%include "cdio.py" +%} +#endif diff --git a/swig/pyiso9660.swg b/swig/pyiso9660.swg new file mode 100644 index 00000000..0573eb8a --- /dev/null +++ b/swig/pyiso9660.swg @@ -0,0 +1,823 @@ +/* -*- c -*- + $Id: pyiso9660.swg,v 1.14 2008/05/01 16:55:05 karl Exp $ + + Copyright (C) 2006, 2008 Rocky Bernstein + + 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 3 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 + MERCHANTABILITY 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, see . +*/ +%define DOCSTRING +"This is a wrapper for The CD Input and Control library's ISO-9660 library +See also the ISO-9660 specification. The freely available European +equivalant standard is called ECMA-119." +%enddef +%module(docstring=DOCSTRING) pyiso9660 + +%{ +/* Includes the header in the wrapper code */ +#include +#include +#include +%} + +#include +#include +#include + +/* Various libcdio constants and typedefs */ +%include "types.swg" +%include "compat.swg" + +%include "typemaps.i" +%include "cstring.i" + +%feature("autodoc", 1); + +typedef unsigned int iso_extension_mask_t; +typedef unsigned int uint8_t; +typedef unsigned int uint16_t; + +%constant long int ISO_BLOCKSIZE = CDIO_CD_FRAMESIZE; +%constant long int PVD_SECTOR = ISO_PVD_SECTOR ; +%constant long int EVD_SECTOR = ISO_EVD_SECTOR ; +%constant long int LEN_ISONAME = LEN_ISONAME; +%constant long int MAX_SYSTEM_ID = ISO_MAX_SYSTEM_ID ; +%constant long int MAX_ISONAME = MAX_ISONAME; +%constant long int MAX_PREPARER_ID = ISO_MAX_PREPARER_ID ; +%constant long int MAX_ISOPATHNAME = MAX_ISOPATHNAME; + +%constant long int FILE = ISO_FILE; +%constant long int EXISTENCE = ISO_EXISTENCE; +%constant long int DIRECTORY = ISO_DIRECTORY; +%constant long int ASSOCIATED = ISO_ASSOCIATED; +%constant long int RECORD = ISO_RECORD; + +#include +#if LIBCDIO_VERSION_NUM <= 76 +%constant long int PROTECTION = ISO_PROTECTION; +#else +%constant long int PROTECTION = 16; +#endif + +%constant long int DRESERVED1 = ISO_DRESERVED1; +%constant long int DRESERVED2 = ISO_DRESERVED2; +%constant long int MULTIEXTENT = ISO_MULTIEXTENT; + +%constant long int VD_BOOT_RECORD = ISO_VD_BOOT_RECORD; +%constant long int VD_PRIMARY = ISO_VD_PRIMARY; +%constant long int VD_SUPPLEMENTARY = ISO_VD_SUPPLEMENTARY; +%constant long int VD_PARITION = ISO_VD_PARITION; +%constant long int VD_END = ISO_VD_END; + +%constant long int MAX_PUBLISHER_ID = ISO_MAX_PUBLISHER_ID; +%constant long int MAX_APPLICATION_ID = ISO_MAX_APPLICATION_ID; +%constant long int MAX_VOLUME_ID = ISO_MAX_VOLUME_ID; +%constant long int MAX_VOLUMESET_ID = ISO_MAX_VOLUMESET_ID; +%constant long int STANDARD_ID = ISO_STANDARD_ID; + +%constant long int NOCHECK = ISO9660_NOCHECK; +%constant long int SEVEN_BIT = ISO9660_7BIT; +%constant long int ACHARS = ISO9660_ACHARS; +%constant long int DCHARS = ISO9660_DCHARS; + +%constant long int EXTENSION_JOLIET_LEVEL1 = ISO_EXTENSION_JOLIET_LEVEL1; +%constant long int EXTENSION_JOLIET_LEVEL2 = ISO_EXTENSION_JOLIET_LEVEL2; +%constant long int EXTENSION_JOLIET_LEVEL3 = ISO_EXTENSION_JOLIET_LEVEL3; +%constant long int EXTENSION_ROCK_RIDGE = ISO_EXTENSION_ROCK_RIDGE; +%constant long int EXTENSION_HIGH_SIERRA = ISO_EXTENSION_HIGH_SIERRA; + +%constant long int EXTENSION_ALL = ISO_EXTENSION_ALL; +%constant long int EXTENSION_NONE = ISO_EXTENSION_NONE; +%constant long int EXTENSION_JOLIET = ISO_EXTENSION_JOLIET; + +/* Set up to allow functions to return stat lists of type "stat + *". We'll use a typedef so we can make sure to isolate this. +*/ +%inline %{ +typedef CdioList_t IsoStatList_t; +typedef iso9660_stat_t IsoStat_t; +%} +typedef CdioList_t IsoStatList_t; +typedef iso9660_stat_t IsoStat_t; + +%typemap(newfree) IsoStatList_t "_cdio_list_free($1, 1);"; + +%typemap(out) IsoStatList_t *{ + CdioList_t *p_entlist = result; + CdioListNode_t *p_entnode; + + if (result) { + resultobj = PyList_New(0); + _CDIO_LIST_FOREACH (p_entnode, p_entlist) { + PyObject *py_list = PyList_New(5); + iso9660_stat_t *p_statbuf = + (iso9660_stat_t *) _cdio_list_node_data (p_entnode); + PyObject *o; + o = PyString_FromString(p_statbuf->filename); + PyList_SetItem(py_list, 0, o); + o = SWIG_From_long((long)(p_statbuf->lsn)); + PyList_SetItem(py_list, 1, o); + o = SWIG_From_long((long)(p_statbuf->size)); + PyList_SetItem(py_list, 2, o); + o = SWIG_From_long((long)(p_statbuf->secsize)); + PyList_SetItem(py_list, 3, o); + o = SWIG_From_long((long)(p_statbuf->type)); + PyList_SetItem(py_list, 4, o); + PyList_Append(resultobj, py_list); + } + } +} +%typemap(out) IsoStat_t *{ + iso9660_stat_t *p_statbuf = result; + + if (result) { + PyObject *o; + resultobj = PyList_New(0); + o = PyString_FromString(p_statbuf->filename); + PyList_Append(resultobj, o); + o = SWIG_From_long((long)(p_statbuf->lsn)); + PyList_Append(resultobj, o); + o = SWIG_From_long((long)(p_statbuf->size)); + PyList_Append(resultobj, o); + o = SWIG_From_long((long)(p_statbuf->secsize)); + PyList_Append(resultobj, o); + o = SWIG_From_long((long)(p_statbuf->type)); + PyList_Append(resultobj, o); + free (p_statbuf); + } +} + +%typemap(out) struct tm *{ + struct tm *p_tm = result; + + if (result) { + PyObject *o; + resultobj = PyList_New(9); + o = SWIG_From_int(p_tm->tm_year+1900); + PyList_SetItem(resultobj, 0, o); + o = SWIG_From_int(p_tm->tm_mon+1); + PyList_SetItem(resultobj, 1, o); + o = SWIG_From_int(p_tm->tm_mday); + PyList_SetItem(resultobj, 2, o); + o = SWIG_From_int(p_tm->tm_hour); + PyList_SetItem(resultobj, 3, o); + o = SWIG_From_int(p_tm->tm_min); + PyList_SetItem(resultobj, 4, o); + o = SWIG_From_int(p_tm->tm_sec); + PyList_SetItem(resultobj, 5, o); + o = SWIG_From_int((p_tm->tm_wday-1)%7); + PyList_SetItem(resultobj, 6, o); + o = SWIG_From_int(p_tm->tm_yday+1); + PyList_SetItem(resultobj, 7, o); + o = SWIG_From_int(p_tm->tm_isdst); + PyList_SetItem(resultobj, 8, o); + free (p_tm); + } +} + +#ifdef FINISHED +%typemap(out) IsoStatList_t *{ + // $1 is of type IsoStatList_t + CdioList_t *p_entlist = result; + CdioListNode_t *p_entnode; + unsigned int num = 0; + + if (!result) goto out; + + PPCODE: + /* Figure out how many items in the array */ + _CDIO_LIST_FOREACH (p_entnode, p_entlist) { + num +=5; + } + + /* For each element in the array of strings, create a new + * mortalscalar, and stuff it into the above array. */ + _CDIO_LIST_FOREACH (p_entnode, p_entlist) { + iso9660_stat_t *p_statbuf = + (iso9660_stat_t *) _cdio_list_node_data (p_entnode); + /* Have perl compute the length of the string using strlen() */ + XPUSHs(sv_2mortal(newSVpv(p_statbuf->filename, 0))); + XPUSHs(sv_2mortal(newSViv(p_statbuf->lsn))); + XPUSHs(sv_2mortal(newSViv(p_statbuf->size))); + XPUSHs(sv_2mortal(newSViv(p_statbuf->secsize))); + XPUSHs(sv_2mortal(newSViv(p_statbuf->type))); + } + _cdio_list_free (p_entlist, true); + argvi += num + 2; + out: ; +} +#endif + +%feature("autodoc", +"open_iso(path) + Open an ISO 9660 image for reading. Maybe in the future we will have + mode. None is returned on error. +"); +%rename iso9660_open open_iso; +iso9660_t *iso9660_open (const char *psz_path /*flags, mode */); + +%feature("autodoc", +"Open an ISO 9660 image for reading allowing various ISO 9660 + extensions. Maybe in the future we will have a mode. None is + returned on error."); +%rename iso9660_open_ext open_ext; +iso9660_t *iso9660_open_ext (const char *psz_path, + iso_extension_mask_t iso_extension_mask); + +%feature("autodoc", +"Open an ISO 9660 image for reading with some tolerence for positioning +of the ISO9660 image. We scan for ISO_STANDARD_ID and use that to set +the eventual offset to adjust by (as long as that is <= i_fuzz). + +Maybe in the future we will have a mode. None is returned on error. + +see iso9660_open"); +%rename iso9660_open_fuzzy open_fuzzy; +iso9660_t *iso9660_open_fuzzy (const char *psz_path /*flags, mode */, + uint16_t i_fuzz); + +%feature("autodoc", +"Open an ISO 9660 image for reading with some tolerence for positioning +of the ISO9660 image. We scan for ISO_STANDARD_ID and use that to set +the eventual offset to adjust by (as long as that is <= i_fuzz). + +Maybe in the future we will have a mode. None is returned on error. + +see open_iso +"); +%rename iso9660_open_fuzzy open_fuzzy_ext; +iso9660_t *iso9660_open_fuzzy_ext (const char *psz_path, + iso_extension_mask_t iso_extension_mask, + uint16_t i_fuzz + /*flags, mode */); +%feature("autodoc", +"Read the Super block of an ISO 9660 image but determine framesize +and datastart and a possible additional offset. Generally here we are +not reading an ISO 9660 image but a CD-Image which contains an ISO 9660 +filesystem. +"); +%rename iso9660_ifs_fuzzy_read_superblock ifs_fuzzy_read_superblock; +bool iso9660_ifs_fuzzy_read_superblock (iso9660_t *p_iso, + iso_extension_mask_t iso_extension_mask, + uint16_t i_fuzz); +%feature("autodoc", +"Close previously opened ISO 9660 image. +True is unconditionally returned. If there was an error false would +be returned."); +%rename iso9660_close close; +bool iso9660_close (iso9660_t * p_iso); + +%feature("autodoc", +"Seek to a position and then read n bytes. (buffer, size) are + returned."); +%cstring_output_withsize(char *p_buf, ssize_t *pi_size); +ssize_t seek_read (const iso9660_t *p_iso, + lsn_t start, char *p_buf, ssize_t *pi_size); +%inline %{ +ssize_t +seek_read (const iso9660_t *p_iso, lsn_t start, char *p_buf, + ssize_t *pi_size) +{ + *pi_size = iso9660_iso_seek_read(p_iso, p_buf, start, + (*pi_size) / ISO_BLOCKSIZE); + return *pi_size; + } +%} + + +%feature("autodoc", +"Read the Primary Volume Descriptor for a CD. +None is returned if there was an error."); +iso9660_pvd_t *fs_read_pvd ( const CdIo_t *p_cdio ); +%inline %{ +iso9660_pvd_t *fs_read_pvd ( const CdIo_t *p_cdio ) { + static iso9660_pvd_t pvd; + bool b_ok = iso9660_fs_read_pvd ( p_cdio, &pvd ); + if (!b_ok) return NULL; + return &pvd; + } +%} + +%feature("autodoc", +"Read the Primary Volume Descriptor for an ISO 9660 image. +None is returned if there was an error."); +iso9660_pvd_t *ifs_read_pvd ( const iso9660_t *p_iso ); +%inline %{ +iso9660_pvd_t *ifs_read_pvd ( const iso9660_t *p_iso ) { + static iso9660_pvd_t pvd; + bool b_ok = iso9660_ifs_read_pvd ( p_iso, &pvd ); + if (!b_ok) return NULL; + return &pvd; + } +%} + +%feature("autodoc", +"Read the Super block of an ISO 9660 image. This is the +Primary Volume Descriptor (PVD) and perhaps a Supplemental Volume +Descriptor if (Joliet) extensions are acceptable."); +%rename iso9660_fs_read_superblock fs_read_superblock; +bool iso9660_fs_read_superblock (CdIo_t *p_cdio, + iso_extension_mask_t iso_extension_mask); + +%feature("autodoc", +"Read the Super block of an ISO 9660 image. This is the + Primary Volume Descriptor (PVD) and perhaps a Supplemental Volume + Descriptor if (Joliet) extensions are acceptable."); +%rename iso9660_ifs_read_superblock ifs_read_superblock; +bool iso9660_ifs_read_superblock (iso9660_t *p_iso, + iso_extension_mask_t iso_extension_mask); + + +/*==================================================== + Time conversion + ====================================================*/ +%feature("autodoc", +"Set time in format used in ISO 9660 directory index record"); +iso9660_dtime_t * +set_dtime ( int year, int mon, int mday, int hour, int min, int sec); +%inline %{ +iso9660_dtime_t * +set_dtime ( int year, int mon, int mday, int hour, int min, int sec) +{ + struct tm tm = { sec, min, hour, mday, mon-1, year-1900, 0, 0, -1 }; + static iso9660_dtime_t dtime; + iso9660_set_dtime (&tm, &dtime); + return &dtime; +} +%} + +%feature("autodoc", +"Set 'long' time in format used in ISO 9660 primary volume descriptor"); +iso9660_ltime_t * +set_ltime ( int year, int mon, int mday, int hour, int min, int sec); + +%inline %{ +iso9660_ltime_t * +set_ltime ( int year, int mon, int mday, int hour, int min, int sec) + { + struct tm tm = { sec, min, hour, mday, mon-1, year-1900, 0, 0, -1 }; + static iso9660_ltime_t ldate; + iso9660_set_ltime (&tm, &ldate); + return &ldate; +} +%} + +%feature("autodoc", +"Get Unix time structure from format use in an ISO 9660 directory index +record. Even though tm_wday and tm_yday fields are not explicitly in +idr_date, they are calculated from the other fields. + +If tm is to reflect the localtime, set 'use_localtime' true, otherwise +tm will reported in GMT."); +struct tm *get_dtime (const iso9660_dtime_t *p_dtime, bool use_localtime); +%inline %{ +struct tm *get_dtime (const iso9660_dtime_t *p_dtime, bool use_localtime) { + struct tm *p_tm = (struct tm *) calloc(1, sizeof(struct tm)); + if (!iso9660_get_dtime (p_dtime, use_localtime, p_tm)) { + free(p_tm); + return NULL; + } + return p_tm; + } +%} + +%feature("autodoc", +"Get 'long' time in format used in ISO 9660 primary volume descriptor + from a Unix time structure."); +struct tm *get_ltime (const iso9660_ltime_t *p_ltime); +%inline %{ +struct tm *get_ltime (const iso9660_ltime_t *p_ltime) +{ + struct tm *p_tm = (struct tm *) calloc(1, sizeof(struct tm)); + if (!iso9660_get_ltime (p_ltime, p_tm)) { + free(p_tm); + return NULL; + } + return p_tm; +} +%} + +/*======================================================== + Characters used in file and directory and manipulation + =======================================================*/ +%feature("autodoc", +" Return true if c is a DCHAR - a character that can appear in an an + ISO-9600 level 1 directory name. These are the ASCII capital + letters A-Z, the digits 0-9 and an underscore."); +%rename iso9660_isdchar is_dchar; +bool iso9660_isdchar (int c); + +%feature("autodoc", +"Return true if c is an ACHAR - + These are the DCHAR's plus some ASCII symbols including the space + symbol."); +%rename iso9660_isachar is_achar; +bool iso9660_isachar (int c); + +%feature("autodoc", +"Convert an ISO-9660 file name that stored in a directory entry into + what's usually listed as the file name in a listing. + Lowercase name, and remove trailing ;1's or .;1's and + turn the other ;'s into version numbers. + + @param psz_oldname the ISO-9660 filename to be translated. + @param psz_newname returned string. The caller allocates this and + it should be at least the size of psz_oldname. + @return length of the translated string is returned."); +%newobject name_translate; +char * name_translate(const char *psz_oldname); +%inline %{ +char * +name_translate(const char *psz_oldname) { + char *psz_newname=calloc(sizeof(char), strlen(psz_oldname)+1); + iso9660_name_translate(psz_oldname, psz_newname); + return psz_newname; +} +%} + +%feature("autodoc", +"Convert an ISO-9660 file name that stored in a directory entry into + what's usually listed as the file name in a listing. Lowercase + name if no Joliet Extension interpretation. Remove trailing ;1's or + .;1's and turn the other ;'s into version numbers. + + @param psz_oldname the ISO-9660 filename to be translated. + @param psz_newname returned string. The caller allocates this and + it should be at least the size of psz_oldname. + @param i_joliet_level 0 if not using Joliet Extension. Otherwise the + Joliet level. + @return length of the translated string is returned. It will be no greater + than the length of psz_oldname."); +%newobject name_translate_ext; +char * name_translate_ext(const char *psz_oldname, uint8_t i_joliet_level); +%inline %{ +char * +name_translate_ext(const char *psz_oldname, uint8_t i_joliet_level) { + char *psz_newname=calloc(sizeof(char), strlen(psz_oldname)+1); + iso9660_name_translate_ext(psz_oldname, psz_newname, i_joliet_level); + return psz_newname; +} +%} + +%feature("autodoc", +"Pad string src with spaces to size len and copy this to dst. If + en is less than the length of src, dst will be truncated to the + first len characters of src. + + src can also be scanned to see if it contains only ACHARs, DCHARs, + 7-bit ASCII chars depending on the enumeration _check. + + In addition to getting changed, dst is the return value. + Note: this string might not be NULL terminated."); +%newobject strncpy_pad; // free malloc'd return value +char *strncpy_pad(const char src[], size_t len, enum strncpy_pad_check _check); +%inline %{ +char * +strncpy_pad(const char src[], size_t len, enum strncpy_pad_check _check) { + char *dst = calloc(sizeof(char), len+1); + return iso9660_strncpy_pad(dst, src, len, _check); +} +%} + +/*===================================================================== + Files and Directory Names +======================================================================*/ + +%feature("autodoc", +"Check that psz_path is a valid ISO-9660 directory name. + + A valid directory name should not start out with a slash (/), + dot (.) or null byte, should be less than 37 characters long, + have no more than 8 characters in a directory component + which is separated by a /, and consist of only DCHARs. + + True is returned if psz_path is valid."); +%rename iso9660_dirname_valid_p dirname_valid_p; +bool iso9660_dirname_valid_p (const char psz_path[]); + +%feature("autodoc", +"Take psz_path and a version number and turn that into a ISO-9660 +pathname. (That's just the pathname followed by ';' and the version +number. For example, mydir/file.ext -> MYDIR/FILE.EXT;1 for version +1. The resulting ISO-9660 pathname is returned."); +%rename iso9660_pathname_isofy pathname_isofy; +%newobject iso9660_pathname_isofy; // free malloc'd return value +char *iso9660_pathname_isofy (const char psz_path[], uint16_t i_version=1); + +%feature("autodoc", +"Check that psz_path is a valid ISO-9660 pathname. + +A valid pathname contains a valid directory name, if one appears and +the filename portion should be no more than 8 characters for the +file prefix and 3 characters in the extension (or portion after a +dot). There should be exactly one dot somewhere in the filename +portion and the filename should be composed of only DCHARs. + +True is returned if psz_path is valid."); +%rename iso9660_pathname_valid_p pathname_valid_p; +bool iso9660_pathname_valid_p (const char psz_path[]); + +/* ... */ + +%feature("autodoc", +"Given a directory pointer, find the filesystem entry that contains +lsn and return information about it. + +Returns stat_t of entry if we found lsn, or None otherwise."); +#if 0 +%rename iso9660_find_fs_lsn fs_find_lsn; +IsoStat_t *iso9660_find_fs_lsn(CdIo_t *p_cdio, lsn_t i_lsn); + + +%feature("autodoc", +"Given a directory pointer, find the filesystem entry that contains +lsn and return information about it. + +Returns stat_t of entry if we found lsn, or None otherwise."); +%rename iso9660_find_ifs_lsn ifs_find_lsn; +IsoStat_t *iso9660_find_ifs_lsn(const iso9660_t *p_iso, lsn_t i_lsn); +#endif + + +%feature("autodoc", +"Return file status for psz_path. None is returned on error."); +%rename iso9660_fs_stat fs_stat; +IsoStat_t *iso9660_fs_stat (CdIo_t *p_cdio, const char psz_path[]); + + +%feature("autodoc", +"Return file status for path name psz_path. None is returned on error. +pathname version numbers in the ISO 9660 name are dropped, i.e. ;1 +is removed and if level 1 ISO-9660 names are lowercased. + +The b_mode2 parameter is not used."); +%rename iso9660_fs_stat_translate fs_stat_translate; +IsoStat_t *iso9660_fs_stat_translate (CdIo_t *p_cdio, + const char psz_path[], + bool b_mode2=false); + +%feature("autodoc", +"Return file status for pathname. None is returned on error."); +%rename iso9660_ifs_stat ifs_stat; +IsoStat_t *iso9660_ifs_stat (iso9660_t *p_iso, const char psz_path[]); + + +%feature("autodoc", +"Return file status for path name psz_path. undef is returned on error. +pathname version numbers in the ISO 9660 name are dropped, i.e. ;1 is +removed and if level 1 ISO-9660 names are lowercased."); +%rename iso9660_ifs_stat_translate ifs_stat_translate; +IsoStat_t *iso9660_ifs_stat_translate (iso9660_t *p_iso, + const char psz_path[]); + +%feature("autodoc", +"Read psz_path (a directory) and return a list of iso9660_stat_t +pointers for the files inside that directory."); +IsoStatList_t *fs_readdir (CdIo_t *p_cdio, const char psz_path[]); + +%inline %{ +IsoStatList_t *fs_readdir (CdIo_t *p_cdio, const char psz_path[]) +{ + CdioList_t *p_statlist = iso9660_fs_readdir (p_cdio, psz_path, false); + return p_statlist; +} +%} + +%feature("autodoc", +"Read psz_path (a directory) and return a list of iso9660_stat_t +pointers for the files inside that directory."); +IsoStatList_t *ifs_readdir (iso9660_t *p_iso, const char psz_path[]); + +%inline %{ +IsoStatList_t *ifs_readdir (iso9660_t *p_iso, const char psz_path[]) +{ + CdioList_t *p_statlist = iso9660_ifs_readdir (p_iso, psz_path); + return p_statlist; +} +%} + +%feature("autodoc", +"Return the PVD's application ID. +None is returned if there is some problem in getting this."); +%rename iso9660_get_application_id get_application_id; +char * iso9660_get_application_id(iso9660_pvd_t *p_pvd); + +%feature("autodoc", +"Get the application ID. Return None if there +is some problem in getting this."); +%newobject get_application_id; // free malloc'd return value +char *ifs_get_application_id(iso9660_t *p_iso); +%inline %{ +char * +ifs_get_application_id(iso9660_t *p_iso) { + char *psz; + bool ok = iso9660_ifs_get_application_id(p_iso, &psz); + if (!ok) return NULL; + return psz; +} +%} + + +%feature("autodoc", +"Return the Joliet level recognized for p_iso."); +%rename iso9660_ifs_get_joliet_level get_joliet_level; +uint8_t iso9660_ifs_get_joliet_level(iso9660_t *p_iso); + +%rename iso9660_get_dir_len get_dir_len; +uint8_t iso9660_get_dir_len(const iso9660_dir_t *p_idr); + +%feature("autodoc", +"Return the directory name stored in the iso9660_dir_t."); +%newobject iso9660_dir_to_name; // free malloc'd return value +%rename iso9660_get_to_name get_to_name; +char * iso9660_dir_to_name (const iso9660_dir_t *p_iso9660_dir); + +#if LIBCDIO_VERSION_NUM > 76 +%feature("autodoc", +"Returns a POSIX mode for a given p_iso_dirent."); +%rename iso9660_get_posix_filemode get_posix_filemode; +mode_t iso9660_get_posix_filemode(const iso9660_stat_t *p_iso_dirent); +#endif + +%feature("autodoc", +"Return a string containing the preparer id with trailing +blanks removed."); +%rename iso9660_get_preparer_id get_preparer_id; +char *iso9660_get_preparer_id(const iso9660_pvd_t *p_pvd); + +%feature("autodoc", +"Get the preparer ID. Return None if there is some problem in + getting this."); +%newobject ifs_get_preparer_id; // free malloc'd return value +char *ifs_get_preparer_id(iso9660_t *p_iso); +%inline %{ +char * +ifs_get_preparer_id(iso9660_t *p_iso) { + char *psz; + bool ok = iso9660_ifs_get_preparer_id(p_iso, &psz); + if (!ok) return NULL; + return psz; +} +%} + +%feature("autodoc", +"Return a string containing the PVD's publisher id with trailing + blanks removed."); +%rename iso9660_get_publisher_id get_publisher_id; +char *iso9660_get_publisher_id(const iso9660_pvd_t *p_pvd); + +%feature("autodoc", +"Get the publisher ID. Return None if there +is some problem in getting this."); +%newobject ifs_get_publisher_id; // free malloc'd return value +char *ifs_get_publisher_id(iso9660_t *p_iso); +%inline %{ +char * +ifs_get_publisher_id(iso9660_t *p_iso) { + char *psz; + bool ok = iso9660_ifs_get_publisher_id(p_iso, &psz); + if (!ok) return NULL; + return psz; +} +%} + +%rename iso9660_get_pvd_type get_pvd_type; +uint8_t iso9660_get_pvd_type(const iso9660_pvd_t *p_pvd); + +%rename iso9660_get_pvd_id get_pvd_id; +const char * iso9660_get_pvd_id(const iso9660_pvd_t *p_pvd); + +%rename iso9660_get_pvd_space_size get_pvd_space_size; +int iso9660_get_pvd_space_size(const iso9660_pvd_t *p_pvd); + +%rename iso9660_get_pvd_block_size get_pvd_block_size; +int iso9660_get_pvd_block_size(const iso9660_pvd_t *p_pvd) ; + +%feature("autodoc", +"Return the primary volume id version number (of pvd). +If there is an error 0 is returned."); +%rename iso9660_get_pvd_version get_pvd_version; +int iso9660_get_pvd_version(const iso9660_pvd_t *pvd) ; + +%feature("autodoc", +"Return a string containing the PVD's system id with trailing + blanks removed."); +%rename iso9660_get_system_id get_system_id; +char *iso9660_get_system_id(const iso9660_pvd_t *p_pvd); + +%feature("autodoc", +"Get the system ID. None is returned if there +is some problem in getting this."); +%newobject ifs_get_system_id; // free malloc'd return value +char *ifs_get_system_id(iso9660_t *p_iso); +%inline %{ +char * +ifs_get_system_id(iso9660_t *p_iso) { + char *psz; + bool ok = iso9660_ifs_get_system_id(p_iso, &psz); + if (!ok) return NULL; + return psz; +} +%} + +%feature("autodoc", +"Return the LSN of the root directory for pvd. If there is an error +INVALID_LSN is returned. +"); +%rename iso9660_get_root_lsn get_root_lsn; +lsn_t iso9660_get_root_lsn(const iso9660_pvd_t *p_pvd); + +%feature("autodoc", +"Return the PVD's volume ID."); +%rename iso9660_get_volume_id get_volume_id; +char *iso9660_get_volume_id(const iso9660_pvd_t *p_pvd); + +%feature("autodoc", +"Get the system ID. None is returned if there +is some problem in getting this."); +%newobject ifs_get_volume_id; // free malloc'd return value +char *ifs_get_volume_id(iso9660_t *p_iso); +%inline %{ +char * +ifs_get_volume_id(iso9660_t *p_iso) { + char *psz; + bool ok = iso9660_ifs_get_volume_id(p_iso, &psz); + if (!ok) return NULL; + return psz; +} +%} + +%feature("autodoc", +" Return the PVD's volumeset ID. + None is returned if there is some problem in getting this. +"); +%rename iso9660_get_volumeset_id get_volumeset_id; +char *iso9660_get_volumeset_id(const iso9660_pvd_t *p_pvd); + +%feature("autodoc", +"Get the volumeset ID. None is returned if there +is some problem in getting this."); +%newobject ifs_get_volumeset_id; // free malloc'd return value +char *ifs_get_volumeset_id(iso9660_t *p_iso); +%inline %{ +char * +ifs_get_volumeset_id(iso9660_t *p_iso) { + char *psz; + bool ok = iso9660_ifs_get_volumeset_id(p_iso, &psz); + if (!ok) return NULL; + return psz; +} +%} + +/* ================= pathtable ================== */ + +%feature("autodoc", +"Zero's out pathable. Do this first."); +%rename iso9660_pathtable_init pathtable_init; +void iso9660_pathtable_init (void *pt); + +%rename iso9660_pathtable_get_size pathtable_get_size; +unsigned int iso9660_pathtable_get_size (const void *pt); + +%rename iso9660_pathtable_l_add_entry pathtable_l_add_entry; +uint16_t iso9660_pathtable_l_add_entry (void *pt, const char name[], + uint32_t extent, uint16_t parent); + +%rename iso9660_pathtable_m_add_entry pathtable_m_add_entry; +uint16_t iso9660_pathtable_m_add_entry (void *pt, const char name[], + uint32_t extent, uint16_t parent); + +/*====================================================================== + Volume Descriptors +========================================================================*/ + +#ifdef FINSHED +void iso9660_set_pvd (void *pd, const char volume_id[], + const char application_id[], + const char publisher_id[], const char preparer_id[], + uint32_t iso_size, const void *root_dir, + uint32_t path_table_l_extent, + uint32_t path_table_m_extent, + uint32_t path_table_size, const time_t *pvd_time); +#endif /*FINISHED*/ + +%rename iso9660_set_evd set_evd; +void iso9660_set_evd (void *pd); + +%feature("autodoc", +"Return true if ISO 9660 image has extended attrributes (XA)."); +%rename iso9660_ifs_is_xa is_xa; +bool iso9660_ifs_is_xa (const iso9660_t * p_iso); + +// %pythoncode %{ +//%} diff --git a/swig/read.swg b/swig/read.swg new file mode 100644 index 00000000..76a52fd6 --- /dev/null +++ b/swig/read.swg @@ -0,0 +1,167 @@ +/* -*- c -*- + $Id: read.swg,v 1.7 2008/05/01 16:55:05 karl Exp $ + + Copyright (C) 2006, 2008 Rocky Bernstein + + 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 3 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 + MERCHANTABILITY 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, see . +*/ +/* See for more extensive documentation. */ + +%constant long int READ_MODE_AUDIO = CDIO_READ_MODE_AUDIO; +%constant long int READ_MODE_M1F1 = CDIO_READ_MODE_M1F1; +%constant long int READ_MODE_M1F2 = CDIO_READ_MODE_M1F2; +%constant long int READ_MODE_M2F1 = CDIO_READ_MODE_M2F1; +%constant long int READ_MODE_M2F2 = CDIO_READ_MODE_M2F2; + +typedef int cdio_read_mode_t; + +%rename cdio_lseek lseek; +%feature("autodoc", +"lseek(cdio, offset, whence)->int +Reposition read offset +Similar to (if not the same as) libc's fseek() + +cdio is object to get adjested, offset is amount to seek and +whence is like corresponding parameter in libc's lseek, e.g. +it should be SEEK_SET or SEEK_END. + +the offset is returned or -1 on error."); +off_t cdio_lseek(const CdIo_t *p_cdio, int offset, int whence=SEEK_SET); + +%feature("autodoc", +"read_cd(cdio, size)->[size, data] + +Reads into buf the next size bytes. +Similar to (if not the same as) libc's read() + +The number of reads read is returned. -1 is returned on error."); +%cstring_output_withsize(char *p_buf, ssize_t *pi_size); +ssize_t read_cd(const CdIo_t *p_cdio, char *p_buf, ssize_t *pi_size); + +%inline %{ +ssize_t +read_cd(const CdIo_t *p_cdio, char *p_buf, ssize_t *pi_size) +{ + *pi_size = cdio_read(p_cdio, p_buf, *pi_size); + return *pi_size; +} +%} + + +%feature("autodoc", +"read_sectors(bytes, lsn, read_mode)->[size, data] +Reads a number of sectors (AKA blocks). + +lsn is sector to read, bytes is the number of bytes. + +If read_mode is pycdio.MODE_AUDIO, the return buffer size will be +truncated to multiple of pycdio.CDIO_FRAMESIZE_RAW i_blocks bytes. + +If read_mode is pycdio.MODE_DATA, buffer will be truncated to a +multiple of pycdio.ISO_BLOCKSIZE, pycdio.M1RAW_SECTOR_SIZE or +pycdio.M2F2_SECTOR_SIZE bytes depending on what mode the data is in. +If read_mode is pycdio.CDIO_MODE_M2F1, buffer will be truncated to a +multiple of pycdio.M2RAW_SECTOR_SIZE bytes. + +If read_mode is CDIO_MODE_M2F2, the return buffer size will be +truncated to a multiple of pycdio.CD_FRAMESIZE bytes. + +If size <= 0 an error has occurred."); + +%inline %{ + +ssize_t read_sectors(const CdIo_t *p_cdio, char *p_buf, ssize_t *pi_size, + lsn_t i_lsn, cdio_read_mode_t read_mode) +{ + + driver_return_code_t drc; + uint32_t i_blocks; + uint16_t i_blocksize; + switch (read_mode) { + case CDIO_READ_MODE_AUDIO: + i_blocksize = CDIO_CD_FRAMESIZE_RAW; + break; + case CDIO_READ_MODE_M1F1: + i_blocksize = M2RAW_SECTOR_SIZE; + break; + case CDIO_READ_MODE_M1F2: + i_blocksize = M2RAW_SECTOR_SIZE; + break; + case CDIO_READ_MODE_M2F1: + i_blocksize = CDIO_CD_FRAMESIZE; + break; + case CDIO_READ_MODE_M2F2: + i_blocksize = M2F2_SECTOR_SIZE; + break; + default: + pi_size = NULL; + return DRIVER_OP_BAD_PARAMETER; + } + + i_blocks = *pi_size / i_blocksize; + drc = cdio_read_sectors(p_cdio, p_buf, i_lsn, read_mode, i_blocks); + if (drc < 0) { + pi_size = NULL; + return drc; + } + return *pi_size; +} +%} + +%feature("autodoc", +"read_data_bytes(lsn, bytes) + +Reads a number of data sectors (AKA blocks). + +lsn is sector to read, bytes is the number of bytes. +If you don't know whether you have a Mode 1/2, +Form 1/ Form 2/Formless sector best to reserve space for the maximum +which is pycdio.M2RAW_SECTOR_SIZE. + +If size <= 0 an error has occurred."); +%inline %{ +ssize_t read_data_bytes(const CdIo_t *p_cdio, char *p_buf, ssize_t *pi_size, + lsn_t i_lsn, int16_t i_blocksize) +{ + driver_return_code_t drc; + uint32_t i_blocks = *pi_size / i_blocksize; + + switch (i_blocksize) { + case CDIO_CD_FRAMESIZE: + case CDIO_CD_FRAMESIZE_RAW: + case M2F2_SECTOR_SIZE: + case M2RAW_SECTOR_SIZE: + break; + default: + /* Don't know about these block sizes */ + pi_size = NULL; + return DRIVER_OP_BAD_PARAMETER; + } + +#if DEBUGGING + printf("p_cdio: %x, i_size: %d, lsn: %d, blocksize %d, blocks %d\n", + p_cdio, *pi_size, i_lsn, i_blocksize, i_blocks); +#endif + drc = cdio_read_data_sectors (p_cdio, p_buf, i_lsn, + i_blocksize, i_blocks); +#if DEBUGGING + printf("drc: %d\n", drc); +#endif + if (drc < 0) { + pi_size = NULL; + return drc; + } + return *pi_size; +} +%} diff --git a/swig/track.swg b/swig/track.swg new file mode 100644 index 00000000..a023c81a --- /dev/null +++ b/swig/track.swg @@ -0,0 +1,206 @@ +/* -*- c -*- + $Id: track.swg,v 1.4 2008/05/01 16:55:05 karl Exp $ + + Copyright (C) 2006, 2008 Rocky Bernstein + + 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 3 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 + MERCHANTABILITY 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, see . +*/ + +/* See For more extensive documentation */ +%constant long int TRACK_FORMAT_AUDIO = TRACK_FORMAT_AUDIO; +%constant long int TRACK_FORMAT_CDI = TRACK_FORMAT_CDI; +%constant long int TRACK_FORMAT_XA = TRACK_FORMAT_XA; +%constant long int TRACK_FORMAT_DATA = TRACK_FORMAT_DATA; +%constant long int TRACK_FORMAT_PSX = TRACK_FORMAT_PSX; + +%constant long int CDIO_TRACK_FLAG_FALSE = CDIO_TRACK_FLAG_FALSE; +%constant long int CDIO_TRACK_FLAG_TRUE = CDIO_TRACK_FLAG_TRUE; +%constant long int CDIO_TRACK_FLAG_ERROR = CDIO_TRACK_FLAG_ERROR; +%constant long int CDIO_TRACK_FLAG_UNKNOWN = CDIO_TRACK_FLAG_UNKNOWN; + +%constant long int CDIO_CDROM_LBA = CDIO_CDROM_LBA; +%constant long int CDIO_CDROM_MSF = CDIO_CDROM_MSF; +%constant long int CDIO_CDROM_DATA_TRACK = CDIO_CDROM_DATA_TRACK; +%constant long int CDIO_CDROM_CDI_TRACK = CDIO_CDROM_CDI_TRACK; +%constant long int CDIO_CDROM_XA_TRACK = CDIO_CDROM_XA_TRACK; + +%constant long int AUDIO = AUDIO; +%constant long int MODE1 = MODE1; +%constant long int MODE1_RAW = MODE1_RAW; +%constant long int MODE2 = MODE2; +%constant long int MODE2_FORM1 = MODE2_FORM1; +%constant long int MODE2_FORM2 = MODE2_FORM2; +%constant long int MODE2_FORM_MIX = MODE2_FORM_MIX; +%constant long int MODE2_RAW = MODE2_RAW; + +%constant long int INVALID_TRACK = CDIO_INVALID_TRACK; +%constant long int CDROM_LEADOUT_TRACK = 0xAA; + +typedef int track_flag_t; + +%rename cdio_get_first_track_num get_first_track_num; +%feature("autodoc", +"get_first_track_num(p_cdio) -> int +Get the number of the first track. + +return the track number or pycdio.INVALID_TRACK if there was +a problem."); +track_t cdio_get_first_track_num(const CdIo_t *p_cdio); + +%rename cdio_get_last_track_num get_last_track_num ; +%feature("autodoc", +"get_last_track_num +Return the last track number. +pycdio.INVALID_TRACK is if there was a problem."); +track_t cdio_get_last_track_num (const CdIo_t *p_cdio); + +%rename cdio_get_track get_track; +%feature("autodoc", +"cdio_get_track(lsn)->int + + Find the track which contains lsn. + pycdio.INVALID_TRACK is returned if the lsn outside of the CD or + if there was some error. + + If the lsn is before the pregap of the first track, 0 is returned. + Otherwise we return the track that spans the lsn."); +track_t cdio_get_track(const CdIo_t *p_cdio, lsn_t lsn); + +%rename cdio_get_track_channels get_track_channels; +%feature("autodoc", +"get_track_channels(cdio, track)->int + +Return number of channels in track: 2 or 4; -2 if implemented or -1 +for error. Not meaningful if track is not an audio track."); +int cdio_get_track_channels(const CdIo_t *p_cdio, track_t i_track); + +%rename cdio_get_track_copy_permit get_track_copy_permit; +%feature("autodoc", +"get_copy_permit(cdio, track)->int + +Return copy protection status on a track. Is this meaningful +not an audio track? +"); +track_flag_t cdio_get_track_copy_permit(const CdIo_t *p_cdio, + track_t i_track); + +%feature("autodoc", +"get_track_format(cdio, track)->format + +Get the format (audio, mode2, mode1) of track. "); +const char *get_track_format(const CdIo_t *p_cdio, track_t i_track); + +%rename cdio_get_track_green is_track_green; +%feature("autodoc", +"is_track_green(cdio, track) -> bool + +Return True if we have XA data (green, mode2 form1) or +XA data (green, mode2 form2). That is track begins: + sync - header - subheader +12 4 - 8 + +FIXME: there's gotta be a better design for this and get_track_format?"); +bool cdio_get_track_green(const CdIo_t *p_cdio, track_t i_track); + +%rename cdio_get_track_last_lsn get_track_last_lsn; +%feature("autodoc", +"cdio_get_track_last_lsn(cdio, track)->lsn + +Return the ending LSN for track number +track in cdio. CDIO_INVALID_LSN is returned on error."); +lsn_t cdio_get_track_last_lsn(const CdIo_t *p_cdio, track_t i_track); + +%rename cdio_get_track_lba get_track_lba; +%feature("autodoc", +"cdio_get_track_lba + Get the starting LBA for track number + i_track in p_cdio. Track numbers usually start at something + greater than 0, usually 1. + + The 'leadout' track is specified either by + using i_track CDIO_CDROM_LEADOUT_TRACK or the total tracks+1. + + @param p_cdio object to get information from + @param i_track the track number we want the LSN for + @return the starting LBA or CDIO_INVALID_LBA on error."); + lba_t cdio_get_track_lba(const CdIo_t *p_cdio, track_t i_track); + +%rename cdio_get_track_lsn get_track_lsn; +%feature("autodoc", +"cdio_get_track_lsn (cdio, track)->int + +Return the starting LSN for track number. +Track numbers usually start at something greater than 0, usually 1. + +The 'leadout' track is specified either by +using i_track pycdio.CDROM_LEADOUT_TRACK or the total tracks+1. + +pycdio.INVALID_LSN is returned on error."); +lsn_t cdio_get_track_lsn(const CdIo_t *p_cdio, track_t i_track); + +%feature("autodoc", +"get_track_msf(cdio,track)->string + + Return the starting MSF (minutes/secs/frames) for track number + track. Track numbers usually start at something + greater than 0, usually 1. + + The 'leadout' track is specified either by + using i_track CDIO_CDROM_LEADOUT_TRACK or the total tracks+1. + +@return string mm:ss:ff if all good, or string 'error' on error."); +char *get_track_msf(const CdIo_t *p_cdio, track_t i_track); +%inline %{ +char *get_track_msf(const CdIo_t *p_cdio, track_t i_track) +{ + msf_t msf; + + if (!cdio_get_track_msf( p_cdio, i_track, &msf )) { + return NULL; + } else { + return cdio_msf_to_str( &msf ); + } +} +%} + +%rename cdio_get_track_preemphasis get_track_preemphasis; +%feature("autodoc", +"cdio_get_track_preemphasis(cdio, track) + +Get linear preemphasis status on an audio track. +This is not meaningful if not an audio track?"); +track_flag_t cdio_get_track_preemphasis(const CdIo_t *p_cdio, track_t i_track); + +%rename cdio_get_track_sec_count get_track_sec_count; +%feature("autodoc", +"get_track_sec_count(cdio, track)->int + +Get the number of sectors between this track an the next. This +includes any pregap sectors before the start of the next track. +Track numbers usually start at something +greater than 0, usually 1. + +0 is returned if there is an error."); +unsigned int cdio_get_track_sec_count(const CdIo_t *p_cdio, track_t i_track); + +%inline %{ +const char +*get_track_format(const CdIo_t *p_cdio, track_t i_track) +{ + track_format_t track_format = cdio_get_track_format(p_cdio, i_track); + return track_format2str[track_format]; +} +%} + + diff --git a/swig/types.swg b/swig/types.swg new file mode 100644 index 00000000..883f9315 --- /dev/null +++ b/swig/types.swg @@ -0,0 +1,56 @@ +/* -*- c -*- + $Id: types.swg,v 1.6 2008/05/01 16:55:06 karl Exp $ + + Copyright (C) 2006, 2008 Rocky Bernstein + + 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 3 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 + MERCHANTABILITY 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, see . +*/ + +/* + Various typedef's "constant"s/variables + + Many of these are documented more fully in libcdio's + I'm not sure why, but including that directly will not allow + SWIG to understand say that a lsn_t is an int. +*/ +#include + +#ifdef NEED_LONG +#define LONG long +#else +#define LONG +#endif + +#define uint32_t LONG unsigned int +#define int32_t LONG int + +typedef long int driver_id_t; +typedef int lsn_t; +typedef int int16_t; +typedef int lba_t; +typedef unsigned int track_t; +typedef long int driver_return_code_t; +typedef long int ssize_t; + +%constant long int VERSION_NUM = LIBCDIO_VERSION_NUM; + +%constant long int INVALID_LBA = CDIO_INVALID_LBA; +%constant long int INVALID_LSN = CDIO_INVALID_LSN; + +/* More documentation on the below is in cdio/sector.h */ +%constant long int CD_FRAMESIZE = CDIO_CD_FRAMESIZE; +%constant long int CD_FRAMESIZE_RAW = CDIO_CD_FRAMESIZE_RAW; +%constant long int ISO_BLOCKSIZE = CDIO_CD_FRAMESIZE; +%constant long int M2F2_SECTOR_SIZE = M2F2_SECTOR_SIZE; +%constant long int M2RAW_SECTOR_SIZE = M2RAW_SECTOR_SIZE; diff --git a/test/cdda.bin b/test/cdda.bin new file mode 100644 index 0000000000000000000000000000000000000000..b1ba1f5e26487701e8f57f237a6a9dfa49c1fc23 GIT binary patch literal 710304 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�`07^)aSIDw&+0faCA-~Ati z6aWAG2f|+ekN*SVr2h>6LHNx7dH+G!mLZ-2gr_t7VgTX03?+;pEW~(`5rjDyw=sh7 zZH8Dz5N>9;%>crZ4E78lyy<`Qe-MuUfBPQ@%l&Wv2f{r6GyZ|F;Q!KpAgu9!=|2#* z`+xBt2nYRt`45Cs{xkds;ne@%{(*4D|4;uwIOqSTe;{1=|Jy$hF8{ClAB5Na&-o9+ zum3;%55h7G4h$gd%+SLC!ifyq89+FkVLAf{gU-eP;j{m@{s-Zj|N8$ySmuAjKM=n7 z@B3d6Uh!|^Ul0b>oggguzu+GT=lo~;55j-`GcbT~E5m695dOr_&j`Y1jGRm$Y{4kR z1j1h!W-@~CBnB=<5SC-`WdPy%|0Di`u;u@?|3H}Yf6hM;7X4rO4}^pM-}?u`ZU0UF zgYc&Rwf{l*+W%euLHP0i6aPW@-2Wy2L3r)|jQ=1!`M>9X5MKYk=syU9>J|`IVenx9 z;ZlZ+3?O`jA(9bx9=|q zH~lO73&NZK-S`W__W!5+1L2GRxBUm<8itb$Ak4~mf)RuZ7$-1+a3AABCJ-)QtYiXV zM#gQ7Al%9Dm;r?O8F&~#c=i9-{~(<6|HD5JuKus|AB5-p_xlgRxBega55hVO_6#6g z%W#+hg!eGGGJ^0QhLwyUti$-35rpj+#h5@?o$(bT2>)kT%?QG`7z!Cdcn5iQebQe7czxjXT zKM0@szvw>*Z~9;UAB5-q5Bm?o^Z&>G2jPSNr~U`wyZ?{;2VoWlZUzuGW(Z*b;RuF4 z1`sY`*u?vHk<$rhg58LAd!}(O(cg^zY7J z5Z3))^$&!P|6lPRgzFftF@UfD;|WF(Uc|VO34}i}CNqQZZ^kHQ5T3<&iV1{W7*&}- z_#lHBBM5``^n&o||I`12aN&Q3{~%odpYJ~i_x$(z55hnGfBg@_{tOKaAUuVEml1?d zGE_5y@CSx@j3E4mVKU=OhEB%I42g{U8LSx>GYBwtGCX7OV#oz!Q3ehM5dQi9>wget zV&GHL0Kx(cvJ4>H_uuY62n+wu z`Uk?B{vG)X!XE#G{(>;aza@V_nDgJVKivOT{E`26{Ez9sXMZC9>He+zSMvA%Kly(k z9Q6O@KM>yZzvMp%KLn2hfYvf^{eS5H)c@=Mm;Z13Z}dO@|GR%6Ebu?$9|&*$x92Yi z$NW?M3&PU>j{N~)*?&j>fUw&?uD>8W@86=oApHBE<3A9N`v34B2(SE~{vU*2{eSTv zgyk6w89>;QA({b%Z5g5&Kv!_}BmE|3UcK|C9eg_}c%2|3UcY|KI;X z*pwle0fb8!jxm7nRt9HA5dOxnnGuA|7{!@DIF&J;34}`-8<;>ihp~VOgyR__nLyZ% zQHKeHAEU;iJ3z5n0+2f}jy8~=eY&;OWzAguVm~qAN3D}AN=F^2g2w7 zz5NTqSO5M03&QXIss00Dw*S%pKv?#F?>`WB{=fSl2&epi`VWK)|Ns66!nyw${)2GN z{~!NAIQRe8e;{1&pY1;gxBOTC55m*_hyDlQW&dmcgYcIB_5VS5!~dfHAUyBC=YJ4x z_^Cxc*<98$*`Hh znehQb2O~e@c}7LX*Nmo&oJ{hJ_ZdO>217F=2zN1jU;tqW1`URn|F8Yu{lDdZ?|=RO z3IA{Xv-v;$pTz$VFlPL3`wxVL{wMzf;o$$L{(ll<7L3jy+0V4?SWUyu2&*02>9NZrbVBF6T&bX7omvJkD3mC6rP-FyQP#Oc_Mus~K zr3||n5*cPP_%T#4*fPX1=rXv1$4)`xr(z893?MASV8Z~y3Jj49iVWclN(^BPN(?^G zu}(_{Wd;K<7GmIK0O8O7Z~X`1TmP5;2jOG?Oa6oKw*QI$L3q`FzyBb-@W1tc5C+99 z2rvJi`X7Wh{crjY!jM~w`u|`3zXpt7{=fMjgn#~LV*ug*|3w(s7?c^f859`=7!(+o z8Q2)U|9|`c(f?gweDHt!e-NJj-{e0Cr~QBUFZ%!ae*ypZ{R{Yi=3l`7+y4^(fBIMS zpZh-ugVHPrgT{bC7$V9;RzVFLzl1`xJnh++U?dxl5`5H@0nU;tq?hHwTDmSzZJ z0AWD}Qw9*`U=U#dVFrfJ|3UcX|3m*lc<29#|3Mfu<_W^R|Ly;SaQ}b3{~)~Rf9!t{ zKJ>rmKM241|L#8s%P|BnfN&VYA_fp{VtB#;!jl=kF@SI%!w&`!PG{J~0K%pWnG7Jz z&A`q8!Z-de{SU(P|2zK&;imtJ|3SFszv_Pwp84PVKL{WCKk+{ZgVqj!Fav`u0|<*T z7&CydI)fzx2#YYtGl1}y|6l)u@U8!=|AX+-|B3%Wxca~Pe-N(vFZ&;ayZ;;i2jNx! zEB}M=mH+$ygYdWitPCKm$l%QY!p;nd3?OXFkj?I}9FAk4z>`9BC>|G)A-2ygzM z{~v@wZ3Pfs@ZbAC2(SEK_#cEX{@?x|gqaw489>;QA(;V$Qy6wKfN(1VKO+dwWiVv~ z;dKn!j37Ldfsqk}OBuE^fUqk=E&~Y5Feo#C@UQf|AR0OgERvO z>o6oTfN&_oat07?VR*{`!Yder89{gvgCrvecQO2D0O1mbs|+9zW9Ise-J+Xf9ih_KK8%+KL{WC-}oPd_y3>%AB6Az-}@hgL35xWEWu#O z0KzT|^$Z}K#&DDYgc}%sGl1}9hK~#&+`#ah0fgfiRxyCE14AwY2&*v|Fn};9Pl52m z|HuA=@QwdF|AX+u|7ZS#@Q?rh|AVj=Ll6T92Qn;W0O5Lu9}FP8h{2i>gm*LeF@o?O z21iB^p2;A>2*Oni#~DD_kD-bIge4hd89?~${}2B`_~!q^{~`F;|C|3o_~iea|3P@q z|C|3oc>VvI|JVON`F{}@ulb+)AB3m=*Z2>@#s6Rb1L4U3_x^!!*#C?FKsfFHlYbyw z`k(zj2si)N`VYbr{@eZs;W_^u{)2G)f8+lkT>k&tKM?l$zw;jmYyZ#r2g1z%_5XqJ zvwuwgK={)?(SIP!|KIN)2z&is`45C^{;T{4;cfrt{|Di3|9|}lVJQYx1`t+ba9{vo zc?N9;5awWDV*p`LT>!!-{;&BD!Uz9P{|~}<{%`sZ!k_;C{SU$-3?2+1?9VWn0fcKA z-Z6mi1_nAe_c~?`acM#{(txngvI~6{{!LM|K9!u;eG#h{srOP|91QZ;j{m){{`U> z|HS@*u)_b;e<1Ajf6G4*&il{&AB1cF3;hS->i>NIK{)&W^?xAj@PENS5El3!`45Ee z{QLA5gtz^h{TGDi{44nj!ae_D|AO$8e`$X~c*DQ-e?j=pzaM`=Smb}uKM;2Pzvmwa z_y1S>55jx?PxueQ5C5O~55oWdfB6r>>2wJfUqA!4+97XFid2C;6{c3hDHWG2465{WZ+}~;XD72 z{s-Z6{}=uT;miNm{s-af|9Ace;aC5k{s&=6=(=P(hFS&?j%L`&0K$z7Ul~AnE`uT? z2rp!iWrSc!#)S-$jC~AW89=y(;UWVFXEUs20O4qcnG7K8!_dkA!gdUW3?S^tkjntV zPT+M6l?;vytzc}=P{;tnHVm;0AgsY)$pFIa45ADm{O141{~&zr|JnZ_eC7Yn{~&z% z|JMH?eChwj{~&zv|K9%~{NVq|{~&zl|JMH?eDeRq{~)~Uf9`(}UiaVgKL}6#Z~7mE zL2W7!F8R;b0AWzvfp9p( zS_TlVWO&H{!jl+S89{gogCHXaPiFYb0K)wYFBw3%lHn=?2&Xa}WB}n{hP4b3Jd+`q zVJ5h42i5O!3>z6hIGkZF0|@&wR5F0DIYTG|2n#VNGJx>s|8M_;@U8zl|AX+c|5N{i z@V5Vr|3P@y|K9%~yzl?a{~&zm|I+^;3>t$3;b;Hv{s-aD|9}1m;lKY`89u zVQB_O1_-ugkY=!DkYexzV-1E-1`r1AaROmW244mcwq@{S0AYKEPzDH2WUyyQ1eeXA z^4W%=lL3S+8A=&I*pwlW0fY@0d>KGko57O-gw+^a89-Q(!H@xjc^G&ZK={}HpZ`Jl z_5Y{;LHPaukN-g!)b<16FaJ3iK=|u_Rt5wge_`Ty&G5QfNu@(u_K zGRQK3Fz8M-5LRF?WdLCn21f=E7Guz50AVf$Nd^#RU|?kc;qU(+{s-av|JVKp;WPhd z{s-Z`|6Biq@W%g*|3P@i|K9%~3@ST8_`(09|3Mh!b`a)f&}9H&MFvj>5H?~6WdLD) zhC~Js)@JZ!0AWyH7=%G*`+_hN!^i(1{N?}I{~-M0|IPm({O<{13v1|F`}J;UoVi{s-Zs|JVKp;cNd-{s-YF|F8ZB;rIU^ z{s-Y7|L^_>;rGyUPoMw4`X7Wrc?*PJ{lEJkgg^a%`5%NKvt^1k-fUp8XAOi^NGQ=`~uo*)n0|?tNgff7zK0_b_2rD!AGJvoIgCqk8 z|NH;+KM240zwti^ANybXAA~_=KM1$|7yS>ymH!$4gK)wBzyCnE>Obdy5N`Re`X7Y* z{yY8$;pP8h|AX+J|CRqic;Ell{~&zef8&1;-t<5BKL{`XANe1I=l^&855isl4gZ5M zXv_wL>;H592jSNLqW?j-`@iCU5MKD-_df`4`Cs`Tgpd56`X7WZ{@?f?gzx-6`yYfM zv!6fzb26whfG{k6RT*L#Kv<60yr!#D20O2Tx zjSL{{$1s-xgq;~W89*3Rc7ZS`Z-THIgD(RJ>oIsTfUq$`C<6$aF(fj8usK650|etqdRxDjPvqoI#fXg#Z2j`5%N|{=fPk zgkSu>_#cFy|3CX5grEJt`5%Oz{)fOP{~!Jb;m7~a{s-Yp|5yG8;UoWB|AX+(|Be4a z_~8HE{~&zg|IGg&eD43!{~&z%|Hl6yeEt8~{~-M8|I7a%46+-9U;ID#AB3;}U;7_~ zPyV0zAB6Y)FZ~a~pfPL^-ta&6KM1e+U-=(|PyL_zAA}$O-}oPdL1hyNvomlqfUq!w zDgy}1FgP-RFldc12#YaDGJr5Q11kdvGcrIRWOVlL{}2Cvf-z_g9E2GdKK=(`NNefu zf6y9W5awb~WdLDF-R8)k!QczVh76GmAZ*T%$^gRF4229JY|D_#0Ku6IwhWmJIt-y; zEX&}?0Kx(cx(pyJ%Am;r!V(Os3?K}--Cd9YGK&GKgJ1st^8e-kKmWgiF=SU4BY4aV zG;YSp0D&OCfiMq)A_EA^FxWDHFla0bghim|Pzx{!GJr4-!{7fP{Nw-2{~!!1`#|{S z|AYTQ_}u@s|3MfumIlJ0xCP;B|2O^zVbGWv2tWRR^FIiK)+B&1Cxapb2!qbT1Yyv; z1_-M&q%wf84MQOV1m`l?fY%U4GFUT2GH5cmg0UonA_E9>GyMGz!vFu@{13uU{~!Gi z!WaH;{13wS|DXL2!mt0o{13vAx|@|jmcbB=Z5d)2Ksbz{lL3Sy7?v_X@IruX@#X(Z|AX-H{|ooO|AX-J|2zMK@RR>r|AR27 zZwJDlyA?qANLHNl3+W#QD>wo5d5Z?4Z^*;!&`Jeb7gjf8J z{SU&+{zv`?;jRBu|AR1S>>Py8{@?o_grEI?`5%OT{r~zOgt-_P89*46wm_JPfs+A* z|NUoW0O4Q%85uwrBnQI$43Z2WEXJV90K%X(Kp-r~V9Nl)@(i8~5bVkz5AI_*GRT4R zC?tJKGFUQzuo^=s0|@Ig6f%IYD?={>2!}GPWB}m=hNBE14C?!Va5lqT1`y6+xXA#* z*$hV+KsbV7BLfJ#GxRcmumwXV0|;v|xH5n+DDQ!=AcHLf2#YaTGJvoogDC?DgWL|n zpu7XYo(wV!o($3qpuokSpf>Hp0CAPgFV z24PU!281vDU;7_~@BH8SAA~`155l+pZ~PC!AUO~QwGBb|`2W`bAbj|L;eQZ5@IUuI z2=Du!`5%PW{!jf6!VCT<{s-Y@|8xI?FsN9gh6db5C-in z0^xuE-~I<-W`>vlL71HZGzakYKdgM?VbEm&VOa)C1`r0t4G4q!ULXt!dr%+4l0lTg z65KZj_0Lrp0vSLUR{m-+crrjRBz`p+0vSM9oxzs@gjE!r}~q3?R(G@b*6lzy5#n zKL|hmfAl{H-}!&=KL}s@zxO`~U;MxGKL~@?@`CWK{|Enr@YDZi|AX+m|1bZ8Fsyw1 z^RZa(GVC7fzc2c4S~@R7!85Z5Eu=C(GVC7fzc2c4S~@R z7!85Z5Eu=C(GVC7fzc2c4S~@R7!85Z5Eu=C(GVC7fzc2c4S~@R7!85Z5Eu=C(GVC7 zfzc2c4S~@R7!85Z5Eu=C(GVC7fzc2c4S~@R7!85Z5Eu=C(GVC7fzc2c4S~@R7!85Z z5Eu=C(GVC7fzc2c4S~@R7!85Z5Eu=C(GVC7fzc2c4S~@R7!85Z5Eu=C(GVC7fzc2c z4S~@R7!85Z5Eu=C(GVC7fzc2c4S~@R7!85Z5Eu=C(GVC7fzc2c4S~@R7!85Z5Eu=C z(GVC7fzc2c4S~@R7!85Z5Eu=C(GVC7fzc2c4S~@R7!85Z5Eu=C(GVC7fzc2c4S~@R z7!85Z5Eu=C(GVC7fzc2c4S~@R7zLvtFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*O zqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8Umvs zFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF z0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71* zAut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@? z8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*O zqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;6Cw1V%$(Gz3ONU^E0qLtr!nMnhmU1V%$( zGz3ONU^E0qLtr!nMnhmU1V%$(Gz3ONU^E0qLtr!nMnhmU1V%$(Gz3ONU^E0qLtr!n zMnhmU1V%$(Gz3ONU^E0qLtr!nMnhmU1V%$(Gz3ONU^E0qLtr!nMnhmU1V%$(Gz3ON zU^E0qLtr!nMnhmU1V%$(Gz3ONU^E0qLtr!nMnhmU1V%$(Gz3ONU^E0qLtr!nMnhmU z1V%$(Gz3ONU^E0qLtr!nMnhmU1V%$(Gz3ONU^E0qLtr!nMnhmU1V%$(Gz3ONU^E0q zLtr!nMnhmU1V%$(Gz3ONU^E0qLtr!nMnhmU1V%$(Gz3ONU^E0qLtr!nMnhmU1V%$( zGz3ONU^E0qLtr!nMnhmU1V%$(Gz3ONU^E0qLtqq)hQMeDjE2By2#kinXb6mkz-S1J zhQMeDjE2By2#kinXb6mkz-S1JhQMeDjE2By2#kinXb6mkz-S1JhQMeDjE2By2#kin zXb6mkz-S1JhQMeDjE2By2#kinXb6mkz-S1JhQMeDjE2By2#kinXb6mkz-S1JhQMeD zjE2By2#kinXb6mkz-S1JhQMeDjE2By2#kinXb6mkz-S1JhQMeDjE2By2#kinXb6mk zz-S1JhQMeDjE2By2#kinXb6mkz-S1JhQMeDjE2By2#kinXb6mkz-S1JhQMeDjE2By z2#kinXb6mkz-S1JhQMeDjE2By2#kinXb6mkz-S1JhQMeDjE2By2#kinXb6mkz-S1J zhQMeDjE2By2#kinXb6mkz-S1JhQMeDjE2By2#kinXb6mg(GVC7fzc2c4S~@R7!85Z z5Eu=C(GVC7fzc2c4S~@R7!85Z5Eu=C(GVC7fzc2c4S~@R7!85Z5Eu=C(GVC7fzc2c z4S~@R7!85Z5Eu=C(GVC7fzc2c4S~@R7!85Z5Eu=C(GVC7fzc2c4S~@R7!85Z5Eu=C z(GVC7fzc2c4S~@R7!85Z5Eu=C(GVC7fzc2c4S~@R7!85Z5Eu=C(GVC7fzc2c4S~@R z7!85Z5Eu=C(GVC7fzc2c4S~@R7!85Z5Eu=C(GVC7fzc2c4S~@R7!85Z5Eu=C(GVC7 zfzc2c4S~@R7!85Z5Eu=C(GVC7fzc2c4S~@R7!85Z5Eu=C(GVC7fzc2c4S~@R7!85Z z5Eu=C(GVC7fzc2c4S~@R7!85Z5Eu=C(GVC7fzc2c4S~@R7!83@Fd71*Aut*OqaiRF z0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71* zAut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@? z8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*O zqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8Umvs zFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF z0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@?3PwX= 76: + # FIXME: Broken on Darwin? + # self.device.open() + self.device.have_ATAPI() + # FIXME: Broken on Darwin? + # self.device.get_media_changed() + self.assertEqual(True, True, "Test misc operations") + + def test_device_default(self): + """Test getting default device""" + result1=cdio.get_default_device_driver(pycdio.DRIVER_DEVICE) + result2=cdio.get_default_device_driver() + self.assertEqual(result1, result2, + "get_default_device with/out parameters") + self.device = cdio.Device() + result2=pycdio.get_device() + if result1 is not None: + self.assertEqual(result1[0], result2) + # Now try getting device using driver that we got back + try: + device=cdio.Device(driver_id=result1[1]) + result1 = device.get_device() + self.assertEqual(result1, result2, + "get_default_device using driver name") + except: + pass + + def test_exceptions(self): + """Test that various routines raise proper exceptions""" + self.device = cdio.Device() + # No CD or or CD image has been set yet. So these fail + try: + lsn = self.device.get_disc_last_lsn() + except IOError: + self.assertEqual(True, True, "get_last_lsn() IO Error") + except cdio.DriverError: + self.assertEqual(True, True, "get_last_lsn() DriverError") + else: + self.assertTrue(False, "get_last_lsn() should raise error") + self.assertRaises(IOError, self.device.get_disc_mode) + try: + track = self.device.get_num_tracks() + except IOError: + self.assertEqual(True, True, "get_num_tracks() IO Error") + except cdio.DriverError: + self.assertEqual(True, True, "get_num_tracks() DriverError") + except cdio.TrackError: + self.assertEqual(True, True, "get_num_tracks() TrackError") + else: + self.assertTrue(False, "get_last_lsn() should raise error") + self.assertRaises(IOError, self.device.get_driver_name) + self.assertRaises(cdio.DriverUninitError, + self.device.get_media_changed) + self.assertRaises(IOError, self.device.open, "***Invalid device***") + + def test_have_image_drivers(self): + """Test that we have image drivers""" + result = cdio.have_driver('CDRDAO') + self.assertEqual(True, result, "Have cdrdrao driver via string") + result = cdio.have_driver(pycdio.DRIVER_CDRDAO) + self.assertEqual(True, result, "Have cdrdrao driver via driver_id") + result = cdio.have_driver('NRG') + self.assertEqual(True, result, "Have NRG driver via string") + result = cdio.have_driver(pycdio.DRIVER_NRG) + self.assertEqual(True, result, "Have NRG driver via driver_id") + result = cdio.have_driver('BIN/CUE') + self.assertEqual(True, result, "Have BIN/CUE driver via string") + result = cdio.have_driver(pycdio.DRIVER_BINCUE) + self.assertEqual(True, result, "Have BIN/CUE driver via driver_id") + + def test_tocfile(self): + """Test functioning of cdrdao image routines""" + ## TOC reading needs to be done in the directory where the + ## TOC/BIN files reside. + olddir=os.getcwd() + os.chdir('.') + tocfile="cdda.toc" + device = cdio.Device(tocfile, pycdio.DRIVER_CDRDAO) + ok, vendor, model, revision = device.get_hwinfo() + self.assertEqual(True, ok, "get_hwinfo ok") + self.assertEqual('libcdio', vendor, "get_hwinfo vendor") + self.assertEqual('cdrdao', model, "get_hwinfo cdrdao") + # Test known values of various access parameters: + # access mode, driver name via string and via driver_id + # and cue name + result = device.get_arg("access-mode") + self.assertEqual(result, 'image', 'get_arg("access_mode")',) + result = device.get_driver_name() + self.assertEqual(result, 'CDRDAO', 'get_driver_name') + result = device.get_driver_id() + self.assertEqual(result, pycdio.DRIVER_CDRDAO, 'get_driver_id') + result = device.get_arg("source") + self.assertEqual(result, tocfile, 'get_arg("source")') + result = device.get_media_changed() + self.assertEqual(False, result, "tocfile: get_media_changed") + # Test getting is_tocfile + result = cdio.is_tocfile(tocfile) + self.assertEqual(True, result, "is_tocfile(tocfile)") + result = cdio.is_nrg(tocfile) + self.assertEqual(False, result, "is_nrgfile(tocfile)") + result = cdio.is_device(tocfile) + self.assertEqual(False, result, "is_device(tocfile)") + self.assertRaises(cdio.DriverUnsupportedError, + device.set_blocksize, 2048) + self.assertRaises(cdio.DriverUnsupportedError, + device.set_speed, 5) + device.close() + os.chdir(olddir) + + def test_read(self): + """Test functioning of read routines""" + cuefile="./../data/isofs-m1.cue" + device = cdio.Device(source=cuefile) + # Read the ISO Primary Volume descriptor + blocks, data=device.read_sectors(16, pycdio.READ_MODE_M1F1) + self.assertEqual(data[1:6], 'CD001') + self.assertEqual(blocks, 1) + blocks, data=device.read_data_blocks(26) + self.assertEqual(data[6:32], 'GNU GENERAL PUBLIC LICENSE') + + def test_bincue(self): + """Test functioning of BIN/CUE image routines""" + cuefile="./cdda.cue" + device = cdio.Device(source=cuefile) + # Test known values of various access parameters: + # access mode, driver name via string and via driver_id + # and cue name + result = device.get_arg("access-mode") + self.assertEqual(result, 'image', 'get_arg("access_mode")',) + result = device.get_driver_name() + self.assertEqual(result, 'BIN/CUE', 'get_driver_name') + result = device.get_driver_id() + self.assertEqual(result, pycdio.DRIVER_BINCUE, 'get_driver_id') + result = device.get_arg("cue") + self.assertEqual(result, cuefile, 'get_arg("cue")') + # Test getting is_binfile and is_cuefile + binfile = cdio.is_cuefile(cuefile) + self.assertEqual(True, binfile != None, "is_cuefile(cuefile)") + cuefile2 = cdio.is_binfile(binfile) + # Could check that cuefile2 == cuefile, but some OS's may + # change the case of files + self.assertEqual(True, cuefile2 != None, "is_cuefile(binfile)") + result = cdio.is_tocfile(cuefile) + self.assertEqual(False, result, "is_tocfile(tocfile)") + ok, vendor, model, revision = device.get_hwinfo() + self.assertEqual(True, ok, "get_hwinfo ok") + self.assertEqual('libcdio', vendor, "get_hwinfo vendor") + self.assertEqual('CDRWIN', model, "get_hwinfo model") + result = cdio.is_device(cuefile) + self.assertEqual(False, result, "is_device(tocfile)") + result = device.get_media_changed() + self.assertEqual(False, result, "binfile: get_media_changed") + if pycdio.VERSION_NUM >= 77: + # There's a bug in libcdio 0.76 that causes these to crash + self.assertRaises(cdio.DriverUnsupportedError, + device.set_blocksize, 2048) + self.assertRaises(cdio.DriverUnsupportedError, + device.set_speed, 5) + device.close() + + def test_cdda(self): + """Test functioning CD-DA""" + device = cdio.Device() + cuefile="./cdda.cue" + device.open(cuefile) + result = device.get_disc_mode() + self.assertEqual(result, 'CD-DA', 'get_disc_mode') + self.assertEqual(device.get_mcn(), '0000010271955', 'get_mcn') + self.assertRaises(cdio.DriverUnsupportedError, + device.get_last_session) + # self.assertRaises(IOError, device.get_joliet_level) + result = device.get_num_tracks() + self.assertEqual(result, 1, 'get_num_tracks') + disc_last_lsn = device.get_disc_last_lsn() + self.assertEqual(disc_last_lsn, 302, 'get_disc_last_lsn') + t=device.get_last_track() + self.assertEqual(t.track, 1, 'get_last_track') + self.assertEqual(t.get_last_lsn(), 301, '(track) get_last_lsn') + self.assertEqual(device.get_track_for_lsn(t.get_last_lsn()).track, + t.track) + t=device.get_first_track() + self.assertEqual(t.track, 1, 'get_first_track') + self.assertEqual(t.get_format(), 'audio', 'get_track_format') + device.close() + +if __name__ == "__main__": + unittest.main() diff --git a/test/test-iso.py b/test/test-iso.py new file mode 100755 index 00000000..7c253efb --- /dev/null +++ b/test/test-iso.py @@ -0,0 +1,167 @@ +#!/usr/bin/env python +"""Unit test for iso9660 + +Test some low-level ISO9660 routines +This is basically the same thing as libcdio's testiso9660.c""" + +import unittest, sys, os + +libdir = os.path.join(os.path.dirname(__file__), '..') +if libdir[-1] != os.path.sep: + libdir += os.path.sep +sys.path.insert(0, libdir) +import pyiso9660 +import iso9660 + +def is_eq(a, b): + if len(a) != len(b): return False + + for i in range(len(a)): + if a[i] != b[i]: + print "position %d: %d != %d\n" % (i, a[i], b[i]) + return False + return True + +achars = ('!', '"', '%', '&', '(', ')', '*', '+', ',', '-', '.', + '/', '?', '<', '=', '>') + +class ISO9660Tests(unittest.TestCase): + + def test_chars(self): + """Test ACHAR and DCHAR""" + bad = 0 + c=ord('A') + while c<=ord('Z'): + if not pyiso9660.is_dchar(c): + print "Failed iso9660_is_achar test on %c" % c + bad += 1 + if not pyiso9660.is_achar(c): + print "Failed iso9660_is_achar test on %c" % c + bad += 1 + c += 1 + + self.assertEqual(True, bad==0, 'is_dchar & is_achar A..Z') + + bad=0 + c=ord('0') + while c<=ord('9'): + if not pyiso9660.is_dchar(c): + print "Failed iso9660_is_dchar test on %c" % c + bad += 1 + if not pyiso9660.is_achar(c): + print "Failed iso9660_is_achar test on %c" % c + bad += 1 + c += 1 + self.assertEqual(True, bad==0, 'is_dchar & is_achar 0..9') + + bad=0 + i=0 + while i<=13: + c=ord(achars[i]) + if pyiso9660.is_dchar(c): + print "Should not pass is_dchar test on %c" % c + bad += 1 + if not pyiso9660.is_achar(c): + print "Failed is_achar test on symbol %c" % c + bad += 1 + i += 1 + + self.assertEqual(True, bad==0, 'is_dchar & is_achar symbols') + + def test_strncpy_pad(self): + """Test pyiso9660.strncpy_pad""" + + dst = pyiso9660.strncpy_pad("1_3", 5, pyiso9660.DCHARS) + self.assertEqual(dst, "1_3 ", "strncpy_pad DCHARS") + + dst = pyiso9660.strncpy_pad("ABC!123", 2, pyiso9660.ACHARS) + self.assertEqual(dst, "AB", "strncpy_pad ACHARS truncation") + + def test_dirname(self): + """Test pyiso9660.dirname_valid_p""" + + self.assertEqual(False, pyiso9660.dirname_valid_p("/NOGOOD"), + "dirname_valid_p - /NOGOOD is no good.") + + self.assertEqual(False, + pyiso9660.dirname_valid_p("LONGDIRECTORY/NOGOOD"), + "pyiso9660.dirname_valid_p - too long directory") + + self.assertEqual(True, pyiso9660.dirname_valid_p("OKAY/DIR"), + "dirname_valid_p - OKAY/DIR should pass ") + + self.assertEqual(False, pyiso9660.dirname_valid_p("OKAY/FILE.EXT"), + "pyiso9660.dirname_valid_p - OKAY/FILENAME.EXT") + + def test_image_info(self): + """Test retrieving image information""" + + # The test ISO 9660 image + image_path="../data" + image_fname=os.path.join(image_path, "copying.iso") + iso = iso9660.ISO9660.IFS(source=image_fname) + self.assertNotEqual(iso, None, "Opening %s" % image_fname) + + self.assertEqual(iso.get_application_id(), +"MKISOFS ISO 9660/HFS FILESYSTEM BUILDER & CDRECORD CD-R/DVD CREATOR (C) 1993 E.YOUNGDALE (C) 1997 J.PEARSON/J.SCHILLING", + "get_application_id()") + + self.assertEqual(iso.get_system_id(), "LINUX", + "get_system_id() eq 'LINUX'") + self.assertEqual(iso.get_volume_id(), "CDROM", + "get_volume_id() eq 'CDROM'") + + file_stats = iso.readdir('/') + + okay_stats = [ + ['.', 23, 2048, 1, 2], + ['..', 23, 2048, 1, 2], + ['COPYING.;1', 24, 17992, 9, 1] + ] + self.assertEqual(file_stats, okay_stats, "file stat info") + + def test_pathname_valid(self): + """Test pyiso9660.pathname_valid_p""" + + self.assertEqual(True, pyiso9660.pathname_valid_p("OKAY/FILE.EXT"), + "pyiso9660.dirname_valid_p - OKAY/FILE.EXT ") + self.assertEqual(False, + pyiso9660.pathname_valid_p("OKAY/FILENAMELONG.EXT"), + 'invalid pathname, long basename') + + self.assertEqual(False, + pyiso9660.pathname_valid_p("OKAY/FILE.LONGEXT"), + "pathname_valid_p - long extension" ) + + dst = pyiso9660.pathname_isofy("this/file.ext", 1) + self.assertNotEqual(dst, "this/file.ext1", "iso9660_pathname_isofy") + + def test_time(self): + """Test time""" + import time + + tm = time.localtime(0) + dtime = pyiso9660.set_dtime(tm[0], tm[1], tm[2], tm[3], tm[4], tm[5]) + new_tm = pyiso9660.get_dtime(dtime, True) + + ### FIXME Don't know why the discrepancy, but there is an hour + ### difference, perhaps daylight savings time. + ### Versions before 0.77 have other bugs. + if new_tm is not None: + # if pyiso9660.VERSION_NUM < 77: new_tm[3] = tm[3] + new_tm[3] = tm[3] + self.assertEqual(True, is_eq(new_tm, tm), 'get_dtime(set_dtime())') + else: + self.assertEqual(True, False, 'get_dtime is None') + +# if pyiso9660.VERSION_NUM >= 77: +# tm = time.gmtime(0) +# ltime = pyiso9660.set_ltime(tm[0], tm[1], tm[2], tm[3], tm[4], +# tm[5]) +# new_tm = pyiso9660.get_ltime(ltime) +# self.assertEqual(True, is_eq(new_tm, tm), +# 'get_ltime(set_ltime())') + return + +if __name__ == "__main__": + unittest.main() diff --git a/test/test-isocopy.py b/test/test-isocopy.py new file mode 100755 index 00000000..bb8c8673 --- /dev/null +++ b/test/test-isocopy.py @@ -0,0 +1,399 @@ +#!/usr/bin/env python +"""Unit test of iso9660 file extraction.""" + +import unittest, sys, os + +libdir = os.path.join(os.path.dirname(__file__), '..') +if libdir[-1] != os.path.sep: + libdir += os.path.sep +sys.path.insert(0, libdir) +import pycdio +import iso9660 + +# Python has rounding (round) and truncation (int), but what about an +# integer ceiling function? Until I learn what it is... +def ceil(x): + return int(round(x+0.5)) + + +# The test CD image +CD_IMAGE_PATH =os.path.join("..", "data") +cd_image_fname=os.path.join(CD_IMAGE_PATH, "isofs-m1.cue") +local_filename="COPYING" + +class ISO9660Tests(unittest.TestCase): + + def test_fs(self): + + cd = iso9660.ISO9660.FS(source=cd_image_fname) + self.assertEqual(True, cd is not None, + "Open CD image %s" % cd_image_fname) + statbuf = cd.stat (os.path.join("/", local_filename)) + + good_stat = { 'LSN': 26, 'filename': 'COPYING', 'is_dir': False, + 'sec_size': 9, 'size' :17992 } + + self.assertEqual(statbuf, good_stat, 'CD 9660 file stats') + + # Get file + buf_all =[] + blocks = ceil(statbuf['size'] / pycdio.ISO_BLOCKSIZE) + for i in range(blocks): + lsn = statbuf['LSN'] + i + size, buf = cd.read_data_blocks(lsn) + + self.assertEqual( True, size >= 0, + "Error reading ISO 9660 file %d" % lsn ) + buf_all.append(buf) + + length=statbuf['size']; + test_file_contents=''.join(buf_all)[0:length-1] + global file_contents + self.assertEqual(test_file_contents, + file_contents, 'File contents comparison') + cd.close() + +file_contents=""" GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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 + MERCHANTABILITY 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 + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License.""" + +if __name__ == "__main__": + unittest.main() +