tinytag 2.3.0__tar.gz → 2.3.1__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.

@@ -0,0 +1,19 @@
1
+ # SPDX-FileCopyrightText: 2025 tinytag Contributors
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ root = true
5
+
6
+ [*]
7
+ charset = utf-8
8
+ end_of_line = lf
9
+ indent_size = 4
10
+ indent_style = space
11
+ insert_final_newline = true
12
+ max_line_length = 79
13
+ trim_trailing_whitespace = true
14
+
15
+ [*.md]
16
+ trim_trailing_whitespace = false
17
+
18
+ [*.yml]
19
+ indent_size = 2
@@ -0,0 +1,24 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 tinytag Contributors
3
+ SPDX-License-Identifier: MIT
4
+ -->
5
+
6
+ # Contributing
7
+
8
+ Before submitting a PR, please discuss the change you wish to make in an issue
9
+ report or discussion thread, in order to see if it fits the direction of the
10
+ project. If you skip this step, a PR containing anything other than a trivial
11
+ bug fix will likely be rejected.
12
+
13
+ Contributions generated or co-authored by LLMs or AI coding tools are not
14
+ allowed. Your contribution must be licensed as original work that is personally
15
+ authored by you under the terms of the
16
+ [MIT License](https://github.com/tinytag/tinytag/blob/HEAD/LICENSE).
17
+ Every part of your contribution must be written, thoroughly tested and fully
18
+ understood by you so that any queries about the logic of your contribution can
19
+ be answered during the review process.
20
+
21
+ You may only use AI assistants or tooling for learning purposes, including
22
+ guided research of the codebase and exploring concepts. Your contribution must
23
+ not include any content produced by generative AI (code, text snippets, images
24
+ nor other media).
@@ -0,0 +1,121 @@
1
+ Creative Commons Legal Code
2
+
3
+ CC0 1.0 Universal
4
+
5
+ CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
6
+ LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
7
+ ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
8
+ INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
9
+ REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
10
+ PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
11
+ THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
12
+ HEREUNDER.
13
+
14
+ Statement of Purpose
15
+
16
+ The laws of most jurisdictions throughout the world automatically confer
17
+ exclusive Copyright and Related Rights (defined below) upon the creator
18
+ and subsequent owner(s) (each and all, an "owner") of an original work of
19
+ authorship and/or a database (each, a "Work").
20
+
21
+ Certain owners wish to permanently relinquish those rights to a Work for
22
+ the purpose of contributing to a commons of creative, cultural and
23
+ scientific works ("Commons") that the public can reliably and without fear
24
+ of later claims of infringement build upon, modify, incorporate in other
25
+ works, reuse and redistribute as freely as possible in any form whatsoever
26
+ and for any purposes, including without limitation commercial purposes.
27
+ These owners may contribute to the Commons to promote the ideal of a free
28
+ culture and the further production of creative, cultural and scientific
29
+ works, or to gain reputation or greater distribution for their Work in
30
+ part through the use and efforts of others.
31
+
32
+ For these and/or other purposes and motivations, and without any
33
+ expectation of additional consideration or compensation, the person
34
+ associating CC0 with a Work (the "Affirmer"), to the extent that he or she
35
+ is an owner of Copyright and Related Rights in the Work, voluntarily
36
+ elects to apply CC0 to the Work and publicly distribute the Work under its
37
+ terms, with knowledge of his or her Copyright and Related Rights in the
38
+ Work and the meaning and intended legal effect of CC0 on those rights.
39
+
40
+ 1. Copyright and Related Rights. A Work made available under CC0 may be
41
+ protected by copyright and related or neighboring rights ("Copyright and
42
+ Related Rights"). Copyright and Related Rights include, but are not
43
+ limited to, the following:
44
+
45
+ i. the right to reproduce, adapt, distribute, perform, display,
46
+ communicate, and translate a Work;
47
+ ii. moral rights retained by the original author(s) and/or performer(s);
48
+ iii. publicity and privacy rights pertaining to a person's image or
49
+ likeness depicted in a Work;
50
+ iv. rights protecting against unfair competition in regards to a Work,
51
+ subject to the limitations in paragraph 4(a), below;
52
+ v. rights protecting the extraction, dissemination, use and reuse of data
53
+ in a Work;
54
+ vi. database rights (such as those arising under Directive 96/9/EC of the
55
+ European Parliament and of the Council of 11 March 1996 on the legal
56
+ protection of databases, and under any national implementation
57
+ thereof, including any amended or successor version of such
58
+ directive); and
59
+ vii. other similar, equivalent or corresponding rights throughout the
60
+ world based on applicable law or treaty, and any national
61
+ implementations thereof.
62
+
63
+ 2. Waiver. To the greatest extent permitted by, but not in contravention
64
+ of, applicable law, Affirmer hereby overtly, fully, permanently,
65
+ irrevocably and unconditionally waives, abandons, and surrenders all of
66
+ Affirmer's Copyright and Related Rights and associated claims and causes
67
+ of action, whether now known or unknown (including existing as well as
68
+ future claims and causes of action), in the Work (i) in all territories
69
+ worldwide, (ii) for the maximum duration provided by applicable law or
70
+ treaty (including future time extensions), (iii) in any current or future
71
+ medium and for any number of copies, and (iv) for any purpose whatsoever,
72
+ including without limitation commercial, advertising or promotional
73
+ purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
74
+ member of the public at large and to the detriment of Affirmer's heirs and
75
+ successors, fully intending that such Waiver shall not be subject to
76
+ revocation, rescission, cancellation, termination, or any other legal or
77
+ equitable action to disrupt the quiet enjoyment of the Work by the public
78
+ as contemplated by Affirmer's express Statement of Purpose.
79
+
80
+ 3. Public License Fallback. Should any part of the Waiver for any reason
81
+ be judged legally invalid or ineffective under applicable law, then the
82
+ Waiver shall be preserved to the maximum extent permitted taking into
83
+ account Affirmer's express Statement of Purpose. In addition, to the
84
+ extent the Waiver is so judged Affirmer hereby grants to each affected
85
+ person a royalty-free, non transferable, non sublicensable, non exclusive,
86
+ irrevocable and unconditional license to exercise Affirmer's Copyright and
87
+ Related Rights in the Work (i) in all territories worldwide, (ii) for the
88
+ maximum duration provided by applicable law or treaty (including future
89
+ time extensions), (iii) in any current or future medium and for any number
90
+ of copies, and (iv) for any purpose whatsoever, including without
91
+ limitation commercial, advertising or promotional purposes (the
92
+ "License"). The License shall be deemed effective as of the date CC0 was
93
+ applied by Affirmer to the Work. Should any part of the License for any
94
+ reason be judged legally invalid or ineffective under applicable law, such
95
+ partial invalidity or ineffectiveness shall not invalidate the remainder
96
+ of the License, and in such case Affirmer hereby affirms that he or she
97
+ will not (i) exercise any of his or her remaining Copyright and Related
98
+ Rights in the Work or (ii) assert any associated claims and causes of
99
+ action with respect to the Work, in either case contrary to Affirmer's
100
+ express Statement of Purpose.
101
+
102
+ 4. Limitations and Disclaimers.
103
+
104
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
105
+ surrendered, licensed or otherwise affected by this document.
106
+ b. Affirmer offers the Work as-is and makes no representations or
107
+ warranties of any kind concerning the Work, express, implied,
108
+ statutory or otherwise, including without limitation warranties of
109
+ title, merchantability, fitness for a particular purpose, non
110
+ infringement, or the absence of latent or other defects, accuracy, or
111
+ the present or absence of errors, whether or not discoverable, all to
112
+ the greatest extent permissible under applicable law.
113
+ c. Affirmer disclaims responsibility for clearing rights of other persons
114
+ that may apply to the Work or any use thereof, including without
115
+ limitation any person's Copyright and Related Rights in the Work.
116
+ Further, Affirmer disclaims responsibility for obtaining any necessary
117
+ consents, permissions or other rights required for any use of the
118
+ Work.
119
+ d. Affirmer understands and acknowledges that Creative Commons is not a
120
+ party to this document and has no duty or obligation with respect to
121
+ this CC0 or use of the Work.
@@ -0,0 +1 @@
1
+ ../LICENSE
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tinytag
3
- Version: 2.3.0
3
+ Version: 2.3.1
4
4
  Summary: Read audio file metadata
5
5
  Keywords: metadata,audio,music,mp3,m4a,wav,ogg,opus,flac,wma,aiff
6
6
  Author: Tom Wallroth, Mat (mathiascode)
@@ -28,14 +28,17 @@ Classifier: Topic :: Multimedia :: Sound/Audio
28
28
  Classifier: Topic :: Multimedia :: Sound/Audio :: Analysis
29
29
  Classifier: Typing :: Typed
30
30
  License-File: LICENSE
31
- Requires-Dist: coverage ; extra == "tests"
32
- Requires-Dist: mypy ; extra == "tests"
33
- Requires-Dist: mypy<1.19.0 ; extra == "tests" and ( platform_python_implementation == 'PyPy')
34
- Requires-Dist: pycodestyle ; extra == "tests"
35
- Requires-Dist: pylint ; extra == "tests"
36
- Requires-Dist: pyright ; extra == "tests"
31
+ Requires-Dist: build ; extra == "dev"
32
+ Requires-Dist: coverage ; extra == "dev"
33
+ Requires-Dist: flit_core ; extra == "dev"
34
+ Requires-Dist: mypy ; extra == "dev"
35
+ Requires-Dist: mypy<1.19.0 ; extra == "dev" and ( platform_python_implementation == 'PyPy')
36
+ Requires-Dist: pycodestyle ; extra == "dev"
37
+ Requires-Dist: pylint ; extra == "dev"
38
+ Requires-Dist: pyright ; extra == "dev"
39
+ Requires-Dist: wheel ; extra == "dev"
37
40
  Project-URL: Homepage, https://github.com/tinytag/tinytag
38
- Provides-Extra: tests
41
+ Provides-Extra: dev
39
42
 
40
43
  <!--
41
44
  SPDX-FileCopyrightText: 2014-2026 tinytag Contributors
@@ -527,6 +530,12 @@ UnsupportedFormatError # File format is not supported
527
530
 
528
531
  ## Changelog
529
532
 
533
+ ### 2.3.1 (2026-08-30)
534
+
535
+ - Don't open unrecognized files when disabling magic byte detection
536
+ - MPEG: Fix parsing of some files with incorrect headers
537
+ - OGG/WAV: Read sizes as little endian instead of native byte order
538
+
530
539
  ### 2.3.0 (2026-07-31)
531
540
 
532
541
  - Add 'mime_type' attribute for audio MIME type/codec
@@ -488,6 +488,12 @@ UnsupportedFormatError # File format is not supported
488
488
 
489
489
  ## Changelog
490
490
 
491
+ ### 2.3.1 (2026-08-30)
492
+
493
+ - Don't open unrecognized files when disabling magic byte detection
494
+ - MPEG: Fix parsing of some files with incorrect headers
495
+ - OGG/WAV: Read sizes as little endian instead of native byte order
496
+
491
497
  ### 2.3.0 (2026-07-31)
492
498
 
493
499
  - Add 'mime_type' attribute for audio MIME type/codec
@@ -0,0 +1,17 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 tinytag Contributors
3
+ SPDX-License-Identifier: MIT
4
+ -->
5
+
6
+ # Security Policy
7
+
8
+ ## Supported Versions
9
+
10
+ tinytag follows a `A.B.C` versioning scheme. We only support the latest
11
+ released `A.B.x` series.
12
+
13
+ ## Reporting a Vulnerability
14
+
15
+ Please report a vulnerability under the Security tab in the GitHub repository.
16
+ Since tinytag is a volunteer-driven project, your report will be reviewed and
17
+ addressed when the maintainers have time.
@@ -57,20 +57,28 @@ dynamic = ["version"]
57
57
  Homepage = "https://github.com/tinytag/tinytag"
58
58
 
59
59
  [project.optional-dependencies]
60
- tests = [
60
+ dev = [
61
+ "build",
61
62
  "coverage",
63
+ "flit_core",
62
64
  "mypy",
63
65
  "mypy<1.19.0; platform_python_implementation == 'PyPy'",
64
66
  "pycodestyle",
65
67
  "pylint",
66
- "pyright"
68
+ "pyright",
69
+ "wheel"
67
70
  ]
68
71
 
69
72
  [tool.flit.sdist]
73
+ include = [
74
+ "LICENSES/",
75
+ ".editorconfig",
76
+ "CONTRIBUTING.md",
77
+ "SECURITY.md"
78
+ ]
70
79
  exclude = [
71
80
  ".gitignore",
72
81
  ".github/",
73
- "tinytag/icons/",
74
82
  "tinytag/tests/"
75
83
  ]
76
84
 
@@ -3,7 +3,7 @@
3
3
 
4
4
  """Audio file metadata reader."""
5
5
 
6
- __version__ = '2.3.0'
6
+ __version__ = '2.3.1'
7
7
 
8
8
  from .tinytag import (
9
9
  TinyTag, Image, Images, OtherFields, OtherImages,
@@ -134,12 +134,16 @@ class TinyTag:
134
134
  check_magic_bytes: bool = True) -> TinyTag:
135
135
  """Return a tag object for an audio file."""
136
136
  should_close_file = file_obj is None
137
+ parser_class = None
137
138
  filename_str = None
138
139
  if filename:
140
+ filename_str = fsdecode(filename)
141
+ if not check_magic_bytes:
142
+ # Raises UnsupportedFormatError
143
+ parser_class = cls._get_parser_class(filename_str)
139
144
  if should_close_file:
140
145
  # pylint: disable=consider-using-with
141
146
  file_obj = open(filename, 'rb')
142
- filename_str = fsdecode(filename)
143
147
  if file_obj is None:
144
148
  raise ValueError(
145
149
  'Either filename or file_obj argument is required')
@@ -149,14 +153,13 @@ class TinyTag:
149
153
  warn('ignore_errors argument is obsolete, and will be removed in '
150
154
  'the future', DeprecationWarning, stacklevel=2)
151
155
  try:
152
- # pylint: disable=protected-access
156
+ if parser_class is None: # check_magic_bytes is True
157
+ # Raises UnsupportedFormatError
158
+ parser_class = cls._get_parser_class(filename_str, file_obj)
153
159
  file_obj.seek(0, SEEK_END)
154
160
  filesize = file_obj.tell()
155
161
  file_obj.seek(0)
156
- if check_magic_bytes:
157
- parser_class = cls._get_parser_class(filename_str, file_obj)
158
- else:
159
- parser_class = cls._get_parser_class(filename_str)
162
+ # pylint: disable=protected-access
160
163
  tag = parser_class()
161
164
  tag._filehandler = file_obj
162
165
  tag._default_encoding = encoding
@@ -232,7 +235,6 @@ class TinyTag:
232
235
  ) -> type[TinyTag] | None:
233
236
  # https://en.wikipedia.org/wiki/List_of_file_signatures
234
237
  header = filehandle.read(30)
235
- filehandle.seek(0)
236
238
  if header.startswith(b'ID3'):
237
239
  return _ID3
238
240
  if (len(header) >= 2
@@ -244,8 +246,6 @@ class TinyTag:
244
246
  except OSError:
245
247
  # File smaller than ID3v1 tag size
246
248
  pass
247
- finally:
248
- filehandle.seek(0)
249
249
  if footer == b'TAG':
250
250
  return _ID3
251
251
  return _MPEG
@@ -420,7 +420,7 @@ class Images:
420
420
  for other_key, other_values in value.items():
421
421
  other_images = images.get(other_key)
422
422
  if not isinstance(other_images, list):
423
- other_images = images[other_key] = other_values
423
+ images[other_key] = other_values
424
424
  else:
425
425
  other_images += other_values
426
426
  return images
@@ -1471,7 +1471,6 @@ class _MPEG(TinyTag):
1471
1471
  _MAX_ESTIMATION_SEC = 30
1472
1472
  _CBR_DETECTION_FRAME_COUNT = 5
1473
1473
  _USE_XING_HEADER = True # much faster, but can be deactivated for testing
1474
- _MAX_INVALID_FRAMES = 200
1475
1474
 
1476
1475
  # see this page for the magic values used in mp3:
1477
1476
  # http://www.mpgedit.org/mpgedit/mpeg_format/mpeghdr.htm
@@ -1532,7 +1531,6 @@ class _MPEG(TinyTag):
1532
1531
  max_estimation_frames = 0
1533
1532
  frame_size_accu = 0
1534
1533
  frames = 0 # count frames for determining mp3 duration
1535
- invalid_frames = 0
1536
1534
  bitrate_accu = 0 # add up bitrates to find average bitrate to detect
1537
1535
  last_bitrates: set[int] = set() # CBR mp3s (many frames with same brs)
1538
1536
  # seek to first position after id3 tag (speedup for large header)
@@ -1568,9 +1566,6 @@ class _MPEG(TinyTag):
1568
1566
  fh.seek(idx - header_len, SEEK_CUR)
1569
1567
  if frames == 0:
1570
1568
  audio_offset += next_offset
1571
- invalid_frames += 1
1572
- if invalid_frames > self._MAX_INVALID_FRAMES:
1573
- raise ParseError("Invalid MPEG frame header")
1574
1569
  continue
1575
1570
  self.channels = self._CHANNELS_PER_CHANNEL_MODE[channel_mode]
1576
1571
  frame_br = self._BITRATE_VERSION_LAYERS[mpeg_id][layer_id][br_id]
@@ -1840,11 +1835,11 @@ class _Ogg(TinyTag):
1840
1835
  # for the spec, see: http://xiph.org/vorbis/doc/v-comment.html
1841
1836
  # discnumber tag based on: https://en.wikipedia.org/wiki/Vorbis_comment
1842
1837
  # https://sno.phy.queensu.ca/~phil/exiftool/TagNames/Vorbis.html
1843
- vendor_length = unpack('I', fh.read(4))[0]
1838
+ vendor_length = unpack('<I', fh.read(4))[0]
1844
1839
  fh.seek(vendor_length, SEEK_CUR) # jump over vendor
1845
- elements = unpack('I', fh.read(4))[0]
1840
+ elements = unpack('<I', fh.read(4))[0]
1846
1841
  for _i in range(elements):
1847
- length = unpack('I', fh.read(4))[0]
1842
+ length = unpack('<I', fh.read(4))[0]
1848
1843
  if not read_data:
1849
1844
  fh.seek(length, SEEK_CUR)
1850
1845
  continue
@@ -2000,7 +1995,7 @@ class _Wave(TinyTag):
2000
1995
  header_len = 8
2001
1996
  chunk_header = fh.read(header_len)
2002
1997
  while len(chunk_header) == header_len:
2003
- subchunk_size = unpack_from('I', chunk_header, 4)[0]
1998
+ subchunk_size = unpack_from('<I', chunk_header, 4)[0]
2004
1999
  subchunk_size_unpadded = subchunk_size
2005
2000
  # IFF chunks are padded to an even number of bytes
2006
2001
  subchunk_size += subchunk_size % 2
@@ -2025,7 +2020,7 @@ class _Wave(TinyTag):
2025
2020
  fh.seek(subchunk_size, SEEK_CUR)
2026
2021
  elif self._parse_duration and chunk_header.startswith(b'fact'):
2027
2022
  chunk = fh.read(subchunk_size)
2028
- num_samples = unpack_from('I', chunk)[0]
2023
+ num_samples = unpack_from('<I', chunk)[0]
2029
2024
  elif self._parse_tags and chunk_header.startswith(b'LIST'):
2030
2025
  chunk = fh.read(subchunk_size)
2031
2026
  if chunk.startswith(b'INFO'):
@@ -2033,7 +2028,7 @@ class _Wave(TinyTag):
2033
2028
  walker.seek(4) # skip header
2034
2029
  field = walker.read(4)
2035
2030
  while len(field) == 4:
2036
- data_length = unpack('I', walker.read(4))[0]
2031
+ data_length = unpack('<I', walker.read(4))[0]
2037
2032
  # IFF chunks are padded to an even size
2038
2033
  data_length += data_length % 2
2039
2034
  data = self._unpad_bytes(walker.read(data_length))
@@ -2498,6 +2493,7 @@ class _Aiff(TinyTag):
2498
2493
  self.bitrate = bitrate
2499
2494
  self.samplerate, self.duration = sr, duration
2500
2495
  except OverflowError:
2496
+ # Invalid sample rate
2501
2497
  pass
2502
2498
  compression_type = chunk[18:22].decode('latin-1')
2503
2499
  if not compression_type:
File without changes
File without changes
File without changes