# Substation Alpha (ASS) generation # Colour values are &HAABBGGRR, &HBBGGRR, or &HAA. # Alpha is actually inverted, i.e. transparency - FF is transparent, 00 is opaque. ass_preamble = '''[Script Info] ScriptType: v4.00+ WrapStyle: 0 ScaledBorderAndShadow: yes YCbCr Matrix: TV.709 PlayResX: {PlayResX} PlayResY: {PlayResY} [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: Default,{LatinFont},72,&H002A0A00,&H000019FF,&H00FFFFFF,&H00000000,0,0,0,0,100,100,0,0,1,2.5,0,8,30,30,25,1 Style: Kanji,{JapaneseFont},{KanjiSize},&H{KaraokeColourPast},&H{KaraokeColourFuture},&H00FFFFFF,&H00000000,0,0,0,0,100,100,0,0,1,4.0,0,2,30,30,{KanjiVMargin},1 Style: Furigana,{JapaneseFont},{FuriSize},&H{KaraokeColourPast},&H{KaraokeColourFuture},&H00FFFFFF,&H00000000,0,0,0,0,100,100,0,0,1,2.5,0,2,0,0,{FuriVMargin},1 Style: Romaji,{LatinFont},{RomajiSize},&H{KaraokeColourPast},&H{KaraokeColourFuture},&H00FFFFFF,&H00000000,0,0,0,0,100,100,0,0,1,2.5,0,8,30,30,20,1 Style: Translation,{LatinFont},{TranslationSize},&H00FFFFFF,&H000019FF,&H00000000,&H00000000,0,1,0,0,100,100,0,0,1,1.0,3,8,30,30,20,1 [Events] Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text ''' format_defaults = { 'PlayResX': 1280, 'PlayResY': 720, 'LatinFont': 'Droid Sans', 'JapaneseFont': 'Droid Sans Japanese', 'TranslationSize': 36, 'RomajiSize': 48, 'KanjiSize': 72, 'KanjiVMargin': 20, 'FuriSize': 36, 'FuriVMargin': 85, 'KaraokeColourFuture': '000019FF', 'KaraokeColourPast': 'E02A0A00', } def seconds_to_timestamp(t: float) -> str: minutes, seconds = divmod(t, 60) hours, minutes = divmod(minutes, 60) return f'{int(hours):02}:{int(minutes):02}:{seconds:05.2f}' from format import LyricTrack from japanese_converters import kana_to_syllable_list def generate_ass(filename: str, lyric_track: LyricTrack, format_overloads: dict = None): format_dict = format_defaults.copy() if format_overloads: format_dict.update(format_overloads) preamble = ass_preamble.format(**format_dict) # Kanji Furigana layout stuff pt_to_fullwidth = 55.0/72.0 # This seems right for DroidSansJapanese, probably different for each font size_kanji_x = format_dict['KanjiSize'] * pt_to_fullwidth size_furi_x = format_dict['FuriSize'] * pt_to_fullwidth res_x = format_dict['PlayResX'] res_xh = res_x/2 with open(filename, 'w') as file: file.write(preamble) t = 68.0 # placeholder for line in lyric_track.lines: t0 = seconds_to_timestamp(t) timestamps = line.get_timestamps(0.5, t) centiseconds = line.get_karaoke_centiseconds(0.5) t = timestamps[-1] + 1.0 # placeholder t1 = seconds_to_timestamp(t) sub_preamble = f'Dialogue: 0,{t0},{t1}' furi_preamble = f'Dialogue: 1,{t0},{t1}' # Different layer to avoid kanji collision detection # Translation line is easy and static file.write(f'{sub_preamble},Translation,,,,,,{line.translated_line}\n') # Romaji line is also easy, just intersperse durations romaji_line = f'{{\\k{centiseconds[0]}}}' i = 0 # syllable counter for syl in line.romaji_syllables: if not syl.strip(): romaji_line += f'{{\\k0}}{syl}' continue romaji_line += f'{{\\K{centiseconds[i+1]-centiseconds[i]}}}{syl}' i += 1 file.write(f'{sub_preamble},Romaji,,,,,,{romaji_line}\n') # Now for the kanji and furi lines... kanji_plain_str = ''.join([b.kanji for b in line.furi_blocks]) full_kanji_width = len(kanji_plain_str) * size_kanji_x kanji_line = f'{{\\k{centiseconds[0]}}}' kanji_line_progress = 0 # increment as we go, to track furi position furi_lines = [] i = 0 # syllable counter for furi_block in line.furi_blocks: if len(furi_block.furi) == 0: # kana or punctuation, nice and simple! syls = kana_to_syllable_list(furi_block.kanji) for syl in syls: if len(syl.strip()) == 0: # don't time spaces kanji_line += f'{{\\k0}}{syl}' kanji_line_progress += len(syl) else: kanji_line += f'{{\\K{centiseconds[i+1]-centiseconds[i]}}}{syl}' kanji_line_progress += len(syl) i += 1 else: # Kanji block i0 = i # Store this to later calculate block time for the kanji syls = kana_to_syllable_list(furi_block.furi) furi_line = f'{{\\k{centiseconds[i]}}}' furi_chars = 0 for syl in syls: furi_line += f'{{\\K{centiseconds[i+1]-centiseconds[i]}}}{syl}' furi_chars += len(syl) i += 1 # Need to calculate kanji block position and span to typeset the furigana above it k = furi_block.kanji k_start = kanji_line_progress kanji_line_progress += len(k) k_end = kanji_line_progress target_middle_x = (size_kanji_x * (k_end+k_start)/2) - (full_kanji_width/2) # x=0 at center furi_width = furi_chars * size_furi_x margin_l = int(res_xh+target_middle_x) margin_r = int(res_xh-target_middle_x) furi_lines.append(f'{furi_preamble},Furigana,,{margin_l},{margin_r},,,{furi_line}\n') kanji_line += f'{{\\K{centiseconds[i]-centiseconds[i0]}}}{k}' file.write(f'{sub_preamble},Kanji,,,,,,{kanji_line}\n') for line in furi_lines: file.write(line)