1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317
|
# -*- coding: utf-8 -*-
# Copyright 2013 Sebastian Ramacher <sebastian+dev@ramacher.at>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
""" Python bindings for libdiscid
libdiscid is a library to calculate MusicBrainz Disc IDs.
This module provides Python-bindings for libdiscid.
>>> disc = libdiscid.read()
>>> disc.id is not None
True
"""
from __future__ import division
import libdiscid._discid
from libdiscid.exceptions import DiscError
import re
import warnings
__version__ = '0.4.1'
DEFAULT_DEVICE = libdiscid._discid.default_device()
""" The default device to use for :func:`DiscId.read` on this platform.
.. deprecated:: 0.2.0
Please use :func:`default_device` instead.
"""
FEATURES = libdiscid._discid.FEATURES
""" List of all available features supported by libdiscid on this platform.
"""
FEATURE_READ = libdiscid._discid.FEATURE_READ
""" Read the TOC of the disc to get the disc ID. This feature is always enabled.
"""
FEATURE_MCN = libdiscid._discid.FEATURE_MCN
""" Read the Media Catalogue Number of the disc.
"""
FEATURE_ISRC = libdiscid._discid.FEATURE_ISRC
""" Read :musicbrainz:`International Standard Recording Codes <ISRC>` of all the
tracks.
"""
FEATURES_MAPPING = libdiscid._discid.FEATURES_MAPPING
""" Mapping between the constants representing a feature and their string
representation.
"""
__discid_version__ = libdiscid._discid.__discid_version__
""" The version of the underlying libdiscid.
"""
class DiscId(object):
""" Disc information
Class holding all the information obtained from a disc.
"""
def __init__(self, cdiscid):
self._id = cdiscid.id
self._freedb_id = cdiscid.freedb_id
self._submission_url = cdiscid.submission_url
self._webservice_url = cdiscid.webservice_url
self._first_track = cdiscid.first_track
self._last_track = cdiscid.last_track
self._sectors = cdiscid.sectors
self._track_offsets = cdiscid.track_offsets
self._track_lengths = cdiscid.track_lengths
self._mcn = cdiscid.mcn
self._track_isrcs = cdiscid.track_isrcs
self._device = cdiscid.device
self._toc = cdiscid.toc
@property
def id(self):
""" The MusicBrainz :musicbrainz:`Disc ID`.
"""
return self._id
@property
def freedb_id(self):
""" The :musicbrainz:`FreeDB` Disc ID (without category).
"""
return self._freedb_id
@property
def submission_url(self):
""" Disc ID / TOC Submission URL for MusicBrainz
With this url you can submit the current TOC as a new MusicBrainz
:musicbrainz:`Disc ID`.
"""
return self._submission_url
@property
def webservice_url(self):
""" The web service URL for info about the CD
With this url you can retrieve information about the CD in XML from the
MusicBrainz web service.
"""
warnings.warn('webservice_url is deprecated since it points to the old '
'webservice. Please use python-musicbrainz-ngs to access '
'the webservice.', DeprecationWarning)
return self._webservice_url
@property
def first_track(self):
""" Number of the first audio track.
"""
return self._first_track
@property
def last_track(self):
""" Number of the last audio track.
"""
return self._last_track
@property
def sectors(self):
""" Total sector count.
"""
return self._sectors
@property
def leadout_track(self):
""" Leadout track.
"""
return self.sectors
@property
def track_offsets(self):
""" Tuple of all track offsets (in sectors).
The first element corresponds to the offset of the track denoted by
:attr:`first_track` and so on.
"""
return self._track_offsets
@property
def pregap(self):
""" Pregap of the first track (in sectors).
"""
return self.track_offsets[0]
@property
def track_lengths(self):
""" Tuple of all track lengths (in sectors).
The first element corresponds to the length of the track denoted by
:attr:`first_track` and so on.
"""
return self._track_lengths
@property
def mcn(self):
""" Media Catalogue Number of the disc.
:raises NotImplementedError: reading MCN is not supported on this platform
"""
if self._mcn is None:
raise NotImplementedError('MCN is not available with this version '
'of libdiscid and/or platform')
return self._mcn
@property
def track_isrcs(self):
""" Tuple of :musicbrainz:`ISRCs <ISRC>` of all tracks.
The first element of the list corresponds to the ISRC of the
:attr:`first_track` and so on.
:raises NotImplementedError: reading ISRCs is not supported on this platform
"""
if self._track_isrcs is None:
raise NotImplementedError('ISRC is not available with this version '
'of libdiscid and/or platform')
return self._track_isrcs
@property
def device(self):
""" The device the data was read from.
If it is ``None``, :func:`libdiscid.put` was called to create the instance.
"""
return self._device
@property
def toc(self):
""" String representing the CD's Table of Contents (TOC).
:raises ValueError: extracting TOC string from the submission URL failed
"""
if self._toc is None:
# extract TOC string from submission URL
match = re.match(r'.*toc=([0-9+]+)$', self.submission_url)
if match is None:
raise ValueError('Failed to extract TOC from submission URL')
self._toc = match.group(1).replace('+', ' ')
return self._toc
def read(device=None, features=None):
""" Reads the TOC from the device given as string.
If *device* is ``None``, :func:`default_device` is used to determine
the device. *features* can be any combination of :data:`FEATURE_MCN` and
:data:`FEATURE_ISRC` and :data:`FEATURE_READ`. Note that prior to libdiscid
version 0.5.0 *features* has no effect and that :data:`FEATURE_READ` is always
assumed, even if not given.
:param device: device to read from
:type device: unicode or None
:param features: selected features, possible values are :data:`FEATURE_READ` \
:data:`FEATURE_MCN`, :data:`FEATURE_ISRC` and any of these values combined \
with bitwise or.
:type features: integer or None
:raises libdiscid.DiscError: reading the disc failed
:raises NotImplementedError: reading discs is not supported
:raises MemoryError: failed to allocate the internal DiscId object
:rtype: :class:`DiscId` object
"""
disc = libdiscid._discid.DiscId()
if features is None:
disc.read(device)
else:
disc.read(device, features)
return DiscId(disc)
def put(first, last, sectors, offsets):
""" Creates a TOC based on the given offsets.
Takes the *first* and *last* audio track, as well as the number of
*sectors* and a list of *offsets* as in :attr:`track_offsets`.
:param first: number of the first audio track
:type first: integer
:param last: number of the last audio track
:type last: integer
:param sectors: total number of sectors on the disc
:type sectors: integer
:param offsets: offsets of each track
:type offsets: list or tuple of integers
:raises libdiscid.DiscError: operation failed for some reason
:raises MemoryError: failed to allocated memory to store the offsets or the \
internal DiscId object
:rtype: :class:`DiscId` object
"""
disc = libdiscid._discid.DiscId()
disc.put(first, last, sectors, offsets)
return DiscId(disc)
def default_device():
""" The default device on this platform.
The default device can change during the run-time of the program. This can
happen with removable devices for example.
:rtype: unicode
"""
return libdiscid._discid.default_device()
def sectors_to_seconds(sectors):
""" Convert sectors to seconds rounded to the nearest second.
:param sectors: number of sectors
:type sectors: integer
:rtype: integer
"""
SECTORS_PER_SECOND = 75
remainder = sectors % SECTORS_PER_SECOND
return sectors // SECTORS_PER_SECOND + \
(1 if remainder > SECTORS_PER_SECOND // 2 else 0)
__all__ = (
'read', 'put', 'default_device', 'sectors_to_seconds',
'__version__', '__discid_version__',
'FEATURES', 'FEATURES_MAPPING', 'FEATURE_READ', 'FEATURE_MCN', 'FEATURE_ISRC',
'DEFAULT_DEVICE',
'DiscId', 'DiscError'
)
|