tinytag 2.0.0__tar.gz → 2.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of tinytag might be problematic. Click here for more details.
- {tinytag-2.0.0 → tinytag-2.1.0}/LICENSE +1 -1
- {tinytag-2.0.0 → tinytag-2.1.0}/PKG-INFO +11 -5
- {tinytag-2.0.0 → tinytag-2.1.0}/README.md +6 -1
- {tinytag-2.0.0 → tinytag-2.1.0}/pyproject.toml +3 -2
- {tinytag-2.0.0 → tinytag-2.1.0}/tinytag/tinytag.py +44 -15
- {tinytag-2.0.0 → tinytag-2.1.0}/tinytag/__init__.py +0 -0
- {tinytag-2.0.0 → tinytag-2.1.0}/tinytag/__main__.py +0 -0
- {tinytag-2.0.0 → tinytag-2.1.0}/tinytag/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2014-
|
|
3
|
+
Copyright (c) 2014-2025 Tom Wallroth, Mat (mathiascode), et al.
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: tinytag
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.1.0
|
|
4
4
|
Summary: Read audio file metadata
|
|
5
5
|
Keywords: metadata,audio,music,mp3,m4a,wav,ogg,opus,flac,wma,aiff
|
|
6
|
-
Author: Mat (mathiascode)
|
|
7
|
-
Author-email: Tom Wallroth <tomwallroth@gmail.com>
|
|
6
|
+
Author: Tom Wallroth, Mat (mathiascode)
|
|
8
7
|
Requires-Python: >=3.7
|
|
9
8
|
Description-Content-Type: text/markdown
|
|
10
9
|
Classifier: Programming Language :: Python
|
|
@@ -15,6 +14,7 @@ Classifier: Programming Language :: Python :: 3.9
|
|
|
15
14
|
Classifier: Programming Language :: Python :: 3.10
|
|
16
15
|
Classifier: Programming Language :: Python :: 3.11
|
|
17
16
|
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
18
|
Classifier: License :: OSI Approved :: MIT License
|
|
19
19
|
Classifier: Development Status :: 5 - Production/Stable
|
|
20
20
|
Classifier: Environment :: Web Environment
|
|
@@ -25,6 +25,7 @@ Classifier: Topic :: Multimedia
|
|
|
25
25
|
Classifier: Topic :: Multimedia :: Sound/Audio
|
|
26
26
|
Classifier: Topic :: Multimedia :: Sound/Audio :: Analysis
|
|
27
27
|
Classifier: Typing :: Typed
|
|
28
|
+
License-File: LICENSE
|
|
28
29
|
Requires-Dist: coverage ; extra == "tests"
|
|
29
30
|
Requires-Dist: mypy ; extra == "tests"
|
|
30
31
|
Requires-Dist: pycodestyle ; extra == "tests"
|
|
@@ -362,7 +363,7 @@ Output:
|
|
|
362
363
|
|
|
363
364
|
> [!WARNING]
|
|
364
365
|
> `tag.images.any` has replaced `tag.get_image()` in tinytag 2.0.0.
|
|
365
|
-
> `tag.get_image()` will be removed in
|
|
366
|
+
> `tag.get_image()` will be removed in the future.
|
|
366
367
|
|
|
367
368
|
To receive a common image, e.g. `front_cover`:
|
|
368
369
|
|
|
@@ -421,6 +422,11 @@ TinyTag.get(file_obj=your_file_obj)
|
|
|
421
422
|
|
|
422
423
|
## Changelog
|
|
423
424
|
|
|
425
|
+
### 2.1.0 (2025-02-23)
|
|
426
|
+
|
|
427
|
+
- Opus: Calculate audio bitrate
|
|
428
|
+
- Opus: Take pre-skip into account when calculating the duration
|
|
429
|
+
|
|
424
430
|
### 2.0.0 (2024-11-03)
|
|
425
431
|
|
|
426
432
|
- **BREAKING:** Store 'disc', 'disc_total', 'track' and 'track_total' values as int instead of str
|
|
@@ -327,7 +327,7 @@ Output:
|
|
|
327
327
|
|
|
328
328
|
> [!WARNING]
|
|
329
329
|
> `tag.images.any` has replaced `tag.get_image()` in tinytag 2.0.0.
|
|
330
|
-
> `tag.get_image()` will be removed in
|
|
330
|
+
> `tag.get_image()` will be removed in the future.
|
|
331
331
|
|
|
332
332
|
To receive a common image, e.g. `front_cover`:
|
|
333
333
|
|
|
@@ -386,6 +386,11 @@ TinyTag.get(file_obj=your_file_obj)
|
|
|
386
386
|
|
|
387
387
|
## Changelog
|
|
388
388
|
|
|
389
|
+
### 2.1.0 (2025-02-23)
|
|
390
|
+
|
|
391
|
+
- Opus: Calculate audio bitrate
|
|
392
|
+
- Opus: Take pre-skip into account when calculating the duration
|
|
393
|
+
|
|
389
394
|
### 2.0.0 (2024-11-03)
|
|
390
395
|
|
|
391
396
|
- **BREAKING:** Store 'disc', 'disc_total', 'track' and 'track_total' values as int instead of str
|
|
@@ -7,10 +7,10 @@ build-backend = "flit_core.buildapi"
|
|
|
7
7
|
|
|
8
8
|
[project]
|
|
9
9
|
name = "tinytag"
|
|
10
|
-
version = "2.
|
|
10
|
+
version = "2.1.0"
|
|
11
11
|
description = "Read audio file metadata"
|
|
12
12
|
authors = [
|
|
13
|
-
{name = "Tom Wallroth"
|
|
13
|
+
{name = "Tom Wallroth"},
|
|
14
14
|
{name = "Mat (mathiascode)"}
|
|
15
15
|
]
|
|
16
16
|
keywords = [
|
|
@@ -35,6 +35,7 @@ classifiers = [
|
|
|
35
35
|
"Programming Language :: Python :: 3.10",
|
|
36
36
|
"Programming Language :: Python :: 3.11",
|
|
37
37
|
"Programming Language :: Python :: 3.12",
|
|
38
|
+
"Programming Language :: Python :: 3.13",
|
|
38
39
|
"License :: OSI Approved :: MIT License",
|
|
39
40
|
"Development Status :: 5 - Production/Stable",
|
|
40
41
|
"Environment :: Web Environment",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# SPDX-FileCopyrightText: 2014-
|
|
1
|
+
# SPDX-FileCopyrightText: 2014-2025 tinytag Contributors
|
|
2
2
|
# SPDX-License-Identifier: MIT
|
|
3
3
|
|
|
4
4
|
# tinytag - an audio file metadata reader
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
# MIT License
|
|
8
8
|
|
|
9
|
-
# Copyright (c) 2014-
|
|
9
|
+
# Copyright (c) 2014-2025 Tom Wallroth, Mat (mathiascode), et al.
|
|
10
10
|
|
|
11
11
|
# Permission is hereby granted, free of charge, to any person obtaining a
|
|
12
12
|
# copy of this software and associated documentation files (the "Software"),
|
|
@@ -36,7 +36,7 @@ from struct import unpack
|
|
|
36
36
|
|
|
37
37
|
# Lazy imports for type checking
|
|
38
38
|
if False: # pylint: disable=using-constant-test
|
|
39
|
-
from collections.abc import Callable, Iterator
|
|
39
|
+
from collections.abc import Callable, Iterator # pylint: disable-all
|
|
40
40
|
from typing import Any, BinaryIO, Dict, List
|
|
41
41
|
|
|
42
42
|
_StringListDict = Dict[str, List[str]]
|
|
@@ -1314,14 +1314,21 @@ class _Ogg(TinyTag):
|
|
|
1314
1314
|
|
|
1315
1315
|
def __init__(self) -> None:
|
|
1316
1316
|
super().__init__()
|
|
1317
|
-
self.
|
|
1317
|
+
self._granule_pos = 0
|
|
1318
|
+
self._pre_skip = 0 # number of samples to skip in opus stream
|
|
1319
|
+
self._audio_size: int | None = None # size of opus audio stream
|
|
1318
1320
|
|
|
1319
1321
|
def _determine_duration(self, fh: BinaryIO) -> None:
|
|
1320
1322
|
if not self._tags_parsed:
|
|
1321
1323
|
self._parse_tag(fh) # determine sample rate
|
|
1322
1324
|
if self.duration is not None or not self.samplerate:
|
|
1323
1325
|
return # either ogg flac or invalid file
|
|
1324
|
-
self.duration =
|
|
1326
|
+
self.duration = max(
|
|
1327
|
+
(self._granule_pos - self._pre_skip) / self.samplerate, 0
|
|
1328
|
+
)
|
|
1329
|
+
if self._audio_size is None or not self.duration:
|
|
1330
|
+
return # not an opus file
|
|
1331
|
+
self.bitrate = self._audio_size * 8 / self.duration / 1000
|
|
1325
1332
|
|
|
1326
1333
|
def _parse_tag(self, fh: BinaryIO) -> None:
|
|
1327
1334
|
check_flac_second_packet = False
|
|
@@ -1341,15 +1348,17 @@ class _Ogg(TinyTag):
|
|
|
1341
1348
|
if self._parse_duration: # parse opus header
|
|
1342
1349
|
# https://www.videolan.org/developers/vlc/modules/codec/opus_header.c
|
|
1343
1350
|
# https://mf4.xiph.org/jenkins/view/opus/job/opusfile-unix/ws/doc/html/structOpusHead.html
|
|
1344
|
-
version, ch = unpack("
|
|
1351
|
+
version, ch, pre_skip = unpack("<BBH", packet[8:12])
|
|
1345
1352
|
if (version & 0xF0) == 0: # only major version 0 supported
|
|
1346
1353
|
self.channels = ch
|
|
1347
|
-
self.samplerate = 48000
|
|
1354
|
+
self.samplerate = 48000
|
|
1355
|
+
self._pre_skip = pre_skip
|
|
1348
1356
|
elif packet.startswith(b'OpusTags'):
|
|
1349
1357
|
if self._parse_tags: # parse opus metadata:
|
|
1350
1358
|
walker = BytesIO(packet)
|
|
1351
1359
|
walker.seek(8) # jump over header name
|
|
1352
1360
|
self._parse_vorbis_comment(walker)
|
|
1361
|
+
self._audio_size = 0 # start counting size of audio stream
|
|
1353
1362
|
elif packet.startswith(b'\x7fFLAC'):
|
|
1354
1363
|
# https://xiph.org/flac/ogg_mapping.html
|
|
1355
1364
|
walker = BytesIO(packet)
|
|
@@ -1392,7 +1401,7 @@ class _Ogg(TinyTag):
|
|
|
1392
1401
|
check_speex_second_packet = False
|
|
1393
1402
|
else:
|
|
1394
1403
|
# Optimization: If we need to determine the duration, read
|
|
1395
|
-
#
|
|
1404
|
+
# granule_pos of remaining pages, but skip contents of
|
|
1396
1405
|
# segments. If we don't need the duration, stop here.
|
|
1397
1406
|
self._tags_parsed = True
|
|
1398
1407
|
if not self._parse_duration:
|
|
@@ -1444,6 +1453,9 @@ class _Ogg(TinyTag):
|
|
|
1444
1453
|
def _parse_pages(self, fh: BinaryIO) -> Iterator[bytes]:
|
|
1445
1454
|
# for the spec, see: https://wiki.xiph.org/Ogg
|
|
1446
1455
|
packet_data = bytearray()
|
|
1456
|
+
current_serial = None
|
|
1457
|
+
last_granule_pos = 0
|
|
1458
|
+
last_audio_size = 0
|
|
1447
1459
|
header_len = 27
|
|
1448
1460
|
page_header = fh.read(header_len) # read ogg page header
|
|
1449
1461
|
while len(page_header) == header_len:
|
|
@@ -1451,26 +1463,43 @@ class _Ogg(TinyTag):
|
|
|
1451
1463
|
if page_header[:4] != b'OggS' or version != 0:
|
|
1452
1464
|
raise ParseError('Invalid OGG header')
|
|
1453
1465
|
# https://xiph.org/ogg/doc/framing.html
|
|
1454
|
-
|
|
1466
|
+
header_type = page_header[5]
|
|
1467
|
+
eos = header_type & 0x04
|
|
1468
|
+
granule_pos, serial = unpack('<qI', page_header[6:18])
|
|
1469
|
+
if current_serial is None:
|
|
1470
|
+
current_serial = serial
|
|
1471
|
+
serial_match = serial == current_serial
|
|
1472
|
+
if serial_match and granule_pos > 0:
|
|
1473
|
+
if eos:
|
|
1474
|
+
self._granule_pos = granule_pos
|
|
1475
|
+
else:
|
|
1476
|
+
self._granule_pos = last_granule_pos
|
|
1477
|
+
last_granule_pos = granule_pos
|
|
1455
1478
|
segments = page_header[26]
|
|
1456
|
-
# pylint: disable=consider-using-max-builtin
|
|
1457
|
-
if pos > self._max_samplenum:
|
|
1458
|
-
self._max_samplenum = pos
|
|
1459
1479
|
seg_sizes = unpack('B' * segments, fh.read(segments))
|
|
1460
1480
|
read_size = 0
|
|
1481
|
+
audio_size = 0
|
|
1461
1482
|
for seg_size in seg_sizes: # read all segments
|
|
1462
1483
|
read_size += seg_size
|
|
1484
|
+
if self._audio_size is not None:
|
|
1485
|
+
audio_size += seg_size
|
|
1463
1486
|
# less than 255 bytes means end of packet
|
|
1464
|
-
if seg_size < 255 and not self._tags_parsed:
|
|
1487
|
+
if seg_size < 255 and serial_match and not self._tags_parsed:
|
|
1465
1488
|
packet_data += fh.read(read_size)
|
|
1466
1489
|
yield packet_data
|
|
1467
1490
|
packet_data.clear()
|
|
1468
1491
|
read_size = 0
|
|
1469
1492
|
if read_size:
|
|
1470
|
-
if self._tags_parsed:
|
|
1493
|
+
if not serial_match or self._tags_parsed:
|
|
1471
1494
|
fh.seek(read_size, SEEK_CUR)
|
|
1472
1495
|
else: # packet continues on next page
|
|
1473
1496
|
packet_data += fh.read(read_size)
|
|
1497
|
+
if serial_match and self._audio_size is not None:
|
|
1498
|
+
if eos:
|
|
1499
|
+
self._audio_size += last_audio_size + audio_size
|
|
1500
|
+
else:
|
|
1501
|
+
self._audio_size += last_audio_size
|
|
1502
|
+
last_audio_size = audio_size
|
|
1474
1503
|
page_header = fh.read(header_len)
|
|
1475
1504
|
|
|
1476
1505
|
|
|
@@ -1635,7 +1664,7 @@ class _Flac(TinyTag):
|
|
|
1635
1664
|
self.duration = duration = tot_samples / sr
|
|
1636
1665
|
self.samplerate = sr
|
|
1637
1666
|
if duration > 0:
|
|
1638
|
-
self.bitrate = self.filesize
|
|
1667
|
+
self.bitrate = self.filesize * 8 / duration / 1000
|
|
1639
1668
|
elif block_type == self._VORBIS_COMMENT and self._parse_tags:
|
|
1640
1669
|
# pylint: disable=protected-access
|
|
1641
1670
|
walker = BytesIO(fh.read(size))
|
|
File without changes
|
|
File without changes
|
|
File without changes
|