VideoTimestamps 0.0.1__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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 moi15moi
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,48 @@
1
+ Metadata-Version: 2.1
2
+ Name: VideoTimestamps
3
+ Version: 0.0.1
4
+ Summary: Get video timestamps.
5
+ Author-email: moi15moi <moi15moismokerlolilol@gmail.com>
6
+ License: MIT
7
+ Project-URL: Source, https://github.com/moi15moi/VideoTimestamps/
8
+ Project-URL: Tracker, https://github.com/moi15moi/VideoTimestamps/issues/
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Programming Language :: Python
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.8
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: License :: OSI Approved :: MIT License
19
+ Classifier: Typing :: Typed
20
+ Requires-Python: >=3.9
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Provides-Extra: dev
24
+ Requires-Dist: coverage >=7.0.0 ; extra == 'dev'
25
+ Requires-Dist: mypy >=1.0.0 ; extra == 'dev'
26
+ Requires-Dist: pillow >=10.0.0 ; extra == 'dev'
27
+ Requires-Dist: pytest >=8.0.0 ; extra == 'dev'
28
+
29
+ # VideoTimestamps
30
+ [![VideoTimestamps - Version](https://img.shields.io/pypi/v/videotimestamps.svg)](https://pypi.org/project/VideoTimestamps)
31
+ [![VideoTimestamps - Python Version](https://img.shields.io/pypi/pyversions/videotimestamps.svg)](https://pypi.org/project/VideoTimestamps)
32
+ [![VideoTimestamps - Coverage](https://img.shields.io/codecov/c/github/moi15moi/VideoTimestamps)](https://app.codecov.io/github/moi15moi/VideoTimestamps)
33
+ [![VideoTimestamps - mypy](https://img.shields.io/badge/mypy-checked-blue)](https://github.com/moi15moi/VideoTimestamps/actions?query=branch:main)
34
+
35
+ This tool allows to recover the timestamps of each frame in a video in milliseconds.
36
+ It also help to convert a frame to a time in milliseconds and vice-versa.
37
+
38
+ ## Installation
39
+ ```
40
+ pip install VideoTimestamps
41
+ ```
42
+
43
+ ## Dependencies
44
+ - [MKVToolNix v82.0 or more](https://mkvtoolnix.download/downloads.html)
45
+ - [ffprobe](https://ffmpeg.org/download.html)
46
+
47
+ ## How to use it?
48
+ See the [example file](./examples/get_timestamps.py).
@@ -0,0 +1,22 @@
1
+ video_timestamps/__init__.py,sha256=AP8zE8hBx8JJ0MSKoJDAR35la5frctq8pZhIz_XvoaI,170
2
+ video_timestamps/_version.py,sha256=sXLh7g3KC4QCFxcZGBTpG2scR7hmmBsMjq6LqRptkRg,22
3
+ video_timestamps/abc_video_parser.py,sha256=9jp4oRx-0GQw3iNOlU4X7zqVA91Ctcc0DNr--aKACkA,1110
4
+ video_timestamps/rounding_method.py,sha256=N9vrIuUi-zt_FOBj2B6s9rZ7cEly7crq9NoCQDJeWxc,495
5
+ video_timestamps/time_type.py,sha256=8g5FHK81-7BCOzfzL2US8wBkUzYUg35jHAs2zohEyI0,1739
6
+ video_timestamps/timestamps.py,sha256=Y8EY9Gu7G7RMfQXalpkQ3Kx_HfG8T1oKhi2Gp_eEai0,9374
7
+ video_timestamps/timestamps_factory.py,sha256=a4_HK3iNie2V8J8WQ-mTIh_TN1QbkAwXmeDE4-D8EFg,6264
8
+ video_timestamps/timestamps_file_parser.py,sha256=PwLES5rsGEpRdwcz46VI4t1Bmc22O6XwVv6uwdDUZHc,9032
9
+ video_timestamps/ffprobe/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ video_timestamps/ffprobe/ffprobe.py,sha256=xIhR-_qTY0yyiyOYpVOP-Bf9i0y4AykuNk_iTExCQ3M,4927
11
+ video_timestamps/ffprobe/timestamps.py,sha256=vUitcCpoJ6dc-3rapZ3HIzg8YpQC7R3ihskGkcHduEo,995
12
+ video_timestamps/mkvtoolnix/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
+ video_timestamps/mkvtoolnix/exit_code.py,sha256=J1Vwkpsbdh9TtDGrzVAAgIlqJ7dX3X4IQuKIDgVv3kM,245
14
+ video_timestamps/mkvtoolnix/mkvextract.py,sha256=egnmla3fkFlWl3YNZzj0dBrcUTeRFVkbOz0OBOxfrKo,2341
15
+ video_timestamps/mkvtoolnix/mkvmerge.py,sha256=arPuDKwPkGUlZGcfkCq-tnbhr2XlirGReSe5YvDpYCQ,2270
16
+ video_timestamps/mkvtoolnix/timestamps.py,sha256=aDJMunNpS_kKDymannTj3wcW5GxSgQ6Ku62dhVE99aI,2312
17
+ video_timestamps/mkvtoolnix/utils.py,sha256=oqr5HRqwwMCRZi0EsUrFazJOCIBALx9Tx9ZyIykaLS8,4571
18
+ VideoTimestamps-0.0.1.dist-info/LICENSE,sha256=u7kVQN3qvSSfxekKWXoKKqxNqpwWYQI6CL_400ISUoU,1065
19
+ VideoTimestamps-0.0.1.dist-info/METADATA,sha256=rt7LUsFUxgO3MSFxGEMx4c6kYQSwm6AfERCX07E8Ets,2112
20
+ VideoTimestamps-0.0.1.dist-info/WHEEL,sha256=Wyh-_nZ0DJYolHNn1_hMa4lM7uDedD_RGVwbmTjyItk,91
21
+ VideoTimestamps-0.0.1.dist-info/top_level.txt,sha256=3tv2UWakWMhmArLdqFycgcuDlcqQbaS4w3RE25yPbdM,17
22
+ VideoTimestamps-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (71.1.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ video_timestamps
@@ -0,0 +1,8 @@
1
+ # Packages
2
+
3
+ # Files
4
+ from ._version import __version__
5
+ from .rounding_method import *
6
+ from .time_type import *
7
+ from .timestamps import *
8
+ from .timestamps_factory import *
@@ -0,0 +1 @@
1
+ __version__ = "0.0.1"
@@ -0,0 +1,32 @@
1
+ from .rounding_method import RoundingMethod
2
+ from .timestamps import Timestamps
3
+ from abc import ABC, abstractmethod
4
+ from fractions import Fraction
5
+ from pathlib import Path
6
+
7
+
8
+ class ABCVideoParser(ABC):
9
+
10
+ @staticmethod
11
+ @abstractmethod
12
+ def is_available() -> bool:
13
+ """
14
+ Returns:
15
+ If the video parser is available.
16
+ """
17
+ pass
18
+
19
+ @staticmethod
20
+ @abstractmethod
21
+ def get_timestamps(video_path: Path, index: int, normalize: bool, rounding_method: RoundingMethod) -> tuple[Timestamps, Fraction]:
22
+ """Create timestamps based on the ``video_path`` provided.
23
+
24
+ Parameters:
25
+ video_path (Path): A video path.
26
+ index (int): Index of the video stream.
27
+ normalize (bool): If True, it will shift the timestamps to make them start from 0. If false, the option does nothing.
28
+ rounding_method (RoundingMethod): A rounding method. See the comment in Timestamps class about FLOOR vs ROUND.
29
+ Returns:
30
+ A tuple containing a Timestamps instance and the time_base of the video.
31
+ """
32
+ pass
File without changes
@@ -0,0 +1,140 @@
1
+ from ..rounding_method import RoundingMethod
2
+ import json
3
+ import sys
4
+ from fractions import Fraction
5
+ from subprocess import CompletedProcess, run
6
+ from pathlib import Path
7
+ from shutil import which
8
+ from typing import Any
9
+
10
+ __all__ = ["FFprobe"]
11
+
12
+
13
+ class FFprobe:
14
+ """
15
+ This class is a collection of static methods that will help
16
+ the user to interact with FFprobe.
17
+ """
18
+
19
+ PROGRAM_NAME = "ffprobe"
20
+
21
+ @staticmethod
22
+ def is_ffprobe_installed() -> bool:
23
+ """
24
+ Checks if the `ffprobe` program is installed and available in the system's PATH.
25
+
26
+ Returns:
27
+ True if `ffprobe` is installed, False otherwise.
28
+ """
29
+ return which("ffprobe") is not None
30
+
31
+ @staticmethod
32
+ def verify_if_ffprobe_is_installed() -> None:
33
+ """
34
+ Verifies if the `mkvextract` program is installed. Raises an error if not found.
35
+
36
+ Raises:
37
+ Exception: If `mkvextract` is not found in the system's PATH.
38
+ """
39
+ if not FFprobe.is_ffprobe_installed():
40
+ raise Exception("ffprobe is not in the environment variable.")
41
+
42
+
43
+ @staticmethod
44
+ def verify_if_command_fails(cmd_output: CompletedProcess[str]) -> None:
45
+ """Checks the result of a command execution and raises an error or warning based on the exit code.
46
+
47
+ Parameters:
48
+ cmd_output (CompletedProcess): The result of the command execution.
49
+
50
+ Raises:
51
+ OSError: If the command reported an error.
52
+ """
53
+
54
+ if cmd_output.returncode != 0:
55
+ raise OSError(f"{FFprobe.PROGRAM_NAME} reported an error: '{cmd_output.stderr}'.")
56
+
57
+
58
+ @staticmethod
59
+ def run_command(cmd: list[Any]) -> CompletedProcess[str]:
60
+ """Runs a command and verifies if it fails.
61
+
62
+ Parameters:
63
+ cmd (List[Any]): The command to be run, including its arguments.
64
+
65
+ Returns:
66
+ The result of the command execution.
67
+ """
68
+
69
+ FFprobe.verify_if_ffprobe_is_installed()
70
+ output = run(cmd, capture_output=True, text=True, encoding="utf-8")
71
+ FFprobe.verify_if_command_fails(output)
72
+
73
+ return output
74
+
75
+
76
+ @staticmethod
77
+ def get_timestamps(video_path: Path, index: int, rounding_method: RoundingMethod) -> tuple[list[int], Fraction, Fraction, Fraction]:
78
+ """
79
+
80
+ Parameters:
81
+ video_path (Path): A video path.
82
+ index (int): Index of the video stream.
83
+ rounding_method (RoundingMethod): A rounding method. See the comment in Timestamps class about FLOOR vs ROUND.
84
+ Returns:
85
+ A tuple containing these 3 informations:
86
+ 1. A list of each timestamps rounded to milliseconds
87
+ 2. The time_base
88
+ 3. The first timestamps not rounded
89
+ 4. The last timestamps not rounded
90
+ """
91
+
92
+ cmd = [
93
+ FFprobe.PROGRAM_NAME,
94
+ "-hide_banner",
95
+ "-select_streams",
96
+ str(index),
97
+ "-show_entries",
98
+ # Technically, we should use frame=pts_time,dts_time instead of packet=pts_time,dts_time
99
+ # But, using frame make the execution really slow
100
+ # I tried to ask [here](https://ffmpeg.org/pipermail/ffmpeg-user/2024-July/058509.html) if I could use a heuristic
101
+ # to know when I need to switch to frame, but I never got an answer.
102
+ "packet=pts_time,dts_time:stream=codec_type,time_base", #todo
103
+ video_path,
104
+ "-print_format",
105
+ "json",
106
+ ]
107
+ ffprobe_output = FFprobe.run_command(cmd)
108
+
109
+ ffprobe_output_dict = json.loads(ffprobe_output.stdout)
110
+
111
+ if len(ffprobe_output_dict["streams"]) == 0:
112
+ raise ValueError(f"The index {index} is not in the file {video_path}.")
113
+
114
+ if ffprobe_output_dict["streams"][0]["codec_type"] != "video":
115
+ raise ValueError(
116
+ f'The index {index} is not a video stream. It is an "{ffprobe_output_dict["streams"][0]["codec_type"]}" stream.'
117
+ )
118
+
119
+ time_base = Fraction(ffprobe_output_dict["streams"][0]["time_base"])
120
+
121
+ timestamps = []
122
+ lowest_timestamp: Fraction = None # type: ignore
123
+ highest_timestamp: Fraction = None # type: ignore
124
+
125
+ for packet in ffprobe_output_dict["packets"]:
126
+ timestamp = Fraction(
127
+ # Sometimes, pts_time isn't available.
128
+ # If it is the case, fallback to dts_time
129
+ packet.get("pts_time", packet.get("dts_time"))
130
+ ) * Fraction(1000)
131
+ if highest_timestamp is None or highest_timestamp < timestamp:
132
+ highest_timestamp = timestamp
133
+ if lowest_timestamp is None or lowest_timestamp > timestamp:
134
+ lowest_timestamp = timestamp
135
+
136
+
137
+ timestamps.append(rounding_method(timestamp))
138
+ timestamps.sort()
139
+
140
+ return timestamps, time_base, lowest_timestamp, highest_timestamp
@@ -0,0 +1,29 @@
1
+ from .ffprobe import FFprobe
2
+ from ..timestamps import Timestamps
3
+ from ..abc_video_parser import ABCVideoParser
4
+ from ..rounding_method import RoundingMethod
5
+ from fractions import Fraction
6
+ from pathlib import Path
7
+
8
+
9
+ class FFprobeTimestamps(ABCVideoParser):
10
+
11
+ @staticmethod
12
+ def is_available() -> bool:
13
+ return FFprobe.is_ffprobe_installed()
14
+
15
+ @staticmethod
16
+ def get_timestamps(video_path: Path, index: int, normalize: bool, rounding_method: RoundingMethod) -> tuple[Timestamps, Fraction]:
17
+ timestamps_list, time_base, first_frame_time, last_frame_time = FFprobe.get_timestamps(video_path, index, rounding_method)
18
+
19
+ fpms = Fraction(len(timestamps_list) - 1) / (last_frame_time - first_frame_time)
20
+
21
+ timestamps = Timestamps(
22
+ rounding_method=rounding_method,
23
+ timestamps=timestamps_list,
24
+ normalize=normalize,
25
+ fpms=fpms,
26
+ last_frame_time=last_frame_time,
27
+ )
28
+
29
+ return timestamps, time_base
File without changes
@@ -0,0 +1,11 @@
1
+ from enum import IntEnum
2
+
3
+ __all__ = ["ExitCode"]
4
+
5
+
6
+ class ExitCode(IntEnum):
7
+ # From https://mkvtoolnix.download/doc/mkvpropedit.html#d4e1266
8
+ # All the MKVToolNix programs use this enumeration
9
+ SUCCESS = 0
10
+ WARNING = 1
11
+ ERROR = 2
@@ -0,0 +1,73 @@
1
+ from .utils import MKVUtils
2
+ from tempfile import TemporaryDirectory
3
+ from pathlib import Path
4
+
5
+ __all__ = ["MKVExtract"]
6
+
7
+
8
+ class MKVExtract:
9
+ """
10
+ This class is a collection of static methods that will help
11
+ the user to interact with mkvextract.
12
+ """
13
+
14
+ PROGRAM_NAME = "mkvextract"
15
+ PROGRAM_PATH = MKVUtils.get_program_path(PROGRAM_NAME)
16
+
17
+ @staticmethod
18
+ def is_mkv_extract_installed() -> bool:
19
+ """
20
+ Checks if the `mkvextract` program is installed and available in the system's PATH.
21
+
22
+ Returns:
23
+ True if `mkvextract` is installed, False otherwise.
24
+ """
25
+ return MKVExtract.PROGRAM_PATH is not None
26
+
27
+
28
+ @staticmethod
29
+ def verify_if_mkvextract_installed() -> None:
30
+ """
31
+ Verifies if the `mkvextract` program is installed. Raises an error if not found.
32
+
33
+ Raises:
34
+ Exception: If `mkvextract` is not found in the system's PATH.
35
+ """
36
+ if not MKVExtract.is_mkv_extract_installed():
37
+ raise Exception(
38
+ f'{MKVExtract.PROGRAM_NAME} is isn\'t found. You need to correct your environnements variable or correctly install the program'
39
+ )
40
+
41
+
42
+ @staticmethod
43
+ def get_timestamps_file_content(mkv_file_path: Path, index: int) -> str:
44
+ """Extracts the timestamps from an MKV file for a specific track index.
45
+
46
+ Parameters:
47
+ mkv_file_path (Path): The path to the MKV file.
48
+ index (int): Index of the video stream.
49
+
50
+ Returns:
51
+ The content of the timestamps file.
52
+ """
53
+
54
+ MKVExtract.verify_if_mkvextract_installed()
55
+ MKVUtils.verify_if_file_mkv(mkv_file_path)
56
+
57
+ with TemporaryDirectory() as tmpdirname:
58
+ timestamps_file_path = Path(tmpdirname, "temp timestamps.txt")
59
+ cmd = [
60
+ MKVExtract.PROGRAM_PATH,
61
+ mkv_file_path,
62
+ "timestamps_v2",
63
+ f"{index}:{timestamps_file_path}",
64
+ ]
65
+
66
+ MKVUtils.run_command(MKVExtract.PROGRAM_NAME, cmd)
67
+ with open(timestamps_file_path, "r", encoding="utf-8") as file:
68
+ lines = file.readlines()
69
+
70
+ # Ignore the last line due to this issue: https://gitlab.com/mbunkus/mkvtoolnix/-/issues/3075
71
+ content = "".join(lines[:-1])
72
+
73
+ return content
@@ -0,0 +1,79 @@
1
+ from .utils import MKVUtils
2
+ import json
3
+ from pathlib import Path
4
+ from typing import Any
5
+
6
+ __all__ = ["MKVMerge"]
7
+
8
+
9
+ class MKVMerge:
10
+ """
11
+ This class is a collection of static methods that will help
12
+ the user to interact with mkvmerge.
13
+ """
14
+
15
+ PROGRAM_NAME = "mkvmerge"
16
+ PROGRAM_PATH = MKVUtils.get_program_path(PROGRAM_NAME)
17
+
18
+ @staticmethod
19
+ def is_mkv_merge_installed() -> bool:
20
+ """
21
+ Checks if the `mkvmerge` program is installed and available in the system's PATH.
22
+
23
+ Returns:
24
+ True if `mkvmerge` is installed, False otherwise.
25
+ """
26
+ return MKVMerge.PROGRAM_PATH is not None
27
+
28
+
29
+ @staticmethod
30
+ def verify_if_mkvmerge_installed() -> None:
31
+ """
32
+ Verifies if the `mkvmerge` program is installed. Raises an error if not found.
33
+
34
+ Raises:
35
+ Exception: If `mkvmerge` is not found in the system's PATH.
36
+ """
37
+ if not MKVMerge.is_mkv_merge_installed():
38
+ raise Exception(f'{MKVMerge.PROGRAM_NAME} is isn\'t found. You need to correct your environnements variable or correctly install the program')
39
+
40
+
41
+ @staticmethod
42
+ def get_version() -> tuple[int, int]:
43
+ """Retrieves the version of `mkvmerge`.
44
+
45
+ Equivalent to running: `mkvmerge --version`
46
+
47
+ Returns:
48
+ The major and minor version numbers of `mkvmerge`.
49
+ """
50
+ MKVMerge.verify_if_mkvmerge_installed()
51
+
52
+ return MKVUtils.get_version(MKVMerge.PROGRAM_NAME, MKVMerge.PROGRAM_PATH) # type: ignore
53
+
54
+
55
+ @staticmethod
56
+ def get_mkv_info(mkv_file_path: Path) -> Any:
57
+ """Retrieves information about an MKV file in JSON format.
58
+
59
+ Equivalent to running: `mkvmerge FILE.mkv -J`
60
+
61
+ Parameters:
62
+ mkv_file_path (Path): The path to the MKV file.
63
+
64
+ Returns:
65
+ The information about the MKV file in JSON format.
66
+ """
67
+ MKVMerge.verify_if_mkvmerge_installed()
68
+ MKVUtils.verify_if_file_mkv(mkv_file_path)
69
+
70
+ cmd = [
71
+ MKVMerge.PROGRAM_PATH,
72
+ mkv_file_path,
73
+ "-J",
74
+ ]
75
+
76
+ mkvmerge_output = MKVUtils.run_command(MKVMerge.PROGRAM_NAME, cmd)
77
+ mkvmerge_output_dict = json.loads(mkvmerge_output.stdout)
78
+
79
+ return mkvmerge_output_dict
@@ -0,0 +1,52 @@
1
+ from .mkvextract import MKVExtract
2
+ from .mkvmerge import MKVMerge
3
+ from ..timestamps import Timestamps
4
+ from ..abc_video_parser import ABCVideoParser
5
+ from ..rounding_method import RoundingMethod
6
+ from fractions import Fraction
7
+ from pathlib import Path
8
+
9
+
10
+ class MKVToolNixTimestamps(ABCVideoParser):
11
+
12
+ @staticmethod
13
+ def is_available() -> bool:
14
+ return MKVExtract.is_mkv_extract_installed() and MKVMerge.is_mkv_merge_installed()
15
+
16
+
17
+ @staticmethod
18
+ def get_timestamps(video_path: Path, index: int, normalize: bool, rounding_method: RoundingMethod) -> tuple[Timestamps, Fraction]:
19
+ mkvmerge_version = MKVMerge.get_version()
20
+ if mkvmerge_version < (82, 0):
21
+ # We need the version 82.0 for the timestamp_scale properties: https://help.mkvtoolnix.download/t/how-to-get-timestamp-scale-information-without-using-mkvinfo/299/3
22
+ raise OSError(f"You need at least the version 82.0 of mkvmerge. You have the version {mkvmerge_version[0]}.{mkvmerge_version[1]}")
23
+
24
+ mkv_info = MKVMerge.get_mkv_info(video_path)
25
+
26
+ is_index_in_video = False
27
+ for track in mkv_info["tracks"]:
28
+ if track["id"] == index:
29
+ if track["type"] != "video":
30
+ raise ValueError(f'The index {index} is not a video stream. It is an "{track["type"]}" stream.')
31
+ is_index_in_video = True
32
+ break
33
+
34
+ if not is_index_in_video:
35
+ raise ValueError(f"The index {index} is not in the file {video_path}.")
36
+
37
+ timestamp_scale = mkv_info["container"]["properties"]["timestamp_scale"]
38
+ # Technically, time_base = TimestampScale * TrackTimestampScale / 10^9
39
+ # but mkvmerge doesn't report the TrackTimestampScale.
40
+ # Even the MKVToolNix author suggested me to ignore the TrackTimestampScale [here](https://help.mkvtoolnix.download/t/how-to-get-timestamp-scale-information-without-using-mkvinfo/299/6?u=moi15moi)
41
+ time_base = Fraction(timestamp_scale, 10**9)
42
+
43
+ content = MKVExtract.get_timestamps_file_content(video_path, index)
44
+
45
+ from ..timestamps_factory import TimestampsFactory
46
+ timestamps = TimestampsFactory.from_timestamps_file(
47
+ content,
48
+ normalize,
49
+ rounding_method
50
+ )
51
+
52
+ return timestamps, time_base