Secure your code as it's written. Use Snyk Code to scan source code in minutes - no build needed - and fix issues immediately.
self.bitrate = self.filesize / self.duration * 8 / 1024
elif block_type == Flac.METADATA_VORBIS_COMMENT and not skip_tags:
oggtag = Ogg(fh, 0)
oggtag._parse_vorbis_comment(fh)
self.update(oggtag)
elif block_type >= 127:
return # invalid block type
else:
fh.seek(size, 1) # seek over this block
if is_last_block:
return
header_data = fh.read(4)
class Wma(TinyTag):
ASF_CONTENT_DESCRIPTION_OBJECT = b'3&\xb2u\x8ef\xcf\x11\xa6\xd9\x00\xaa\x00b\xcel'
ASF_EXTENDED_CONTENT_DESCRIPTION_OBJECT = b'@\xa4\xd0\xd2\x07\xe3\xd2\x11\x97\xf0\x00\xa0\xc9^\xa8P'
STREAM_BITRATE_PROPERTIES_OBJECT = b'\xceu\xf8{\x8dF\xd1\x11\x8d\x82\x00`\x97\xc9\xa2\xb2'
ASF_FILE_PROPERTY_OBJECT = b'\xa1\xdc\xab\x8cG\xa9\xcf\x11\x8e\xe4\x00\xc0\x0c Se'
ASF_STREAM_PROPERTIES_OBJECT = b'\x91\x07\xdc\xb7\xb7\xa9\xcf\x11\x8e\xe6\x00\xc0\x0c Se'
STREAM_TYPE_ASF_AUDIO_MEDIA = b'@\x9ei\xf8M[\xcf\x11\xa8\xfd\x00\x80_\\D+'
# see:
# http://web.archive.org/web/20131203084402/http://msdn.microsoft.com/en-us/library/bb643323.aspx
# and (japanese, but none the less helpful)
# http://uguisu.skr.jp/Windows/format_asf.html
def __init__(self, filehandler, filesize):
TinyTag.__init__(self, filehandler, filesize)
self.__tag_parsed = False
def _determine_duration(self, fh):
for segsize in segsizes: # read all segments
total += segsize
if total < 255: # less than 255 bytes means end of page
yield previous_page + fh.read(total)
previous_page = b''
total = 0
if total != 0:
if total % 255 == 0:
previous_page += fh.read(total)
else:
yield previous_page + fh.read(total)
previous_page = b''
header_data = fh.read(27)
class Wave(TinyTag):
# https://sno.phy.queensu.ca/~phil/exiftool/TagNames/RIFF.html
riff_mapping = {
b'INAM': 'title',
b'TITL': 'title',
b'IART': 'artist',
b'ICMT': 'comment',
b'ICRD': 'year',
b'IGNR': 'genre',
b'TRCK': 'track',
b'PRT1': 'track',
b'PRT2': 'track_number',
b'YEAR': 'year',
# riff format is lacking the composer field.
}
def __init__(self, filehandler, filesize):
def get(cls, filename, tags=True, duration=True, image=False):
filename = os.path.expanduser(str(filename)) # cast pathlib.Path to str
size = os.path.getsize(filename)
if not size > 0:
return TinyTag(None, 0)
if cls == TinyTag: # if `get` is invoked on TinyTag, find parser by ext
parser_class = cls._get_parser_for_filename(filename, exception=True)
else: # otherwise use the class on which `get` was invoked
parser_class = cls
with io.open(filename, 'rb') as af:
tag = parser_class(af, size)
tag.load(tags=tags, duration=duration, image=image)
return tag
def _determine_duration(self, fh):
raise NotImplementedError()
def _parse_tag(self, fh):
raise NotImplementedError()
def update(self, other):
"""update the values of this tag with the values from another tag"""
for key in ['track', 'track_total', 'title', 'artist',
'album', 'year', 'duration']:
if not getattr(self, key) and getattr(other, key):
setattr(self, key, getattr(other, key))
class ID3(TinyTag):
FID_TO_FIELD = { # Mapping from Frame ID to a field of the TinyTag
'TRCK': 'track', 'TRK': 'track',
'TYER': 'year', 'TYE': 'year',
'TALB': 'album', 'TAL': 'album',
'TPE1': 'artist', 'TP1': 'artist',
'TIT2': 'title', 'TT2': 'title',
}
_MAX_ESTIMATION_SEC = 30
def __init__(self, filehandler, filesize):
TinyTag.__init__(self, filehandler, filesize)
# save position after the ID3 tag for duration mesurement speedup
self._bytepos_after_id3v2 = 0
@classmethod
def set_estimation_precision(cls, estimation_in_seconds):
elif callable(sub_path):
for fieldname, value in sub_path(fh.read(atom_size)).items():
if DEBUG:
stderr(' ' * 4 * len(curr_path), 'FIELD: ', fieldname)
if fieldname:
self._set_field(fieldname, value)
# if no action was specified using dict or callable, jump over atom
else:
fh.seek(atom_size, os.SEEK_CUR)
# check if we have reached the end of this branch:
if stop_pos and fh.tell() >= stop_pos:
return # return to parent (next parent node in tree)
atom_header = fh.read(header_size) # read next atom
class ID3(TinyTag):
FRAME_ID_TO_FIELD = { # Mapping from Frame ID to a field of the TinyTag
'COMM': 'comment', 'COM': 'comment',
'TRCK': 'track', 'TRK': 'track',
'TYER': 'year', 'TYE': 'year',
'TALB': 'album', 'TAL': 'album',
'TPE1': 'artist', 'TP1': 'artist',
'TIT2': 'title', 'TT2': 'title',
'TCON': 'genre', 'TPOS': 'disc',
'TPE2': 'albumartist', 'TCOM': 'composer',
}
IMAGE_FRAME_IDS = {'APIC', 'PIC'}
PARSABLE_FRAME_IDS = set(FRAME_ID_TO_FIELD.keys()).union(IMAGE_FRAME_IDS)
_MAX_ESTIMATION_SEC = 30
_CBR_DETECTION_FRAME_COUNT = 5
_USE_XING_HEADER = True # much faster, but can be deactivated for testing
ret <<= 7 # bit is always set to zero, so it has to be
ret += b & 127 # removed.
return ret #
class StringWalker(object):
"""file obj like string. probably there are buildins doing this already"""
def __init__(self, string):
self.string = string
def read(self, nbytes):
retstring, self.string = self.string[:nbytes], self.string[nbytes:]
return retstring
class Ogg(TinyTag):
def __init__(self, filehandler, filesize):
TinyTag.__init__(self, filehandler, filesize)
self._tags_parsed = False
self._max_samplenum = 0 # maximum sample position ever read
def _determine_duration(self, fh):
MAX_PAGE_SIZE = 65536 # https://xiph.org/ogg/doc/libogg/ogg_page.html
if not self._tags_parsed:
self._parse_tag(fh) # determine sample rate
fh.seek(0) # and rewind to start
if self.filesize > MAX_PAGE_SIZE:
fh.seek(-MAX_PAGE_SIZE, 2) # go to last possible page position
while True:
b = fh.read(1)
if len(b) == 0:
return # EOF
field = sub_fh.read(4)
elif subchunkid == b'id3 ' or subchunkid == b'ID3 ':
id3 = ID3(fh, 0)
id3._parse_id3v2(fh)
self.update(id3)
else: # some other chunk, just skip the data
fh.seek(subchunksize, 1)
chunk_header = fh.read(8)
self._duration_parsed = True
def _parse_tag(self, fh):
if not self._duration_parsed:
self._determine_duration(fh) # parse whole file to determine tags:(
class Flac(TinyTag):
METADATA_STREAMINFO = 0
METADATA_VORBIS_COMMENT = 4
def load(self, tags, duration, image=False):
header = self._filehandler.peek(4)
if header[:3] == b'ID3': # parse ID3 header if it exists
id3 = ID3(self._filehandler, 0)
id3._parse_id3v2(self._filehandler)
self.update(id3)
header = self._filehandler.peek(4) # after ID3 should be fLaC
if header[:4] != b'fLaC':
raise TinyTagException('Invalid flac header')
self._filehandler.seek(4, os.SEEK_CUR)
self._determine_duration(self._filehandler, skip_tags=not tags)
def _determine_duration(self, fh, skip_tags=False):
def update(self, other):
# update the values of this tag with the values from another tag
for key in ['track', 'track_total', 'title', 'artist',
'album', 'albumartist', 'year', 'duration',
'genre', 'disc', 'disc_total', 'comment', 'composer']:
if not getattr(self, key) and getattr(other, key):
setattr(self, key, getattr(other, key))
@staticmethod
def _unpad(s):
# strings in mp3 and asf *may* be terminated with a zero byte at the end
return s[:s.index('\x00')] if '\x00' in s else s
class MP4(TinyTag):
# see: https://developer.apple.com/library/mac/documentation/QuickTime/QTFF/Metadata/Metadata.html
# and: https://developer.apple.com/library/mac/documentation/QuickTime/QTFF/QTFFChap2/qtff2.html
class Parser:
# https://developer.apple.com/library/mac/documentation/QuickTime/QTFF/Metadata/Metadata.html#//apple_ref/doc/uid/TP40000939-CH1-SW34
ATOM_DECODER_BY_TYPE = {
0: lambda x: x, # 'reserved',
1: lambda x: codecs.decode(x, 'utf-8', 'replace'), # UTF-8
2: lambda x: codecs.decode(x, 'utf-16', 'replace'), # UTF-16
3: lambda x: codecs.decode(x, 's/jis', 'replace'), # S/JIS
# 16: duration in millis
13: lambda x: x, # JPEG
14: lambda x: x, # PNG
21: lambda x: struct.unpack('>b', x)[0], # BE Signed int
22: lambda x: struct.unpack('>B', x)[0], # BE Unsigned int
23: lambda x: struct.unpack('>f', x)[0], # BE Float32