Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
|
@@ -146,8 +146,7 @@ def parse_srt_from_text(srt_text):
|
|
| 146 |
start_str, end_str = times.split("-->")
|
| 147 |
start = srt_time_to_sec(start_str)
|
| 148 |
end = srt_time_to_sec(end_str)
|
| 149 |
-
# захоўваем пераносы
|
| 150 |
-
text = "\n".join(lines[2:]).strip()
|
| 151 |
subs.append({"start": start, "end": end, "text": text})
|
| 152 |
except Exception:
|
| 153 |
continue
|
|
@@ -158,13 +157,11 @@ def parse_srt_from_text(srt_text):
|
|
| 158 |
# Wrap: аўтаматычны перанос радкоў па шырыні кадра
|
| 159 |
# ----------------------------
|
| 160 |
def _line_width_px(draw: ImageDraw.ImageDraw, line: str, font: ImageFont.ImageFont, stroke_width: int = 0) -> int:
|
| 161 |
-
"""Вяртае шырыню радка ў пікселях (з улікам stroke), максімальна сумяшчальна з Pillow."""
|
| 162 |
try:
|
| 163 |
l, t, r, b = draw.textbbox((0, 0), line, font=font, stroke_width=stroke_width)
|
| 164 |
return int(r - l)
|
| 165 |
except Exception:
|
| 166 |
try:
|
| 167 |
-
# textlength не ўлічвае stroke, таму дадамо запас
|
| 168 |
w = draw.textlength(line, font=font)
|
| 169 |
return int(w + 2 * stroke_width)
|
| 170 |
except Exception:
|
|
@@ -173,7 +170,6 @@ def _line_width_px(draw: ImageDraw.ImageDraw, line: str, font: ImageFont.ImageFo
|
|
| 173 |
|
| 174 |
|
| 175 |
def _break_long_word(draw, word: str, font, max_width: int, stroke_width: int) -> list:
|
| 176 |
-
"""Калі слова вельмі доўгае — разбіваем па сімвалах (greedy)."""
|
| 177 |
chunks = []
|
| 178 |
cur = ""
|
| 179 |
for ch in word:
|
|
@@ -190,10 +186,6 @@ def _break_long_word(draw, word: str, font, max_width: int, stroke_width: int) -
|
|
| 190 |
|
| 191 |
def wrap_text_to_width(text: str, draw: ImageDraw.ImageDraw, font: ImageFont.ImageFont,
|
| 192 |
max_width: int, stroke_width: int = 0) -> str:
|
| 193 |
-
"""
|
| 194 |
-
Аўта-перанос: захоўвае існуючыя '\n' як абзацы,
|
| 195 |
-
але ўнутры кожнага радка робіць word-wrap пад max_width.
|
| 196 |
-
"""
|
| 197 |
if not text:
|
| 198 |
return text
|
| 199 |
|
|
@@ -210,7 +202,6 @@ def wrap_text_to_width(text: str, draw: ImageDraw.ImageDraw, font: ImageFont.Ima
|
|
| 210 |
line = ""
|
| 211 |
for w in words:
|
| 212 |
if not line:
|
| 213 |
-
# калі слова занадта доўгае — разбіваем
|
| 214 |
if _line_width_px(draw, w, font, stroke_width) <= max_width:
|
| 215 |
line = w
|
| 216 |
else:
|
|
@@ -224,7 +215,6 @@ def wrap_text_to_width(text: str, draw: ImageDraw.ImageDraw, font: ImageFont.Ima
|
|
| 224 |
line = test
|
| 225 |
else:
|
| 226 |
out_lines.append(line)
|
| 227 |
-
# пачынаем новы радок
|
| 228 |
if _line_width_px(draw, w, font, stroke_width) <= max_width:
|
| 229 |
line = w
|
| 230 |
else:
|
|
@@ -264,6 +254,10 @@ def _measure_multiline_bbox(draw, text, font, stroke_width=0, spacing=4):
|
|
| 264 |
return (0, 0, w, h)
|
| 265 |
|
| 266 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 267 |
def create_animated_text_clip(
|
| 268 |
text,
|
| 269 |
duration,
|
|
@@ -273,14 +267,14 @@ def create_animated_text_clip(
|
|
| 273 |
stroke_color,
|
| 274 |
stroke_width,
|
| 275 |
position_type,
|
| 276 |
-
|
| 277 |
custom_y,
|
| 278 |
animation,
|
| 279 |
video_width,
|
| 280 |
video_height,
|
| 281 |
bg_color=None,
|
| 282 |
bg_opacity=1.0,
|
| 283 |
-
wrap_ratio: float = 0.90,
|
| 284 |
):
|
| 285 |
try:
|
| 286 |
txt_rgb = hex_to_rgb(color)
|
|
@@ -313,7 +307,6 @@ def create_animated_text_clip(
|
|
| 313 |
spacing = max(0, int(fontsize * 0.15))
|
| 314 |
|
| 315 |
# --- AUTO WRAP ---
|
| 316 |
-
# Макс. шырыня тэксту = wrap_ratio * video_width (але мінімум 200px)
|
| 317 |
max_text_width = max(200, int(video_width * float(wrap_ratio)))
|
| 318 |
wrapped_text = wrap_text_to_width(text, dummy_draw, pil_font, max_text_width, stroke_width=stroke_width)
|
| 319 |
|
|
@@ -332,7 +325,7 @@ def create_animated_text_clip(
|
|
| 332 |
img = Image.new("RGBA", (img_w, img_h), bg_rgb + (bg_a,))
|
| 333 |
draw = ImageDraw.Draw(img)
|
| 334 |
|
| 335 |
-
# галоўнае: улічваем bbox (можа быць адмоўны)
|
| 336 |
text_x = (pad_x + safe) - l
|
| 337 |
text_y = (pad_y + safe) - t
|
| 338 |
|
|
@@ -355,19 +348,28 @@ def create_animated_text_clip(
|
|
| 355 |
mask = mpe.ImageClip(alpha, ismask=True).set_duration(duration)
|
| 356 |
clip = clip.set_mask(mask)
|
| 357 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 358 |
if position_type == "bottom":
|
| 359 |
-
x =
|
| 360 |
-
y = video_height - clip.h - 20
|
| 361 |
elif position_type == "top":
|
| 362 |
-
x =
|
| 363 |
-
y = 20
|
| 364 |
elif position_type == "center":
|
| 365 |
-
x =
|
| 366 |
y = (video_height - clip.h) / 2
|
| 367 |
-
else:
|
| 368 |
-
x = float(
|
| 369 |
y = float(custom_y)
|
| 370 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 371 |
anim = (animation or "").lower()
|
| 372 |
|
| 373 |
if anim == "fade":
|
|
@@ -401,7 +403,7 @@ def apply_subtitles(
|
|
| 401 |
stroke_color,
|
| 402 |
stroke_width,
|
| 403 |
position_type,
|
| 404 |
-
|
| 405 |
custom_y,
|
| 406 |
animation,
|
| 407 |
export_quality,
|
|
@@ -427,7 +429,7 @@ def apply_subtitles(
|
|
| 427 |
stroke_color,
|
| 428 |
stroke_width,
|
| 429 |
position_type,
|
| 430 |
-
|
| 431 |
custom_y,
|
| 432 |
animation,
|
| 433 |
w,
|
|
@@ -476,7 +478,7 @@ def create_single_frame_video(
|
|
| 476 |
stroke_color,
|
| 477 |
stroke_width,
|
| 478 |
position_type,
|
| 479 |
-
|
| 480 |
custom_y,
|
| 481 |
animation,
|
| 482 |
bg_color=None,
|
|
@@ -498,7 +500,7 @@ def create_single_frame_video(
|
|
| 498 |
stroke_color,
|
| 499 |
stroke_width,
|
| 500 |
position_type,
|
| 501 |
-
|
| 502 |
custom_y,
|
| 503 |
animation,
|
| 504 |
w,
|
|
@@ -540,28 +542,81 @@ CSS = f"""
|
|
| 540 |
|
| 541 |
|
| 542 |
# ----------------------------
|
| 543 |
-
# Gradio UI
|
| 544 |
# ----------------------------
|
| 545 |
with gr.Blocks(css=CSS) as demo:
|
| 546 |
gr.Markdown("# Аўтаматычнае стварэнне і накладанне субтытраў")
|
| 547 |
|
| 548 |
-
# а
|
| 549 |
-
video_input = gr.Video(
|
| 550 |
-
label="Загрузіце відэа (тут жа будзе перадпрагляд і вынік)",
|
| 551 |
-
elem_id="main_video",
|
| 552 |
-
)
|
| 553 |
-
|
| 554 |
uploaded_video = gr.State(None)
|
| 555 |
first_frame = gr.State(None)
|
| 556 |
|
| 557 |
-
with gr.Row():
|
| 558 |
-
|
| 559 |
-
|
| 560 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 561 |
def _on_video_change(v, current_uploaded, current_frame):
|
| 562 |
if not v:
|
| 563 |
return None, None
|
| 564 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 565 |
name = os.path.basename(str(v))
|
| 566 |
if name in ("preview_video.mp4", "output_video.mp4"):
|
| 567 |
return current_uploaded, current_frame
|
|
@@ -575,6 +630,13 @@ with gr.Blocks(css=CSS) as demo:
|
|
| 575 |
outputs=[uploaded_video, first_frame],
|
| 576 |
)
|
| 577 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 578 |
def _on_generate_click(v_orig):
|
| 579 |
if not v_orig:
|
| 580 |
return "Не выбрана відэа"
|
|
@@ -583,41 +645,15 @@ with gr.Blocks(css=CSS) as demo:
|
|
| 583 |
|
| 584 |
gen_btn.click(_on_generate_click, inputs=uploaded_video, outputs=subs_box)
|
| 585 |
|
| 586 |
-
|
| 587 |
-
|
| 588 |
-
|
| 589 |
-
|
| 590 |
-
size_sl = gr.Slider(10, 100, 1, value=40, label="Памер шрыфта")
|
| 591 |
-
|
| 592 |
-
with gr.Row():
|
| 593 |
-
color_txt = gr.ColorPicker(value="#FFFFFF", label="Колер тэксту")
|
| 594 |
-
color_st = gr.ColorPicker(value="#000000", label="Колер абводкі")
|
| 595 |
-
width_sl = gr.Slider(0, 10, 1, value=2, label="Таўшчыня абводкі")
|
| 596 |
-
|
| 597 |
-
with gr.Row():
|
| 598 |
-
pos_dd = gr.Dropdown(["bottom", "top", "center", "custom"], value="bottom", label="Пазіцыя")
|
| 599 |
-
anim_dd = gr.Dropdown(["None", "fade", "slide", "zoom"], value="None", label="Анімацыя")
|
| 600 |
-
|
| 601 |
-
with gr.Row():
|
| 602 |
-
x_sl = gr.Slider(0, 1920, 1, value=100, label="X (для custom)")
|
| 603 |
-
y_sl = gr.Slider(0, 1080, 1, value=100, label="Y (для custom)")
|
| 604 |
|
| 605 |
-
|
| 606 |
-
bg_color = gr.ColorPicker(value="#000000", label="Колер фону")
|
| 607 |
-
bg_opac = gr.Slider(0.0, 1.0, 0.1, value=0.5, label="Празрыстасць фону")
|
| 608 |
|
| 609 |
-
#
|
| 610 |
-
|
| 611 |
-
wrap_en = gr.Checkbox(value=True, label="Аўта-перанос радкоў")
|
| 612 |
-
wrap_pct = gr.Slider(50, 100, 1, value=90, label="Макс. шырыня радка (%)")
|
| 613 |
-
|
| 614 |
-
exp_dd = gr.Dropdown(
|
| 615 |
-
["мінімальнае", "арыгінальнае", "сярэдняе", "максімальнае"],
|
| 616 |
-
value="арыгінальнае",
|
| 617 |
-
label="Якасць",
|
| 618 |
-
)
|
| 619 |
-
|
| 620 |
-
def update_preview(frame, text, font, size, col, colst, wd, pos, x, y, anim, bgc, bgo, do_wrap, w_pct):
|
| 621 |
if frame is None:
|
| 622 |
return None
|
| 623 |
|
|
@@ -636,7 +672,7 @@ with gr.Blocks(css=CSS) as demo:
|
|
| 636 |
colst,
|
| 637 |
wd,
|
| 638 |
pos,
|
| 639 |
-
|
| 640 |
y,
|
| 641 |
anim,
|
| 642 |
bg_color=bgc,
|
|
@@ -644,30 +680,29 @@ with gr.Blocks(css=CSS) as demo:
|
|
| 644 |
wrap_ratio=wrap_ratio,
|
| 645 |
)
|
| 646 |
|
| 647 |
-
|
| 648 |
-
|
| 649 |
-
|
| 650 |
-
|
| 651 |
-
|
| 652 |
-
|
| 653 |
-
|
| 654 |
-
|
| 655 |
-
|
| 656 |
-
|
| 657 |
-
|
| 658 |
-
|
| 659 |
-
|
| 660 |
-
|
| 661 |
-
|
| 662 |
-
|
| 663 |
-
|
| 664 |
-
|
| 665 |
-
|
| 666 |
-
|
| 667 |
-
|
| 668 |
-
|
| 669 |
-
|
| 670 |
-
|
| 671 |
def _on_apply(
|
| 672 |
v_orig,
|
| 673 |
t,
|
|
@@ -677,7 +712,7 @@ with gr.Blocks(css=CSS) as demo:
|
|
| 677 |
cs,
|
| 678 |
wd,
|
| 679 |
p,
|
| 680 |
-
|
| 681 |
y,
|
| 682 |
a,
|
| 683 |
eq,
|
|
@@ -699,7 +734,7 @@ with gr.Blocks(css=CSS) as demo:
|
|
| 699 |
cs,
|
| 700 |
wd,
|
| 701 |
p,
|
| 702 |
-
|
| 703 |
y,
|
| 704 |
a,
|
| 705 |
eq,
|
|
@@ -708,7 +743,6 @@ with gr.Blocks(css=CSS) as demo:
|
|
| 708 |
wrap_ratio=wrap_ratio,
|
| 709 |
)
|
| 710 |
|
| 711 |
-
apply_btn = gr.Button("Накласці субтытры (фінальны вынік)")
|
| 712 |
apply_btn.click(
|
| 713 |
_on_apply,
|
| 714 |
inputs=[
|
|
@@ -720,7 +754,7 @@ with gr.Blocks(css=CSS) as demo:
|
|
| 720 |
color_st,
|
| 721 |
width_sl,
|
| 722 |
pos_dd,
|
| 723 |
-
|
| 724 |
y_sl,
|
| 725 |
anim_dd,
|
| 726 |
exp_dd,
|
|
|
|
| 146 |
start_str, end_str = times.split("-->")
|
| 147 |
start = srt_time_to_sec(start_str)
|
| 148 |
end = srt_time_to_sec(end_str)
|
| 149 |
+
text = "\n".join(lines[2:]).strip() # захоўваем пераносы
|
|
|
|
| 150 |
subs.append({"start": start, "end": end, "text": text})
|
| 151 |
except Exception:
|
| 152 |
continue
|
|
|
|
| 157 |
# Wrap: аўтаматычны перанос радкоў па шырыні кадра
|
| 158 |
# ----------------------------
|
| 159 |
def _line_width_px(draw: ImageDraw.ImageDraw, line: str, font: ImageFont.ImageFont, stroke_width: int = 0) -> int:
|
|
|
|
| 160 |
try:
|
| 161 |
l, t, r, b = draw.textbbox((0, 0), line, font=font, stroke_width=stroke_width)
|
| 162 |
return int(r - l)
|
| 163 |
except Exception:
|
| 164 |
try:
|
|
|
|
| 165 |
w = draw.textlength(line, font=font)
|
| 166 |
return int(w + 2 * stroke_width)
|
| 167 |
except Exception:
|
|
|
|
| 170 |
|
| 171 |
|
| 172 |
def _break_long_word(draw, word: str, font, max_width: int, stroke_width: int) -> list:
|
|
|
|
| 173 |
chunks = []
|
| 174 |
cur = ""
|
| 175 |
for ch in word:
|
|
|
|
| 186 |
|
| 187 |
def wrap_text_to_width(text: str, draw: ImageDraw.ImageDraw, font: ImageFont.ImageFont,
|
| 188 |
max_width: int, stroke_width: int = 0) -> str:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 189 |
if not text:
|
| 190 |
return text
|
| 191 |
|
|
|
|
| 202 |
line = ""
|
| 203 |
for w in words:
|
| 204 |
if not line:
|
|
|
|
| 205 |
if _line_width_px(draw, w, font, stroke_width) <= max_width:
|
| 206 |
line = w
|
| 207 |
else:
|
|
|
|
| 215 |
line = test
|
| 216 |
else:
|
| 217 |
out_lines.append(line)
|
|
|
|
| 218 |
if _line_width_px(draw, w, font, stroke_width) <= max_width:
|
| 219 |
line = w
|
| 220 |
else:
|
|
|
|
| 254 |
return (0, 0, w, h)
|
| 255 |
|
| 256 |
|
| 257 |
+
def _clamp(v: float, lo: float, hi: float) -> float:
|
| 258 |
+
return max(lo, min(hi, v))
|
| 259 |
+
|
| 260 |
+
|
| 261 |
def create_animated_text_clip(
|
| 262 |
text,
|
| 263 |
duration,
|
|
|
|
| 267 |
stroke_color,
|
| 268 |
stroke_width,
|
| 269 |
position_type,
|
| 270 |
+
custom_x_shift, # ЗРУХ X (адносна цэнтра)
|
| 271 |
custom_y,
|
| 272 |
animation,
|
| 273 |
video_width,
|
| 274 |
video_height,
|
| 275 |
bg_color=None,
|
| 276 |
bg_opacity=1.0,
|
| 277 |
+
wrap_ratio: float = 0.90,
|
| 278 |
):
|
| 279 |
try:
|
| 280 |
txt_rgb = hex_to_rgb(color)
|
|
|
|
| 307 |
spacing = max(0, int(fontsize * 0.15))
|
| 308 |
|
| 309 |
# --- AUTO WRAP ---
|
|
|
|
| 310 |
max_text_width = max(200, int(video_width * float(wrap_ratio)))
|
| 311 |
wrapped_text = wrap_text_to_width(text, dummy_draw, pil_font, max_text_width, stroke_width=stroke_width)
|
| 312 |
|
|
|
|
| 325 |
img = Image.new("RGBA", (img_w, img_h), bg_rgb + (bg_a,))
|
| 326 |
draw = ImageDraw.Draw(img)
|
| 327 |
|
| 328 |
+
# галоўнае: улічваем bbox (можа быць адмоўны)
|
| 329 |
text_x = (pad_x + safe) - l
|
| 330 |
text_y = (pad_y + safe) - t
|
| 331 |
|
|
|
|
| 348 |
mask = mpe.ImageClip(alpha, ismask=True).set_duration(duration)
|
| 349 |
clip = clip.set_mask(mask)
|
| 350 |
|
| 351 |
+
# --- Пазіцыя (аптымізавана) ---
|
| 352 |
+
# верх/ніз ссоўваем да цэнтра на 15% вышыні кадра
|
| 353 |
+
center_pull = 0.15 * float(video_height)
|
| 354 |
+
base_x_center = (video_width - clip.w) / 2
|
| 355 |
+
|
| 356 |
if position_type == "bottom":
|
| 357 |
+
x = base_x_center
|
| 358 |
+
y = (video_height - clip.h - 20) - center_pull
|
| 359 |
elif position_type == "top":
|
| 360 |
+
x = base_x_center
|
| 361 |
+
y = 20 + center_pull
|
| 362 |
elif position_type == "center":
|
| 363 |
+
x = base_x_center
|
| 364 |
y = (video_height - clip.h) / 2
|
| 365 |
+
else: # custom: Х заўсёды ад цэнтра (як ва ўсіх), custom_x_shift — дадатковы зрух
|
| 366 |
+
x = base_x_center + float(custom_x_shift)
|
| 367 |
y = float(custom_y)
|
| 368 |
|
| 369 |
+
# clamp, каб не вылятала за кадр
|
| 370 |
+
x = _clamp(x, 0, max(0, video_width - clip.w))
|
| 371 |
+
y = _clamp(y, 0, max(0, video_height - clip.h))
|
| 372 |
+
|
| 373 |
anim = (animation or "").lower()
|
| 374 |
|
| 375 |
if anim == "fade":
|
|
|
|
| 403 |
stroke_color,
|
| 404 |
stroke_width,
|
| 405 |
position_type,
|
| 406 |
+
custom_x_shift,
|
| 407 |
custom_y,
|
| 408 |
animation,
|
| 409 |
export_quality,
|
|
|
|
| 429 |
stroke_color,
|
| 430 |
stroke_width,
|
| 431 |
position_type,
|
| 432 |
+
custom_x_shift,
|
| 433 |
custom_y,
|
| 434 |
animation,
|
| 435 |
w,
|
|
|
|
| 478 |
stroke_color,
|
| 479 |
stroke_width,
|
| 480 |
position_type,
|
| 481 |
+
custom_x_shift,
|
| 482 |
custom_y,
|
| 483 |
animation,
|
| 484 |
bg_color=None,
|
|
|
|
| 500 |
stroke_color,
|
| 501 |
stroke_width,
|
| 502 |
position_type,
|
| 503 |
+
custom_x_shift,
|
| 504 |
custom_y,
|
| 505 |
animation,
|
| 506 |
w,
|
|
|
|
| 542 |
|
| 543 |
|
| 544 |
# ----------------------------
|
| 545 |
+
# Gradio UI (аптымізаваны layout)
|
| 546 |
# ----------------------------
|
| 547 |
with gr.Blocks(css=CSS) as demo:
|
| 548 |
gr.Markdown("# Аўтаматычнае стварэнне і накладанне субтытраў")
|
| 549 |
|
| 550 |
+
# Стан: арыгінальны файл (каб не губляць пры preview/output)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 551 |
uploaded_video = gr.State(None)
|
| 552 |
first_frame = gr.State(None)
|
| 553 |
|
| 554 |
+
with gr.Row(equal_height=True):
|
| 555 |
+
with gr.Column(scale=6):
|
| 556 |
+
video_input = gr.Video(
|
| 557 |
+
label="Відэа (тут жа будзе перадпрагляд і вынік)",
|
| 558 |
+
elem_id="main_video",
|
| 559 |
+
)
|
| 560 |
+
with gr.Row():
|
| 561 |
+
show_original_btn = gr.Button("Паказаць арыгінал")
|
| 562 |
+
# (кнопка дадатковая, але карысная пасля preview)
|
| 563 |
+
|
| 564 |
+
with gr.Column(scale=5):
|
| 565 |
+
# 1) Стварэнне субтытраў — разварочваецца/згортваецца
|
| 566 |
+
with gr.Accordion("Стварэнне субтытраў", open=False):
|
| 567 |
+
gen_btn = gr.Button("Стварыць субтытры")
|
| 568 |
+
subs_box = gr.Textbox(label="Тэкст субтытраў", lines=14)
|
| 569 |
+
|
| 570 |
+
# 2) Стыль
|
| 571 |
+
with gr.Accordion("Стыль субтытраў", open=True):
|
| 572 |
+
with gr.Row():
|
| 573 |
+
font_dd = gr.Dropdown(list(AVAILABLE_FONTS.keys()), value="DejaVuSans-Bold", label="Шрыфт")
|
| 574 |
+
size_sl = gr.Slider(10, 100, 1, value=40, label="Памер")
|
| 575 |
+
|
| 576 |
+
with gr.Row():
|
| 577 |
+
color_txt = gr.ColorPicker(value="#FFFFFF", label="Колер тэксту")
|
| 578 |
+
color_st = gr.ColorPicker(value="#000000", label="Колер абводкі")
|
| 579 |
+
width_sl = gr.Slider(0, 10, 1, value=2, label="Абводка")
|
| 580 |
+
|
| 581 |
+
with gr.Row():
|
| 582 |
+
bg_color = gr.ColorPicker(value="#000000", label="Фон")
|
| 583 |
+
bg_opac = gr.Slider(0.0, 1.0, 0.1, value=0.5, label="Празрыстасць")
|
| 584 |
+
|
| 585 |
+
with gr.Row():
|
| 586 |
+
pos_dd = gr.Dropdown(["bottom", "top", "center", "custom"], value="bottom", label="Пазіцыя")
|
| 587 |
+
anim_dd = gr.Dropdown(["None", "fade", "slide", "zoom"], value="None", label="Анімацыя")
|
| 588 |
+
|
| 589 |
+
# Custom controls (паказваем толькі калі патрэбна)
|
| 590 |
+
with gr.Row():
|
| 591 |
+
x_shift_sl = gr.Slider(-960, 960, 1, value=0, label="X-зрух ад цэнтра", visible=False)
|
| 592 |
+
y_sl = gr.Slider(0, 1080, 1, value=100, label="Y (для custom)", visible=False)
|
| 593 |
+
|
| 594 |
+
with gr.Row():
|
| 595 |
+
wrap_en = gr.Checkbox(value=True, label="Аўта-перанос радкоў")
|
| 596 |
+
wrap_pct = gr.Slider(50, 100, 1, value=90, label="Макс. шырыня радка (%)")
|
| 597 |
+
|
| 598 |
+
# 3) Экспарт
|
| 599 |
+
with gr.Accordion("Экспарт", open=False):
|
| 600 |
+
exp_dd = gr.Dropdown(
|
| 601 |
+
["мінімальнае", "арыгінальнае", "сярэдняе", "максімальнае"],
|
| 602 |
+
value="арыгінальнае",
|
| 603 |
+
label="Якасць",
|
| 604 |
+
)
|
| 605 |
+
apply_btn = gr.Button("Накласці субтытры (фінальны вынік)")
|
| 606 |
+
|
| 607 |
+
# --- Лагічныя апрацоўшчыкі ---
|
| 608 |
+
|
| 609 |
+
# Калі значэнне video_input змянілася:
|
| 610 |
+
# - калі гэта preview/output -> НЕ пераціраем uploaded_video
|
| 611 |
+
# - калі гэта арыгінал (загрузка) -> абнаўляем uploaded_video і first_frame
|
| 612 |
def _on_video_change(v, current_uploaded, current_frame):
|
| 613 |
if not v:
|
| 614 |
return None, None
|
| 615 |
|
| 616 |
+
# калі проста вярнуліся да арыгінала праз кнопку і ён ужо вядомы — не пералічваем кадр
|
| 617 |
+
if current_uploaded and str(v) == str(current_uploaded):
|
| 618 |
+
return current_uploaded, current_frame
|
| 619 |
+
|
| 620 |
name = os.path.basename(str(v))
|
| 621 |
if name in ("preview_video.mp4", "output_video.mp4"):
|
| 622 |
return current_uploaded, current_frame
|
|
|
|
| 630 |
outputs=[uploaded_video, first_frame],
|
| 631 |
)
|
| 632 |
|
| 633 |
+
# Паказаць арыгінал у тым жа плэеры
|
| 634 |
+
def _show_original(v_orig):
|
| 635 |
+
return v_orig
|
| 636 |
+
|
| 637 |
+
show_original_btn.click(_show_original, inputs=uploaded_video, outputs=video_input)
|
| 638 |
+
|
| 639 |
+
# Генерацыя субтытраў заўсёды па арыгінальным uploaded_video
|
| 640 |
def _on_generate_click(v_orig):
|
| 641 |
if not v_orig:
|
| 642 |
return "Не выбрана відэа"
|
|
|
|
| 645 |
|
| 646 |
gen_btn.click(_on_generate_click, inputs=uploaded_video, outputs=subs_box)
|
| 647 |
|
| 648 |
+
# Пераключэнне бачнасці custom-слайдэраў
|
| 649 |
+
def _toggle_custom(pos):
|
| 650 |
+
vis = (pos == "custom")
|
| 651 |
+
return gr.update(visible=vis), gr.update(visible=vis)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 652 |
|
| 653 |
+
pos_dd.change(_toggle_custom, inputs=pos_dd, outputs=[x_shift_sl, y_sl])
|
|
|
|
|
|
|
| 654 |
|
| 655 |
+
# Перадпрагляд: 1 секунда на першым кадры, у тым жа video_input
|
| 656 |
+
def update_preview(frame, text, font, size, col, colst, wd, pos, x_shift, y, anim, bgc, bgo, do_wrap, w_pct):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 657 |
if frame is None:
|
| 658 |
return None
|
| 659 |
|
|
|
|
| 672 |
colst,
|
| 673 |
wd,
|
| 674 |
pos,
|
| 675 |
+
x_shift,
|
| 676 |
y,
|
| 677 |
anim,
|
| 678 |
bg_color=bgc,
|
|
|
|
| 680 |
wrap_ratio=wrap_ratio,
|
| 681 |
)
|
| 682 |
|
| 683 |
+
preview_inputs = [
|
| 684 |
+
first_frame,
|
| 685 |
+
subs_box,
|
| 686 |
+
font_dd,
|
| 687 |
+
size_sl,
|
| 688 |
+
color_txt,
|
| 689 |
+
color_st,
|
| 690 |
+
width_sl,
|
| 691 |
+
pos_dd,
|
| 692 |
+
x_shift_sl,
|
| 693 |
+
y_sl,
|
| 694 |
+
anim_dd,
|
| 695 |
+
bg_color,
|
| 696 |
+
bg_opac,
|
| 697 |
+
wrap_en,
|
| 698 |
+
wrap_pct,
|
| 699 |
+
]
|
| 700 |
+
|
| 701 |
+
# Любая змена ў стылі/тэксце абнаўляе перадпрагляд
|
| 702 |
+
for c in [subs_box, font_dd, size_sl, color_txt, color_st, width_sl, pos_dd, x_shift_sl, y_sl, anim_dd, bg_color, bg_opac, wrap_en, wrap_pct]:
|
| 703 |
+
c.change(update_preview, inputs=preview_inputs, outputs=video_input)
|
| 704 |
+
|
| 705 |
+
# Фінальны рэндэр у тым жа плэеры, але па арыгінальным uploaded_video
|
|
|
|
| 706 |
def _on_apply(
|
| 707 |
v_orig,
|
| 708 |
t,
|
|
|
|
| 712 |
cs,
|
| 713 |
wd,
|
| 714 |
p,
|
| 715 |
+
x_shift,
|
| 716 |
y,
|
| 717 |
a,
|
| 718 |
eq,
|
|
|
|
| 734 |
cs,
|
| 735 |
wd,
|
| 736 |
p,
|
| 737 |
+
x_shift,
|
| 738 |
y,
|
| 739 |
a,
|
| 740 |
eq,
|
|
|
|
| 743 |
wrap_ratio=wrap_ratio,
|
| 744 |
)
|
| 745 |
|
|
|
|
| 746 |
apply_btn.click(
|
| 747 |
_on_apply,
|
| 748 |
inputs=[
|
|
|
|
| 754 |
color_st,
|
| 755 |
width_sl,
|
| 756 |
pos_dd,
|
| 757 |
+
x_shift_sl,
|
| 758 |
y_sl,
|
| 759 |
anim_dd,
|
| 760 |
exp_dd,
|