pax_global_header 0000666 0000000 0000000 00000000064 15247572624 0014530 g ustar 00root root 0000000 0000000 52 comment=c123836e950a897bd4bbee04d3700bec8762ca27
tinytag-tinytag-c123836/ 0000775 0000000 0000000 00000000000 15247572624 0015137 5 ustar 00root root 0000000 0000000 tinytag-tinytag-c123836/.editorconfig 0000664 0000000 0000000 00000000476 15247572624 0017623 0 ustar 00root root 0000000 0000000 # SPDX-FileCopyrightText: 2025 tinytag Contributors
# SPDX-License-Identifier: MIT
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
max_line_length = 79
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[*.yml]
indent_size = 2
tinytag-tinytag-c123836/.github/ 0000775 0000000 0000000 00000000000 15247572624 0016477 5 ustar 00root root 0000000 0000000 tinytag-tinytag-c123836/.github/ISSUE_TEMPLATE/ 0000775 0000000 0000000 00000000000 15247572624 0020662 5 ustar 00root root 0000000 0000000 tinytag-tinytag-c123836/.github/ISSUE_TEMPLATE/bug_report.md 0000664 0000000 0000000 00000001063 15247572624 0023354 0 ustar 00root root 0000000 0000000 ---
name: Bug report
about: Something is broken or doesn't work as expected
labels: bug
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Sample file**
Please provide a sample file that shows the behavior that you have described in this report. You can use any upload site (such as wetransfer) and provide the link here.
tinytag-tinytag-c123836/.github/ISSUE_TEMPLATE/bug_report.md.license 0000664 0000000 0000000 00000000124 15247572624 0024772 0 ustar 00root root 0000000 0000000 SPDX-FileCopyrightText: 2021-2024 tinytag Contributors
SPDX-License-Identifier: MIT
tinytag-tinytag-c123836/.github/ISSUE_TEMPLATE/config.yml 0000664 0000000 0000000 00000000370 15247572624 0022652 0 ustar 00root root 0000000 0000000 # SPDX-FileCopyrightText: 2025 tinytag Contributors
# SPDX-License-Identifier: MIT
blank_issues_enabled: false
contact_links:
- name: Other
url: https://github.com/tinytag/tinytag/discussions
about: Question, discussion or anything else
tinytag-tinytag-c123836/.github/ISSUE_TEMPLATE/feature_request.md 0000664 0000000 0000000 00000001104 15247572624 0024403 0 ustar 00root root 0000000 0000000 ---
name: Feature request
about: Suggest an idea for this project
labels: enhancement
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
tinytag-tinytag-c123836/.github/ISSUE_TEMPLATE/feature_request.md.license 0000664 0000000 0000000 00000000124 15247572624 0026025 0 ustar 00root root 0000000 0000000 SPDX-FileCopyrightText: 2021-2024 tinytag Contributors
SPDX-License-Identifier: MIT
tinytag-tinytag-c123836/.github/PULL_REQUEST_TEMPLATE.md 0000664 0000000 0000000 00000001714 15247572624 0022303 0 ustar 00root root 0000000 0000000
tinytag-tinytag-c123836/.github/PULL_REQUEST_TEMPLATE.md.license 0000664 0000000 0000000 00000000117 15247572624 0023720 0 ustar 00root root 0000000 0000000 SPDX-FileCopyrightText: 2026 tinytag Contributors
SPDX-License-Identifier: MIT
tinytag-tinytag-c123836/.github/dependabot.yml 0000664 0000000 0000000 00000000355 15247572624 0021332 0 ustar 00root root 0000000 0000000 # SPDX-FileCopyrightText: 2026 tinytag Contributors
# SPDX-License-Identifier: MIT
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
cooldown:
default-days: 7
tinytag-tinytag-c123836/.github/icons/ 0000775 0000000 0000000 00000000000 15247572624 0017612 5 ustar 00root root 0000000 0000000 tinytag-tinytag-c123836/.github/icons/icon.svg 0000664 0000000 0000000 00000000715 15247572624 0021266 0 ustar 00root root 0000000 0000000
tinytag-tinytag-c123836/.github/icons/icon_bg.svg 0000664 0000000 0000000 00000001343 15247572624 0021734 0 ustar 00root root 0000000 0000000
tinytag-tinytag-c123836/.github/icons/icon_bg_round.svg 0000664 0000000 0000000 00000001345 15247572624 0023145 0 ustar 00root root 0000000 0000000
tinytag-tinytag-c123836/.github/workflows/ 0000775 0000000 0000000 00000000000 15247572624 0020534 5 ustar 00root root 0000000 0000000 tinytag-tinytag-c123836/.github/workflows/build.yml 0000664 0000000 0000000 00000002040 15247572624 0022352 0 ustar 00root root 0000000 0000000 # SPDX-FileCopyrightText: 2026 tinytag Contributors
# SPDX-License-Identifier: MIT
name: Build
on: [push, pull_request, workflow_dispatch]
permissions: {}
jobs:
build:
runs-on: ubuntu-26.04
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Set reproducible timestamp
run: echo "SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)" >> $GITHUB_ENV
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install flit python3-build twine
- name: Build package
run: |
python -m build --no-isolation
touch -d "@${SOURCE_DATE_EPOCH}" dist/*
- name: Upload artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: dist
path: dist/
if-no-files-found: error
- name: Verify package
run: python -m twine check --strict dist/*
tinytag-tinytag-c123836/.github/workflows/fuzz.yml 0000664 0000000 0000000 00000002244 15247572624 0022257 0 ustar 00root root 0000000 0000000 # SPDX-FileCopyrightText: 2026 tinytag Contributors
# SPDX-License-Identifier: MIT
name: Fuzz
on: [workflow_dispatch]
permissions: {}
jobs:
fuzz:
runs-on: ubuntu-26.04
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: 3.14
cache: 'pip'
- name: Install dependencies
run: python -m pip install atheris
- name: Run fuzzer
run: |
set -o pipefail
mkdir -p fuzz/artifacts
python -m fuzz.fuzz_tinytag tinytag/tests/samples \
-atheris_runs=5000000 -timeout=1 -rss_limit_mb=80 -artifact_prefix=fuzz/artifacts/ \
2>&1 | tee fuzz/fuzz.log
- name: Upload artifacts
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: fuzz
path: |
fuzz/artifacts/
tinytag/tests/samples/
fuzz/fuzz.log
tinytag-tinytag-c123836/.github/workflows/reuse.yml 0000664 0000000 0000000 00000001076 15247572624 0022406 0 ustar 00root root 0000000 0000000 # SPDX-FileCopyrightText: 2024-2026 tinytag Contributors
# SPDX-License-Identifier: MIT
name: REUSE Compliance
on: [push, pull_request]
permissions: {}
jobs:
check:
runs-on: ubuntu-26.04
timeout-minutes: 15
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install reuse
- name: REUSE compliance
run: python -m reuse lint
tinytag-tinytag-c123836/.github/workflows/tests.yml 0000664 0000000 0000000 00000007252 15247572624 0022427 0 ustar 00root root 0000000 0000000 # SPDX-FileCopyrightText: 2022-2026 tinytag Contributors
# SPDX-License-Identifier: MIT
name: Tests
on: [push, pull_request]
permissions: {}
env:
MIN_COVERAGE_PERCENTAGE: 100
jobs:
tests:
runs-on: ${{ matrix.os }}
timeout-minutes: 15
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python: [
'3.8', '3.9', '3.10', '3.11', '3.12', '3.13', '3.14', '3.15-dev',
'3.14t', '3.15t-dev',
'pypy-3.8', 'pypy-3.9', 'pypy-3.10', 'pypy-3.11'
]
include:
- os: ubuntu-22.04
python: 3.7
- os: macos-15-intel
python: 3.7
- os: windows-latest
python: 3.7
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
persist-credentials: false
- name: Set up Python ${{ matrix.python }}
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: ${{ matrix.python }}
cache: 'pip'
- name: Install dependencies
run: python -m pip install .[dev]
- name: PEP 8 style checks
run: python -m pycodestyle .
- name: Linting
run: python -m pylint --recursive=y .
- name: Typing (mypy)
run: python -m mypy -p tinytag
- name: Typing (pyright)
run: python -m pyright
- name: Unit tests
run: python -m coverage run --parallel-mode -m unittest
env:
TINYTAG_DEBUG: true
- name: Coverage report
run: |
coverage combine --keep --quiet
coverage report
coverage html --quiet
- name: Build package
run: python -m build
- name: Build package without isolation
run: python -m build --no-isolation
- name: Upload coverage data
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: coverage-${{ matrix.os }}-${{ matrix.python }}
path: |
.coverage.*
htmlcov
include-hidden-files: true
if-no-files-found: error
coverage:
needs: tests
runs-on: ubuntu-slim
timeout-minutes: 15
permissions:
code-quality: write
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
persist-credentials: false
- name: Download coverage data
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: coverage-*
merge-multiple: true
- name: Install dependencies
run: python -m pip install coverage
- name: Generate coverage report
run: |
coverage combine --quiet
coverage html --quiet
coverage xml --quiet
- name: Upload coverage report (HTML)
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: combined-coverage-report
path: |
htmlcov
coverage.xml
if-no-files-found: error
- name: Upload coverage report (GitHub)
uses: actions/upload-code-coverage@d8e329117199404bba6fc81efe8093dc7c015e34 # v1.4.2
with:
file: coverage.xml
language: Python
label: code-coverage/tinytag
fail-on-error: false
- name: Show coverage report
run: coverage report --fail-under=${MIN_COVERAGE_PERCENTAGE}
tinytag-tinytag-c123836/.gitignore 0000664 0000000 0000000 00000000513 15247572624 0017126 0 ustar 00root root 0000000 0000000 # SPDX-FileCopyrightText: 2020-2024 tinytag Contributors
# SPDX-License-Identifier: MIT
*.py[cod]
# Packages
*.egg-info
dist
build
__pycache__
# Unit test / coverage reports
.coverage
.mypy_cache
.pytest_cache
# Mr Developer
.mr.developer.cfg
.project
.pydevproject
# Jetbrains
.idea/
.venv
venv
# Visual Studio Code
.vscode
tinytag-tinytag-c123836/CONTRIBUTING.md 0000664 0000000 0000000 00000002076 15247572624 0017375 0 ustar 00root root 0000000 0000000
# Contributing
Before submitting a PR, please discuss the change you wish to make in an issue
report or discussion thread, in order to see if it fits the direction of the
project. If you skip this step, a PR containing anything other than a trivial
bug fix will likely be rejected.
Contributions generated or co-authored by LLMs or AI coding tools are not
allowed. Your contribution must be licensed as original work that is personally
authored by you under the terms of the
[MIT License](https://github.com/tinytag/tinytag/blob/HEAD/LICENSE).
Every part of your contribution must be written, thoroughly tested and fully
understood by you so that any queries about the logic of your contribution can
be answered during the review process.
You may only use AI assistants or tooling for learning purposes, including
guided research of the codebase and exploring concepts. Your contribution must
not include any content produced by generative AI (code, text snippets, images
nor other media).
tinytag-tinytag-c123836/LICENSE 0000664 0000000 0000000 00000002116 15247572624 0016144 0 ustar 00root root 0000000 0000000 MIT License
Copyright (c) 2014-2026 Tom Wallroth, Mat (mathiascode), et al.
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.
tinytag-tinytag-c123836/LICENSES/ 0000775 0000000 0000000 00000000000 15247572624 0016344 5 ustar 00root root 0000000 0000000 tinytag-tinytag-c123836/LICENSES/CC0-1.0.txt 0000664 0000000 0000000 00000015610 15247572624 0017751 0 ustar 00root root 0000000 0000000 Creative Commons Legal Code
CC0 1.0 Universal
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
HEREUNDER.
Statement of Purpose
The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator
and subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").
Certain owners wish to permanently relinquish those rights to a Work for
the purpose of contributing to a commons of creative, cultural and
scientific works ("Commons") that the public can reliably and without fear
of later claims of infringement build upon, modify, incorporate in other
works, reuse and redistribute as freely as possible in any form whatsoever
and for any purposes, including without limitation commercial purposes.
These owners may contribute to the Commons to promote the ideal of a free
culture and the further production of creative, cultural and scientific
works, or to gain reputation or greater distribution for their Work in
part through the use and efforts of others.
For these and/or other purposes and motivations, and without any
expectation of additional consideration or compensation, the person
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
is an owner of Copyright and Related Rights in the Work, voluntarily
elects to apply CC0 to the Work and publicly distribute the Work under its
terms, with knowledge of his or her Copyright and Related Rights in the
Work and the meaning and intended legal effect of CC0 on those rights.
1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not
limited to, the following:
i. the right to reproduce, adapt, distribute, perform, display,
communicate, and translate a Work;
ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or
likeness depicted in a Work;
iv. rights protecting against unfair competition in regards to a Work,
subject to the limitations in paragraph 4(a), below;
v. rights protecting the extraction, dissemination, use and reuse of data
in a Work;
vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal
protection of databases, and under any national implementation
thereof, including any amended or successor version of such
directive); and
vii. other similar, equivalent or corresponding rights throughout the
world based on applicable law or treaty, and any national
implementations thereof.
2. Waiver. To the greatest extent permitted by, but not in contravention
of, applicable law, Affirmer hereby overtly, fully, permanently,
irrevocably and unconditionally waives, abandons, and surrenders all of
Affirmer's Copyright and Related Rights and associated claims and causes
of action, whether now known or unknown (including existing as well as
future claims and causes of action), in the Work (i) in all territories
worldwide, (ii) for the maximum duration provided by applicable law or
treaty (including future time extensions), (iii) in any current or future
medium and for any number of copies, and (iv) for any purpose whatsoever,
including without limitation commercial, advertising or promotional
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
member of the public at large and to the detriment of Affirmer's heirs and
successors, fully intending that such Waiver shall not be subject to
revocation, rescission, cancellation, termination, or any other legal or
equitable action to disrupt the quiet enjoyment of the Work by the public
as contemplated by Affirmer's express Statement of Purpose.
3. Public License Fallback. Should any part of the Waiver for any reason
be judged legally invalid or ineffective under applicable law, then the
Waiver shall be preserved to the maximum extent permitted taking into
account Affirmer's express Statement of Purpose. In addition, to the
extent the Waiver is so judged Affirmer hereby grants to each affected
person a royalty-free, non transferable, non sublicensable, non exclusive,
irrevocable and unconditional license to exercise Affirmer's Copyright and
Related Rights in the Work (i) in all territories worldwide, (ii) for the
maximum duration provided by applicable law or treaty (including future
time extensions), (iii) in any current or future medium and for any number
of copies, and (iv) for any purpose whatsoever, including without
limitation commercial, advertising or promotional purposes (the
"License"). The License shall be deemed effective as of the date CC0 was
applied by Affirmer to the Work. Should any part of the License for any
reason be judged legally invalid or ineffective under applicable law, such
partial invalidity or ineffectiveness shall not invalidate the remainder
of the License, and in such case Affirmer hereby affirms that he or she
will not (i) exercise any of his or her remaining Copyright and Related
Rights in the Work or (ii) assert any associated claims and causes of
action with respect to the Work, in either case contrary to Affirmer's
express Statement of Purpose.
4. Limitations and Disclaimers.
a. No trademark or patent rights held by Affirmer are waived, abandoned,
surrendered, licensed or otherwise affected by this document.
b. Affirmer offers the Work as-is and makes no representations or
warranties of any kind concerning the Work, express, implied,
statutory or otherwise, including without limitation warranties of
title, merchantability, fitness for a particular purpose, non
infringement, or the absence of latent or other defects, accuracy, or
the present or absence of errors, whether or not discoverable, all to
the greatest extent permissible under applicable law.
c. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without
limitation any person's Copyright and Related Rights in the Work.
Further, Affirmer disclaims responsibility for obtaining any necessary
consents, permissions or other rights required for any use of the
Work.
d. Affirmer understands and acknowledges that Creative Commons is not a
party to this document and has no duty or obligation with respect to
this CC0 or use of the Work.
tinytag-tinytag-c123836/LICENSES/MIT.txt 0000777 0000000 0000000 00000000000 15247572624 0020751 2../LICENSE ustar 00root root 0000000 0000000 tinytag-tinytag-c123836/README.md 0000664 0000000 0000000 00000055223 15247572624 0016425 0 ustar 00root root 0000000 0000000
# tinytag
tinytag is a Python library for reading audio file metadata
[](https://github.com/tinytag/tinytag/actions?query=workflow:%22Tests%22)
[](https://pypi.org/project/tinytag/)
[](https://pypistats.org/packages/tinytag)
## Install
```
python3 -m pip install tinytag
```
## Features
* Read tags, images and properties of audio files
* Supported formats:
* ID3 v1, v1.1, v2.2, v2.3, v2.4
* MP3 / MP2 / MP1
* MP4 (MPEG-4 Audio / AAC / ALAC)
* WAVE / WAV
* OGG (FLAC / Opus / Speex / Vorbis)
* FLAC
* ASF (WMA)
* AIFF / AIFF-C
* Same API for all formats
* Small, portable library
* High code coverage
* Pure Python, no dependencies
* Supports Python 3.7 or higher
> [!IMPORTANT]
> Support for changing/writing metadata will not be added. Use another library
> such as [Mutagen](https://mutagen.readthedocs.io/) or [pytaglib](https://github.com/supermihi/pytaglib)
> for this.
## Usage
tinytag only provides the minimum needed for _reading_ metadata, and presents
it in a simple format. It can determine track number, total tracks, title,
artist, album, year, duration and more.
```python
from tinytag import TinyTag
tag: TinyTag = TinyTag.get('/some/music.mp3')
print(f'This track is by {tag.artist}.')
print(f'It is {tag.duration:.2f} seconds long.')
```
> [!WARNING]
> The `ignore_errors` parameter of `TinyTag.get()` is obsolete as of tinytag
> 2.0.0, and will be removed in the future.
Alternatively you can use tinytag directly on the command line:
```console
$ python3 -m tinytag /some/music.mp3
{
"filename": "/some/music.mp3",
"filesize": 3243226,
"mime_type": "audio/mpeg",
"is_lossless": false,
"duration": 173.52,
"channels": 2,
"bitrate": 128,
"samplerate": 44100,
"artist": [
"artist name"
],
"album": [
"album name"
],
"title": [
"track name"
],
"track": [
"4"
],
"genre": [
"Jazz"
],
"year": [
"2010"
],
"comment": [
"Some comment here"
]
}
```
Check `python3 -m tinytag --help` for all CLI options, for example other
output formats.
### Supported Files
To receive a tuple of file extensions tinytag supports, use the
`SUPPORTED_FILE_EXTENSIONS` constant:
```python
TinyTag.SUPPORTED_FILE_EXTENSIONS
```
Alternatively, check if a file is supported by providing its path:
```python
is_supported = TinyTag.is_supported('/some/music.mp3')
```
### Common Metadata
tinytag provides some common attributes, which always contain a single value.
These are helpful when you need quick access to common metadata.
#### File/Audio Properties
```python
tag.filename: str | None # file name
tag.filesize: int # file size in bytes
tag.bitdepth: int | None # audio bit depth
tag.bitrate: float | None # audio bitrate in kbps
tag.channels: int | None # number of audio channels
tag.duration: float | None # audio duration in seconds
tag.is_lossless: bool | None # is audio lossless? (added in tinytag 2.3.0)
tag.mime_type: str | None # audio MIME type (added in tinytag 2.3.0)
tag.samplerate: int | None # audio samples per second
```
> [!WARNING]
> The `tag.audio_offset` attribute is obsolete as of tinytag 2.0.0, and will
> be removed in the future.
#### Metadata Fields
```python
tag.album: str | None # album name
tag.albumartist: str | None # album artist name
tag.artist: str | None # artist name
tag.comment: str | None # file comment
tag.composer: str | None # composer name
tag.disc: int | None # disc number
tag.disc_total: int | None # total number of discs
tag.genre: str | None # genre
tag.title: str | None # title
tag.track: int | None # track number
tag.track_total: int | None # total number of tracks
tag.year: str | None # year/date
```
#### Audio MIME Types
At present, tinytag may return the audio MIME types documented in this section.
When available, a `codecs` parameter as described in the [RFC 6381](https://www.rfc-editor.org/info/rfc6381)
standard is included.
For standard codec values, see:
* `audio/mp4`: [RFC 6381](https://www.rfc-editor.org/info/rfc6381)
* `audio/ogg`: [RFC 5334](https://www.rfc-editor.org/info/rfc5334)
and [RFC 7845](https://www.rfc-editor.org/info/rfc7845)
* `audio/wav`: [RFC 2361](https://www.rfc-editor.org/info/rfc2361)
No official standards exists for `codecs` values for the
`application/vnd.ms-asf` and `audio/aiff` MIME types. The most appropriate
value provided by the format is used.
# ASF
application/vnd.ms-asf
application/vnd.ms-asf; codecs="<1..>" # WAVE format tag, e.g. "161" (WMA)
# AIFF
audio/aiff
audio/aiff; codecs="" # AIFF-C compression type, e.g. "alaw"
# FLAC
audio/flac
# MP4
audio/mp4
audio/mp4; codecs="alac"
audio/mp4; codecs="mp4a.oo[.A]" # E.g. "mp4a.40.2" for AAC-LC
# MP3 / MP2 / MP1
audio/mpeg
# OGG
audio/ogg
audio/ogg; codecs="flac"
audio/ogg; codecs="opus"
audio/ogg; codecs="speex"
audio/ogg; codecs="vorbis"
# WAV
audio/wav
audio/wav; codecs="<1..>" # WAVE format tag, e.g. "1" for PCM
### Additional Metadata
For additional values of the same field type, uncommon metadata fields, or
metadata specific to certain file formats, use `other`:
```python
tag.other: OtherFields # a dictionary of additional fields
```
> [!WARNING]
> The `other` dictionary has replaced the `extra` dictionary in tinytag 2.0.0.
> The latter will be removed in a future release.
The following `other` field names are standardized in tinytag, and optionally
present when files provide such metadata:
barcode
bpm
catalog_number
conductor
copyright
director
encoded_by
encoder_settings
grouping
initial_key
isrc
language
license
lyricist
lyrics
media
movement
movement_name
movement_total
publisher
set_subtitle
show_movement
url
work
xmp
Additional `other` field names not documented above may be present, but are
format-specific and may change or disappear in future tinytag releases. If
tinytag does not expose metadata you need, or you wish to standardize more
field names, open a feature request on GitHub for discussion.
`other` values are always provided as strings, and are not guaranteed to be
valid. Should e.g. the `bpm` value in the file contain non-numeric characters,
tinytag will provide the string as-is. It is your responsibility to handle
possible exceptions, e.g. when converting the value to an integer.
Multiple values of the same field type are provided if a file contains them.
Values are always provided as a list, even when only a single value exists.
Example:
```python
from tinytag import OtherFields, TinyTag
tag: TinyTag = TinyTag.get('/some/music.mp3')
other_fields: OtherFields = tag.other
catalog_numbers: list[str] | None = other_fields.get('catalog_number')
if catalog_numbers:
catalog_number: str = catalog_numbers[0]
print(catalog_number)
print(catalog_numbers)
```
Output:
```console
> 10
> ['10']
```
When a file contains multiple values for a [common metadata field](#common-metadata)
(e.g. `artist`), the primary value is accessed through the common attribute
(`tag.artist`), and any additional values through the `other` dictionary
(`tag.other['artist']`).
Example:
```python
from tinytag import TinyTag
tag: TinyTag = TinyTag.get('/some/music.mp3')
artist: str | None = tag.artist
additional_artists: list[str] | None = tag.other.get('artist')
print(artist)
print(additional_artists)
```
Output:
```console
> main artist
> ['another artist', 'yet another artist']
```
### All Metadata
If you need to receive all available metadata as key-value pairs in a flat
dictionary, use the `as_dict()` method. This combines the common attributes
and `other` dictionary, which can be more convenient in some cases.
```python
from tinytag import TinyTag
tag: TinyTag = TinyTag.get('/some/music.mp3')
metadata: dict = tag.as_dict()
```
### Images
Additionally, you can also read embedded images by passing a `image=True`
keyword argument to `TinyTag.get()`.
If you need to receive an image of a specific kind, including its description,
use `images`:
```python
tag.images: Images # available embedded images
```
The following common image attributes are available, providing the first
located `Image` object of each kind:
```python
tag.images.front_cover: Image # front cover
tag.images.back_cover: Image # back cover
tag.images.media: Image # media (e.g. CD label)
```
When present, any additional images are available in an `images.other`
dictionary, using the following standardized key names:
generic
icon
alt_icon
front_cover
back_cover
media
leaflet
lead_artist
artist
conductor
band
composer
lyricist
recording_location
during_recording
during_performance
screen_capture
bright_colored_fish
illustration
band_logo
publisher_logo
unknown
Provided values are always lists containing at least one `Image` object.
The `Image` object provides the following attributes:
```python
image.data: bytes # image data
image.size: int # image size in bytes (added in tinytag 2.3.0)
image.name: str # image name/kind
image.mime_type: str | None # image MIME type
image.description: str | None # image description
```
To receive any available image, prioritizing the front cover, use `images.any`:
```python
from tinytag import Image, TinyTag
tag: TinyTag = TinyTag.get('/some/music.ogg', image=True)
image: Image | None = tag.images.any
if image is not None:
data: bytes = image.data
size: int = image.size
name: str = image.name
mime_type: str | None = image.mime_type
description: str | None = image.description
print(len(data))
print(size)
print(name)
print(mime_type)
print(description)
```
Output:
```console
> 74452
> 74452
> front_cover
> image/jpeg
> some image description
```
> [!WARNING]
> `tag.images.any` has replaced `tag.get_image()` in tinytag 2.0.0.
> `tag.get_image()` will be removed in the future.
To receive a common image, e.g. `front_cover`:
```python
from tinytag import Image, Images, TinyTag
tag: TinyTag = TinyTag.get('/some/music.ogg', image=True)
images: Images = tag.images
cover_image: Image = images.front_cover
if cover_image is not None:
data: bytes = cover_image.data
description: str | None = cover_image.description
```
To receive an additional image, e.g. `bright_colored_fish`:
```python
from tinytag import Image, OtherImages, TinyTag
tag: TinyTag = TinyTag.get('/some/music.ogg', image=True)
other_images: OtherImages = tag.images.other
fish_images: list[Image] | None = other_images.get('bright_colored_fish')
if fish_images:
image = fish_images[0] # Use first image
data = image.data
description = image.description
```
### Magic Byte Detection
By default, tinytag will determine the file type by 1. checking the file name
extension, and 2. reading and inspecting magic bytes in the file header.
In case you want to disable magic byte detection, e.g. to minimize read
operations when many non-audio files exist in a folder, pass a
`check_magic_bytes` argument with a value of `False` (added in tinytag 2.3.0).
```python
tag: TinyTag = TinyTag.get('invalid_file.jpg', check_magic_bytes=False)
```
### Encoding
To open files using a specific encoding, you can use the `encoding` parameter.
This parameter is however only used for formats where the encoding is not
explicitly specified.
```python
tag: TinyTag = TinyTag.get('a_file_with_gbk_encoding.mp3', encoding='gbk')
```
### File-like Objects
To use a file-like object (e.g. BytesIO) instead of a file path, pass a
`file_obj` keyword argument:
```python
tag: TinyTag = TinyTag.get(file_obj=your_file_obj)
```
### Exceptions
```python
TinyTagException # Base class for exceptions
ParseError # Parsing an audio file failed
UnsupportedFormatError # File format is not supported
```
## Changelog
### 2.3.2 (2026-09-07)
- ID3: Optimize parsing of UTF-16 strings
- MP4: Only parse decoder specific info if it exists
- ASF: Decode strings as UTF-16LE instead of system byte order
### 2.3.1 (2026-08-30)
- Don't open unrecognized files when disabling magic byte detection
- MPEG: Fix parsing of some files with incorrect headers
- OGG/WAV: Read sizes as little endian instead of native byte order
### 2.3.0 (2026-07-31)
- Add 'mime_type' attribute for audio MIME type/codec
- Add 'is_lossless' attribute to indicate lossless audio
- Add 'size' attribute to images for size in bytes
- Add option to disable magic byte detection
- Make XMP metadata available under 'other.xmp' field
- Make bit depth available for lossy files providing it
- Add .asf, .ogv and .wmv to list of supported file extensions
- Mark deprecated 'extra' dictionary as obsolete
- Fix incorrect key when adding common images to 'other' dictionary
- Fix disc/track numbers not being returned as lists in 'as_dict()'
- Only set audio properties with a positive value
- Always read tags and audio properties in a single pass
- ID3: Parse multi-value text frames correctly
- ID3: Parse COMM and USLT frames correctly
- ID3: Fix some errors in parsed size calculations
- ID3: Exclude a few frames with custom structures
- MPEG: Read VBRI and Xing Info headers for duration/bitrate
- MPEG: Fix parsing of layer I and MPEG-2 layer III audio properties
- MPEG: Fix slightly incorrect duration estimation
- MPEG/MP4: Raise ParseError if header is invalid
- MP4: Add 'image/bmp' and 'image/gif' MIME types
- MP4: Fix infinite loop if size of certain sub-atoms is zero
- MP4: Fix incorrect parsing of descriptor size
- MP4: Don't read image data when image reading is disabled
- OGG: Parse multi-stream files correctly
- Speex: Don't read vendor string as comment
- FLAC: Provide actual audio bitrate instead of overall bitrate
- FLAC: Parse VORBIS_COMMENT and PICTURE blocks with invalid size
- FLAC: Ignore subsequent STREAMINFO blocks
- WAV: Include unknown fields in 'other' dictionary
- WAV: Calculate bitrate/duration correctly
- AIFF/WAV: Read whole ID3 chunk before parsing
- ASF: Support parsing additional metadata objects
- ASF: Stop parsing if object size is smaller than header
- ASF: Make magic header detection more strict
- Various performance improvements
- Various code refactors
### 2.2.1 (2026-03-15)
- ID3: Prevent infinite loop due to malformed SYLT strings
### 2.2.0 (2025-12-15)
- Add support for movement, work and grouping fields
- ID3: Make synced lyrics available in 'other.lyrics' (LRC format)
- ID3: Continue reading after encountering empty frame
- ID3: Fix frame reading when image parsing is disabled
- ID3: Exclude more frames containing binary data
- ID3: Avoid unnecessary string decoding
- M4A: Support extended atom sizes
- M4A: Ensure all field names are lowercase
- OGG: Stop reading after reaching EOS page
- Vorbis: Map UNSYNCEDLYRICS field to other.lyrics
### 2.1.2 (2025-08-14)
- M4A: Add a few missing additional metadata fields
- M4A: Support '©com' composer atom
- M4A: Fix reading of multi-value custom fields
- M4A: Use correct encoding when reading data names
- ID3: Don't read entire file to determine duration
- ID3: Skip stray null byte before image data
- Add missing `__version__` attribute
- Avoid some unnecessary work in hot code paths
- Improve a few incomplete type hints
### 2.1.1 (2025-04-23)
- ID3: Stop removing 'b' character from strings
- Port unit tests from pytest to built-in unittest module
### 2.1.0 (2025-02-23)
- Opus: Calculate audio bitrate
- Opus: Take pre-skip into account when calculating the duration
### 2.0.0 (2024-11-03)
- **BREAKING:** Store 'disc', 'disc_total', 'track' and 'track_total' values as int instead of str
- **BREAKING:** 'as_dict()' method (previously undocumented) returns tag field values in list form
- **BREAKING:** TinyTagException no longer inherits LookupError
- **BREAKING:** TinyTag subclasses are now private
- **BREAKING:** Remove function to use custom audio file samples in tests
- **BREAKING:** Remove support for Python 2
- **DEPRECATION:** Mark 'ignore_errors' parameter for TinyTag.get() as obsolete
- **DEPRECATION:** Mark 'audio_offset' attribute as obsolete
- **DEPRECATION:** Deprecate 'extra' dict in favor of 'other' dict with values in list form
- **DEPRECATION:** Deprecate 'get_image()' method in favor of 'images.any' property
- Add type hints to codebase
- Provide access to custom metadata fields through the 'other' dict
- Provide access to all available images
- Add more standard 'other' fields
- Use Flit as Python build backend instead of Setuptools
- ID3: Fix invalid sample rate/duration in some cases
- ID3: Fix reading of UTF-16 strings without BOM
- FLAC: Apply ID3 tags after Vorbis
- OGG/WMA: Set missing 'channels' field
- WMA: Set missing 'other.copyright' field
- WMA: Raise exception if file is invalid
- Various optimizations
### 1.10.1 (2023-10-26)
- Update 'extra' fields with data from other tags #188
- ID3: Add missing 'extra.copyright' field
### 1.10.0 (2023-10-18)
- Add support for OGG FLAC format #182
- Add support for OGG Speex format #181
- Wave: support image loading
- Add support for file-like objects (BytesIO) #178
- Add list of supported file extensions #177
- Fix deprecations related to setuptools #176
- Fix pathlib support in TinyTag.is_supported()
- Only remove zero bytes at the end of strings
- Stricter conditions in while loops
- OGG: Add stricter magic byte matching for OGG files
- Compatibility with Python 3.4 and 3.5 is no longer tested
### 1.9.0 (2023-04-23)
- Add bitdepth attribute for lossless audio #157
- Add recognition of Audible formats #163 (thanks to snowskeleton)
- Add .m4v to list of supported file extensions #142
- Aiff: Implement replacement for Python's aifc module #164
- ID3: Only check for language in COMM and USLT frames #147
- ID3: Read the correct number of bytes from Xing header #154
- ID3: Add support for ID3v2.4 TDRC frame #156 (thanks to Uninen)
- M4A: Add description fields #168 (thanks to snowskeleton)
- RIFF: Handle tags containing extra zero-byte #141
- Vorbis: Parse OGG cover art #144 (thanks to Pseurae)
- Vorbis: Support standard disctotal/tracktotal comments #171
- Wave: Add proper support for padded IFF chunks
### 1.8.1 (2022-03-12) [still mathiascode-edition]
- MP3 ID3: Set correct file position if tag reading is disabled #119 (thanks to mathiascode)
- MP3: Fix incorrect calculation of duration for VBR encoded MP3s #128 (thanks to mathiascode)
### 1.8.0 (2022-03-05) [mathiascode-edition]
- Add support for ALAC audio files #130 (thanks to mathiascode)
- AIFF: Fixed bitrate calculation for certain files #129 (thanks to mathiascode)
- MP3: Do not round MP3 bitrates #131 (thanks to mathiascode)
- MP3 ID3: Support any language in COMM and USLT frames #135 (thanks to mathiascode)
- Performance: Don't use regex when parsing genre #136 (thanks to mathiascode)
- Disable tag parsing for all formats when requested #137 (thanks to mathiascode)
- M4A: Fix invalid bitrates in certain files #132 (thanks to mathiascode)
- WAV: Fix metadata parsing for certain files #133 (thanks to mathiascode)
### 1.7.0. (2021-12-14)
- fixed rare occasion of ID3v2 tags missing their first character, #106
- allow overriding the default encoding of ID3 tags (e.g. `TinyTag.get(..., encoding='gbk'))`)
- fixed calculation of bitrate for very short mp3 files, #99
- utf-8 support for AIFF files, #123
- fixed image parsing for id3v2 with images containing utf-16LE descriptions, #117
- fixed ID3v1 tags overwriting ID3v2 tags, #121
- Set correct file position if tag reading is disabled for ID3 (thanks to mathiascode)
### 1.6.0 (2021-08-28) [aw-edition]
- fixed handling of non-latin encoding types for images (thanks to aw-was-here)
- added support for ISRC data, available in `extra['isrc']` field (thanks to aw-was-here)
- added support for AIFF/AIFF-C (thanks to aw-was-here)
- fixed import deprecation warnings (thanks to idotobi)
- fixed exception for TinyTag misuse being different in different python versions (thanks to idotobi)
- added support for ID3 initial key tonality hint, available in `extra['initial_key']`
- added support for ID3 unsynchronized lyrics, available in `extra['lyrics']`
- added `extra` field, which may contain additional metadata not available in all file formats
### 1.5.0 (2020-11-05)
- fixed data type to always return str for disc, disc_total, track, track_total #97 (thanks to kostalski)
- fixed package install being reported as UNKNOWN for some python/pip variations #90 (thanks to russpoutine)
- Added automatic detection for certain MP4 file headers
### 1.4.0 (2020-04-23)
- detecting file types based on their magic header bytes, #85
- fixed opus duration being wrong for files with lower sample rate #81
- implemented support for binary paths #72
- always cast mp3 bitrates to int, so that CBR and VBR output behaves the sam
- made __str__ deterministic and use json as output format
### 1.3.0 (2020-03-09)
- added option to ignore encoding errors `ignore_errors` #73
- Improved text decoding for many malformed files
### 1.2.2 (2019-04-13)
- Improved stability when reading corrupted mp3 files
### 1.2.1 (2019-04-13)
- fixed wav files not correctly reporting the number of channels #61
### 1.2.0 (2019-04-13)
- using setup.cfg instead of setup.py (thanks to scivision)
- added support for calling TinyTag.get with pathlib.Path (thanks to scivision)
- added appveyor windows test CI (thanks to scivision)
- using pytest instead of nosetest (thanks to scivision)
### 1.1.0 (2019-04-13)
- added new field "composer" (Thanks to Phil Borman)
### 1.0.1 (2019-04-13)
- fixed ID3 loading for files with corrupt header (thanks to Ian Homer)
- fixed parsing of duration in wav file (thanks to Ian Homer)
### 1.0.0 (2018-12-12)
- added comment field
- added wav-riff format support
- use MP4 parser for m4b files
- added simple cli tool
- fix parsing of FLAC files with ID3 header (thanks to minus7)
- added method `TinyTag.is_supported(filename)`
### 0.19.0 (2018-02-11)
- fixed corrupted images for some mp3s (#45)
### 0.18.0 (2017-04-29)
- fixed wrong bitrate and crash when parsing xing header
### 0.17.0 (2016-10-02)
- supporting ID3v2.2 images
### 0.16.0 (2016-08-06)
- MP4 cover image support
### 0.15.2 (2016-08-06)
- fixed crash for malformed MP4 files (#34)
### 0.15.0 (2016-08-06)
- fixed decoding of UTF-16LE ID3v2 Tags, improved overall stability
### 0.14.0 (2016-06-05)
- MP4/M4A and Opus support
tinytag-tinytag-c123836/SECURITY.md 0000664 0000000 0000000 00000000716 15247572624 0016734 0 ustar 00root root 0000000 0000000
# Security Policy
## Supported Versions
tinytag follows a `A.B.C` versioning scheme. We only support the latest
released `A.B.x` series.
## Reporting a Vulnerability
Please report a vulnerability under the Security tab in the GitHub repository.
Since tinytag is a volunteer-driven project, your report will be reviewed and
addressed when the maintainers have time.
tinytag-tinytag-c123836/fuzz/ 0000775 0000000 0000000 00000000000 15247572624 0016135 5 ustar 00root root 0000000 0000000 tinytag-tinytag-c123836/fuzz/fuzz_tinytag.py 0000664 0000000 0000000 00000001271 15247572624 0021245 0 ustar 00root root 0000000 0000000 # SPDX-FileCopyrightText: 2026 tinytag Contributors
# SPDX-License-Identifier: MIT
# pylint: disable=import-error,missing-function-docstring
# pylint: disable=missing-module-docstring
# pyright: reportAttributeAccessIssue=false,reportMissingModuleSource=false
# pyright: reportUnknownMemberType=false
import io
import sys
import atheris
from tinytag import TinyTag, TinyTagException
def test_one_input(data: bytes) -> None:
try:
tag = TinyTag.get(
file_obj=io.BytesIO(data), tags=True, duration=True, image=True
)
except TinyTagException:
return
tag.as_dict()
atheris.instrument_all()
atheris.Setup(sys.argv, test_one_input)
atheris.Fuzz()
tinytag-tinytag-c123836/pyproject.toml 0000664 0000000 0000000 00000007213 15247572624 0020056 0 ustar 00root root 0000000 0000000 # SPDX-FileCopyrightText: 2024 tinytag Contributors
# SPDX-License-Identifier: MIT
[build-system]
requires = ["flit_core>=3.2"]
build-backend = "flit_core.buildapi"
[project]
name = "tinytag"
description = "Read audio file metadata"
authors = [
{name = "Tom Wallroth"},
{name = "Mat (mathiascode)"}
]
keywords = [
"metadata",
"audio",
"music",
"mp3",
"m4a",
"wav",
"ogg",
"opus",
"flac",
"wma",
"aiff"
]
classifiers = [
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Programming Language :: Python :: Free Threading :: 4 - Resilient",
"License :: OSI Approved :: MIT License",
"Development Status :: 5 - Production/Stable",
"Environment :: Web Environment",
"Intended Audience :: Developers",
"Operating System :: OS Independent",
"Topic :: Internet :: WWW/HTTP",
"Topic :: Multimedia",
"Topic :: Multimedia :: Sound/Audio",
"Topic :: Multimedia :: Sound/Audio :: Analysis",
"Typing :: Typed"
]
license = {file = "LICENSE"}
readme = "README.md"
requires-python = ">=3.7"
dynamic = ["version"]
[project.urls]
Homepage = "https://github.com/tinytag/tinytag"
[project.optional-dependencies]
dev = [
"build",
"coverage",
"flit_core",
"mypy",
"mypy<1.19.0; platform_python_implementation == 'PyPy'",
"pycodestyle",
"pylint",
"pyright",
"wheel"
]
[tool.flit.sdist]
include = [
"LICENSES/",
".editorconfig",
"CONTRIBUTING.md",
"SECURITY.md"
]
exclude = [
".gitignore",
".github/",
"tinytag/tests/"
]
[tool.pylint.master]
disable = [
"invalid-name",
"too-few-public-methods",
"too-many-arguments",
"too-many-boolean-expressions",
"too-many-branches",
"too-many-instance-attributes",
"too-many-lines",
"too-many-locals",
"too-many-nested-blocks",
"too-many-positional-arguments",
"too-many-public-methods",
"too-many-return-statements",
"too-many-statements",
"unknown-option-value"
]
enable = [
"consider-using-augmented-assign"
]
load-plugins = [
"pylint.extensions.bad_builtin",
"pylint.extensions.check_elif",
"pylint.extensions.code_style",
"pylint.extensions.comparison_placement",
"pylint.extensions.consider_refactoring_into_while_condition",
"pylint.extensions.consider_ternary_expression",
"pylint.extensions.dict_init_mutate",
"pylint.extensions.docstyle",
"pylint.extensions.dunder",
"pylint.extensions.empty_comment",
"pylint.extensions.eq_without_hash",
"pylint.extensions.for_any_all",
"pylint.extensions.no_self_use",
"pylint.extensions.overlapping_exceptions",
"pylint.extensions.private_import",
"pylint.extensions.redefined_loop_name",
"pylint.extensions.redefined_variable_type",
"pylint.extensions.set_membership",
"pylint.extensions.typing"
]
py-version = "3.7"
[tool.mypy]
strict = true
[tool.pyright]
pythonVersion = "3.7"
typeCheckingMode = "strict"
reportCallInDefaultInitializer = true
reportImportCycles = true
reportPropertyTypeMismatch = true
reportUninitializedInstanceVariable = true
reportUnnecessaryTypeIgnoreComment = true
reportUnreachable = true
[tool.coverage.report]
exclude_lines = [
"if TYPE_CHECKING:"
]
precision = 2
show_missing = true
[tool.coverage.run]
relative_files = true
tinytag-tinytag-c123836/tinytag/ 0000775 0000000 0000000 00000000000 15247572624 0016616 5 ustar 00root root 0000000 0000000 tinytag-tinytag-c123836/tinytag/__init__.py 0000664 0000000 0000000 00000000650 15247572624 0020730 0 ustar 00root root 0000000 0000000 # SPDX-FileCopyrightText: 2014-2026 tinytag Contributors
# SPDX-License-Identifier: MIT
"""Audio file metadata reader."""
__version__ = '2.3.2'
from .tinytag import (
TinyTag, Image, Images, OtherFields, OtherImages,
TinyTagException, ParseError, UnsupportedFormatError
)
__all__ = (
"TinyTag", "Image", "Images", "OtherFields", "OtherImages",
"TinyTagException", "ParseError", "UnsupportedFormatError"
)
tinytag-tinytag-c123836/tinytag/__main__.py 0000664 0000000 0000000 00000007025 15247572624 0020714 0 ustar 00root root 0000000 0000000 # SPDX-FileCopyrightText: 2015-2024 tinytag Contributors
# SPDX-License-Identifier: MIT
# pylint: disable=missing-module-docstring,protected-access
from __future__ import annotations
import sys
from io import StringIO
from os.path import isfile, splitext
from tinytag import TinyTag, TinyTagException
def _usage() -> None:
print('''tinytag [options]
-h, --help
Display help
-i, --save-image
Save the cover art to a file
-f, --format json|csv|tsv|tabularcsv
Specify how the output should be formatted
-s, --skip-unsupported
Skip files that do not have a file extension supported by tinytag
''')
def _pop_param(name: str, _default: str | None) -> str | None:
if name in sys.argv:
idx = sys.argv.index(name)
sys.argv.pop(idx)
return sys.argv.pop(idx)
return _default
def _pop_switch(name: str) -> bool:
if name in sys.argv:
idx = sys.argv.index(name)
sys.argv.pop(idx)
return True
return False
def _print_tag(tag: TinyTag, fmt: str, header_printed: bool = False) -> bool:
data = tag.as_dict()
if fmt == 'json':
import json # pylint: disable=import-outside-toplevel
print(json.dumps(data, ensure_ascii=False, indent=2))
return header_printed
if fmt not in {'csv', 'tsv', 'tabularcsv'}:
return header_printed
import csv # pylint: disable=import-outside-toplevel
for field, value in data.items():
if isinstance(value, str):
# use a more friendly separator for output
data[field] = value.replace('\x00', ';')
csv_file = StringIO()
delimiter = '\t' if fmt == 'tsv' else ','
writer = csv.writer(csv_file, delimiter=delimiter, lineterminator='\n')
if fmt == 'tabularcsv':
if not header_printed:
writer.writerow(data.keys())
header_printed = True
writer.writerow(data.values())
value = csv_file.getvalue().strip()
else:
writer.writerows(data.items())
value = csv_file.getvalue()
print(value)
return header_printed
def _run() -> int:
header_printed = False
image_path = _pop_param('--save-image', None) or _pop_param('-i', None)
fmt = (_pop_param('--format', None) or _pop_param('-f', None)) or 'json'
skip_unsupported = _pop_switch('--skip-unsupported') or _pop_switch('-s')
filenames = sys.argv[1:]
display_help = not filenames or _pop_switch('--help') or _pop_switch('-h')
if display_help:
_usage()
return 0
for i, filename in enumerate(filenames):
if (skip_unsupported
and not (TinyTag.is_supported(filename) and isfile(filename))):
continue
try:
tag = TinyTag.get(filename, image=image_path is not None)
if image_path:
# allow for saving the image of multiple files
actual_image_path = image_path
if len(filenames) > 1:
actual_image_path, ext = splitext(actual_image_path)
actual_image_path += f'{i:05d}{ext}'
image = tag.images.any
if image is not None:
with open(actual_image_path, 'wb') as file_handle:
file_handle.write(image.data)
header_printed = _print_tag(tag, fmt, header_printed)
except (OSError, TinyTagException) as exc:
sys.stderr.write(f'{filename}: {exc}\n')
return 1
return 0
if __name__ == '__main__':
sys.exit(_run())
tinytag-tinytag-c123836/tinytag/py.typed 0000664 0000000 0000000 00000000000 15247572624 0020303 0 ustar 00root root 0000000 0000000 tinytag-tinytag-c123836/tinytag/tests/ 0000775 0000000 0000000 00000000000 15247572624 0017760 5 ustar 00root root 0000000 0000000 tinytag-tinytag-c123836/tinytag/tests/__init__.py 0000664 0000000 0000000 00000000000 15247572624 0022057 0 ustar 00root root 0000000 0000000 tinytag-tinytag-c123836/tinytag/tests/samples/ 0000775 0000000 0000000 00000000000 15247572624 0021424 5 ustar 00root root 0000000 0000000 tinytag-tinytag-c123836/tinytag/tests/samples/106-invalid-streaminfo.flac 0000664 0000000 0000000 00000011124 15247572624 0026351 0 ustar 00root root 0000000 0000000 fLaC
ÄBð zÀ� <