trackparse 0.2.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.
- trackparse-0.2.0/.gitignore +27 -0
- trackparse-0.2.0/LICENSE +21 -0
- trackparse-0.2.0/PKG-INFO +389 -0
- trackparse-0.2.0/README.md +357 -0
- trackparse-0.2.0/pyproject.toml +88 -0
- trackparse-0.2.0/src/trackparse/__init__.py +119 -0
- trackparse-0.2.0/src/trackparse/_artists.py +101 -0
- trackparse-0.2.0/src/trackparse/_assemble.py +141 -0
- trackparse-0.2.0/src/trackparse/_classify.py +468 -0
- trackparse-0.2.0/src/trackparse/_context.py +74 -0
- trackparse-0.2.0/src/trackparse/_credits.py +218 -0
- trackparse-0.2.0/src/trackparse/_data.py +513 -0
- trackparse-0.2.0/src/trackparse/_format.py +149 -0
- trackparse-0.2.0/src/trackparse/_mode.py +204 -0
- trackparse-0.2.0/src/trackparse/_normalize.py +107 -0
- trackparse-0.2.0/src/trackparse/_parser.py +257 -0
- trackparse-0.2.0/src/trackparse/_prefix.py +143 -0
- trackparse-0.2.0/src/trackparse/_scanner.py +240 -0
- trackparse-0.2.0/src/trackparse/_separator.py +209 -0
- trackparse-0.2.0/src/trackparse/_tables.py +228 -0
- trackparse-0.2.0/src/trackparse/_title.py +246 -0
- trackparse-0.2.0/src/trackparse/_words.py +344 -0
- trackparse-0.2.0/src/trackparse/options.py +62 -0
- trackparse-0.2.0/src/trackparse/py.typed +0 -0
- trackparse-0.2.0/src/trackparse/types.py +259 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Node
|
|
2
|
+
node_modules/
|
|
3
|
+
dist/
|
|
4
|
+
coverage/
|
|
5
|
+
*.tsbuildinfo
|
|
6
|
+
.pnpm-store/
|
|
7
|
+
*.tgz
|
|
8
|
+
|
|
9
|
+
# Python
|
|
10
|
+
__pycache__/
|
|
11
|
+
*.py[cod]
|
|
12
|
+
.venv/
|
|
13
|
+
.pytest_cache/
|
|
14
|
+
.mypy_cache/
|
|
15
|
+
.ruff_cache/
|
|
16
|
+
python/dist/
|
|
17
|
+
*.egg-info/
|
|
18
|
+
|
|
19
|
+
# Rust
|
|
20
|
+
target/
|
|
21
|
+
rust/Cargo.lock
|
|
22
|
+
|
|
23
|
+
# Editors / OS
|
|
24
|
+
.DS_Store
|
|
25
|
+
.idea/
|
|
26
|
+
.vscode/
|
|
27
|
+
*.swp
|
trackparse-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Maksim Maksimov
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: trackparse
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Spec-driven parser for music track titles: artists, feat, remixes, versions, junk, positions.
|
|
5
|
+
Project-URL: Homepage, https://github.com/4matic/trackparse
|
|
6
|
+
Project-URL: Repository, https://github.com/4matic/trackparse
|
|
7
|
+
Project-URL: Issues, https://github.com/4matic/trackparse/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/4matic/trackparse/blob/main/python/CHANGELOG.md
|
|
9
|
+
Project-URL: Specification, https://github.com/4matic/trackparse/blob/main/spec/SPEC.md
|
|
10
|
+
Author: Maksim Maksimov
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: artist,featuring,id3,metadata,music,parser,remix,title,track,youtube
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
26
|
+
Classifier: Topic :: Multimedia :: Sound/Audio
|
|
27
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
28
|
+
Classifier: Topic :: Text Processing
|
|
29
|
+
Classifier: Typing :: Typed
|
|
30
|
+
Requires-Python: >=3.9
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# trackparse
|
|
34
|
+
|
|
35
|
+
[](https://pypi.org/project/trackparse/)
|
|
36
|
+
[](https://pypi.org/project/trackparse/)
|
|
37
|
+
[](https://github.com/4matic/trackparse/actions/workflows/python.yml)
|
|
38
|
+
[](https://github.com/4matic/trackparse/blob/main/LICENSE)
|
|
39
|
+
|
|
40
|
+
**Turn messy track names into structured data: who made it, who's featured, who remixed it, and
|
|
41
|
+
what's just noise.**
|
|
42
|
+
|
|
43
|
+
```text
|
|
44
|
+
01. Wilkinson ft. Becky Hill & Tom Cane - Afterglow (Sub Focus VIP Remix) [Official Video]
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Most title parsers split that into `"Wilkinson ft. Becky Hill & Tom Cane"` and
|
|
48
|
+
`"Afterglow (Sub Focus VIP Remix)"` and stop. Others "clean" it and throw the feat and the remix
|
|
49
|
+
away. trackparse keeps all of it. Everything it recognises goes into a field, and anything it
|
|
50
|
+
doesn't recognise stays in the title, word for word.
|
|
51
|
+
|
|
52
|
+
This is the Python port of [trackparse](https://github.com/4matic/trackparse). Its behaviour is
|
|
53
|
+
defined by a [shared spec](https://github.com/4matic/trackparse/blob/main/spec/SPEC.md) and pinned
|
|
54
|
+
by 11,891 fixtures. It passes all of them, and for every fixture input its output is byte-identical
|
|
55
|
+
to the JavaScript package.
|
|
56
|
+
|
|
57
|
+
- Python 3.9 to 3.14
|
|
58
|
+
- No dependencies
|
|
59
|
+
- Typed (`py.typed`), immutable results
|
|
60
|
+
- Never raises on string input
|
|
61
|
+
|
|
62
|
+
## Install
|
|
63
|
+
|
|
64
|
+
```sh
|
|
65
|
+
pip install trackparse
|
|
66
|
+
# or
|
|
67
|
+
uv add trackparse
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Quick start
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
from trackparse import format_track, parse
|
|
74
|
+
|
|
75
|
+
track = parse(
|
|
76
|
+
"01. Wilkinson ft. Becky Hill & Tom Cane - Afterglow (Sub Focus VIP Remix) [Official Video]"
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
[f"{a.name} ({a.role})" for a in track.artists]
|
|
80
|
+
# ['Wilkinson (primary)', 'Becky Hill (featured)', 'Tom Cane (featured)']
|
|
81
|
+
track.title # 'Afterglow'
|
|
82
|
+
track.full_title # 'Afterglow (Sub Focus VIP Remix)'
|
|
83
|
+
track.versions[0].type # 'remix'
|
|
84
|
+
track.versions[0].artists[0].name # 'Sub Focus'
|
|
85
|
+
track.versions[0].modifiers # ('vip',)
|
|
86
|
+
track.junk # (Junk(raw='Official Video', kind='video'),)
|
|
87
|
+
track.position # Position(raw='01', number=1)
|
|
88
|
+
track.mode # 'youtube'
|
|
89
|
+
|
|
90
|
+
format_track(track, joiners="canonical")
|
|
91
|
+
# 'Wilkinson feat. Becky Hill & Tom Cane - Afterglow (Sub Focus VIP Remix)'
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
`to_dict()` returns the spec's JSON form, with the same camelCase keys the JS package uses:
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
import json
|
|
98
|
+
|
|
99
|
+
print(json.dumps(track.to_dict(), indent=2))
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
<details>
|
|
103
|
+
<summary>Output</summary>
|
|
104
|
+
|
|
105
|
+
```json
|
|
106
|
+
{
|
|
107
|
+
"input": "01. Wilkinson ft. Becky Hill & Tom Cane - Afterglow (Sub Focus VIP Remix) [Official Video]",
|
|
108
|
+
"mode": "youtube",
|
|
109
|
+
"position": { "raw": "01", "number": 1 },
|
|
110
|
+
"timestamp": null,
|
|
111
|
+
"artists": [
|
|
112
|
+
{ "name": "Wilkinson", "role": "primary", "joiner": null, "source": "artist" },
|
|
113
|
+
{ "name": "Becky Hill", "role": "featured", "joiner": "ft.", "source": "artist" },
|
|
114
|
+
{ "name": "Tom Cane", "role": "featured", "joiner": "&", "source": "artist" }
|
|
115
|
+
],
|
|
116
|
+
"title": "Afterglow",
|
|
117
|
+
"fullTitle": "Afterglow (Sub Focus VIP Remix)",
|
|
118
|
+
"versions": [
|
|
119
|
+
{
|
|
120
|
+
"type": "remix",
|
|
121
|
+
"raw": "Sub Focus VIP Remix",
|
|
122
|
+
"artists": [{ "name": "Sub Focus", "role": "remixer", "joiner": null, "source": "version" }],
|
|
123
|
+
"modifiers": ["vip"],
|
|
124
|
+
"descriptor": null,
|
|
125
|
+
"year": null,
|
|
126
|
+
"unknownArtist": false,
|
|
127
|
+
"delimiter": "("
|
|
128
|
+
}
|
|
129
|
+
],
|
|
130
|
+
"year": null,
|
|
131
|
+
"flags": { "explicit": false, "clean": false, "unknownArtist": false, "unknownTitle": false },
|
|
132
|
+
"junk": [{ "raw": "Official Video", "kind": "video" }],
|
|
133
|
+
"warnings": []
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
(Reformatted for width; the keys and values are exactly what `to_dict()` returns.)
|
|
138
|
+
|
|
139
|
+
</details>
|
|
140
|
+
|
|
141
|
+
## API
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
parse(input, *, mode="auto", uploader=None, known_artists=(), split_and="auto", keywords=None) -> ParsedTrack
|
|
145
|
+
parse_artists(input, *, <same options>) -> list[Artist]
|
|
146
|
+
format_track(track, *, feat="source", feat_marker=None, joiners="original",
|
|
147
|
+
versions="all", producers=True, position=False) -> str
|
|
148
|
+
normalize(s) -> str
|
|
149
|
+
all_artists(track) -> list[Artist]
|
|
150
|
+
create_parser(**options) -> Parser # Parser.parse(...), Parser.parse_artists(...)
|
|
151
|
+
SPEC_VERSION, __version__
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### `parse(input, **options)`
|
|
155
|
+
|
|
156
|
+
| Option | Default | |
|
|
157
|
+
|---|---|---|
|
|
158
|
+
| `mode` | `"auto"` | `"clean"`, `"youtube"`, `"filename"`, or `"auto"` to detect from the string |
|
|
159
|
+
| `uploader` | `None` | YouTube channel name, used as the artist when there's no separator (`- Topic` and `VEVO` are dropped) |
|
|
160
|
+
| `known_artists` | `()` | Names that are never split. There is no built-in list |
|
|
161
|
+
| `split_and` | `"auto"` | `"auto"` splits on `and` only after a comma (`A, B and C`), so `Simon and Garfunkel` stays whole. Also `"always"`, `"never"` |
|
|
162
|
+
| `keywords` | `None` | Extra vocabulary, added to the built-in lists (see below) |
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
[a.name for a in parse("Chase & Status - Baddadan").artists]
|
|
166
|
+
# ['Chase', 'Status']
|
|
167
|
+
[a.name for a in parse("Chase & Status - Baddadan", known_artists=["Chase & Status"]).artists]
|
|
168
|
+
# ['Chase & Status']
|
|
169
|
+
|
|
170
|
+
[a.name for a in parse("Simon and Garfunkel - Mrs. Robinson", split_and="always").artists]
|
|
171
|
+
# ['Simon', 'Garfunkel']
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
`keywords` is a dict with any of these keys:
|
|
175
|
+
|
|
176
|
+
| Key | Type | |
|
|
177
|
+
|---|---|---|
|
|
178
|
+
| `version_heads` | `dict[str, VersionType]` | Word that sets a version type (`{"rerub": "rework"}`) |
|
|
179
|
+
| `descriptors` | `list[str]` | Words kept as a version's `descriptor` |
|
|
180
|
+
| `genres` | `list[str]` | Genre words |
|
|
181
|
+
| `junk` | `dict[str, JunkKind]` | Phrase to junk kind (`{"some label rip": "label"}`) |
|
|
182
|
+
| `feat_markers` | `list[str]` | Extra feat markers |
|
|
183
|
+
|
|
184
|
+
```python
|
|
185
|
+
parse("Artist - Song [Some Label Rip]").title
|
|
186
|
+
# 'Song [Some Label Rip]'
|
|
187
|
+
parse("Artist - Song [Some Label Rip]", keywords={"junk": {"some label rip": "label"}}).junk
|
|
188
|
+
# (Junk(raw='Some Label Rip', kind='label'),)
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Parsing is total: any `str` gives a `ParsedTrack`, including `""`. Bad option values fall back to
|
|
192
|
+
the defaults. Passing something that isn't a `str` raises `TypeError`.
|
|
193
|
+
|
|
194
|
+
### `ParsedTrack`
|
|
195
|
+
|
|
196
|
+
| Attribute | Type | |
|
|
197
|
+
|---|---|---|
|
|
198
|
+
| `input` | `str` | The original string, untouched |
|
|
199
|
+
| `mode` | `"clean" \| "youtube" \| "filename"` | Resolved mode |
|
|
200
|
+
| `position` | `Position \| None` | Track number: `Position(raw='01', number=1)` |
|
|
201
|
+
| `timestamp` | `Timestamp \| None` | Leading cue time: `[12:34]` gives `Timestamp(raw='12:34', seconds=754)` |
|
|
202
|
+
| `artists` | `tuple[Artist, ...]` | Primary, featured and producer credits. Remixers live in `versions` |
|
|
203
|
+
| `title` | `str` | Clean title. Brackets it doesn't recognise stay in |
|
|
204
|
+
| `full_title` | `str` | `title` with the versions re-attached |
|
|
205
|
+
| `versions` | `tuple[Version, ...]` | Remixes and other versions, left to right |
|
|
206
|
+
| `year` | `int \| None` | From `(2015)` or `- 2015` |
|
|
207
|
+
| `flags` | `Flags` | `explicit`, `clean`, `unknown_artist`, `unknown_title` |
|
|
208
|
+
| `junk` | `tuple[Junk, ...]` | What was taken out, each with a `kind` |
|
|
209
|
+
| `warnings` | `tuple[Warning, ...]` | Heuristics that fired |
|
|
210
|
+
|
|
211
|
+
`Artist(name, role, joiner, source)`:
|
|
212
|
+
|
|
213
|
+
- `role` is `"primary"`, `"featured"`, `"remixer"` or `"producer"`.
|
|
214
|
+
- `joiner` is the text before the name as written (`"&"`, `","`, `"ft."`, `"prod. by"`), or `None`
|
|
215
|
+
for the first name in a list.
|
|
216
|
+
- `source` is where the credit came from: `"artist"` (left of the dash), `"title"` (right of it)
|
|
217
|
+
or `"version"`.
|
|
218
|
+
|
|
219
|
+
`Version(type, raw, artists, modifiers, descriptor, year, unknown_artist, delimiter)`:
|
|
220
|
+
|
|
221
|
+
```python
|
|
222
|
+
parse("Pendulum - Watercolour (Extended VIP Mix)").versions[0]
|
|
223
|
+
# Version(type='vip', raw='Extended VIP Mix', artists=(), modifiers=('extended',),
|
|
224
|
+
# descriptor=None, year=None, unknown_artist=False, delimiter='(')
|
|
225
|
+
|
|
226
|
+
parse("Queen - Bohemian Rhapsody (Live at Wembley 1986)").versions[0]
|
|
227
|
+
# Version(type='live', raw='Live at Wembley 1986', artists=(), modifiers=(),
|
|
228
|
+
# descriptor='at Wembley', year=1986, unknown_artist=False, delimiter='(')
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Version types, junk kinds and warnings are listed in the
|
|
232
|
+
[main README](https://github.com/4matic/trackparse#api) and typed as `Literal` aliases
|
|
233
|
+
(`VersionType`, `JunkKind`, `Warning`, `ArtistRole`, `ArtistSource`, `Mode`, `ModeOption`,
|
|
234
|
+
`VersionDelimiter`).
|
|
235
|
+
|
|
236
|
+
### `to_dict()` and `from_dict()`
|
|
237
|
+
|
|
238
|
+
Every result class has `to_dict()`, which returns plain dicts and lists matching
|
|
239
|
+
[`parsed-track.schema.json`](https://github.com/4matic/trackparse/blob/main/spec/schema/parsed-track.schema.json)
|
|
240
|
+
(`fullTitle`, `unknownArtist`, ...). With `json.dumps(track.to_dict(), separators=(",", ":"),
|
|
241
|
+
ensure_ascii=False)` the bytes are the same as `JSON.stringify(parse(s))` in JS. The `from_dict()`
|
|
242
|
+
classmethod goes the other way:
|
|
243
|
+
|
|
244
|
+
```python
|
|
245
|
+
ParsedTrack.from_dict(track.to_dict()) == track # True
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### `parse_artists(input, **options)`
|
|
249
|
+
|
|
250
|
+
Splits a bare artist string. Takes the same options as `parse`.
|
|
251
|
+
|
|
252
|
+
```python
|
|
253
|
+
from trackparse import parse_artists
|
|
254
|
+
|
|
255
|
+
[f"{a.name}:{a.role}" for a in parse_artists("Sub Focus, Wilkinson & Dimension feat. Kojo")]
|
|
256
|
+
# ['Sub Focus:primary', 'Wilkinson:primary', 'Dimension:primary', 'Kojo:featured']
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### `format_track(track, **options)`
|
|
260
|
+
|
|
261
|
+
Renders a track back to a string.
|
|
262
|
+
|
|
263
|
+
| Option | Default | |
|
|
264
|
+
|---|---|---|
|
|
265
|
+
| `feat` | `"source"` | Where featured artists go: `"source"` (where they were), `"artist"`, `"title"` or `"omit"` |
|
|
266
|
+
| `feat_marker` | `None` | Marker for featured artists, such as `"feat."` |
|
|
267
|
+
| `joiners` | `"original"` | `"original"` keeps the joiners as written, `"canonical"` uses the standard ones |
|
|
268
|
+
| `versions` | `"all"` | `"all"` or `"none"` |
|
|
269
|
+
| `producers` | `True` | Include `(prod. ...)` credits |
|
|
270
|
+
| `position` | `False` | Include the track number |
|
|
271
|
+
|
|
272
|
+
```python
|
|
273
|
+
from trackparse import format_track
|
|
274
|
+
|
|
275
|
+
format_track(
|
|
276
|
+
parse("Wilkinson ft. Becky Hill - Afterglow (Sub Focus Remix)"),
|
|
277
|
+
feat="title",
|
|
278
|
+
joiners="canonical",
|
|
279
|
+
)
|
|
280
|
+
# 'Wilkinson - Afterglow (feat. Becky Hill) (Sub Focus Remix)'
|
|
281
|
+
|
|
282
|
+
format_track(track, feat="omit", versions="none")
|
|
283
|
+
# 'Wilkinson - Afterglow'
|
|
284
|
+
format_track(track, position=True)
|
|
285
|
+
# '01. Wilkinson ft. Becky Hill & Tom Cane - Afterglow (Sub Focus VIP Remix)'
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### `normalize(s)`
|
|
289
|
+
|
|
290
|
+
The Unicode cleanup every function runs first: NFC, invisible characters, and dash, bracket and
|
|
291
|
+
quote variants.
|
|
292
|
+
|
|
293
|
+
```python
|
|
294
|
+
from trackparse import normalize
|
|
295
|
+
|
|
296
|
+
normalize("Noisia – Stigma (VIP)")
|
|
297
|
+
# 'Noisia - Stigma (VIP)'
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### `all_artists(track)`
|
|
301
|
+
|
|
302
|
+
Credits plus every remixer, deduplicated.
|
|
303
|
+
|
|
304
|
+
```python
|
|
305
|
+
from trackparse import all_artists
|
|
306
|
+
|
|
307
|
+
[(a.name, a.role) for a in all_artists(track)]
|
|
308
|
+
# [('Wilkinson', 'primary'), ('Becky Hill', 'featured'), ('Tom Cane', 'featured'), ('Sub Focus', 'remixer')]
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### `create_parser(**options)`
|
|
312
|
+
|
|
313
|
+
Compiles the options once, for many calls. Per-call options are merged over the parser's: a value
|
|
314
|
+
passed per call replaces the base one (`known_artists` included), `None` leaves it alone, and
|
|
315
|
+
`keywords` dicts are merged.
|
|
316
|
+
|
|
317
|
+
```python
|
|
318
|
+
from trackparse import create_parser
|
|
319
|
+
|
|
320
|
+
parser = create_parser(
|
|
321
|
+
known_artists=["Chase & Status", "Camo & Krooked"],
|
|
322
|
+
keywords={"version_heads": {"rerub": "rework"}},
|
|
323
|
+
)
|
|
324
|
+
parser.parse("Chase & Status x Camo & Krooked - Track (Alix Perez Rerub)").versions[0].type
|
|
325
|
+
# 'rework'
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### `SPEC_VERSION`
|
|
329
|
+
|
|
330
|
+
The spec version this build implements. The PyPI package, the npm package and the spec are
|
|
331
|
+
released together and always share one version number.
|
|
332
|
+
|
|
333
|
+
## Modes
|
|
334
|
+
|
|
335
|
+
| Mode | For | Adds |
|
|
336
|
+
|---|---|---|
|
|
337
|
+
| `clean` | Store metadata, tags | Only a spaced dash separates artist and title |
|
|
338
|
+
| `youtube` | Video titles, scrobbles | Pipe segments, `"Title" by Artist`, `Artist: "Title"`, unspaced dashes, trailing junk, uploader fallback |
|
|
339
|
+
| `filename` | Files | Strips the extension, `_` becomes a space, `01-Track` numbering |
|
|
340
|
+
| `auto` | Anything | `filename` for known extensions, `youtube` when it sees junk, pipes or platform suffixes, `clean` otherwise |
|
|
341
|
+
|
|
342
|
+
```python
|
|
343
|
+
t = parse('"Numb" by Linkin Park', mode="youtube")
|
|
344
|
+
[a.name for a in t.artists], t.title, t.warnings
|
|
345
|
+
# (['Linkin Park'], 'Numb', ('bySplit',))
|
|
346
|
+
|
|
347
|
+
t = parse("07_Burial_-_Archangel.mp3")
|
|
348
|
+
t.mode, [a.name for a in t.artists], t.title, t.position
|
|
349
|
+
# ('filename', ['Burial'], 'Archangel', Position(raw='07', number=7))
|
|
350
|
+
|
|
351
|
+
parse("Shelter", mode="youtube", uploader="Porter Robinson").artists
|
|
352
|
+
# (Artist(name='Porter Robinson', role='primary', joiner=None, source='artist'),)
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
## Typing
|
|
356
|
+
|
|
357
|
+
The package ships `py.typed` and passes `mypy --strict`. `ParsedTrack`, `Artist`, `Version`,
|
|
358
|
+
`Junk`, `Flags`, `Position` and `Timestamp` are frozen dataclasses with snake_case attributes.
|
|
359
|
+
Sequences are tuples, so results are immutable and hashable and can go in sets or be used as dict
|
|
360
|
+
keys. Option dicts have `TypedDict` types (`ParseOptions`, `KeywordOptions`, `FormatOptions`).
|
|
361
|
+
|
|
362
|
+
## Development
|
|
363
|
+
|
|
364
|
+
From `python/` in the [repository](https://github.com/4matic/trackparse), with
|
|
365
|
+
[uv](https://docs.astral.sh/uv/):
|
|
366
|
+
|
|
367
|
+
```sh
|
|
368
|
+
uv sync
|
|
369
|
+
uv run python scripts/gen_data.py --check # spec/data codegen is up to date
|
|
370
|
+
uv run ruff check . && uv run ruff format --check .
|
|
371
|
+
uv run mypy --strict src
|
|
372
|
+
uv run pytest # every shared fixture, properties, API
|
|
373
|
+
uv run python scripts/conformance.py # pass rate per fixture file and per rule
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
Modules mirror the JS reference one to one (`_scanner.py` is `scanner.ts`, and so on) and cite the
|
|
377
|
+
same spec rule IDs. Behaviour changes start in the spec, not here: see
|
|
378
|
+
[AGENTS.md](https://github.com/4matic/trackparse/blob/main/AGENTS.md).
|
|
379
|
+
|
|
380
|
+
## Links
|
|
381
|
+
|
|
382
|
+
- [Spec](https://github.com/4matic/trackparse/blob/main/spec/SPEC.md)
|
|
383
|
+
- [Project README](https://github.com/4matic/trackparse#readme), with a table of real outputs
|
|
384
|
+
- [Changelog](https://github.com/4matic/trackparse/blob/main/python/CHANGELOG.md)
|
|
385
|
+
- [npm package](https://www.npmjs.com/package/trackparse)
|
|
386
|
+
|
|
387
|
+
## License
|
|
388
|
+
|
|
389
|
+
[MIT](https://github.com/4matic/trackparse/blob/main/LICENSE)
|