| import json |
|
|
| from font_utils import build_font_map |
|
|
|
|
| def sec_to_ass(seconds): |
|
|
| h = int(seconds // 3600) |
| m = int((seconds % 3600) // 60) |
| s = seconds % 60 |
|
|
| return f"{h}:{m:02d}:{s:05.2f}" |
|
|
|
|
| def ass_escape(text): |
|
|
| if not text: |
| return "" |
|
|
| return ( |
| text.replace("\\", "\\\\") |
| .replace("{", "\\{") |
| .replace("}", "\\}") |
| ) |
|
|
|
|
| def wrap_text( |
| text, |
| width=40, |
| max_lines=2 |
| ): |
|
|
| if not text: |
| return "" |
|
|
| words = text.split() |
|
|
| lines = [] |
| current = [] |
|
|
| for word in words: |
|
|
| candidate = " ".join( |
| current + [word] |
| ) |
|
|
| if len(candidate) > width: |
|
|
| if current: |
|
|
| lines.append( |
| " ".join(current) |
| ) |
|
|
| current = [word] |
|
|
| else: |
|
|
| current.append(word) |
|
|
| if current: |
|
|
| lines.append( |
| " ".join(current) |
| ) |
|
|
| if len(lines) <= max_lines: |
|
|
| return r"\N".join(lines) |
|
|
| merged = [] |
|
|
| per = ( |
| len(lines) + max_lines - 1 |
| ) // max_lines |
|
|
| for i in range( |
| 0, |
| len(lines), |
| per |
| ): |
|
|
| merged.append( |
| " ".join( |
| lines[i:i + per] |
| ) |
| ) |
|
|
| return r"\N".join(merged) |
|
|
|
|
| def generate_ass( |
| json_file, |
| output_ass, |
| arabic_font, |
| arabic_y=380, |
| translation_gap=25, |
| arabic_font_size=120, |
| translation_font_size=34 |
| ): |
|
|
| with open( |
| json_file, |
| "r", |
| encoding="utf-8" |
| ) as f: |
|
|
| data = json.load(f) |
|
|
| font_map = build_font_map() |
|
|
| font_name = font_map.get( |
| arabic_font, |
| arabic_font |
| ) |
|
|
| print("=" * 60) |
| print("FONT DEBUG") |
| print("=" * 60) |
|
|
| print("SELECTED FONT:") |
| print(arabic_font) |
|
|
| print("FONT FAMILY:") |
| print(font_name) |
|
|
| header = f""" |
| [Script Info] |
| Title: Quran Video |
| ScriptType: v4.00+ |
| PlayResX: 1920 |
| PlayResY: 1080 |
| ScaledBorderAndShadow: yes |
| WrapStyle: 2 |
| |
| [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: Arabic,{font_name},{arabic_font_size},&H00FFFFFF,&H000000FF,&H18000000,&H00000000,0,0,0,0,100,100,0,0,1,0.5,2,2,0,0,0,1 |
| Style: Translation,Arial,{translation_font_size},&H00FFFFFF,&H000000FF,&H18000000,&H00000000,0,0,0,0,100,100,0,0,1,0.3,2,2,0,0,0,1 |
| |
| [Events] |
| Format: Layer,Start,End,Style,Name,MarginL,MarginR,MarginV,Effect,Text |
| """ |
|
|
| events = [] |
|
|
| for item in data: |
|
|
| start = sec_to_ass( |
| item["start_time"] |
| ) |
|
|
| end = sec_to_ass( |
| item["end_time"] |
| ) |
|
|
| arabic = wrap_text( |
| ass_escape( |
| item["text"] |
| ), |
| width=120, |
| max_lines=2 |
| ) |
|
|
| translation = wrap_text( |
| ass_escape( |
| item["translation"] |
| ), |
| width=80, |
| max_lines=2 |
| ) |
|
|
| arabic_lines = arabic.count(r"\N") + 1 |
| |
| line_height = 90 |
| |
| translation_y = ( |
| arabic_y |
| + (arabic_lines * line_height) |
| + translation_gap |
| ) |
| |
| events.append( |
| f"Dialogue: 0,{start},{end},Arabic,,0,0,0,," |
| f"{{\\an8\\pos(960,{arabic_y})}}" |
| f"{arabic}" |
| ) |
| |
| events.append( |
| f"Dialogue: 0,{start},{end},Translation,,0,0,0,," |
| f"{{\\an8\\pos(960,{translation_y})}}" |
| f"{translation}" |
| ) |
|
|
| with open( |
| output_ass, |
| "w", |
| encoding="utf-8" |
| ) as f: |
|
|
| f.write(header) |
| f.write("\n".join(events)) |
|
|
| print("=" * 60) |
| print("ASS GENERATED") |
| print("=" * 60) |
|
|
| print( |
| f"Font used: {font_name}" |
| ) |
|
|
| return output_ass |