tocparser 0.1.0__py3-none-any.whl
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.
- tocparser/__init__.py +88 -0
- tocparser/errors.py +80 -0
- tocparser/grammar.lark +89 -0
- tocparser/models.py +739 -0
- tocparser/parser.py +695 -0
- tocparser/py.typed +0 -0
- tocparser/serializer.py +243 -0
- tocparser/times.py +92 -0
- tocparser-0.1.0.dist-info/METADATA +205 -0
- tocparser-0.1.0.dist-info/RECORD +12 -0
- tocparser-0.1.0.dist-info/WHEEL +4 -0
- tocparser-0.1.0.dist-info/licenses/LICENSE.txt +201 -0
tocparser/__init__.py
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""Parse and write cdrdao TOC files.
|
|
2
|
+
|
|
3
|
+
>>> from tocparser import parse, dumps
|
|
4
|
+
>>> toc = parse('CD_DA\\nTRACK AUDIO\\nFILE "hurt.wav" 0\\n')
|
|
5
|
+
>>> toc.tracks[0].mode.value
|
|
6
|
+
'AUDIO'
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from importlib.metadata import version
|
|
12
|
+
|
|
13
|
+
from tocparser.errors import (
|
|
14
|
+
Loc,
|
|
15
|
+
TocError,
|
|
16
|
+
TocParseError,
|
|
17
|
+
TocValidationError,
|
|
18
|
+
)
|
|
19
|
+
from tocparser.models import (
|
|
20
|
+
CdText,
|
|
21
|
+
CdTextBlock,
|
|
22
|
+
CdTextEncoding,
|
|
23
|
+
CdTextItemName,
|
|
24
|
+
CdTextValue,
|
|
25
|
+
ContentItem,
|
|
26
|
+
DataFile,
|
|
27
|
+
DataMode,
|
|
28
|
+
DiscType,
|
|
29
|
+
End,
|
|
30
|
+
Fifo,
|
|
31
|
+
File,
|
|
32
|
+
Silence,
|
|
33
|
+
Start,
|
|
34
|
+
SubChannelMode,
|
|
35
|
+
Toc,
|
|
36
|
+
Track,
|
|
37
|
+
TrackMode,
|
|
38
|
+
TrackStatement,
|
|
39
|
+
Zero,
|
|
40
|
+
)
|
|
41
|
+
from tocparser.parser import parse, parse_file
|
|
42
|
+
from tocparser.serializer import dump, dumps
|
|
43
|
+
from tocparser.times import (
|
|
44
|
+
FRAMES_PER_SECOND,
|
|
45
|
+
SAMPLES_PER_FRAME,
|
|
46
|
+
SECONDS_PER_MINUTE,
|
|
47
|
+
Msf,
|
|
48
|
+
Time,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
__version__ = version("tocparser")
|
|
52
|
+
|
|
53
|
+
__all__ = [
|
|
54
|
+
"FRAMES_PER_SECOND",
|
|
55
|
+
"SAMPLES_PER_FRAME",
|
|
56
|
+
"SECONDS_PER_MINUTE",
|
|
57
|
+
"CdText",
|
|
58
|
+
"CdTextBlock",
|
|
59
|
+
"CdTextEncoding",
|
|
60
|
+
"CdTextItemName",
|
|
61
|
+
"CdTextValue",
|
|
62
|
+
"ContentItem",
|
|
63
|
+
"DataFile",
|
|
64
|
+
"DataMode",
|
|
65
|
+
"DiscType",
|
|
66
|
+
"End",
|
|
67
|
+
"Fifo",
|
|
68
|
+
"File",
|
|
69
|
+
"Loc",
|
|
70
|
+
"Msf",
|
|
71
|
+
"Silence",
|
|
72
|
+
"Start",
|
|
73
|
+
"SubChannelMode",
|
|
74
|
+
"Time",
|
|
75
|
+
"Toc",
|
|
76
|
+
"TocError",
|
|
77
|
+
"TocParseError",
|
|
78
|
+
"TocValidationError",
|
|
79
|
+
"Track",
|
|
80
|
+
"TrackMode",
|
|
81
|
+
"TrackStatement",
|
|
82
|
+
"Zero",
|
|
83
|
+
"__version__",
|
|
84
|
+
"dump",
|
|
85
|
+
"dumps",
|
|
86
|
+
"parse",
|
|
87
|
+
"parse_file",
|
|
88
|
+
]
|
tocparser/errors.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""Exceptions raised by :mod:`tocparser`."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TypeAlias
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"Loc",
|
|
9
|
+
"TocError",
|
|
10
|
+
"TocParseError",
|
|
11
|
+
"TocValidationError",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
#: A path into a model, made of field names and indexes, e.g.
|
|
15
|
+
#: ``("tracks", 0, "statements", 2)``.
|
|
16
|
+
Loc: TypeAlias = tuple[str | int, ...]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TocError(Exception):
|
|
20
|
+
"""Base class for every error raised by :mod:`tocparser`."""
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class TocParseError(TocError):
|
|
24
|
+
"""The input is not syntactically valid.
|
|
25
|
+
|
|
26
|
+
Mirrors cdrdao's ``file:line: message`` reporting.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
message: str,
|
|
32
|
+
*,
|
|
33
|
+
line: int | None = None,
|
|
34
|
+
column: int | None = None,
|
|
35
|
+
filename: str | None = None,
|
|
36
|
+
) -> None:
|
|
37
|
+
self.message = message
|
|
38
|
+
self.line = line
|
|
39
|
+
self.column = column
|
|
40
|
+
self.filename = filename
|
|
41
|
+
super().__init__(_format_location(message, filename, line, column))
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class TocValidationError(TocError):
|
|
45
|
+
"""The input breaks a rule cdrdao itself enforces.
|
|
46
|
+
|
|
47
|
+
``loc`` locates the problem inside the model that raised it, so an error
|
|
48
|
+
raised while building models by hand still says where it is. ``line`` is
|
|
49
|
+
only known when the error comes from parsing text.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
def __init__(
|
|
53
|
+
self,
|
|
54
|
+
message: str,
|
|
55
|
+
*,
|
|
56
|
+
line: int | None = None,
|
|
57
|
+
filename: str | None = None,
|
|
58
|
+
loc: Loc = (),
|
|
59
|
+
) -> None:
|
|
60
|
+
self.message = message
|
|
61
|
+
self.line = line
|
|
62
|
+
self.filename = filename
|
|
63
|
+
self.loc = loc
|
|
64
|
+
super().__init__(_format_location(message, filename, line, None))
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _format_location(
|
|
68
|
+
message: str,
|
|
69
|
+
filename: str | None,
|
|
70
|
+
line: int | None,
|
|
71
|
+
column: int | None,
|
|
72
|
+
) -> str:
|
|
73
|
+
location = ""
|
|
74
|
+
if filename is not None:
|
|
75
|
+
location += f"{filename}:"
|
|
76
|
+
if line is not None:
|
|
77
|
+
location += f"{line}:"
|
|
78
|
+
if column is not None:
|
|
79
|
+
location += f"{column}:"
|
|
80
|
+
return f"{location} {message}" if location else message
|
tocparser/grammar.lark
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// Grammar for cdrdao TOC files.
|
|
2
|
+
//
|
|
3
|
+
// Derived from cdrdao's own ANTLR grammar, trackdb/TocParser.g at tag
|
|
4
|
+
// rel_1_2_6, and the TOC FILES section of cdrdao(1). Where the two disagree
|
|
5
|
+
// the grammar is the authority: FIRST_TRACK_NO, END, CD_I, MODE0, SWAP,
|
|
6
|
+
// #offset, RESERVED1..RESERVED4, CLOSED, ENCODING_* and the string escapes are
|
|
7
|
+
// all missing from the man page. Rule and token names follow TocParser.g so the
|
|
8
|
+
// two can be compared side by side.
|
|
9
|
+
|
|
10
|
+
toc: (catalog | disc_type)* first_track_no? cd_text_global? track+
|
|
11
|
+
|
|
12
|
+
catalog: "CATALOG" STRING
|
|
13
|
+
disc_type: DISC_TYPE
|
|
14
|
+
first_track_no: "FIRST_TRACK_NO" INT
|
|
15
|
+
|
|
16
|
+
track: "TRACK" MODE SUB_CHANNEL_MODE? track_flag* cd_text_track? pregap? \
|
|
17
|
+
track_statement+ index*
|
|
18
|
+
|
|
19
|
+
?track_flag: isrc | copy | pre_emphasis | channels
|
|
20
|
+
isrc: "ISRC" STRING
|
|
21
|
+
copy: NO? "COPY"
|
|
22
|
+
pre_emphasis: NO? "PRE_EMPHASIS"
|
|
23
|
+
channels: CHANNELS
|
|
24
|
+
|
|
25
|
+
pregap: "PREGAP" msf
|
|
26
|
+
index: "INDEX" msf
|
|
27
|
+
|
|
28
|
+
?track_statement: file | datafile | fifo | silence | zero | start | end
|
|
29
|
+
|
|
30
|
+
file: FILE_KEYWORD STRING SWAP? offset? time time?
|
|
31
|
+
datafile: "DATAFILE" STRING offset? time?
|
|
32
|
+
fifo: "FIFO" STRING time
|
|
33
|
+
silence: "SILENCE" time
|
|
34
|
+
zero: "ZERO" MODE? SUB_CHANNEL_MODE? time
|
|
35
|
+
start: "START" msf?
|
|
36
|
+
end: "END" msf?
|
|
37
|
+
|
|
38
|
+
offset: "#" INT
|
|
39
|
+
msf: MSF
|
|
40
|
+
time: MSF | INT
|
|
41
|
+
|
|
42
|
+
// A global CD_TEXT block may open with a language map; a track one may not.
|
|
43
|
+
cd_text_global: "CD_TEXT" "{" language_map? cd_text_block* "}"
|
|
44
|
+
cd_text_track: "CD_TEXT" "{" cd_text_block* "}"
|
|
45
|
+
|
|
46
|
+
language_map: "LANGUAGE_MAP" "{" language_map_entry+ "}"
|
|
47
|
+
language_map_entry: INT ":" language_code
|
|
48
|
+
language_code: INT | LANGUAGE_EN
|
|
49
|
+
|
|
50
|
+
// cdrdao 1.2.5 added an optional encoding at the top of a language block.
|
|
51
|
+
cd_text_block: "LANGUAGE" INT "{" CD_TEXT_ENCODING? cd_text_item* "}"
|
|
52
|
+
cd_text_item: CD_TEXT_ITEM cd_text_value
|
|
53
|
+
cd_text_value: STRING | binary_data
|
|
54
|
+
binary_data: "{" (INT ("," INT)*)? "}"
|
|
55
|
+
|
|
56
|
+
NO: "NO"
|
|
57
|
+
SWAP: "SWAP"
|
|
58
|
+
LANGUAGE_EN: "EN"
|
|
59
|
+
CHANNELS: "TWO_CHANNEL_AUDIO" | "FOUR_CHANNEL_AUDIO"
|
|
60
|
+
FILE_KEYWORD: "AUDIOFILE" | "FILE"
|
|
61
|
+
DISC_TYPE: "CD_DA" | "CD_ROM_XA" | "CD_ROM" | "CD_I"
|
|
62
|
+
|
|
63
|
+
CD_TEXT_ENCODING: "ENCODING_ISO_8859_1" | "ENCODING_ASCII" | "ENCODING_MS_JIS" \
|
|
64
|
+
| "ENCODING_KOREAN" | "ENCODING_MANDARIN"
|
|
65
|
+
|
|
66
|
+
// MODE0 is only legal after ZERO; the transformer rejects it as a track mode.
|
|
67
|
+
MODE: "MODE1_RAW" | "MODE1" | "MODE2_FORM_MIX" | "MODE2_FORM1" | "MODE2_FORM2" \
|
|
68
|
+
| "MODE2_RAW" | "MODE2" | "MODE0" | "AUDIO"
|
|
69
|
+
|
|
70
|
+
SUB_CHANNEL_MODE: "RW_RAW" | "RW"
|
|
71
|
+
|
|
72
|
+
CD_TEXT_ITEM: "TITLE" | "PERFORMER" | "SONGWRITER" | "COMPOSER" | "ARRANGER" \
|
|
73
|
+
| "MESSAGE" | "DISC_ID" | "GENRE" | "TOC_INFO1" | "TOC_INFO2" \
|
|
74
|
+
| "RESERVED1" | "RESERVED2" | "RESERVED3" | "RESERVED4" \
|
|
75
|
+
| "CLOSED" \
|
|
76
|
+
| "UPC_EAN" | "ISRC" | "SIZE_INFO"
|
|
77
|
+
|
|
78
|
+
// Inside a string a backslash may only introduce another backslash, a quote,
|
|
79
|
+
// or three digits; cdrdao rejects any other use of one. Raw newlines and tabs
|
|
80
|
+
// are accepted, as cdrdao accepts them.
|
|
81
|
+
STRING: /"(?:\\[0-9][0-9][0-9]|\\["\\]|[^"\\])*"/
|
|
82
|
+
|
|
83
|
+
MSF.2: /[0-9]+:[0-9]+:[0-9]+/
|
|
84
|
+
INT: /[0-9]+/
|
|
85
|
+
COMMENT: /\/\/[^\n]*/
|
|
86
|
+
|
|
87
|
+
%import common.WS
|
|
88
|
+
%ignore WS
|
|
89
|
+
%ignore COMMENT
|