| import os |
| from schemas import SubtitlePreset, WordTiming, SentenceTiming |
|
|
| def rgb_to_ass(hex_color): |
| """ุชุญููู ุฃููุงู Hex ุฅูู ุตูุบุฉ ASS BGR""" |
| hex_color = hex_color.lstrip('#') |
| if len(hex_color) == 6: |
| r, g, b = hex_color[0:2], hex_color[2:4], hex_color[4:6] |
| return f"&H00{b}{g}{r}" |
| |
| return "&H00FFFFFF" |
|
|
| def format_ass_time(seconds): |
| """ุชุญููู ุงูุซูุงูู ุฅูู ุชูููุช ASS ุฏููู (H:MM:SS.cc)""" |
| hours = int(seconds // 3600) |
| minutes = int((seconds % 3600) // 60) |
| secs = seconds % 60 |
| centiseconds = int(round((secs - int(secs)) * 100)) |
| if centiseconds == 100: |
| secs += 1 |
| centiseconds = 0 |
| return f"{hours}:{minutes:02d}:{int(secs):02d}.{centiseconds:02d}" |
|
|
| def generate_pro_ass(transcription, preset, output_path): |
| """ุชูููุฏ ู
ูู ASS ุงุญุชุฑุงูู ู
ุน ุฏุนู
ูุถุน ุงูุฌู
ู ูุงูููู
ุงุช""" |
| primary = rgb_to_ass(preset.primary_color) |
| secondary = rgb_to_ass(preset.secondary_color) |
| outline = rgb_to_ass(preset.outline_color) |
| |
| |
| |
| back_alpha = int((1.0 - getattr(preset, 'background_opacity', 1.0)) * 255) |
| |
| |
| raw_back_color = rgb_to_ass(preset.back_color).replace("&H00", "") |
| back_color = f"&H{back_alpha:02X}{raw_back_color}" |
| |
| spacing = getattr(preset, 'letter_spacing', 0.0) |
| angle = getattr(preset, 'rotation_angle', 0.0) |
| margin_h = getattr(preset, 'margin_h', 20) |
| |
| header = f"""[Script Info] |
| ScriptType: v4.00+ |
| PlayResX: 1080 |
| PlayResY: 1920 |
| ScaledBorderAndShadow: yes |
| |
| [V4+ Styles] |
| Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding |
| Style: Main,{preset.font_name},{preset.font_size},{primary},{secondary},{outline},&H00000000,-1,0,0,0,100,100,{spacing},{angle},1,{preset.outline_width},{preset.shadow_depth},{preset.alignment},{margin_h},{margin_h},{preset.margin_v},1 |
| Style: BackBox,{preset.font_name},{preset.font_size},{secondary},{secondary},&H00000000,{back_color},-1,0,0,0,100,100,{spacing},{angle},3,{getattr(preset, 'box_rounding', 10)},0,{preset.alignment},{margin_h},{margin_h},{preset.margin_v},1 |
| """ |
| |
| events = "\n[Events]\nFormat: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\n" |
| |
| |
| sentences = [] |
| if transcription and isinstance(transcription[0], (dict, SentenceTiming)) and ('words' in transcription[0] or hasattr(transcription[0], 'words')): |
| for s in transcription: |
| sentences.append(s if hasattr(s, 'text') else SentenceTiming(**s)) |
| else: |
| |
| current_words = [] |
| for item in transcription: |
| word_data = item if hasattr(item, 'word') else WordTiming(**item) |
| current_words.append(word_data) |
| if len(current_words) >= preset.max_words_per_line: |
| sentences.append(SentenceTiming( |
| text=" ".join([w.word for w in current_words]), |
| start=current_words[0].start, |
| end=current_words[-1].end, |
| words=current_words |
| )) |
| current_words = [] |
| if current_words: |
| sentences.append(SentenceTiming( |
| text=" ".join([w.word for w in current_words]), |
| start=current_words[0].start, |
| end=current_words[-1].end, |
| words=current_words |
| )) |
|
|
| for sentence in sentences: |
| s_start = format_ass_time(sentence.start) |
| s_end = format_ass_time(sentence.end) |
| |
| if preset.display_mode == "word": |
| |
| for i, word_data in enumerate(sentence.words): |
| word_text = word_data.word.upper() if preset.uppercase else word_data.word |
| start_t = format_ass_time(word_data.start) |
| end_t = format_ass_time(word_data.end) |
| duration = word_data.end - word_data.start |
| |
| t1 = min(60, int(duration * 150)) |
| t2 = t1 + 80 |
| scale = int(preset.pop_up_scale * 100) |
| |
| |
| glow_val = getattr(preset, 'glow_intensity', 0) |
| blur_tag = f"\\be{glow_val}" if glow_val > 0 else "" |
| |
| |
| if angle != 0: |
| rotation = f"\\frz{angle}" |
| else: |
| rotation = "\\frz-2" if i % 2 == 0 else "\\frz2" |
| |
| anim = f"{{\\fscx80\\fscy80{rotation}{blur_tag}\\t(0,{t1},0.3,\\fscx{scale}\\fscy{scale})\\t({t1},{t2},1.5,\\fscx100\\fscy100)}}" |
| |
| if preset.back_box_enabled: |
| events += f"Dialogue: 0,{start_t},{end_t},BackBox,,0,0,0,,{{\\bord5\\1a&H40&}}{anim}{word_text}\n" |
| events += f"Dialogue: 1,{start_t},{end_t},Main,,0,0,0,,{anim}{word_text}\n" |
| |
| else: |
| |
| |
| |
| for i, target_word in enumerate(sentence.words): |
| w_start = format_ass_time(target_word.start) |
| w_end = format_ass_time(target_word.end) |
| duration = target_word.end - target_word.start |
| |
| |
| words_list_base = [w.word.upper() if preset.uppercase else w.word for w in sentence.words] |
| prefix_base = " ".join(words_list_base[:i]) + (" " if i > 0 else "") |
| suffix_base = (" " if i < len(words_list_base)-1 else "") + " ".join(words_list_base[i+1:]) |
| target_word_base = words_list_base[i] |
| |
| |
| dim_alpha = "\\1a&H80&" |
| box_highlight_type = getattr(preset, 'box_highlight_type', 'word') |
| |
| |
| if preset.back_box_enabled and box_highlight_type == "sentence": |
| box_hide = "" |
| base_display_text = f"{{{dim_alpha}}}{prefix_base}{{\\alpha&HFF&}}{target_word_base}{{\\alpha&H00&}}{{{dim_alpha}}}{suffix_base}" |
| events += f"Dialogue: 0,{w_start},{w_end},BackBox,,0,0,0,,{base_display_text}\n" |
| else: |
| |
| box_hide = "\\4a&HFF&" |
| base_display_text = f"{{{dim_alpha}}}{prefix_base}{{\\alpha&HFF&{box_hide}}}{target_word_base}{{\\alpha&H00&}}{{{dim_alpha}}}{suffix_base}" |
| events += f"Dialogue: 0,{w_start},{w_end},Main,,0,0,0,,{base_display_text}\n" |
|
|
| |
| prefix_hl = " ".join(words_list_base[:i]) + (" " if i > 0 else "") |
| suffix_hl = (" " if i < len(words_list_base)-1 else "") + " ".join(words_list_base[i+1:]) |
| target_word_hl = words_list_base[i] |
| |
| t1 = min(60, int(duration * 150)) |
| t2 = t1 + 80 |
| |
| |
| glow_val = getattr(preset, 'glow_intensity', 0) |
| blur_tag = f"\\be{glow_val}" if glow_val > 0 else "" |
| |
| if angle != 0: |
| rotation = f"\\frz{angle}" |
| else: |
| rotation = "\\frz-2" if i % 2 == 0 else "\\frz2" |
| |
| |
| scale = int(preset.pop_up_scale * 100) |
| if preset.back_box_enabled: |
| if box_highlight_type == "word": |
| |
| |
| color_tags = f"\\1c{primary}\\4c{secondary}\\1a&H00&\\4a&H00&" |
| anim = f"{{{color_tags}\\fscx{scale}\\fscy{scale}{rotation}{blur_tag}}}" |
| |
| display_text_hl = f"{{\\alpha&HFF&\\4a&HFF&}}{prefix_hl}{anim}{target_word_hl}{{\\alpha&HFF&\\4a&HFF&}}{suffix_hl}" |
| events += f"Dialogue: 1,{w_start},{w_end},BackBox,,0,0,0,,{display_text_hl}\n" |
| else: |
| |
| color_tags = f"\\1c{secondary}\\1a&H00&" |
| anim = f"{{{color_tags}\\fscx{scale}\\fscy{scale}{rotation}{blur_tag}}}" |
| display_text_hl = f"{{\\alpha&HFF&}}{prefix_hl}{anim}{target_word_hl}{{\\alpha&HFF&}}{suffix_hl}" |
| events += f"Dialogue: 1,{w_start},{w_end},Main,,0,0,0,,{display_text_hl}\n" |
| else: |
| |
| color_tags = f"\\1c{secondary}\\1a&H00&" |
| anim = f"{{{color_tags}\\fscx{scale}\\fscy{scale}{rotation}{blur_tag}}}" |
| display_text_hl = f"{{\\alpha&HFF&}}{prefix_hl}{anim}{target_word_hl}{{\\alpha&HFF&}}{suffix_hl}" |
| events += f"Dialogue: 1,{w_start},{w_end},Main,,0,0,0,,{display_text_hl}\n" |
|
|
| with open(output_path, "w", encoding="utf-8-sig") as f: |
| f.write(header + events) |