lyrics-transcriber 0.30.0__py3-none-any.whl → 0.32.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.
- lyrics_transcriber/__init__.py +2 -1
- lyrics_transcriber/cli/{main.py → cli_main.py} +47 -14
- lyrics_transcriber/core/config.py +35 -0
- lyrics_transcriber/core/controller.py +164 -166
- lyrics_transcriber/correction/anchor_sequence.py +471 -0
- lyrics_transcriber/correction/corrector.py +256 -0
- lyrics_transcriber/correction/handlers/__init__.py +0 -0
- lyrics_transcriber/correction/handlers/base.py +30 -0
- lyrics_transcriber/correction/handlers/extend_anchor.py +91 -0
- lyrics_transcriber/correction/handlers/levenshtein.py +147 -0
- lyrics_transcriber/correction/handlers/no_space_punct_match.py +98 -0
- lyrics_transcriber/correction/handlers/relaxed_word_count_match.py +55 -0
- lyrics_transcriber/correction/handlers/repeat.py +71 -0
- lyrics_transcriber/correction/handlers/sound_alike.py +223 -0
- lyrics_transcriber/correction/handlers/syllables_match.py +182 -0
- lyrics_transcriber/correction/handlers/word_count_match.py +54 -0
- lyrics_transcriber/correction/handlers/word_operations.py +135 -0
- lyrics_transcriber/correction/phrase_analyzer.py +426 -0
- lyrics_transcriber/correction/text_utils.py +30 -0
- lyrics_transcriber/lyrics/base_lyrics_provider.py +125 -0
- lyrics_transcriber/lyrics/genius.py +73 -0
- lyrics_transcriber/lyrics/spotify.py +82 -0
- lyrics_transcriber/output/ass/__init__.py +21 -0
- lyrics_transcriber/output/{ass.py → ass/ass.py} +150 -690
- lyrics_transcriber/output/ass/ass_specs.txt +732 -0
- lyrics_transcriber/output/ass/config.py +37 -0
- lyrics_transcriber/output/ass/constants.py +23 -0
- lyrics_transcriber/output/ass/event.py +94 -0
- lyrics_transcriber/output/ass/formatters.py +132 -0
- lyrics_transcriber/output/ass/lyrics_line.py +219 -0
- lyrics_transcriber/output/ass/lyrics_screen.py +252 -0
- lyrics_transcriber/output/ass/section_detector.py +89 -0
- lyrics_transcriber/output/ass/section_screen.py +106 -0
- lyrics_transcriber/output/ass/style.py +187 -0
- lyrics_transcriber/output/cdg.py +503 -0
- lyrics_transcriber/output/cdgmaker/__init__.py +0 -0
- lyrics_transcriber/output/cdgmaker/cdg.py +262 -0
- lyrics_transcriber/output/cdgmaker/composer.py +1919 -0
- lyrics_transcriber/output/cdgmaker/config.py +151 -0
- lyrics_transcriber/output/cdgmaker/images/instrumental.png +0 -0
- lyrics_transcriber/output/cdgmaker/images/intro.png +0 -0
- lyrics_transcriber/output/cdgmaker/pack.py +507 -0
- lyrics_transcriber/output/cdgmaker/render.py +346 -0
- lyrics_transcriber/output/cdgmaker/transitions/centertexttoplogobottomtext.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/circlein.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/circleout.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/fizzle.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/largecentertexttoplogo.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/rectangle.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/spiral.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/topleftmusicalnotes.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/wipein.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/wipeleft.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/wipeout.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/wiperight.png +0 -0
- lyrics_transcriber/output/cdgmaker/utils.py +132 -0
- lyrics_transcriber/output/fonts/AvenirNext-Bold.ttf +0 -0
- lyrics_transcriber/output/fonts/DMSans-VariableFont_opsz,wght.ttf +0 -0
- lyrics_transcriber/output/fonts/DMSerifDisplay-Regular.ttf +0 -0
- lyrics_transcriber/output/fonts/Oswald-SemiBold.ttf +0 -0
- lyrics_transcriber/output/fonts/Zurich_Cn_BT_Bold.ttf +0 -0
- lyrics_transcriber/output/fonts/arial.ttf +0 -0
- lyrics_transcriber/output/fonts/georgia.ttf +0 -0
- lyrics_transcriber/output/fonts/verdana.ttf +0 -0
- lyrics_transcriber/output/generator.py +140 -171
- lyrics_transcriber/output/lyrics_file.py +102 -0
- lyrics_transcriber/output/plain_text.py +91 -0
- lyrics_transcriber/output/segment_resizer.py +416 -0
- lyrics_transcriber/output/subtitles.py +328 -302
- lyrics_transcriber/output/video.py +219 -0
- lyrics_transcriber/review/__init__.py +1 -0
- lyrics_transcriber/review/server.py +138 -0
- lyrics_transcriber/storage/dropbox.py +110 -134
- lyrics_transcriber/transcribers/audioshake.py +171 -105
- lyrics_transcriber/transcribers/base_transcriber.py +149 -0
- lyrics_transcriber/transcribers/whisper.py +267 -133
- lyrics_transcriber/types.py +454 -0
- {lyrics_transcriber-0.30.0.dist-info → lyrics_transcriber-0.32.1.dist-info}/METADATA +14 -3
- lyrics_transcriber-0.32.1.dist-info/RECORD +86 -0
- {lyrics_transcriber-0.30.0.dist-info → lyrics_transcriber-0.32.1.dist-info}/WHEEL +1 -1
- lyrics_transcriber-0.32.1.dist-info/entry_points.txt +4 -0
- lyrics_transcriber/core/corrector.py +0 -56
- lyrics_transcriber/core/fetcher.py +0 -143
- lyrics_transcriber/storage/tokens.py +0 -116
- lyrics_transcriber/transcribers/base.py +0 -31
- lyrics_transcriber-0.30.0.dist-info/RECORD +0 -22
- lyrics_transcriber-0.30.0.dist-info/entry_points.txt +0 -3
- {lyrics_transcriber-0.30.0.dist-info → lyrics_transcriber-0.32.1.dist-info}/LICENSE +0 -0
@@ -1,423 +1,26 @@
|
|
1
1
|
#!/usr/bin/env python
|
2
2
|
import os, re, sys, functools, collections
|
3
|
+
from lyrics_transcriber.output.ass.event import Event
|
4
|
+
from lyrics_transcriber.output.ass.style import Style
|
5
|
+
from lyrics_transcriber.output.ass.formatters import Formatters
|
6
|
+
from lyrics_transcriber.output.ass.constants import (
|
7
|
+
ALIGN_BOTTOM_LEFT,
|
8
|
+
ALIGN_BOTTOM_CENTER,
|
9
|
+
ALIGN_BOTTOM_RIGHT,
|
10
|
+
ALIGN_MIDDLE_LEFT,
|
11
|
+
ALIGN_MIDDLE_CENTER,
|
12
|
+
ALIGN_MIDDLE_RIGHT,
|
13
|
+
ALIGN_TOP_LEFT,
|
14
|
+
ALIGN_TOP_CENTER,
|
15
|
+
ALIGN_TOP_RIGHT,
|
16
|
+
LEGACY_ALIGNMENT_TO_REGULAR,
|
17
|
+
)
|
3
18
|
|
4
19
|
version_info = (1, 0, 4)
|
5
20
|
|
6
21
|
|
7
22
|
# Advanced SubStation Alpha read/write/modification class
|
8
23
|
class ASS:
|
9
|
-
class Formatters:
|
10
|
-
__re_color_format = re.compile(r"([a-fA-F0-9]{1,8})", re.U)
|
11
|
-
__re_tag_number = re.compile(
|
12
|
-
r"^\s*([\+\-]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+))", re.U
|
13
|
-
)
|
14
|
-
|
15
|
-
@classmethod
|
16
|
-
def same(cls, val, *args):
|
17
|
-
return val
|
18
|
-
|
19
|
-
@classmethod
|
20
|
-
def color_to_str(cls, val, *args):
|
21
|
-
return "&H{0:02X}{1:02X}{2:02X}{3:02X}".format(
|
22
|
-
255 - val[3], val[2], val[1], val[0]
|
23
|
-
)
|
24
|
-
|
25
|
-
@classmethod
|
26
|
-
def str_to_color(cls, val, *args):
|
27
|
-
match = cls.__re_color_format.search(val)
|
28
|
-
if match:
|
29
|
-
hex_val = "{0:>08s}".format(match.group(1))
|
30
|
-
return (
|
31
|
-
int(hex_val[6:8], 16), # Red
|
32
|
-
int(hex_val[4:6], 16), # Green
|
33
|
-
int(hex_val[2:4], 16), # Blue
|
34
|
-
255 - int(hex_val[0:2], 16), # Alpha
|
35
|
-
)
|
36
|
-
else:
|
37
|
-
return (255, 255, 255, 255)
|
38
|
-
|
39
|
-
@classmethod
|
40
|
-
def n1bool_to_str(cls, val, *args):
|
41
|
-
if val:
|
42
|
-
return "-1"
|
43
|
-
return "0"
|
44
|
-
|
45
|
-
@classmethod
|
46
|
-
def str_to_n1bool(cls, val, *args):
|
47
|
-
try:
|
48
|
-
val = int(val, 10)
|
49
|
-
except ValueError:
|
50
|
-
return False
|
51
|
-
return val != 0
|
52
|
-
|
53
|
-
@classmethod
|
54
|
-
def integer_to_str(cls, val, *args):
|
55
|
-
return str(int(val))
|
56
|
-
|
57
|
-
@classmethod
|
58
|
-
def str_to_integer(cls, val, *args):
|
59
|
-
try:
|
60
|
-
return int(val, 10)
|
61
|
-
except ValueError:
|
62
|
-
return 0
|
63
|
-
|
64
|
-
@classmethod
|
65
|
-
def number_to_str(cls, val, *args):
|
66
|
-
if int(val) == val:
|
67
|
-
return str(int(val))
|
68
|
-
# No decimal
|
69
|
-
return str(val)
|
70
|
-
|
71
|
-
@classmethod
|
72
|
-
def str_to_number(cls, val, *args):
|
73
|
-
try:
|
74
|
-
return float(val)
|
75
|
-
except ValueError:
|
76
|
-
return 0.0
|
77
|
-
|
78
|
-
@classmethod
|
79
|
-
def timecode_to_str_generic(
|
80
|
-
cls,
|
81
|
-
timecode,
|
82
|
-
decimal_length=2,
|
83
|
-
seconds_length=2,
|
84
|
-
minutes_length=2,
|
85
|
-
hours_length=1,
|
86
|
-
):
|
87
|
-
if decimal_length > 0:
|
88
|
-
total_length = seconds_length + decimal_length + 1
|
89
|
-
else:
|
90
|
-
total_length = seconds_length
|
91
|
-
|
92
|
-
tc_parts = [
|
93
|
-
"{{0:0{0:d}d}}".format(hours_length).format(int(timecode // 3600)),
|
94
|
-
"{{0:0{0:d}d}}".format(minutes_length).format(
|
95
|
-
int((timecode // 60) % 60)
|
96
|
-
),
|
97
|
-
"{{0:0{0:d}.{1:d}f}}".format(total_length, decimal_length).format(
|
98
|
-
timecode % 60
|
99
|
-
),
|
100
|
-
]
|
101
|
-
return ":".join(tc_parts)
|
102
|
-
|
103
|
-
@classmethod
|
104
|
-
def timecode_to_str(cls, val, *args):
|
105
|
-
return cls.timecode_to_str_generic(val, 2)
|
106
|
-
|
107
|
-
@classmethod
|
108
|
-
def str_to_timecode(cls, val, *args):
|
109
|
-
time = 0.0
|
110
|
-
mult = 1
|
111
|
-
|
112
|
-
for t in reversed(val.split(":")):
|
113
|
-
time += float(t) * mult
|
114
|
-
mult *= 60
|
115
|
-
|
116
|
-
return time
|
117
|
-
|
118
|
-
@classmethod
|
119
|
-
def style_to_str(cls, val, *args):
|
120
|
-
if val is None:
|
121
|
-
return ""
|
122
|
-
return val.Name
|
123
|
-
|
124
|
-
@classmethod
|
125
|
-
def str_to_style(cls, val, style_map, style_constructor, *args):
|
126
|
-
if val in style_map:
|
127
|
-
return style_map[val]
|
128
|
-
|
129
|
-
# Create fake
|
130
|
-
style = style_constructor()
|
131
|
-
style.fake = True
|
132
|
-
style.Name = val
|
133
|
-
|
134
|
-
# Add to map (will not be included in global style list, but allows for duplicate "fake" styles to reference the same object)
|
135
|
-
style_map[style.Name] = style
|
136
|
-
|
137
|
-
# Return the new style
|
138
|
-
return style
|
139
|
-
|
140
|
-
@classmethod
|
141
|
-
def tag_argument_to_number(cls, arg, default_value=None):
|
142
|
-
match = cls.__re_tag_number.match(arg)
|
143
|
-
if match is None:
|
144
|
-
return default_value
|
145
|
-
return float(match.group(1))
|
146
|
-
|
147
|
-
class Style:
|
148
|
-
aliases = {}
|
149
|
-
formatters = None
|
150
|
-
order = [
|
151
|
-
"Name",
|
152
|
-
"Fontname",
|
153
|
-
"Fontsize",
|
154
|
-
"PrimaryColour",
|
155
|
-
"SecondaryColour",
|
156
|
-
"OutlineColour",
|
157
|
-
"BackColour",
|
158
|
-
"Bold",
|
159
|
-
"Italic",
|
160
|
-
"Underline",
|
161
|
-
"StrikeOut",
|
162
|
-
"ScaleX",
|
163
|
-
"ScaleY",
|
164
|
-
"Spacing",
|
165
|
-
"Angle",
|
166
|
-
"BorderStyle",
|
167
|
-
"Outline",
|
168
|
-
"Shadow",
|
169
|
-
"Alignment",
|
170
|
-
"MarginL",
|
171
|
-
"MarginR",
|
172
|
-
"MarginV",
|
173
|
-
"Encoding",
|
174
|
-
]
|
175
|
-
|
176
|
-
# Constructor
|
177
|
-
def __init__(self):
|
178
|
-
self.type = None
|
179
|
-
self.fake = False
|
180
|
-
|
181
|
-
self.Name = ""
|
182
|
-
self.Fontname = ""
|
183
|
-
self.Fontsize = 1.0
|
184
|
-
self.PrimaryColour = (255, 255, 255, 255)
|
185
|
-
self.SecondaryColour = (255, 255, 255, 255)
|
186
|
-
self.OutlineColour = (255, 255, 255, 255)
|
187
|
-
self.BackColour = (255, 255, 255, 255)
|
188
|
-
self.Bold = False
|
189
|
-
self.Italic = False
|
190
|
-
self.Underline = False
|
191
|
-
self.StrikeOut = False
|
192
|
-
self.ScaleX = 100
|
193
|
-
self.ScaleY = 100
|
194
|
-
self.Spacing = 0
|
195
|
-
self.Angle = 0.0
|
196
|
-
self.BorderStyle = 1
|
197
|
-
self.Outline = 0
|
198
|
-
self.Shadow = 0
|
199
|
-
self.Alignment = ASS.ALIGN_BOTTOM_CENTER
|
200
|
-
self.MarginL = 0
|
201
|
-
self.MarginR = 0
|
202
|
-
self.MarginV = 0
|
203
|
-
self.Encoding = 0
|
204
|
-
|
205
|
-
def set(self, attribute_name, value, *args):
|
206
|
-
if hasattr(self, attribute_name):
|
207
|
-
if not attribute_name[0].isupper():
|
208
|
-
return
|
209
|
-
elif attribute_name in self.aliases:
|
210
|
-
attribute_name = self.aliases[attribute_name]
|
211
|
-
else:
|
212
|
-
return
|
213
|
-
|
214
|
-
setattr(
|
215
|
-
self, attribute_name, self.formatters[attribute_name][0](value, *args)
|
216
|
-
)
|
217
|
-
|
218
|
-
def get(self, attribute_name, *args):
|
219
|
-
if hasattr(self, attribute_name):
|
220
|
-
if not attribute_name[0].isupper():
|
221
|
-
return None
|
222
|
-
elif attribute_name in self.aliases:
|
223
|
-
attribute_name = self.aliases[attribute_name]
|
224
|
-
else:
|
225
|
-
return None
|
226
|
-
|
227
|
-
return self.formatters[attribute_name][1](
|
228
|
-
getattr(self, attribute_name), *args
|
229
|
-
)
|
230
|
-
|
231
|
-
def copy(self, other=None):
|
232
|
-
if other is None:
|
233
|
-
other = self.__class__()
|
234
|
-
obj1 = other
|
235
|
-
obj2 = self
|
236
|
-
else:
|
237
|
-
obj1 = self
|
238
|
-
obj2 = other
|
239
|
-
|
240
|
-
obj1.type = obj2.type
|
241
|
-
|
242
|
-
obj1.Name = obj2.Name
|
243
|
-
obj1.Fontname = obj2.Fontname
|
244
|
-
obj1.Fontsize = obj2.Fontsize
|
245
|
-
obj1.PrimaryColour = obj2.PrimaryColour
|
246
|
-
obj1.SecondaryColour = obj2.SecondaryColour
|
247
|
-
obj1.OutlineColour = obj2.OutlineColour
|
248
|
-
obj1.BackColour = obj2.BackColour
|
249
|
-
obj1.Bold = obj2.Bold
|
250
|
-
obj1.Italic = obj2.Italic
|
251
|
-
obj1.Underline = obj2.Underline
|
252
|
-
obj1.StrikeOut = obj2.StrikeOut
|
253
|
-
obj1.ScaleX = obj2.ScaleX
|
254
|
-
obj1.ScaleY = obj2.ScaleY
|
255
|
-
obj1.Spacing = obj2.Spacing
|
256
|
-
obj1.Angle = obj2.Angle
|
257
|
-
obj1.BorderStyle = obj2.BorderStyle
|
258
|
-
obj1.Outline = obj2.Outline
|
259
|
-
obj1.Shadow = obj2.Shadow
|
260
|
-
obj1.Alignment = obj2.Alignment
|
261
|
-
obj1.MarginL = obj2.MarginL
|
262
|
-
obj1.MarginR = obj2.MarginR
|
263
|
-
obj1.MarginV = obj2.MarginV
|
264
|
-
obj1.Encoding = obj2.Encoding
|
265
|
-
|
266
|
-
return obj1
|
267
|
-
|
268
|
-
def equals(self, other, names_can_differ=False):
|
269
|
-
return (
|
270
|
-
self.type == other.type
|
271
|
-
and not self.fake
|
272
|
-
and not other.fake
|
273
|
-
and not other.fake
|
274
|
-
and (names_can_differ or self.Name == other.Name)
|
275
|
-
and self.Fontname == other.Fontname
|
276
|
-
and self.Fontsize == other.Fontsize
|
277
|
-
and self.PrimaryColour == other.PrimaryColour
|
278
|
-
and self.SecondaryColour == other.SecondaryColour
|
279
|
-
and self.OutlineColour == other.OutlineColour
|
280
|
-
and self.BackColour == other.BackColour
|
281
|
-
and self.Bold == other.Bold
|
282
|
-
and self.Italic == other.Italic
|
283
|
-
and self.Underline == other.Underline
|
284
|
-
and self.StrikeOut == other.StrikeOut
|
285
|
-
and self.ScaleX == other.ScaleX
|
286
|
-
and self.ScaleY == other.ScaleY
|
287
|
-
and self.Spacing == other.Spacing
|
288
|
-
and self.Angle == other.Angle
|
289
|
-
and self.BorderStyle == other.BorderStyle
|
290
|
-
and self.Outline == other.Outline
|
291
|
-
and self.Shadow == other.Shadow
|
292
|
-
and self.Alignment == other.Alignment
|
293
|
-
and self.MarginL == other.MarginL
|
294
|
-
and self.MarginR == other.MarginR
|
295
|
-
and self.MarginV == other.MarginV
|
296
|
-
and self.Encoding == other.Encoding
|
297
|
-
)
|
298
|
-
|
299
|
-
Style.formatters = {
|
300
|
-
"Name": (Formatters.same, Formatters.same),
|
301
|
-
"Fontname": (Formatters.same, Formatters.same),
|
302
|
-
"Fontsize": (Formatters.str_to_number, Formatters.number_to_str),
|
303
|
-
"PrimaryColour": (Formatters.str_to_color, Formatters.color_to_str),
|
304
|
-
"SecondaryColour": (Formatters.str_to_color, Formatters.color_to_str),
|
305
|
-
"OutlineColour": (Formatters.str_to_color, Formatters.color_to_str),
|
306
|
-
"BackColour": (Formatters.str_to_color, Formatters.color_to_str),
|
307
|
-
"Bold": (Formatters.str_to_n1bool, Formatters.n1bool_to_str),
|
308
|
-
"Italic": (Formatters.str_to_n1bool, Formatters.n1bool_to_str),
|
309
|
-
"Underline": (Formatters.str_to_n1bool, Formatters.n1bool_to_str),
|
310
|
-
"StrikeOut": (Formatters.str_to_n1bool, Formatters.n1bool_to_str),
|
311
|
-
"ScaleX": (Formatters.str_to_integer, Formatters.integer_to_str),
|
312
|
-
"ScaleY": (Formatters.str_to_integer, Formatters.integer_to_str),
|
313
|
-
"Spacing": (Formatters.str_to_integer, Formatters.integer_to_str),
|
314
|
-
"Angle": (Formatters.str_to_number, Formatters.number_to_str),
|
315
|
-
"BorderStyle": (Formatters.str_to_integer, Formatters.integer_to_str),
|
316
|
-
"Outline": (Formatters.str_to_integer, Formatters.integer_to_str),
|
317
|
-
"Shadow": (Formatters.str_to_integer, Formatters.integer_to_str),
|
318
|
-
"Alignment": (Formatters.str_to_integer, Formatters.integer_to_str),
|
319
|
-
"MarginL": (Formatters.str_to_integer, Formatters.integer_to_str),
|
320
|
-
"MarginR": (Formatters.str_to_integer, Formatters.integer_to_str),
|
321
|
-
"MarginV": (Formatters.str_to_integer, Formatters.integer_to_str),
|
322
|
-
"Encoding": (Formatters.str_to_integer, Formatters.integer_to_str),
|
323
|
-
}
|
324
|
-
|
325
|
-
class Event:
|
326
|
-
aliases = {}
|
327
|
-
formatters = None
|
328
|
-
order = [
|
329
|
-
"Layer",
|
330
|
-
"Start",
|
331
|
-
"End",
|
332
|
-
"Style",
|
333
|
-
"Name",
|
334
|
-
"MarginL",
|
335
|
-
"MarginR",
|
336
|
-
"MarginV",
|
337
|
-
"Effect",
|
338
|
-
"Text",
|
339
|
-
]
|
340
|
-
|
341
|
-
# Constructor
|
342
|
-
def __init__(self):
|
343
|
-
self.type = None
|
344
|
-
|
345
|
-
self.Layer = 0
|
346
|
-
self.Start = 0.0
|
347
|
-
self.End = 0.0
|
348
|
-
self.Style = None
|
349
|
-
self.Name = ""
|
350
|
-
self.MarginL = 0
|
351
|
-
self.MarginR = 0
|
352
|
-
self.MarginV = 0
|
353
|
-
self.Effect = ""
|
354
|
-
self.Text = ""
|
355
|
-
|
356
|
-
def set(self, attribute_name, value, *args):
|
357
|
-
if hasattr(self, attribute_name) and attribute_name[0].isupper():
|
358
|
-
setattr(
|
359
|
-
self,
|
360
|
-
attribute_name,
|
361
|
-
self.formatters[attribute_name][0](value, *args),
|
362
|
-
)
|
363
|
-
|
364
|
-
def get(self, attribute_name, *args):
|
365
|
-
if hasattr(self, attribute_name) and attribute_name[0].isupper():
|
366
|
-
return self.formatters[attribute_name][1](
|
367
|
-
getattr(self, attribute_name), *args
|
368
|
-
)
|
369
|
-
return None
|
370
|
-
|
371
|
-
def copy(self, other=None):
|
372
|
-
if other is None:
|
373
|
-
other = self.__class__()
|
374
|
-
obj1 = other
|
375
|
-
obj2 = self
|
376
|
-
else:
|
377
|
-
obj1 = self
|
378
|
-
obj2 = other
|
379
|
-
|
380
|
-
obj1.type = obj2.type
|
381
|
-
|
382
|
-
obj1.Layer = obj2.Layer
|
383
|
-
obj1.Start = obj2.Start
|
384
|
-
obj1.End = obj2.End
|
385
|
-
obj1.Style = obj2.Style
|
386
|
-
obj1.Name = obj2.Name
|
387
|
-
obj1.MarginL = obj2.MarginL
|
388
|
-
obj1.MarginR = obj2.MarginR
|
389
|
-
obj1.MarginV = obj2.MarginV
|
390
|
-
obj1.Effect = obj2.Effect
|
391
|
-
obj1.Text = obj2.Text
|
392
|
-
|
393
|
-
return obj1
|
394
|
-
|
395
|
-
def equals(self, other):
|
396
|
-
return (
|
397
|
-
self.type == other.type
|
398
|
-
and self.Layer == other.Layer
|
399
|
-
and self.Start == other.Start
|
400
|
-
and self.End == other.End
|
401
|
-
and self.Style is other.Style
|
402
|
-
and self.Name == other.Name
|
403
|
-
and self.MarginL == other.MarginL
|
404
|
-
and self.MarginR == other.MarginR
|
405
|
-
and self.MarginV == other.MarginV
|
406
|
-
and self.Effect == other.Effect
|
407
|
-
and self.Text == other.Text
|
408
|
-
)
|
409
|
-
|
410
|
-
def same_style(self, other):
|
411
|
-
return (
|
412
|
-
self.type == other.type
|
413
|
-
and self.Layer == other.Layer
|
414
|
-
and self.Style is other.Style
|
415
|
-
and self.Name == other.Name
|
416
|
-
and self.MarginL == other.MarginL
|
417
|
-
and self.MarginR == other.MarginR
|
418
|
-
and self.MarginV == other.MarginV
|
419
|
-
and self.Effect == other.Effect
|
420
|
-
)
|
421
24
|
|
422
25
|
Event.formatters = {
|
423
26
|
"Layer": (Formatters.str_to_integer, Formatters.integer_to_str),
|
@@ -438,16 +41,6 @@ class ASS:
|
|
438
41
|
self.key = key
|
439
42
|
self.value = value
|
440
43
|
|
441
|
-
ALIGN_BOTTOM_LEFT = 1
|
442
|
-
ALIGN_BOTTOM_CENTER = 2
|
443
|
-
ALIGN_BOTTOM_RIGHT = 3
|
444
|
-
ALIGN_MIDDLE_LEFT = 4
|
445
|
-
ALIGN_MIDDLE_CENTER = 5
|
446
|
-
ALIGN_MIDDLE_RIGHT = 6
|
447
|
-
ALIGN_TOP_LEFT = 7
|
448
|
-
ALIGN_TOP_CENTER = 8
|
449
|
-
ALIGN_TOP_RIGHT = 9
|
450
|
-
|
451
44
|
__re_ass_read_section_label = re.compile(r"^(?:\[(.+)\])$", re.U)
|
452
45
|
__re_ass_read_key_value = re.compile(r"^([^:]+):\s?(.+)$", re.U)
|
453
46
|
__re_tag_block = re.compile(r"(\{)(.*?)(\})", re.U)
|
@@ -525,17 +118,6 @@ class ASS:
|
|
525
118
|
__re_draw_command_split = re.compile(r"\s+")
|
526
119
|
__re_draw_commands_ord_min = ord("a")
|
527
120
|
__re_draw_commands_ord_max = ord("z")
|
528
|
-
__legacy_alignment_to_regular = {
|
529
|
-
"1": ALIGN_BOTTOM_LEFT,
|
530
|
-
"2": ALIGN_BOTTOM_CENTER,
|
531
|
-
"3": ALIGN_BOTTOM_RIGHT,
|
532
|
-
"5": ALIGN_TOP_LEFT,
|
533
|
-
"6": ALIGN_TOP_CENTER,
|
534
|
-
"7": ALIGN_TOP_RIGHT,
|
535
|
-
"9": ALIGN_MIDDLE_LEFT,
|
536
|
-
"10": ALIGN_MIDDLE_CENTER,
|
537
|
-
"11": ALIGN_MIDDLE_RIGHT,
|
538
|
-
}
|
539
121
|
|
540
122
|
@classmethod
|
541
123
|
def __split_line(cls, line, split_time, naive):
|
@@ -561,7 +143,7 @@ class ASS:
|
|
561
143
|
return (before, after)
|
562
144
|
|
563
145
|
@classmethod
|
564
|
-
def __split_line3(cls, line, split_time, naive):
|
146
|
+
def __split_line3(cls, line, split_time, naive=False):
|
565
147
|
if split_time < line.Start or split_time > line.End:
|
566
148
|
return None
|
567
149
|
# Nothing to split
|
@@ -628,9 +210,7 @@ class ASS:
|
|
628
210
|
state = {
|
629
211
|
"animations": 0,
|
630
212
|
}
|
631
|
-
cls.parse_text(
|
632
|
-
text, modify_tag=lambda t: cls.__line_has_animations_modify_tag(state, t)
|
633
|
-
)
|
213
|
+
cls.parse_text(text, modify_tag=lambda t: cls.__line_has_animations_modify_tag(state, t))
|
634
214
|
return state["animations"] > 0
|
635
215
|
|
636
216
|
@classmethod
|
@@ -710,13 +290,9 @@ class ASS:
|
|
710
290
|
line = self.events[i]
|
711
291
|
if filter_types is None or line.type in filter_types:
|
712
292
|
if full_inclusion:
|
713
|
-
perform = (start is None or line.Start >= start) and (
|
714
|
-
end is None or line.End <= end
|
715
|
-
)
|
293
|
+
perform = (start is None or line.Start >= start) and (end is None or line.End <= end)
|
716
294
|
else:
|
717
|
-
perform = (start is None or line.End > start) and (
|
718
|
-
end is None or line.Start < end
|
719
|
-
)
|
295
|
+
perform = (start is None or line.End > start) and (end is None or line.Start < end)
|
720
296
|
|
721
297
|
if perform ^ inverse:
|
722
298
|
# action should return None if the line should be removed, else it should return an Event object (likely the same one that was input)
|
@@ -791,7 +367,7 @@ class ASS:
|
|
791
367
|
|
792
368
|
s = s.decode("utf-8")
|
793
369
|
# Decode using UTF-8
|
794
|
-
s = s.replace(
|
370
|
+
s = s.replace("\ufeff", "")
|
795
371
|
# Replace any BOM
|
796
372
|
|
797
373
|
# Target region
|
@@ -861,9 +437,7 @@ class ASS:
|
|
861
437
|
instance.type = match.group(1)
|
862
438
|
|
863
439
|
for i in range(len(values)):
|
864
|
-
instance.set(
|
865
|
-
target_format[i], values[i], *target_class_set_args
|
866
|
-
)
|
440
|
+
instance.set(target_format[i], values[i], *target_class_set_args)
|
867
441
|
|
868
442
|
target_list.append(instance)
|
869
443
|
if target_map is not None:
|
@@ -875,7 +449,7 @@ class ASS:
|
|
875
449
|
def write(self, filename, comments=None):
|
876
450
|
# Generate source
|
877
451
|
source = [
|
878
|
-
|
452
|
+
"[Script Info]\n",
|
879
453
|
]
|
880
454
|
|
881
455
|
# Comments
|
@@ -883,49 +457,45 @@ class ASS:
|
|
883
457
|
# Default comment
|
884
458
|
source.extend(
|
885
459
|
[
|
886
|
-
|
887
|
-
self.__re_filename_format[0].sub(
|
888
|
-
self.__re_filename_format[1], os.path.split(__file__)[1]
|
889
|
-
)
|
460
|
+
"; Script generated by {0:s}\n".format(
|
461
|
+
self.__re_filename_format[0].sub(self.__re_filename_format[1], os.path.split(__file__)[1])
|
890
462
|
),
|
891
463
|
]
|
892
464
|
)
|
893
465
|
else:
|
894
466
|
# Custom comments
|
895
|
-
source.extend([
|
467
|
+
source.extend(["; {0:s}".format(c) for c in comments])
|
896
468
|
|
897
469
|
# Script info
|
898
470
|
for entry in self.script_info_ordered:
|
899
471
|
if entry.key in self.script_info:
|
900
|
-
source.append(
|
472
|
+
source.append("{0:s}: {1:s}\n".format(entry.key, entry.value))
|
901
473
|
|
902
|
-
source.append(
|
474
|
+
source.append("\n")
|
903
475
|
|
904
476
|
# Styles
|
905
|
-
source.append(
|
906
|
-
source.append(
|
477
|
+
source.append("[V4+ Styles]\n")
|
478
|
+
source.append("Format: {0:s}\n".format(", ".join(self.styles_format)))
|
907
479
|
for style in self.styles:
|
908
480
|
style_list = []
|
909
481
|
for key in self.styles_format:
|
910
482
|
style_list.append(style.get(key))
|
911
|
-
source.append(
|
912
|
-
source.append(
|
483
|
+
source.append("{0:s}: {1:s}\n".format(style.type, ",".join(style_list)))
|
484
|
+
source.append("\n")
|
913
485
|
|
914
486
|
# Events
|
915
|
-
source.append(
|
916
|
-
source.append(
|
487
|
+
source.append("[Events]\n")
|
488
|
+
source.append("Format: {0:s}\n".format(", ".join(self.events_format)))
|
917
489
|
for event in self.events:
|
918
490
|
if event.Start >= 0 and event.End >= 0:
|
919
491
|
event_list = []
|
920
492
|
for key in self.events_format:
|
921
493
|
event_list.append(event.get(key))
|
922
|
-
source.append(
|
923
|
-
u"{0:s}: {1:s}\n".format(event.type, u",".join(event_list))
|
924
|
-
)
|
494
|
+
source.append("{0:s}: {1:s}\n".format(event.type, ",".join(event_list)))
|
925
495
|
|
926
496
|
# Write file
|
927
497
|
f = open(filename, "wb")
|
928
|
-
s = f.write((
|
498
|
+
s = f.write(("".join(source)).encode("utf-8"))
|
929
499
|
f.close()
|
930
500
|
|
931
501
|
# Done
|
@@ -951,11 +521,7 @@ class ASS:
|
|
951
521
|
sorted_events = []
|
952
522
|
for i in range(len(self.events)):
|
953
523
|
event = self.events[i]
|
954
|
-
if
|
955
|
-
event.type == "Dialogue"
|
956
|
-
and event.Start < event.End
|
957
|
-
and event.Start >= 0
|
958
|
-
):
|
524
|
+
if event.type == "Dialogue" and event.Start < event.End and event.Start >= 0:
|
959
525
|
meta_event = self.__WriteSRTMetaEvent(event, i)
|
960
526
|
meta_event.format_text(self, newlines)
|
961
527
|
if len(meta_event.text) > 0:
|
@@ -1017,18 +583,11 @@ class ASS:
|
|
1017
583
|
block_end = event_data.event.End
|
1018
584
|
for i in range(1, event_count):
|
1019
585
|
event_data = sorted_events[i]
|
1020
|
-
if
|
1021
|
-
event_data.start < block_start + self.__same_time_max_delta
|
1022
|
-
): # will set even if same
|
586
|
+
if event_data.start < block_start + self.__same_time_max_delta: # will set even if same
|
1023
587
|
block_start = event_data.start
|
1024
|
-
if
|
1025
|
-
event_data.event.End
|
1026
|
-
<= block_end - self.__same_time_max_delta
|
1027
|
-
): # will set only if lower
|
588
|
+
if event_data.event.End <= block_end - self.__same_time_max_delta: # will set only if lower
|
1028
589
|
block_end = event_data.event.End
|
1029
|
-
elif
|
1030
|
-
event_data.start <= block_end - self.__same_time_max_delta
|
1031
|
-
): # will set only if lower
|
590
|
+
elif event_data.start <= block_end - self.__same_time_max_delta: # will set only if lower
|
1032
591
|
block_end = event_data.start
|
1033
592
|
assert block_start < block_end
|
1034
593
|
# should never happen
|
@@ -1047,10 +606,7 @@ class ASS:
|
|
1047
606
|
stack_lines_ordered.append(event_data)
|
1048
607
|
else:
|
1049
608
|
stack_lines_unordered.append(event_data)
|
1050
|
-
if
|
1051
|
-
event_data.event.End
|
1052
|
-
<= block_end + self.__same_time_max_delta
|
1053
|
-
):
|
609
|
+
if event_data.event.End <= block_end + self.__same_time_max_delta:
|
1054
610
|
# Remove
|
1055
611
|
sorted_events.pop(i)
|
1056
612
|
event_count -= 1
|
@@ -1080,9 +636,7 @@ class ASS:
|
|
1080
636
|
# Sort by vertical position; this is convenient for multiple lines appearing simultaneously; there are still cases ordering may be messed up
|
1081
637
|
stack_lines_unordered = sorted(
|
1082
638
|
stack_lines_unordered,
|
1083
|
-
key=functools.cmp_to_key(
|
1084
|
-
lambda e1, e2: self.__write_srt_sort_lines_compare(e1, e2)
|
1085
|
-
),
|
639
|
+
key=functools.cmp_to_key(lambda e1, e2: self.__write_srt_sort_lines_compare(e1, e2)),
|
1086
640
|
)
|
1087
641
|
stack_lines.extend(stack_lines_unordered)
|
1088
642
|
for i in range(len(stack_lines)):
|
@@ -1114,22 +668,18 @@ class ASS:
|
|
1114
668
|
for i in range(len(lines)):
|
1115
669
|
line_start, line_end, line_text = lines[i]
|
1116
670
|
|
1117
|
-
source.append(
|
671
|
+
source.append("{0:d}\n".format(i + 1))
|
1118
672
|
source.append(
|
1119
|
-
|
1120
|
-
|
1121
|
-
|
1122
|
-
).replace(".", ","),
|
1123
|
-
self.Formatters.timecode_to_str_generic(
|
1124
|
-
line_end, 3, 2, 2, 2
|
1125
|
-
).replace(".", ","),
|
673
|
+
"{0:s} --> {1:s}\n".format(
|
674
|
+
Formatters.timecode_to_str_generic(line_start, 3, 2, 2, 2).replace(".", ","),
|
675
|
+
Formatters.timecode_to_str_generic(line_end, 3, 2, 2, 2).replace(".", ","),
|
1126
676
|
)
|
1127
677
|
)
|
1128
|
-
source.append(
|
678
|
+
source.append("{0:s}\n\n".format(line_text))
|
1129
679
|
|
1130
680
|
# Write file
|
1131
681
|
f = open(filename, "wb")
|
1132
|
-
s = f.write((
|
682
|
+
s = f.write(("".join(source)).encode("utf-8"))
|
1133
683
|
f.close()
|
1134
684
|
|
1135
685
|
# Done
|
@@ -1188,30 +738,22 @@ class ASS:
|
|
1188
738
|
self.text = None
|
1189
739
|
|
1190
740
|
def equals(self, other):
|
1191
|
-
return
|
1192
|
-
self.text == other.text
|
1193
|
-
and self.start == other.start
|
1194
|
-
and self.event.End == other.event.End
|
1195
|
-
)
|
741
|
+
return self.text == other.text and self.start == other.start and self.event.End == other.event.End
|
1196
742
|
|
1197
743
|
def format_text(self, parent, newlines):
|
1198
744
|
self.text = parent.parse_text(
|
1199
745
|
self.event.Text,
|
1200
|
-
modify_text=(
|
1201
|
-
lambda t: self.__write_srt_format_text(parent, newlines, t)
|
1202
|
-
),
|
746
|
+
modify_text=(lambda t: self.__write_srt_format_text(parent, newlines, t)),
|
1203
747
|
modify_tag_block=(lambda b: ""),
|
1204
748
|
modify_geometry=(lambda g: ""),
|
1205
749
|
)
|
1206
750
|
|
1207
751
|
def __write_srt_format_text(self, parent, newlines, text):
|
1208
|
-
return parent.replace_special(
|
1209
|
-
text, (lambda c: self.__write_srt_format_text_space(newlines, c)), 1, 1
|
1210
|
-
)
|
752
|
+
return parent.replace_special(text, (lambda c: self.__write_srt_format_text_space(newlines, c)), 1, 1)
|
1211
753
|
|
1212
754
|
def __write_srt_format_text_space(self, newlines, character):
|
1213
755
|
if character == "h":
|
1214
|
-
return
|
756
|
+
return "\u00A0"
|
1215
757
|
if newlines:
|
1216
758
|
return "\n"
|
1217
759
|
return " "
|
@@ -1259,38 +801,36 @@ class ASS:
|
|
1259
801
|
if state[0] is None:
|
1260
802
|
tag_name = tag[0]
|
1261
803
|
if tag_name == "a":
|
1262
|
-
state[0] = cls.__legacy_align_to_regular(
|
1263
|
-
cls.Formatters.str_to_number(tag[1])
|
1264
|
-
)
|
804
|
+
state[0] = cls.__legacy_align_to_regular(Formatters.str_to_number(tag[1]))
|
1265
805
|
elif tag_name == "an":
|
1266
|
-
state[0] =
|
806
|
+
state[0] = Formatters.str_to_number(tag[1])
|
1267
807
|
|
1268
808
|
# Done
|
1269
809
|
return [tag]
|
1270
810
|
|
1271
811
|
@classmethod
|
1272
812
|
def get_xy_alignment(cls, align):
|
1273
|
-
if align >=
|
813
|
+
if align >= ALIGN_TOP_LEFT and align <= ALIGN_TOP_RIGHT:
|
1274
814
|
align_y = -1
|
1275
|
-
if align ==
|
815
|
+
if align == ALIGN_TOP_LEFT:
|
1276
816
|
align_x = -1
|
1277
|
-
elif align ==
|
817
|
+
elif align == ALIGN_TOP_RIGHT:
|
1278
818
|
align_x = 1
|
1279
819
|
else:
|
1280
820
|
align_x = 0
|
1281
|
-
elif align >=
|
821
|
+
elif align >= ALIGN_MIDDLE_LEFT and align <= ALIGN_MIDDLE_RIGHT:
|
1282
822
|
align_y = 0
|
1283
|
-
if align ==
|
823
|
+
if align == ALIGN_MIDDLE_LEFT:
|
1284
824
|
align_x = -1
|
1285
|
-
elif align ==
|
825
|
+
elif align == ALIGN_MIDDLE_RIGHT:
|
1286
826
|
align_x = 1
|
1287
827
|
else:
|
1288
828
|
align_x = 0
|
1289
|
-
else: # if (align >=
|
829
|
+
else: # if (align >= ALIGN_BOTTOM_LEFT and align <= ALIGN_BOTTOM_RIGHT):
|
1290
830
|
align_y = 1
|
1291
|
-
if align ==
|
831
|
+
if align == ALIGN_BOTTOM_LEFT:
|
1292
832
|
align_x = -1
|
1293
|
-
elif align ==
|
833
|
+
elif align == ALIGN_BOTTOM_RIGHT:
|
1294
834
|
align_x = 1
|
1295
835
|
else:
|
1296
836
|
align_x = 0
|
@@ -1336,25 +876,25 @@ class ASS:
|
|
1336
876
|
modify_geometry=None,
|
1337
877
|
):
|
1338
878
|
"""
|
1339
|
-
|
1340
|
-
|
1341
|
-
|
1342
|
-
|
1343
|
-
|
1344
|
-
|
1345
|
-
|
1346
|
-
|
1347
|
-
|
1348
|
-
|
1349
|
-
|
1350
|
-
|
1351
|
-
|
1352
|
-
|
1353
|
-
|
1354
|
-
|
1355
|
-
|
1356
|
-
|
1357
|
-
|
879
|
+
modify_tag:
|
880
|
+
inputs:
|
881
|
+
tag_args - an array of the form:
|
882
|
+
[ tag_name , tag_arg1 , tag_arg2 , ... ]
|
883
|
+
where all tag_arg#'s are optional
|
884
|
+
return:
|
885
|
+
must return an array containing only "tag_args" and strings
|
886
|
+
- "tag_args" are auto-converted into strings
|
887
|
+
- strings are treated as comments, or pre-formatted tags
|
888
|
+
|
889
|
+
<everything else>:
|
890
|
+
inputs:
|
891
|
+
the relevant string
|
892
|
+
return:
|
893
|
+
the relevant string, modified
|
894
|
+
|
895
|
+
Note:
|
896
|
+
if modify_special is None, then "\\h", "\\n", and "\\N" will be treated part of text sections (i.e. they are not separated)
|
897
|
+
"""
|
1358
898
|
text_new = []
|
1359
899
|
|
1360
900
|
if modify_special is None:
|
@@ -1387,9 +927,7 @@ class ASS:
|
|
1387
927
|
tag_new = [match.group(1)]
|
1388
928
|
|
1389
929
|
# Parse individual tags
|
1390
|
-
tag_text, next_geometry_scale = cls.parse_tags(
|
1391
|
-
match.group(2), modify_tag, modify_comment, next_geometry_scale
|
1392
|
-
)
|
930
|
+
tag_text, next_geometry_scale = cls.parse_tags(match.group(2), modify_tag, modify_comment, next_geometry_scale)
|
1393
931
|
tag_text = match.group(1) + tag_text + match.group(3)
|
1394
932
|
|
1395
933
|
if modify_tag_block is not None:
|
@@ -1416,26 +954,24 @@ class ASS:
|
|
1416
954
|
return "".join(text_new)
|
1417
955
|
|
1418
956
|
@classmethod
|
1419
|
-
def parse_tags(
|
1420
|
-
|
1421
|
-
|
957
|
+
def parse_tags(cls, text, modify_tag=None, modify_comment=None, next_geometry_scale=0):
|
958
|
+
"""
|
959
|
+
modify_tag:
|
960
|
+
inputs:
|
961
|
+
tag_args - an array of the form:
|
962
|
+
[ tag_name , tag_arg1 , tag_arg2 , ... ]
|
963
|
+
where all tag_arg#'s are optional
|
964
|
+
return:
|
965
|
+
must return an array containing only "tag_args" and strings
|
966
|
+
- "tag_args" are auto-converted into strings
|
967
|
+
- strings are treated as comments, or pre-formatted tags
|
968
|
+
|
969
|
+
<everything else>:
|
970
|
+
inputs:
|
971
|
+
the relevant string
|
972
|
+
return:
|
973
|
+
the relevant string, modified
|
1422
974
|
"""
|
1423
|
-
modify_tag:
|
1424
|
-
inputs:
|
1425
|
-
tag_args - an array of the form:
|
1426
|
-
[ tag_name , tag_arg1 , tag_arg2 , ... ]
|
1427
|
-
where all tag_arg#'s are optional
|
1428
|
-
return:
|
1429
|
-
must return an array containing only "tag_args" and strings
|
1430
|
-
- "tag_args" are auto-converted into strings
|
1431
|
-
- strings are treated as comments, or pre-formatted tags
|
1432
|
-
|
1433
|
-
<everything else>:
|
1434
|
-
inputs:
|
1435
|
-
the relevant string
|
1436
|
-
return:
|
1437
|
-
the relevant string, modified
|
1438
|
-
"""
|
1439
975
|
text_new = []
|
1440
976
|
pos = 0
|
1441
977
|
for match in cls.__re_tag.finditer(text):
|
@@ -1481,9 +1017,7 @@ class ASS:
|
|
1481
1017
|
for tag_args in tag_args_array:
|
1482
1018
|
if tag_args[0] == "p":
|
1483
1019
|
# Drawing command
|
1484
|
-
next_geometry_scale =
|
1485
|
-
tag_args[1], 0
|
1486
|
-
)
|
1020
|
+
next_geometry_scale = Formatters.tag_argument_to_number(tag_args[1], 0)
|
1487
1021
|
|
1488
1022
|
text_new.append(tt)
|
1489
1023
|
|
@@ -1502,28 +1036,18 @@ class ASS:
|
|
1502
1036
|
|
1503
1037
|
# Other parsing
|
1504
1038
|
@classmethod
|
1505
|
-
def replace_special(
|
1506
|
-
cls, text, space=" ", min_whitespace_length=1, max_whitespace_length=1
|
1507
|
-
):
|
1039
|
+
def replace_special(cls, text, space=" ", min_whitespace_length=1, max_whitespace_length=1):
|
1508
1040
|
return cls.__re_remove_special.sub(
|
1509
|
-
(
|
1510
|
-
lambda m: cls.__replace_special_replacer(
|
1511
|
-
m, space, min_whitespace_length, max_whitespace_length
|
1512
|
-
)
|
1513
|
-
),
|
1041
|
+
(lambda m: cls.__replace_special_replacer(m, space, min_whitespace_length, max_whitespace_length)),
|
1514
1042
|
text,
|
1515
1043
|
)
|
1516
1044
|
|
1517
1045
|
@classmethod
|
1518
|
-
def __replace_special_replacer(
|
1519
|
-
cls, match, space, min_whitespace_length, max_whitespace_length
|
1520
|
-
):
|
1046
|
+
def __replace_special_replacer(cls, match, space, min_whitespace_length, max_whitespace_length):
|
1521
1047
|
ws = match.group(1) + match.group(3)
|
1522
1048
|
ws_len = len(ws)
|
1523
1049
|
|
1524
|
-
if ws_len < min_whitespace_length or (
|
1525
|
-
ws_len > max_whitespace_length and max_whitespace_length >= 0
|
1526
|
-
):
|
1050
|
+
if ws_len < min_whitespace_length or (ws_len > max_whitespace_length and max_whitespace_length >= 0):
|
1527
1051
|
if hasattr(space, "__call__"):
|
1528
1052
|
return space(match.group(2))
|
1529
1053
|
return space
|
@@ -1713,17 +1237,13 @@ class ASS:
|
|
1713
1237
|
# Done
|
1714
1238
|
return self
|
1715
1239
|
|
1716
|
-
def tidy_styles(
|
1717
|
-
self, **kwargs
|
1718
|
-
): # Generate unique names, remove duplicates, and remove unused
|
1240
|
+
def tidy_styles(self, **kwargs): # Generate unique names, remove duplicates, and remove unused
|
1719
1241
|
# Parse kwargs
|
1720
1242
|
sort = self.__kwarg_default(kwargs, "sort", False)
|
1721
1243
|
# if True, events are sorted by name
|
1722
1244
|
join = self.__kwarg_default(kwargs, "join", False)
|
1723
1245
|
# if True, duplicates are joined into a single style
|
1724
|
-
join_if_names_differ = self.__kwarg_default(
|
1725
|
-
kwargs, "join_if_names_differ", False
|
1726
|
-
)
|
1246
|
+
join_if_names_differ = self.__kwarg_default(kwargs, "join_if_names_differ", False)
|
1727
1247
|
# if True, styles are joined even if their names are different
|
1728
1248
|
rename = self.__kwarg_default(kwargs, "rename", False)
|
1729
1249
|
# if True, styles with identical names are renamed
|
@@ -1838,9 +1358,7 @@ class ASS:
|
|
1838
1358
|
# (x,y) new total resolution
|
1839
1359
|
geometry_scale = self.__kwarg_default(kwargs, "geometry_scale", None)
|
1840
1360
|
# (x,y) factors by which to scale geometry
|
1841
|
-
geometry_scale_origin = self.__kwarg_default(
|
1842
|
-
kwargs, "geometry_scale_origin", (0.0, 0.0)
|
1843
|
-
)
|
1361
|
+
geometry_scale_origin = self.__kwarg_default(kwargs, "geometry_scale_origin", (0.0, 0.0))
|
1844
1362
|
# (x,y) geometry scaling origin
|
1845
1363
|
geometry_offset = self.__kwarg_default(kwargs, "geometry_offset", (0.0, 0.0))
|
1846
1364
|
# (x,y) geometry shifting offset
|
@@ -1973,23 +1491,13 @@ class ASS:
|
|
1973
1491
|
line,
|
1974
1492
|
):
|
1975
1493
|
# Modify
|
1976
|
-
line.Start = (
|
1977
|
-
|
1978
|
-
+ time_scale_origin
|
1979
|
-
+ time_offset
|
1980
|
-
)
|
1981
|
-
line.End = (
|
1982
|
-
(line.End - time_scale_origin) * time_scale
|
1983
|
-
+ time_scale_origin
|
1984
|
-
+ time_offset
|
1985
|
-
)
|
1494
|
+
line.Start = (line.Start - time_scale_origin) * time_scale + time_scale_origin + time_offset
|
1495
|
+
line.End = (line.End - time_scale_origin) * time_scale + time_scale_origin + time_offset
|
1986
1496
|
|
1987
1497
|
# Modify timed tags
|
1988
1498
|
line.Text = self.parse_text(
|
1989
1499
|
line.Text,
|
1990
|
-
modify_tag=(
|
1991
|
-
lambda tag: self.__shiftscale_action_time_modify_tag(time_scale, tag)
|
1992
|
-
),
|
1500
|
+
modify_tag=(lambda tag: self.__shiftscale_action_time_modify_tag(time_scale, tag)),
|
1993
1501
|
)
|
1994
1502
|
|
1995
1503
|
# Clip
|
@@ -2017,23 +1525,23 @@ class ASS:
|
|
2017
1525
|
tag_name = tag[0]
|
2018
1526
|
if tag_name in ["k", "K", "kf", "ko"]:
|
2019
1527
|
tag = list(tag)
|
2020
|
-
tag[1] = str(int(
|
1528
|
+
tag[1] = str(int(Formatters.str_to_number(tag[1]) * time_scale))
|
2021
1529
|
elif tag_name == "move":
|
2022
1530
|
if len(tag) == 7:
|
2023
1531
|
tag = list(tag)
|
2024
|
-
tag[5] = str(int(
|
2025
|
-
tag[6] = str(int(
|
1532
|
+
tag[5] = str(int(Formatters.str_to_number(tag[5]) * time_scale))
|
1533
|
+
tag[6] = str(int(Formatters.str_to_number(tag[6]) * time_scale))
|
2026
1534
|
elif tag_name == "fade":
|
2027
1535
|
tag = list(tag)
|
2028
|
-
tag[4] = str(int(
|
2029
|
-
tag[5] = str(int(
|
2030
|
-
tag[6] = str(int(
|
2031
|
-
tag[7] = str(int(
|
1536
|
+
tag[4] = str(int(Formatters.str_to_number(tag[4]) * time_scale))
|
1537
|
+
tag[5] = str(int(Formatters.str_to_number(tag[5]) * time_scale))
|
1538
|
+
tag[6] = str(int(Formatters.str_to_number(tag[6]) * time_scale))
|
1539
|
+
tag[7] = str(int(Formatters.str_to_number(tag[7]) * time_scale))
|
2032
1540
|
elif tag_name == "t":
|
2033
1541
|
if len(tag) >= 4:
|
2034
1542
|
tag = list(tag)
|
2035
|
-
tag[1] = str(int(
|
2036
|
-
tag[2] = str(int(
|
1543
|
+
tag[1] = str(int(Formatters.str_to_number(tag[1]) * time_scale))
|
1544
|
+
tag[2] = str(int(Formatters.str_to_number(tag[2]) * time_scale))
|
2037
1545
|
|
2038
1546
|
return [tag]
|
2039
1547
|
|
@@ -2055,14 +1563,10 @@ class ASS:
|
|
2055
1563
|
line.Text = self.parse_text(
|
2056
1564
|
line.Text,
|
2057
1565
|
modify_tag=(
|
2058
|
-
lambda tag: self.__shiftscale_action_geometry_modify_tag(
|
2059
|
-
state, geometry_scale, geometry_scale_origin, geometry_offset, tag
|
2060
|
-
)
|
1566
|
+
lambda tag: self.__shiftscale_action_geometry_modify_tag(state, geometry_scale, geometry_scale_origin, geometry_offset, tag)
|
2061
1567
|
),
|
2062
1568
|
modify_geometry=(
|
2063
|
-
lambda geo: self.__shiftscale_action_geometry_modify_geometry(
|
2064
|
-
geometry_scale, geometry_scale_origin, geometry_offset, geo
|
2065
|
-
)
|
1569
|
+
lambda geo: self.__shiftscale_action_geometry_modify_geometry(geometry_scale, geometry_scale_origin, geometry_offset, geo)
|
2066
1570
|
),
|
2067
1571
|
)
|
2068
1572
|
|
@@ -2090,44 +1594,34 @@ class ASS:
|
|
2090
1594
|
# Done
|
2091
1595
|
return line
|
2092
1596
|
|
2093
|
-
def __shiftscale_action_geometry_modify_tag(
|
2094
|
-
self, state, geometry_scale, geometry_scale_origin, geometry_offset, tag
|
2095
|
-
):
|
1597
|
+
def __shiftscale_action_geometry_modify_tag(self, state, geometry_scale, geometry_scale_origin, geometry_offset, tag):
|
2096
1598
|
tag_name = tag[0]
|
2097
1599
|
if tag_name in ["bord", "shad", "be", "blur", "fs"]:
|
2098
1600
|
scale = (geometry_scale[0] + geometry_scale[1]) / 2.0
|
2099
1601
|
tag = [
|
2100
1602
|
tag_name,
|
2101
|
-
|
2102
|
-
self.Formatters.str_to_number(tag[1]) * scale
|
2103
|
-
),
|
1603
|
+
Formatters.number_to_str(Formatters.str_to_number(tag[1]) * scale),
|
2104
1604
|
]
|
2105
1605
|
elif tag_name in ["xbord", "xshad", "fsp"]:
|
2106
1606
|
tag = [
|
2107
1607
|
tag_name,
|
2108
|
-
|
2109
|
-
self.Formatters.str_to_number(tag[1]) * geometry_scale[0]
|
2110
|
-
),
|
1608
|
+
Formatters.number_to_str(Formatters.str_to_number(tag[1]) * geometry_scale[0]),
|
2111
1609
|
]
|
2112
1610
|
elif tag_name in ["ybord", "yshad"]:
|
2113
1611
|
tag = [
|
2114
1612
|
tag_name,
|
2115
|
-
|
2116
|
-
self.Formatters.str_to_number(tag[1]) * geometry_scale[1]
|
2117
|
-
),
|
1613
|
+
Formatters.number_to_str(Formatters.str_to_number(tag[1]) * geometry_scale[1]),
|
2118
1614
|
]
|
2119
1615
|
elif tag_name in ["pos", "org"]:
|
2120
1616
|
tag = [
|
2121
1617
|
tag_name,
|
2122
|
-
|
2123
|
-
(
|
2124
|
-
* geometry_scale[0]
|
1618
|
+
Formatters.number_to_str(
|
1619
|
+
(Formatters.str_to_number(tag[1]) - geometry_scale_origin[0]) * geometry_scale[0]
|
2125
1620
|
+ geometry_scale_origin[0]
|
2126
1621
|
+ geometry_offset[0]
|
2127
1622
|
),
|
2128
|
-
|
2129
|
-
(
|
2130
|
-
* geometry_scale[1]
|
1623
|
+
Formatters.number_to_str(
|
1624
|
+
(Formatters.str_to_number(tag[2]) - geometry_scale_origin[1]) * geometry_scale[1]
|
2131
1625
|
+ geometry_scale_origin[1]
|
2132
1626
|
+ geometry_offset[1]
|
2133
1627
|
),
|
@@ -2138,34 +1632,24 @@ class ASS:
|
|
2138
1632
|
tag = list(tag)
|
2139
1633
|
for i in range(1, len(tag)):
|
2140
1634
|
xy = (i + 1) % 2
|
2141
|
-
val =
|
2142
|
-
val = (
|
2143
|
-
|
2144
|
-
+ geometry_scale_origin[xy]
|
2145
|
-
+ geometry_offset[xy]
|
2146
|
-
)
|
2147
|
-
tag[i] = self.Formatters.number_to_str(val)
|
1635
|
+
val = Formatters.str_to_number(tag[i])
|
1636
|
+
val = (val - geometry_scale_origin[xy]) * geometry_scale[xy] + geometry_scale_origin[xy] + geometry_offset[xy]
|
1637
|
+
tag[i] = Formatters.number_to_str(val)
|
2148
1638
|
else:
|
2149
1639
|
# Draw command
|
2150
1640
|
tag = list(tag)
|
2151
|
-
tag[-1] = self.__shiftscale_action_geometry_modify_geometry(
|
2152
|
-
geometry_scale, geometry_scale_origin, geometry_offset, tag[-1]
|
2153
|
-
)
|
1641
|
+
tag[-1] = self.__shiftscale_action_geometry_modify_geometry(geometry_scale, geometry_scale_origin, geometry_offset, tag[-1])
|
2154
1642
|
elif tag_name == "move":
|
2155
1643
|
tag = list(tag)
|
2156
1644
|
for i in range(1, len(tag)):
|
2157
1645
|
xy = (i + 1) % 2
|
2158
|
-
val =
|
2159
|
-
val = (
|
2160
|
-
|
2161
|
-
+ geometry_scale_origin[xy]
|
2162
|
-
+ geometry_offset[xy]
|
2163
|
-
)
|
2164
|
-
tag[i] = self.Formatters.number_to_str(val)
|
1646
|
+
val = Formatters.str_to_number(tag[i])
|
1647
|
+
val = (val - geometry_scale_origin[xy]) * geometry_scale[xy] + geometry_scale_origin[xy] + geometry_offset[xy]
|
1648
|
+
tag[i] = Formatters.number_to_str(val)
|
2165
1649
|
elif tag_name == "pbo":
|
2166
1650
|
tag = [
|
2167
1651
|
tag_name,
|
2168
|
-
str(int(
|
1652
|
+
str(int(Formatters.str_to_number(tag[1]) * geometry_scale[1])),
|
2169
1653
|
]
|
2170
1654
|
elif tag_name == "t":
|
2171
1655
|
# Parse more tags
|
@@ -2183,11 +1667,9 @@ class ASS:
|
|
2183
1667
|
)
|
2184
1668
|
elif tag_name in ["a", "an"]:
|
2185
1669
|
if tag_name == "a":
|
2186
|
-
align = self.__legacy_align_to_regular(
|
2187
|
-
self.Formatters.str_to_number(tag[1])
|
2188
|
-
)
|
1670
|
+
align = self.__legacy_align_to_regular(Formatters.str_to_number(tag[1]))
|
2189
1671
|
else: # if (tag_name == "an"):
|
2190
|
-
align =
|
1672
|
+
align = Formatters.str_to_number(tag[1])
|
2191
1673
|
|
2192
1674
|
# State update
|
2193
1675
|
if state is not None and state["align"] is None:
|
@@ -2197,30 +1679,21 @@ class ASS:
|
|
2197
1679
|
|
2198
1680
|
return [tag]
|
2199
1681
|
|
2200
|
-
def __shiftscale_action_geometry_modify_geometry(
|
2201
|
-
self, geometry_scale, geometry_scale_origin, geometry_offset, geo
|
2202
|
-
):
|
1682
|
+
def __shiftscale_action_geometry_modify_geometry(self, geometry_scale, geometry_scale_origin, geometry_offset, geo):
|
2203
1683
|
points = self.__re_draw_command_split.split(geo.strip())
|
2204
1684
|
xy = 0
|
2205
1685
|
for i in range(len(points)):
|
2206
1686
|
coord = points[i]
|
2207
1687
|
if len(coord) == 1:
|
2208
1688
|
coord_ord = ord(coord)
|
2209
|
-
if
|
2210
|
-
coord_ord >= self.__re_draw_commands_ord_min
|
2211
|
-
and coord_ord <= self.__re_draw_commands_ord_max
|
2212
|
-
):
|
1689
|
+
if coord_ord >= self.__re_draw_commands_ord_min and coord_ord <= self.__re_draw_commands_ord_max:
|
2213
1690
|
# New command
|
2214
1691
|
xy = 0
|
2215
1692
|
continue
|
2216
1693
|
|
2217
1694
|
# Value
|
2218
|
-
val =
|
2219
|
-
val = (
|
2220
|
-
(val - geometry_scale_origin[xy]) * geometry_scale[xy]
|
2221
|
-
+ geometry_scale_origin[xy]
|
2222
|
-
+ geometry_offset[xy]
|
2223
|
-
)
|
1695
|
+
val = Formatters.str_to_number(coord)
|
1696
|
+
val = (val - geometry_scale_origin[xy]) * geometry_scale[xy] + geometry_scale_origin[xy] + geometry_offset[xy]
|
2224
1697
|
points[i] = str(int(val))
|
2225
1698
|
|
2226
1699
|
# Next
|
@@ -2240,10 +1713,8 @@ class ASS:
|
|
2240
1713
|
return (
|
2241
1714
|
geometry_offset[0],
|
2242
1715
|
geometry_offset[1],
|
2243
|
-
(resolution_new[0] - resolution_old[0]) * geometry_scale[0]
|
2244
|
-
+ geometry_offset[
|
2245
|
-
(resolution_new[1] - resolution_old[1]) * geometry_scale[1]
|
2246
|
-
+ geometry_offset[1],
|
1716
|
+
(resolution_new[0] - resolution_old[0]) * geometry_scale[0] + geometry_offset[0],
|
1717
|
+
(resolution_new[1] - resolution_old[1]) * geometry_scale[1] + geometry_offset[1],
|
2247
1718
|
)
|
2248
1719
|
|
2249
1720
|
def __shiftscale_action_get_new_margins(
|
@@ -2268,17 +1739,13 @@ class ASS:
|
|
2268
1739
|
if margin_left != 0:
|
2269
1740
|
margin_left = margin_left * geometry_scale[0] + bounds[0]
|
2270
1741
|
if margin_right != 0:
|
2271
|
-
margin_right = resolution_new[0] - (
|
2272
|
-
bounds[2] - margin_right * geometry_scale[0]
|
2273
|
-
)
|
1742
|
+
margin_right = resolution_new[0] - (bounds[2] - margin_right * geometry_scale[0])
|
2274
1743
|
if margin_vertical != 0:
|
2275
1744
|
align_xy = self.get_xy_alignment(align)
|
2276
1745
|
if align_xy[1] < 0: # Top
|
2277
1746
|
margin_vertical = margin_vertical * geometry_scale[1] + bounds[1]
|
2278
1747
|
elif align_xy[1] > 0: # Bottom
|
2279
|
-
margin_vertical = resolution_new[1] - (
|
2280
|
-
bounds[3] - margin_vertical * geometry_scale[1]
|
2281
|
-
)
|
1748
|
+
margin_vertical = resolution_new[1] - (bounds[3] - margin_vertical * geometry_scale[1])
|
2282
1749
|
else: # if (align_xy[1] == 0): # Middle
|
2283
1750
|
margin_vertical = margin_vertical * geometry_scale[1]
|
2284
1751
|
|
@@ -2394,9 +1861,7 @@ class ASS:
|
|
2394
1861
|
length -= end - start
|
2395
1862
|
# account for the self.extract call
|
2396
1863
|
for line in self.events:
|
2397
|
-
if (
|
2398
|
-
filter_types is None or line.type in filter_types
|
2399
|
-
) and line.Start >= end:
|
1864
|
+
if (filter_types is None or line.type in filter_types) and line.Start >= end:
|
2400
1865
|
line.Start += length
|
2401
1866
|
line.End += length
|
2402
1867
|
|
@@ -2404,9 +1869,7 @@ class ASS:
|
|
2404
1869
|
time_offset = 0.0
|
2405
1870
|
while count >= 1:
|
2406
1871
|
# Merge
|
2407
|
-
self.merge(
|
2408
|
-
other=temp, remove=False, filter_types=None, time_shift=time_offset
|
2409
|
-
)
|
1872
|
+
self.merge(other=temp, remove=False, filter_types=None, time_shift=time_offset)
|
2410
1873
|
|
2411
1874
|
# Shift for next
|
2412
1875
|
time_offset += length_single
|
@@ -2415,9 +1878,7 @@ class ASS:
|
|
2415
1878
|
# Cut
|
2416
1879
|
temp.extract(start=start, end=start + length_single * count, inverse=True)
|
2417
1880
|
# Add
|
2418
|
-
self.merge(
|
2419
|
-
other=temp, remove=True, filter_types=None, time_shift=time_offset
|
2420
|
-
)
|
1881
|
+
self.merge(other=temp, remove=True, filter_types=None, time_shift=time_offset)
|
2421
1882
|
|
2422
1883
|
# Done
|
2423
1884
|
return self
|
@@ -2625,4 +2086,3 @@ class ASS:
|
|
2625
2086
|
)
|
2626
2087
|
|
2627
2088
|
return line
|
2628
|
-
|