diff --git a/CHANGELOG.md b/CHANGELOG.md index 89e2965..6c9c422 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,9 +5,15 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] +## [1.0.1] - 2025-11-30 + +### Added +- Support for some new REM keywords in parser. + ### Fixed -- Fixed metadata and URLs in `pyproject.toml`. +- Metadata and URLs in `pyproject.toml`. - Improved formatting and clarity in `CHANGELOG.md`, added older versions. +- Track index validation. Now, when INDEX 00 is greater than 0, everything works correctly. ## [1.0.0] - 2025-11-27 @@ -82,6 +88,7 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). [Unreleased]: https://github.com/Olezhich/CueTools/compare/v1.0.0...main +[1.0.1]: https://github.com/Olezhich/CueTools/compare/v1.0.1...v1.0.1 [1.0.0]: https://github.com/Olezhich/CueTools/compare/v0.1.5...v1.0.0 [0.1.5]: https://github.com/Olezhich/CueTools/compare/v0.1.4...v0.1.5 [0.1.4]: https://github.com/Olezhich/CueTools/compare/v0.1.3...v0.1.4 diff --git a/cuetools/models.py b/cuetools/models.py index 02ae672..226980b 100644 --- a/cuetools/models.py +++ b/cuetools/models.py @@ -1,6 +1,6 @@ from __future__ import annotations from pathlib import Path -from pydantic import BaseModel, ConfigDict, Field, model_validator +from pydantic import BaseModel, ConfigDict, Field from cuetools.types import FrameTime, ReplayGainGain, ReplayGainPeak from cuetools.types.frame_time import FrameTimeCls @@ -32,11 +32,9 @@ class TrackData(BaseModel): description="The index 01 (the beginning of the current track), corresponds to the line like *'INDEX 01 00:00:00'*", ) - @model_validator(mode='after') - def validate_index(self) -> TrackData: + def validate_index(self) -> None: if self.index00 is not None and self.index00.frames > self.index01.frames: raise ValueError('Expected INDEX 00 <= INDEX 01') - return self def set_performer(self, performer: TitleCase) -> None: """Set track performer with a **Title Case** validation using `TitleCase` class consructor for string""" @@ -80,6 +78,7 @@ class AlbumData(BaseModel): ) def add_track(self, track: TrackData) -> None: + track.validate_index() self.tracks.append(track) def set_performer(self, performer: TitleCase) -> None: diff --git a/cuetools/parser/lex.py b/cuetools/parser/lex.py index d825f4f..7e107c6 100644 --- a/cuetools/parser/lex.py +++ b/cuetools/parser/lex.py @@ -14,8 +14,14 @@ class Token(Enum): REM = r'(REM)\b' GENRE = r'(GENRE)\b' DATE = r'(DATE)\b' + REPLAYGAIN_ALBUM_GAIN = r'(REPLAYGAIN_ALBUM_GAIN)\b' REPLAYGAIN_ALBUM_PEAK = r'(REPLAYGAIN_ALBUM_PEAK)\b' + REPLAYGAIN_TRACK_GAIN = r'(REPLAYGAIN_TRACK_GAIN)\b' + REPLAYGAIN_TRACK_PEAK = r'(REPLAYGAIN_TRACK_PEAK)\b' + + DISCID = r'(DISCID)\b' + COMMENT = r'(COMMENT)\b' TRACK = r'TRACK (\d\d+)\b' AUDIO = r'(AUDIO)\b' diff --git a/cuetools/parser/parser.py b/cuetools/parser/parser.py index bbb5dfa..0150532 100644 --- a/cuetools/parser/parser.py +++ b/cuetools/parser/parser.py @@ -142,6 +142,14 @@ def load_f_iter(cue: Iterator[str], strict_title_case: bool = False) -> AlbumDat raise CueValidationError( current_line, line, value.pos, value.lexeme, e ) + case Token.DISCID: + ... + case Token.COMMENT: + ... + case Token.REPLAYGAIN_TRACK_GAIN: + ... + case Token.REPLAYGAIN_TRACK_PEAK: + ... case _: raise CueParseError( current_line, @@ -168,7 +176,12 @@ def load_f_iter(cue: Iterator[str], strict_title_case: bool = False) -> AlbumDat tokens[0].pos, ) if current_track: - album.add_track(current_track) + try: + album.add_track(current_track) + except ValueError as e: + raise CueValidationError( + current_line, line, 0, 'Invalid previous track', e + ) current_track = TrackData( track=int(tokens[0].lexeme), file=current_file[0], @@ -195,6 +208,7 @@ def load_f_iter(cue: Iterator[str], strict_title_case: bool = False) -> AlbumDat current_track.index00 = tokens[1].lexeme # type: ignore[assignment] elif index_type == 1: current_track.index01 = tokens[1].lexeme # type: ignore[assignment] + print('INDEX WARN:: ', tokens[1].lexeme, current_track.index01) case _: raise CueParseError( current_line, diff --git a/tests/conftest.py b/tests/conftest.py index f634e34..014e640 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -232,71 +232,54 @@ def obj_sample_one_file_many_tracks() -> AlbumData: @pytest.fixture() -def cue_sample_rem(): - album = album_rem_gen( - genre='Hard Rock', - date='1969', - replaygain_album_gain='-4.10 db', - replaygain_album_peak='0.987654', - ) - return '\n'.join(album) - - -@pytest.fixture() -def obj_sample_rem() -> AlbumData: - album = AlbumData( - rem=RemData( - genre='Hard Rock', - date=1969, - replaygain_album_gain='-4.10 db', # type: ignore - replaygain_album_peak='0.987654', # type: ignore - ) - ) - return album - - -if __name__ == '__main__': - print('___album rem tests___') - - print(album_rem_gen(genre='Rock', date='1969', comment='comment')) - print( - album_rem_gen( - genre=['Hard Rock', 'Progressive Rock', 'Rock'], - date='1969', - comment='comment', - ) - ) - print(album_rem_gen(quotes=True, genre='Rock', date='1969', comment='comment')) - print( - album_rem_gen( - quotes=True, - genre=['Hard Rock', 'Progressive Rock', 'Rock'], - date='1969', - comment='comment', - ) - ) - - print('___album meta tests___') - - print(album_meta_gen(performer='The Performer', title='The Title Of Album')) - print( - album_meta_gen( - quotes=True, performer='The Performer', title='The Title Of Album' - ) - ) - - print('___another tests___') - - print(track_gen('song.flac', 'song', 3)) - print(track_gen('song 05.flac', 'song', 1)) - print([track_gen(f'0{i} - Song 0{i}.flac', f'Song {i}') for i in range(1, 8)]) - - print('___cue_gen_test___') - - cuesheet = album_rem_default() + album_meta_default() - cuesheet += [track_gen(f'0{i} - Song 0{i}.flac', f'Song {i}') for i in range(1, 8)] - # for i in cuesheet: - # print(i.endswith('\n'), i) - - # cuesheet = [i[-1:] if i.endswith('\n') else i for i in cuesheet] - print('\n'.join(cuesheet)) +def cue_sample_real() -> str: + return """REM GENRE Hard Rock +REM DATE 1972 +REM DISCID 12345678 +REM COMMENT ExactAudioCopy v1.0b3 +PERFORMER "Scorpions" +TITLE "Lonesome Crow" +REM REPLAYGAIN_ALBUM_GAIN -7.99 dB +REM REPLAYGAIN_ALBUM_PEAK 1.054599 +FILE "Scorpions - Lonesome Crow.flac" WAVE + TRACK 01 AUDIO + TITLE "I'm Going Mad" + REM REPLAYGAIN_TRACK_GAIN -7.97 dB + REM REPLAYGAIN_TRACK_PEAK 1.033902 + INDEX 01 00:00:00 + TRACK 02 AUDIO + TITLE "It All Depends" + REM REPLAYGAIN_TRACK_GAIN -8.17 dB + REM REPLAYGAIN_TRACK_PEAK 1.054599 + INDEX 01 04:53:27 + TRACK 03 AUDIO + TITLE "Leave Me" + REM REPLAYGAIN_TRACK_GAIN -8.56 dB + REM REPLAYGAIN_TRACK_PEAK 1.053317 + INDEX 00 08:17:15 + INDEX 01 08:18:42 + TRACK 04 AUDIO + TITLE "In Search Of The Peace Of Mind" + REM REPLAYGAIN_TRACK_GAIN -6.73 dB + REM REPLAYGAIN_TRACK_PEAK 1.035224 + INDEX 00 13:21:00 + INDEX 01 13:22:52 + TRACK 05 AUDIO + TITLE "Inheritance" + REM REPLAYGAIN_TRACK_GAIN -8.17 dB + REM REPLAYGAIN_TRACK_PEAK 1.038148 + INDEX 00 18:16:10 + INDEX 01 18:17:62 + TRACK 06 AUDIO + TITLE "Action" + REM REPLAYGAIN_TRACK_GAIN -8.43 dB + REM REPLAYGAIN_TRACK_PEAK 1.031126 + INDEX 00 22:55:50 + INDEX 01 22:58:17 + TRACK 07 AUDIO + TITLE "Lonesome Crow" + REM REPLAYGAIN_TRACK_GAIN -7.85 dB + REM REPLAYGAIN_TRACK_PEAK 1.042687 + INDEX 00 26:51:00 + INDEX 01 26:52:37 +""" diff --git a/tests/test_parser.py b/tests/test_parser.py index 050a7fa..5f9ac53 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -56,6 +56,12 @@ def test_one_file_many_tracks( == obj_sample_one_file_many_tracks_strict ) + def test_real_cue(self, cue_sample_real: str): + res = cuetools.loads(cue_sample_real, strict_title_case=True) + assert res.performer == 'Scorpions', ( + 'Thats correct if it can parse the entire file without throwing an errors' + ) + def test_line_parsing(): cue_sheet = """PERFORMER TITLE""" @@ -113,3 +119,10 @@ def test_line_parsing(): cue = cuetools.loads(cue_sheet) logger.debug(cue) + + cue_sheet = """FILE "track.flac" WAVE + TRACK 01 AUDIO + INDEX 00 00:00:50 + INDEX 01 00:00:65""" + cue = cuetools.loads(cue_sheet) + logger.debug(cue)