-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtext_tools.py
More file actions
322 lines (274 loc) · 15.5 KB
/
Copy pathtext_tools.py
File metadata and controls
322 lines (274 loc) · 15.5 KB
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
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
import os
import unicodedata
import numpy as np
from PIL import Image, ImageDraw, ImageFont
def get_width_count(text):
"""全角文字を考慮して、文字列の表示幅を計算します。"""
count = 0
for c in text:
if unicodedata.east_asian_width(c) in 'FWA':
count += 2
else:
count += 1
return count
def get_width_from_text(text, settings, mode="lyric", is_chorus=False):
"""フォント設定に基づいて、文字列のバウンディングボックス、幅、および余白を計算します。"""
if mode == "lyric":
if is_chorus:
font_size = settings.LYRIC_CHORUS.FONT_SIZE
font_path = settings.LYRIC_CHORUS.FONT_PATH
margin_full = settings.LYRIC_CHORUS.MARGIN_FULL
margin_half = settings.LYRIC_CHORUS.MARGIN_HALF
text_with_min = settings.LYRIC_CHORUS.TEXT_WIDTH_MIN
margin_space = settings.LYRIC_CHORUS.MARGIN_SPACE
else:
font_size = settings.LYRIC.FONT_SIZE
font_path = settings.LYRIC.FONT_PATH
margin_full = settings.LYRIC.MARGIN_FULL
margin_half = settings.LYRIC.MARGIN_HALF
text_with_min = settings.LYRIC.TEXT_WIDTH_MIN
margin_space = settings.LYRIC.MARGIN_SPACE
else:
if is_chorus:
font_size = settings.RUBY_CHORUS.FONT_SIZE
font_path = settings.RUBY_CHORUS.FONT_PATH
margin_full = settings.RUBY_CHORUS.MARGIN_FULL
margin_half = settings.RUBY_CHORUS.MARGIN_HALF
text_with_min = settings.RUBY_CHORUS.TEXT_WIDTH_MIN
margin_space = settings.RUBY_CHORUS.MARGIN_SPACE
else:
font_size = settings.RUBY.FONT_SIZE
font_path = settings.RUBY.FONT_PATH
margin_full = settings.RUBY.MARGIN_FULL
margin_half = settings.RUBY.MARGIN_HALF
text_with_min = settings.RUBY.TEXT_WIDTH_MIN
margin_space = settings.RUBY.MARGIN_SPACE
image = Image.new('RGBA', (font_size + 100, font_size + 100), (255, 255, 255, 0))
draw = ImageDraw.Draw(image)
font = ImageFont.truetype(font_path, font_size)
draw.text((0, 0), text, font=font, fill=(255, 0, 0, 255))
bbox = list(draw.textbbox((0, 0), text, font=font))
image_np = np.array(image)
red_pixels = np.where((image_np[:, :, 0] == 255) & (image_np[:, :, 1] == 0) & (image_np[:, :, 2] == 0))
if red_pixels[1].size > 0:
leftmost_x = np.min(red_pixels[1])
rightmost_x = np.max(red_pixels[1])
bbox[0] = leftmost_x
bbox[2] = rightmost_x
_width = rightmost_x - leftmost_x
else:
_width = bbox[2] - bbox[0]
margin = margin_full if get_width_count(text) > 1 else margin_half
if not text != "" and _width < text_with_min:
_width = text_with_min
if text == " ":
_width = 0
margin = margin_space
del image
return {"bbox": bbox, "margin": margin, "width": _width}
def draw_text_with_bbox(text_units, settings, mode="lyric", is_chorus=False, output_path=None):
"""文字列をバウンディングボックス付きで描画し、描画後の最終的なx座標を返します。"""
if mode == "lyric":
if is_chorus:
font_size = settings.LYRIC_CHORUS.FONT_SIZE
font_path = settings.LYRIC_CHORUS.FONT_PATH
stroke_size = settings.LYRIC_CHORUS.STROKE_WIDTH
else:
font_size = settings.LYRIC.FONT_SIZE
font_path = settings.LYRIC.FONT_PATH
stroke_size = settings.LYRIC.STROKE_WIDTH
else:
if is_chorus:
font_size = settings.RUBY_CHORUS.FONT_SIZE
font_path = settings.RUBY_CHORUS.FONT_PATH
stroke_size = settings.RUBY_CHORUS.STROKE_WIDTH
else:
font_size = settings.RUBY.FONT_SIZE
font_path = settings.RUBY.FONT_PATH
stroke_size = settings.RUBY.STROKE_WIDTH
# Create an image with transparency (RGBA)
x_base = stroke_size
image = Image.new('RGBA', (settings.GENERAL.WIDTH, settings.GENERAL.HEIGHT), (255, 255, 255, 0))
draw = ImageDraw.Draw(image)
font = ImageFont.truetype(font_path, font_size)
_x_starts = []
_x_ends = []
for text in text_units:
_x_starts.append(x_base)
_margin = 0
for t in text:
_data = get_width_from_text(t, settings=settings, mode=mode, is_chorus=is_chorus)
_bbox = _data["bbox"]
_width = _data["width"]
_margin = _data["margin"]
draw.text((x_base - _bbox[0], 100), t, font=font, fill=(255, 255, 255, 255), stroke_width=stroke_size, stroke_fill=(0, 0, 0, 230))
x_base += _width + _margin
_x_ends.append(x_base - _margin)
# Save the image
if output_path is not None:
image.save(output_path, 'PNG')
return x_base + stroke_size, _x_starts, _x_ends
COLOR_FILL_BEFORE_MAIN = None
COLOR_STROKE_FILL_BEFORE_MAIN = None
COLOR_FILL_AFTER_MAIN = None
COLOR_STROKE_FILL_AFTER_MAIN = None
COLOR_FILL_BEFORE_CURRENT = None
COLOR_STROKE_FILL_BEFORE_CURRENT = None
COLOR_FILL_AFTER_CURRENT = None
COLOR_STROKE_FILL_AFTER_CURRENT = None
IS_CHORUS = False
def draw_lyric_image_with_ruby(data, settings, output_path_1="images/output_1.png", output_path_2="images/output_2.png"):
"""歌詞とルビを含む画像を生成し、2つの画像を保存して分析データを返します。"""
global COLOR_FILL_BEFORE_MAIN, COLOR_STROKE_FILL_BEFORE_MAIN, COLOR_FILL_AFTER_MAIN, COLOR_STROKE_FILL_AFTER_MAIN
global COLOR_FILL_BEFORE_CURRENT, COLOR_STROKE_FILL_BEFORE_CURRENT, COLOR_FILL_AFTER_CURRENT, COLOR_STROKE_FILL_AFTER_CURRENT
global IS_CHORUS
x_base = settings.GENERAL.X_BASE_INIT
if COLOR_FILL_BEFORE_MAIN == None:
COLOR_FILL_BEFORE_MAIN = settings.GENERAL.COLOR_FILL_BEFORE
COLOR_STROKE_FILL_BEFORE_MAIN = settings.GENERAL.COLOR_STROKE_FILL_BEFORE
COLOR_FILL_AFTER_MAIN = settings.GENERAL.COLOR_FILL_AFTER
COLOR_STROKE_FILL_AFTER_MAIN = settings.GENERAL.COLOR_STROKE_FILL_AFTER
COLOR_FILL_BEFORE_CURRENT = settings.GENERAL.COLOR_FILL_BEFORE
COLOR_STROKE_FILL_BEFORE_CURRENT = settings.GENERAL.COLOR_STROKE_FILL_BEFORE
COLOR_FILL_AFTER_CURRENT = settings.GENERAL.COLOR_FILL_AFTER
COLOR_STROKE_FILL_AFTER_CURRENT = settings.GENERAL.COLOR_STROKE_FILL_AFTER
# Before wipe
image_1 = Image.new('RGBA', (settings.GENERAL.WIDTH, settings.GENERAL.HEIGHT), (255, 255, 255, 0))
draw_1 = ImageDraw.Draw(image_1)
# After wipe
image_2 = Image.new('RGBA', (settings.GENERAL.WIDTH, settings.GENERAL.HEIGHT), (255, 255, 255, 0))
draw_2 = ImageDraw.Draw(image_2)
if IS_CHORUS:
lyric_cat = "LYRIC_CHORUS"
ruby_cat = "RUBY_CHORUS"
else:
lyric_cat = "LYRIC"
ruby_cat = "RUBY"
font_lyric = ImageFont.truetype(getattr(settings, lyric_cat).FONT_PATH, getattr(settings, lyric_cat).FONT_SIZE)
font_ruby = ImageFont.truetype(getattr(settings, ruby_cat).FONT_PATH, getattr(settings, ruby_cat).FONT_SIZE)
x_base = settings.GENERAL.X_BASE_INIT
x_start_lyric = []
x_end_lyric = []
x_start_ruby = []
x_end_ruby = []
for i, (lyric_units, ruby_units) in enumerate(zip(data["lyrics"], data["rubys"])):
lyric = ''.join(lyric_units)
ruby = ''.join(ruby_units)
_width_lyric, _, _ = draw_text_with_bbox(lyric_units, settings=settings, mode="lyric", is_chorus=IS_CHORUS)
_width_ruby, _xs_start_ruby, _xs_end_ruby = draw_text_with_bbox(ruby_units, settings=settings, mode="ruby", is_chorus=IS_CHORUS)
if lyric == " " and ruby == "":
x_start_lyric.append([x_end_lyric[-1][-1]])
x_start_ruby.append([x_end_ruby[-1][-1]])
x_base += getattr(settings, lyric_cat).MARGIN_SPACE
x_end_lyric.append([int(x_base - getattr(settings, lyric_cat).STROKE_WIDTH)])
x_end_ruby.append([int(x_base - getattr(settings, lyric_cat).STROKE_WIDTH)])
continue
elif lyric == "":
x_start_lyric.append([int(x_base - getattr(settings, lyric_cat).STROKE_WIDTH)])
x_start_ruby.append([int(x_base - getattr(settings, ruby_cat).STROKE_WIDTH)])
x_end_lyric.append([int(x_base - getattr(settings, lyric_cat).STROKE_WIDTH)])
x_end_ruby.append([int(x_base - getattr(settings, ruby_cat).STROKE_WIDTH)])
continue
if _width_lyric >= _width_ruby:
_x_lyric = x_base
_x_ruby = x_base + (_width_lyric - _width_ruby) // 2
else:
_x_lyric = x_base + (_width_ruby - _width_lyric) // 2
_x_ruby = x_base
# draw icon
if lyric in settings.GENERAL.CHANGE_TO_PART_STR:
idx = settings.GENERAL.CHANGE_TO_PART_STR.index(lyric)
paste_image = Image.open(settings.GENERAL.PART_ICON[idx]).convert("RGBA")
original_width, original_height = paste_image.size
new_width = int((settings.GENERAL.PART_ICON_HEIGHT / original_height) * original_width)
resized_image = paste_image.resize((new_width, settings.GENERAL.PART_ICON_HEIGHT), Image.ANTIALIAS)
_x_lyric = _x_lyric - settings.GENERAL.PART_ICON_MARGIN_X
# image_1.paste(resized_image, (_x_lyric + settings.GENERAL.PART_ICON_OFFSET_X, settings.GENERAL.Y_LYRIC + settings.GENERAL.PART_ICON_OFFSET_Y), resized_image)
image_2.paste(resized_image, (_x_lyric + settings.GENERAL.PART_ICON_OFFSET_X, settings.GENERAL.Y_LYRIC + settings.GENERAL.PART_ICON_OFFSET_Y), resized_image)
x_start_lyric.append([int(_x_lyric)])
x_start_ruby.append([int(_x_lyric)])
x_base = _x_lyric + new_width + settings.GENERAL.PART_ICON_MARGIN_X
x_end_lyric.append([int(_x_lyric + new_width)])
x_end_ruby.append([int(_x_lyric + new_width)])
# change color
COLOR_FILL_BEFORE_CURRENT = tuple(settings.GENERAL.COLOR_FILL_BEFORE_PART[idx])
COLOR_STROKE_FILL_BEFORE_CURRENT = tuple(settings.GENERAL.COLOR_STROKE_FILL_BEFORE_PART[idx])
COLOR_FILL_AFTER_CURRENT = tuple(settings.GENERAL.COLOR_FILL_AFTER_PART[idx])
COLOR_STROKE_FILL_AFTER_CURRENT = tuple(settings.GENERAL.COLOR_STROKE_FILL_AFTER_PART[idx])
continue
# draw lyric
x_start_lyric.append([int(_x_lyric - getattr(settings, lyric_cat).STROKE_WIDTH)])
_margin = 0
for t in lyric:
# Change color
if t in settings.GENERAL.CHANGE_TO_CHORUS_STR:
COLOR_FILL_BEFORE_CURRENT = settings.GENERAL.COLOR_FILL_BEFORE_CHORUS
COLOR_STROKE_FILL_BEFORE_CURRENT = settings.GENERAL.COLOR_STROKE_FILL_BEFORE_CHORUS
COLOR_FILL_AFTER_CURRENT = settings.GENERAL.COLOR_FILL_AFTER_CHORUS
COLOR_STROKE_FILL_AFTER_CURRENT = settings.GENERAL.COLOR_STROKE_FILL_AFTER_CHORUS
IS_CHORUS = True
font_lyric = ImageFont.truetype(settings.LYRIC_CHORUS.FONT_PATH, settings.LYRIC_CHORUS.FONT_SIZE)
font_ruby = ImageFont.truetype(settings.RUBY_CHORUS.FONT_PATH, settings.RUBY_CHORUS.FONT_SIZE)
lyric_cat = "LYRIC_CHORUS"
ruby_cat = "RUBY_CHORUS"
_data = get_width_from_text(t, settings=settings, mode="lyric", is_chorus=IS_CHORUS)
_bbox = _data["bbox"]
_width = _data["width"]
_margin = _data["margin"]
draw_1.text((_x_lyric - _bbox[0], settings.GENERAL.Y_LYRIC + getattr(settings, lyric_cat).Y_DRAW_OFFSET), t, font=font_lyric, fill=COLOR_FILL_BEFORE_CURRENT, stroke_width=getattr(settings, lyric_cat).STROKE_WIDTH, stroke_fill=COLOR_STROKE_FILL_BEFORE_CURRENT)
draw_2.text((_x_lyric - _bbox[0], settings.GENERAL.Y_LYRIC + getattr(settings, lyric_cat).Y_DRAW_OFFSET), t, font=font_lyric, fill=COLOR_FILL_AFTER_CURRENT, stroke_width=getattr(settings, lyric_cat).STROKE_WIDTH, stroke_fill=COLOR_STROKE_FILL_AFTER_CURRENT)
_x_lyric += _width + _margin
x_end_lyric.append([int(_x_lyric - _margin + getattr(settings, lyric_cat).STROKE_WIDTH)])
# draw ruby
_x_start = int(_x_ruby - getattr(settings, ruby_cat).STROKE_WIDTH)
_x_start_offset = _x_start - _xs_start_ruby[0]
x_start_ruby.append([int(_x + _x_start_offset) for _x in _xs_start_ruby])
_margin = 0
for t in ruby:
_data = get_width_from_text(t, settings=settings, mode="ruby", is_chorus=IS_CHORUS)
_bbox = _data["bbox"]
_width = _data["width"]
_margin = _data["margin"]
draw_1.text((_x_ruby - _bbox[0], settings.GENERAL.Y_RUBY + getattr(settings, ruby_cat).Y_DRAW_OFFSET), t, font=font_ruby, fill=COLOR_FILL_BEFORE_CURRENT, stroke_width=getattr(settings, ruby_cat).STROKE_WIDTH, stroke_fill=COLOR_STROKE_FILL_BEFORE_CURRENT)
draw_2.text((_x_ruby - _bbox[0], settings.GENERAL.Y_RUBY + getattr(settings, ruby_cat).Y_DRAW_OFFSET), t, font=font_ruby, fill=COLOR_FILL_AFTER_CURRENT, stroke_width=getattr(settings, ruby_cat).STROKE_WIDTH, stroke_fill=COLOR_STROKE_FILL_AFTER_CURRENT)
_x_ruby += _width + _margin
_x_end = int(_x_ruby - _margin + getattr(settings, ruby_cat).STROKE_WIDTH)
_x_end_offset = _x_end - _xs_end_ruby[-1]
x_end_ruby.append([int(_x + _x_end_offset) for _x in _xs_end_ruby])
if _width_lyric >= _width_ruby:
x_base = _x_lyric
else:
x_base = _x_ruby
if lyric[-1] in settings.GENERAL.CHANGE_TO_MAIN_STR:
COLOR_FILL_BEFORE_CURRENT = COLOR_FILL_BEFORE_MAIN
COLOR_STROKE_FILL_BEFORE_CURRENT = COLOR_STROKE_FILL_BEFORE_MAIN
COLOR_FILL_AFTER_CURRENT = COLOR_FILL_AFTER_MAIN
COLOR_STROKE_FILL_AFTER_CURRENT = COLOR_STROKE_FILL_AFTER_MAIN
IS_CHORUS = False
font_lyric = ImageFont.truetype(settings.LYRIC.FONT_PATH, settings.LYRIC.FONT_SIZE)
font_ruby = ImageFont.truetype(settings.RUBY.FONT_PATH, settings.RUBY.FONT_SIZE)
lyric_cat = "LYRIC"
ruby_cat = "RUBY"
# アンチエイリアス描画でワイプ前・後の文字を重ねた時の透け防止対策
if settings.GENERAL.DISABLE_STROKE_ANTIALIASING:
arr_1 = np.array(image_1)
alpha_channel = arr_1[:, :, 3]
mask = (alpha_channel > 0) & (alpha_channel < 255)
arr_1[mask, 3] = 255
image_1 = Image.fromarray(arr_1, mode="RGBA")
# ワイプ後字幕のアンチエイリアシング処理無効化はスキップ
# arr_2 = np.array(image_2)
# alpha_channel = arr_2[:, :, 3]
# mask = (alpha_channel > 0) & (alpha_channel < 255)
# arr_2[mask, 3] = 255
# image_2 = Image.fromarray(arr_2, mode="RGBA")
image_1.save(output_path_1, 'PNG')
image_2.save(output_path_2, 'PNG')
data["x_start_lyric"] = x_start_lyric
data["x_end_lyric"] = x_end_lyric
data["x_start_ruby"] = x_start_ruby
data["x_end_ruby"] = x_end_ruby
data["x_length"] = x_end_lyric[-1][-1] + settings.GENERAL.X_BASE_INIT if x_end_lyric[-1][-1] > x_end_ruby[-1][-1] else x_end_ruby[-1][-1] + settings.GENERAL.X_BASE_INIT
data["image_1"] = os.path.abspath(output_path_1)
data["image_2"] = os.path.abspath(output_path_2)
return data