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
|
# Copyright 2013-2021 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.
# cython: language_level=3, warn.unused=True
from . cimport cdiscid
cimport cpython
from libc cimport limits
from libc.stdlib cimport malloc, free
from cpython cimport bool
from libdiscid.exceptions import DiscError
cdef bool _has_feature(int feature):
return cdiscid.wrap_has_feature(feature) == 1
cdef str _to_str(char* s):
return s.decode('UTF-8', 'strict')
cdef class DiscId:
"""Class to calculate MusicBrainz Disc IDs.
>>> d = DiscId()
>>> d.read()
>>> d.id is not None
True
Note that all the properties are only set after an successful call to
:func:`DiscId.read` or :func:`DiscId.put`.
"""
cdef cdiscid.DiscId *_c_discid
cdef bool _have_read
cdef str _device
def __cinit__(self):
self._c_discid = cdiscid.discid_new()
if self._c_discid is NULL:
raise MemoryError('Failed to allocate DiscId object')
self._have_read = False
self._device = None
def __dealloc__(self):
if self._c_discid is not NULL:
cdiscid.discid_free(self._c_discid)
cdef _read(self, char* device, unsigned int features):
if not _has_feature(cdiscid.DISCID_FEATURE_READ):
raise NotImplementedError(
"read is not available with this version of libdiscid and/or platform"
)
if not cdiscid.wrap_read_sparse(self._c_discid, device, features):
raise DiscError(self._get_error_msg())
self._have_read = True
def read(self, str device=None, unsigned int features=limits.UINT_MAX):
"""Reads the TOC from the device given as string.
If *device* is ``None``, :func:`libdiscid.default_device` is used.
*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.
A :exc:`libdiscid.DiscError` exception is raised when reading fails, and
:py:exc:`NotImplementedError` when libdiscid does not support reading discs
on the current platform.
"""
if device is None:
device = default_device()
py_byte_device = device.encode('UTF-8')
cdef char* cdevice = py_byte_device
self._read(cdevice, features)
self._device = device
cdef _put(self, int first, int last, int* offsets):
if not cdiscid.discid_put(self._c_discid, first, last, offsets):
raise DiscError(self._get_error_msg())
self._device = None
self._have_read = True
def put(self, int first, int last, int 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:`DiscId.track_offsets`.
If the operation fails for some reason, a :exc:`libdiscid.DiscError`
exception is raised.
"""
cdef int* coffsets = <int*> malloc((len(offsets) + 1) * sizeof(int))
if coffsets is NULL:
raise MemoryError('Failed to allocate memory to store offsets')
try:
coffsets[0] = sectors
for (i, v) in enumerate(offsets):
coffsets[i + 1] = v
return self._put(first, last, coffsets)
finally:
free(coffsets)
cdef str _get_error_msg(self):
return _to_str(cdiscid.discid_get_error_msg(self._c_discid))
@property
def id(self):
"""The MusicBrainz Disc ID.
"""
return _to_str(cdiscid.discid_get_id(self._c_discid))
@property
def freedb_id(self):
"""The FreeDB Disc ID (without category).
"""
return _to_str(cdiscid.discid_get_freedb_id(self._c_discid))
@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
Disc ID.
"""
return _to_str(cdiscid.discid_get_submission_url(self._c_discid))
@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.
"""
return _to_str(cdiscid.discid_get_webservice_url(self._c_discid))
@property
def first_track(self):
"""Number of the first audio track.
"""
return cdiscid.discid_get_first_track_num(self._c_discid)
@property
def last_track(self):
"""Number of the last audio track.
"""
return cdiscid.discid_get_last_track_num(self._c_discid)
@property
def sectors(self):
"""Total sector count.
"""
return cdiscid.discid_get_sectors(self._c_discid)
@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 tuple(
cdiscid.discid_get_track_offset(self._c_discid, track)
for track in range(self.first_track, self.last_track + 1)
)
@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 tuple(
cdiscid.discid_get_track_length(self._c_discid, track)
for track in range(self.first_track, self.last_track + 1)
)
@property
def mcn(self):
"""Media Catalogue Number of the disc.
"""
if not _has_feature(cdiscid.DISCID_FEATURE_MCN):
return None
return _to_str(cdiscid.wrap_get_mcn(self._c_discid))
@property
def track_isrcs(self):
"""Tuple of ISRCs of all tracks.
The first element of the list corresponds to the ISRC of the
:attr:`first_track` and so on.
"""
if not _has_feature(cdiscid.DISCID_FEATURE_ISRC):
return None
return tuple(
_to_str(cdiscid.wrap_get_track_isrc(self._c_discid, track))
for track in range(self.first_track, self.last_track + 1)
)
@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).
"""
assert self._have_read
cdef char* tocstr = cdiscid.wrap_get_toc(self._c_discid)
if tocstr is not NULL:
return _to_str(tocstr)
return None
FEATURES_MAPPING = {
cdiscid.DISCID_FEATURE_READ: _to_str(cdiscid.DISCID_FEATURE_STR_READ),
cdiscid.DISCID_FEATURE_MCN: _to_str(cdiscid.DISCID_FEATURE_STR_MCN),
cdiscid.DISCID_FEATURE_ISRC: _to_str(cdiscid.DISCID_FEATURE_STR_ISRC)
}
cdef _feature_list():
return tuple(s for f, s in FEATURES_MAPPING.items() if _has_feature(f))
def default_device():
"""The default device on this platform.
"""
return _to_str(cdiscid.discid_get_default_device())
FEATURES = _feature_list()
FEATURE_READ = cdiscid.DISCID_FEATURE_READ
FEATURE_MCN = cdiscid.DISCID_FEATURE_MCN
FEATURE_ISRC = cdiscid.DISCID_FEATURE_ISRC
__discid_version__ = _to_str(cdiscid.wrap_get_version_string())
|