-
Notifications
You must be signed in to change notification settings - Fork 37
/
time.py
167 lines (123 loc) · 4.53 KB
/
time.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
from collections import namedtuple
import re
from typing import Optional, List, Tuple, Sequence
from pysubs2.common import IntOrFloat
#: Pattern that matches both SubStation and SubRip timestamps.
TIMESTAMP = re.compile(r"(\d{1,2}):(\d{1,2}):(\d{1,2})[.,](\d{1,3})")
#: Pattern that matches H:MM:SS or HH:MM:SS timestamps.
TIMESTAMP_SHORT = re.compile(r"(\d{1,2}):(\d{2}):(\d{2})")
Times = namedtuple("Times", ["h", "m", "s", "ms"])
def make_time(h: IntOrFloat=0, m: IntOrFloat=0, s: IntOrFloat=0, ms: IntOrFloat=0,
frames: Optional[int]=None, fps: Optional[float]=None):
"""
Convert time to milliseconds.
See :func:`pysubs2.time.times_to_ms()`. When both frames and fps are specified,
:func:`pysubs2.time.frames_to_ms()` is called instead.
Raises:
ValueError: Invalid fps, or one of frames/fps is missing.
Example:
>>> make_time(s=1.5)
1500
>>> make_time(frames=50, fps=25)
2000
"""
if frames is None and fps is None:
return times_to_ms(h, m, s, ms)
elif frames is not None and fps is not None:
return frames_to_ms(frames, fps)
else:
raise ValueError("Both fps and frames must be specified")
def timestamp_to_ms(groups: Sequence[str]):
"""
Convert groups from :data:`pysubs2.time.TIMESTAMP` or :data:`pysubs2.time.TIMESTAMP_SHORT`
match to milliseconds.
Example:
>>> timestamp_to_ms(TIMESTAMP.match("0:00:00.42").groups())
420
>>> timestamp_to_ms(TIMESTAMP_SHORT.match("0:00:01").groups())
1000
"""
if len(groups) == 4:
h, m, s, frac = map(int, groups)
ms = frac * 10**(3 - len(groups[-1]))
elif len(groups) == 3:
h, m, s = map(int, groups)
ms = 0
else:
raise ValueError("Unexpected number of groups")
ms += s * 1000
ms += m * 60000
ms += h * 3600000
return ms
def times_to_ms(h: IntOrFloat=0, m: IntOrFloat=0, s: IntOrFloat=0, ms: IntOrFloat=0) -> int:
"""
Convert hours, minutes, seconds to milliseconds.
Arguments may be positive or negative, int or float,
need not be normalized (``s=120`` is okay).
Returns:
Number of milliseconds (rounded to int).
"""
ms += s * 1000
ms += m * 60000
ms += h * 3600000
return int(round(ms))
def frames_to_ms(frames: int, fps: float) -> int:
"""
Convert frame-based duration to milliseconds.
Arguments:
frames: Number of frames (should be int).
fps: Framerate (must be a positive number, eg. 23.976).
Returns:
Number of milliseconds (rounded to int).
Raises:
ValueError: fps was negative or zero.
"""
if fps <= 0:
raise ValueError("Framerate must be positive number (%f)." % fps)
return int(round(frames * (1000 / fps)))
def ms_to_frames(ms: IntOrFloat, fps: float) -> int:
"""
Convert milliseconds to number of frames.
Arguments:
ms: Number of milliseconds (may be int, float or other numeric class).
fps: Framerate (must be a positive number, eg. 23.976).
Returns:
Number of frames (int).
Raises:
ValueError: fps was negative or zero.
"""
if fps <= 0:
raise ValueError("Framerate must be positive number (%f)." % fps)
return int(round((ms / 1000) * fps))
def ms_to_times(ms: IntOrFloat) -> Tuple[int, int, int, int]:
"""
Convert milliseconds to normalized tuple (h, m, s, ms).
Arguments:
ms: Number of milliseconds (may be int, float or other numeric class).
Should be non-negative.
Returns:
Named tuple (h, m, s, ms) of ints.
Invariants: ``ms in range(1000) and s in range(60) and m in range(60)``
"""
ms = int(round(ms))
h, ms = divmod(ms, 3600000)
m, ms = divmod(ms, 60000)
s, ms = divmod(ms, 1000)
return Times(h, m, s, ms)
def ms_to_str(ms: IntOrFloat, fractions: bool=False) -> str:
"""
Prettyprint milliseconds to [-]H:MM:SS[.mmm]
Handles huge and/or negative times. Non-negative times with ``fractions=True``
are matched by :data:`pysubs2.time.TIMESTAMP`.
Arguments:
ms: Number of milliseconds (int, float or other numeric class).
fractions: Whether to print up to millisecond precision.
Returns:
str
"""
sgn = "-" if ms < 0 else ""
h, m, s, ms = ms_to_times(abs(ms))
if fractions:
return f"{sgn}{h:01d}:{m:02d}:{s:02d}.{ms:03d}"
else:
return f"{sgn}{h:01d}:{m:02d}:{s:02d}"