diff --git a/pymkv/MKVFile.py b/pymkv/MKVFile.py index 8de3221..fd1bd20 100644 --- a/pymkv/MKVFile.py +++ b/pymkv/MKVFile.py @@ -435,7 +435,7 @@ def add_attachment(self, attachment: str | MKVAttachment) -> None: msg = "Attachment is not str of MKVAttachment" raise TypeError(msg) - def get_track(self, track_num: int | None = None) -> MKVTrack: + def get_track(self, track_num: int | None = None) -> MKVTrack | list[MKVTrack]: """Get a :class:`~pymkv.MKVTrack` from the :class:`~pymkv.MKVFile` object. Parameters diff --git a/pymkv/Timestamp.py b/pymkv/Timestamp.py index 96a0af5..bf9dedd 100644 --- a/pymkv/Timestamp.py +++ b/pymkv/Timestamp.py @@ -1,6 +1,6 @@ from __future__ import annotations -from re import match +import re class Timestamp: @@ -175,7 +175,7 @@ def __getitem__(self, index: int) -> int: def ts(self) -> str: """Generates the timestamp specified in the object.""" # parse timestamp format - format_groups = match(r"^(([Hh]{1,2}):)?([Mm]{1,2}):([Ss]{1,2})(\.([Nn]{1,9}))?$", self.form).groups() + format_groups = re.match(r"^(([Hh]{1,2}):)?([Mm]{1,2}):([Ss]{1,2})(\.([Nn]{1,9}))?$", self.form).groups() timestamp_format = [format_groups[i] is not None for i in (1, 2, 3, 5)] # create timestamp string @@ -248,16 +248,16 @@ def form(self, form: str) -> None: self._form = form @staticmethod - def verify(timestamp: str | int) -> bool: + def verify(timestamp: str) -> bool: """Verify a timestamp has the proper form to be used in mkvmerge. - timestamp (str, int): + timestamp (str): The timestamp to be verified. """ if not isinstance(timestamp, str): msg = f'"{type(timestamp)}" is not str type' raise TypeError(msg) - elif match(r"^[0-9]{1,2}(:[0-9]{1,2}){1,2}(\.[0-9]{1,9})?$", timestamp): # noqa: RET506 + elif re.match(r"^[0-9]{1,2}(:[0-9]{1,2}){1,2}(\.[0-9]{1,9})?$", timestamp): # noqa: RET506 return True return False @@ -305,7 +305,7 @@ def splitting_timestamp(self, timestamp: str) -> None: The seconds (self.ss) will be set to 56. The nanoseconds (self.nn) will be set to 789012345. """ - timestamp_groups = match(r"^(([0-9]{1,2}):)?([0-9]{1,2}):([0-9]{1,2})(\.([0-9]{1,9}))?$", timestamp).groups() + timestamp_groups = re.match(r"^(([0-9]{1,2}):)?([0-9]{1,2}):([0-9]{1,2})(\.([0-9]{1,9}))?$", timestamp).groups() timestamp = [timestamp_groups[i] for i in (1, 2, 3, 4)] timestamp_clean = [] diff --git a/tests/conftest.py b/tests/conftest.py index 7bbb098..70b6aa7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -21,7 +21,14 @@ def get_path_test_file_two(get_base_path: Path) -> Path: @pytest.fixture(autouse=True) def cleanup_mkv_files(get_base_path: Path, get_path_test_file: Path, get_path_test_file_two: Path) -> None: # noqa: PT004 yield - for ext in ["*.mkv", "*.mp4", "*.ogg"]: + for ext in ["*.mkv", "*.mp4", "*.ogg", "*.txt"]: for file_path in get_base_path.glob(ext): if file_path not in (get_path_test_file, get_path_test_file_two): file_path.unlink() + + +@pytest.fixture() +def temp_file(tmp_path: Path) -> str: + file = tmp_path / "test_attachment.txt" + file.write_text("Test content") + return str(file) diff --git a/tests/test_actions_track.py b/tests/test_actions_track.py index b21da67..cec296f 100644 --- a/tests/test_actions_track.py +++ b/tests/test_actions_track.py @@ -66,3 +66,120 @@ def test_move_track_front_and_mux(get_base_path: Path, get_path_test_file: Path) assert len(mkv.tracks) == 2 # noqa: PLR2004 assert mkv.tracks[0].track_type == "audio" assert mkv.tracks[1].track_type == "video" + + +def test_mux_and_test_title(get_base_path: Path, get_path_test_file: Path) -> None: + output_file = get_base_path / "file-test.mkv" + + mkv = MKVFile(get_path_test_file) + mkv.title = "Test title in mkv file" + mkv.mux(output_file) + + mkv = MKVFile(output_file) + + assert mkv.title == "Test title in mkv file" + + +def test_mux_and_test_track_name(get_base_path: Path, get_path_test_file: Path) -> None: + output_file = get_base_path / "file-test.mkv" + + mkv = MKVFile(get_path_test_file) + mkv.tracks[0].track_name = "Test track name" + mkv.mux(output_file) + + mkv = MKVFile(output_file) + + assert mkv.tracks[0].track_name == "Test track name" + + +def test_move_track_end_raises(get_path_test_file: Path) -> None: + mkv = MKVFile(get_path_test_file) + + with pytest.raises(IndexError): + mkv.move_track_end(-1) + + with pytest.raises(IndexError): + mkv.move_track_end(2) + + +def test_move_track_end_and_mux(get_base_path: Path, get_path_test_file: Path) -> None: + output_file = get_base_path / "file-test.mkv" + + mkv = MKVFile(get_path_test_file) + mkv.move_track_end(0) + mkv.mux(output_file) + + mkv = MKVFile(output_file) + + assert len(mkv.tracks) == 2 # noqa: PLR2004 + assert mkv.tracks[0].track_type == "audio" + assert mkv.tracks[1].track_type == "video" + + +def test_move_track_forward_raises(get_path_test_file: Path) -> None: + mkv = MKVFile(get_path_test_file) + + with pytest.raises(IndexError): + mkv.move_track_forward(-1) + + with pytest.raises(IndexError): + mkv.move_track_forward(2) + + with pytest.raises(IndexError): + mkv.move_track_forward(1) + + +def test_move_track_forward_and_mux(get_base_path: Path, get_path_test_file: Path) -> None: + output_file = get_base_path / "file-test.mkv" + + mkv = MKVFile(get_path_test_file) + mkv.move_track_forward(0) + mkv.mux(output_file) + + mkv = MKVFile(output_file) + + assert len(mkv.tracks) == 2 # noqa: PLR2004 + assert mkv.tracks[0].track_type == "audio" + assert mkv.tracks[1].track_type == "video" + + +def test_move_track_backward_raises(get_path_test_file: Path) -> None: + mkv = MKVFile(get_path_test_file) + + with pytest.raises(IndexError): + mkv.move_track_backward(-1) + + with pytest.raises(IndexError): + mkv.move_track_backward(2) + + +def test_move_track_backward_and_mux(get_base_path: Path, get_path_test_file: Path) -> None: + output_file = get_base_path / "file-test.mkv" + + mkv = MKVFile(get_path_test_file) + mkv.move_track_backward(1) + mkv.mux(output_file) + + mkv = MKVFile(output_file) + + assert len(mkv.tracks) == 2 # noqa: PLR2004 + assert mkv.tracks[0].track_type == "audio" + assert mkv.tracks[1].track_type == "video" + + +def test_get_track(get_path_test_file: Path) -> None: + mkv = MKVFile(get_path_test_file) + track = mkv.get_track(1) + + assert track.track_type == "audio" + + tracks = mkv.get_track() + + assert isinstance(tracks, list) + assert len(tracks) == 2 # noqa: PLR2004 + + +def test_get_track_error(get_path_test_file: Path) -> None: + mkv = MKVFile(get_path_test_file) + with pytest.raises(IndexError): + mkv.get_track(2) diff --git a/tests/test_add_file.py b/tests/test_add_file.py index b976858..9168139 100644 --- a/tests/test_add_file.py +++ b/tests/test_add_file.py @@ -12,6 +12,12 @@ def test_add_file(get_base_path: Path, get_path_test_file: Path, get_path_test_f mkv_two = MKVFile(get_path_test_file_two) mkv.add_file(mkv_two) + for i, track in enumerate(mkv.tracks): + expected_file_id = i // 2 # the file_id should change every two tracks + expected_track_id = i % 2 # the track_id should alternate between 0 and 1 + assert track.file_id == expected_file_id + assert track.track_id == expected_track_id + assert len(mkv.tracks) == 4 # noqa: PLR2004 mkv.mux(output_file) diff --git a/tests/test_mkv_attachment.py b/tests/test_mkv_attachment.py new file mode 100644 index 0000000..465c2f3 --- /dev/null +++ b/tests/test_mkv_attachment.py @@ -0,0 +1,74 @@ +from pathlib import Path + +import pytest + +from pymkv.MKVAttachment import MKVAttachment + + +def test_init(temp_file: str) -> None: + attachment = MKVAttachment(temp_file) + assert attachment.file_path == temp_file + assert attachment.mime_type == "text/plain" + assert attachment.name is None + assert attachment.description is None + assert attachment.attach_once is False + + +def test_init_with_options(temp_file: str) -> None: + attachment = MKVAttachment(temp_file, name="Test", description="Test Description", attach_once=True) + assert attachment.file_path == temp_file + assert attachment.name == "Test" + assert attachment.description == "Test Description" + assert attachment.attach_once is True + + +def test_file_path_setter_valid(temp_file: str) -> None: + attachment = MKVAttachment(temp_file) + new_file = Path(temp_file).parent / "new_file.txt" + new_file.write_text("New content") + attachment.file_path = str(new_file) + assert attachment.file_path == str(new_file) + assert attachment.mime_type == "text/plain" + assert attachment.name is None + + +def test_file_path_setter_invalid() -> None: + with pytest.raises(FileNotFoundError): + MKVAttachment("non_existent_file.txt") + + +def test_repr(temp_file: str) -> None: + attachment = MKVAttachment(temp_file, name="Test", description="Test Description") + repr_str = repr(attachment) + assert "file_path" in repr_str + assert "name" in repr_str + assert "description" in repr_str + assert "mime_type" in repr_str + + +def test_mime_type_guess(tmp_path: Path) -> None: + # Test different file types + file_types = { + "test.txt": "text/plain", + "test.jpg": "image/jpeg", + "test.png": "image/png", + "test.mp3": "audio/mpeg", + } + + for file_name, expected_mime in file_types.items(): + file_path = tmp_path / file_name + file_path.write_text("Test content") + attachment = MKVAttachment(str(file_path)) + assert attachment.mime_type == expected_mime + + +def test_file_path_expansion(tmp_path: Path, monkeypatch) -> None: # noqa: ANN001 + fake_home = tmp_path / "fake_home" + fake_home.mkdir() + monkeypatch.setenv("HOME", str(fake_home)) + + test_file = fake_home / "test_file.txt" + test_file.write_text("Test content") + + attachment = MKVAttachment("~/test_file.txt") + assert attachment.file_path == str(test_file) diff --git a/tests/test_open_files.py b/tests/test_open_files.py index 9ed99d7..7475dbc 100644 --- a/tests/test_open_files.py +++ b/tests/test_open_files.py @@ -8,6 +8,7 @@ def test_open_file(get_path_test_file: Path) -> None: mkv = MKVFile(get_path_test_file) + assert mkv.title is None assert len(mkv.tracks) == 2 # noqa: PLR2004 @@ -45,3 +46,11 @@ def test_empty_mkv_file() -> None: assert mkv.title == "test" assert len(mkv.tracks) == 0 + + +def test_verify_mkvmerge_in_mkv_file() -> None: + with pytest.raises( + FileNotFoundError, + match="mkvmerge is not at the specified path, add it there or changed mkvmerge_path property", + ): + MKVFile(title="test", mkvmerge_path="mkvmerge_test") diff --git a/tests/test_timestamp.py b/tests/test_timestamp.py new file mode 100644 index 0000000..0be9a87 --- /dev/null +++ b/tests/test_timestamp.py @@ -0,0 +1,91 @@ +import pytest + +from pymkv.Timestamp import Timestamp + + +def test_init() -> None: + ts1 = Timestamp("01:23:45.678") + assert str(ts1) == "01:23:45.678" + + ts2 = Timestamp(3661) # 1 hour, 1 minute, 1 second + assert str(ts2) == "01:01:01" + + ts3 = Timestamp(hh=2, mm=30, ss=15, nn=500000000) + assert str(ts3) == "02:30:15.5" + + +def test_comparison() -> None: + ts1 = Timestamp("01:00:00") + ts2 = Timestamp("02:00:00") + ts3 = Timestamp("01:00:00") + + assert ts1 < ts2 + assert ts2 > ts1 + assert ts1 <= ts3 + assert ts1 >= ts3 + assert ts1 == ts3 + assert ts1 != ts2 + + +def test_properties() -> None: + ts = Timestamp("12:34:56.789") + assert ts.hh == 12 # noqa: PLR2004 + assert ts.mm == 34 # noqa: PLR2004 + assert ts.ss == 56 # noqa: PLR2004 + assert ts.nn == 789000000 # noqa: PLR2004 + + +def test_setters() -> None: + ts = Timestamp() + ts.hh = 10 + ts.mm = 20 + ts.ss = 30 + ts.nn = 400000000 + assert str(ts) == "10:20:30.4" + + # Test overflow handling + ts.mm = 70 + assert ts.mm == 0 + + +def test_verify() -> None: + assert Timestamp.verify("01:23:45") + assert Timestamp.verify("01:23:45.678") + assert not Timestamp.verify("25.00:00") + + +def test_extract() -> None: + ts = Timestamp() + ts.extract(3661) # 1 hour, 1 minute, 1 second + + assert ts.hh == 1 + assert ts.mm == 1 + assert ts.ss == 1 + assert ts.nn == 0 + + +def test_getitem() -> None: + ts = Timestamp("01:23:45.678") + assert ts[0] == 1 # hours + assert ts[1] == 23 # minutes # noqa: PLR2004 + assert ts[2] == 45 # seconds # noqa: PLR2004 + assert ts[3] == 45 # seconds (again, as per the implementation) # noqa: PLR2004 + + +def test_invalid_input() -> None: + with pytest.raises(TypeError): + Timestamp([]) # Invalid type + + with pytest.raises(ValueError): # noqa: PT011 + Timestamp("invalid_timestamp") + + +def test_ts_property() -> None: + ts = Timestamp("01:23:45.678") + assert ts.ts == "01:23:45.678" + + ts.ts = "02:30:00" + assert ts.ts == "02:30:00" + + with pytest.raises(TypeError): + ts.ts = [] # Invalid type