Created
January 22, 2026 21:36
-
-
Save Schaka/7d7baa2f7cd5fe38632a785958fd82b2 to your computer and use it in GitHub Desktop.
Convert SRT to ASS with black bar
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import os | |
| import re | |
| # CONFIGURATION | |
| # ------------------------------------------------------------------ | |
| PLAY_RES_X = 3840 | |
| PLAY_RES_Y = 2160 | |
| # Styles provided in your original script | |
| STYLES_BLOCK = """[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: Background,Arial,48,&H00000000,&H00000000,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,2,7,10,10,10,1 | |
| Style: Default,Arial,72,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,2,2,10,10,240,1 | |
| """ | |
| # The drawing code for your background box | |
| BG_DRAWING = r"{\pos(800,1820)\p1}m 0 0 l 2200 0 l 2200 140 l 0 140{\p0}" | |
| # SEGMENTATION SETTING | |
| # 1000 centiseconds = 10 seconds. | |
| # Shorter segments fix "disappearing" backgrounds when skipping in MKVs. | |
| SEGMENT_DURATION_CS = 1000 | |
| # ------------------------------------------------------------------ | |
| def srt_to_cs(srt_time): | |
| """Converts SRT time (00:00:01,000) to total centiseconds.""" | |
| h, m, s_ms = srt_time.split(':') | |
| s, ms = s_ms.split(',') | |
| return (int(h) * 360000) + (int(m) * 6000) + (int(s) * 100) + (int(ms) // 10) | |
| def cs_to_ass(total_cs): | |
| """Converts total centiseconds to ASS timestamp (H:MM:SS.cs).""" | |
| if total_cs < 0: total_cs = 0 | |
| h = total_cs // 360000 | |
| rem = total_cs % 360000 | |
| m = rem // 6000 | |
| rem = rem % 6000 | |
| s = rem // 100 | |
| cs = rem % 100 | |
| return f"{h}:{m:02d}:{s:02d}.{cs:02d}" | |
| def process_conversion(): | |
| # Get all .srt files in current directory | |
| files = [f for f in os.listdir('.') if f.lower().endswith('.srt')] | |
| if not files: | |
| print("No .srt files found in this directory.") | |
| return | |
| for filename in files: | |
| base_name = os.path.splitext(filename)[0] | |
| ass_filename = f"{base_name}.ass" | |
| try: | |
| with open(filename, 'r', encoding='utf-8-sig') as f: | |
| content = f.read() | |
| except Exception as e: | |
| print(f"Could not read {filename}: {e}") | |
| continue | |
| # Regex to find SRT blocks | |
| pattern = re.compile(r'(\d+)\n(\d{2}:\d{2}:\d{2},\d{3}) --> (\d{2}:\d{2}:\d{2},\d{3})\n((?:.|\n)*?)(?=\n\d+\n|\Z)', re.MULTILINE) | |
| parsed_events = [] | |
| min_cs = float('inf') | |
| max_cs = 0 | |
| for match in pattern.finditer(content): | |
| start_cs = srt_to_cs(match.group(2)) | |
| end_cs = srt_to_cs(match.group(3)) | |
| text = match.group(4).strip().replace('\n', '\\N') | |
| parsed_events.append({ | |
| 'start': start_cs, | |
| 'end': end_cs, | |
| 'text': text | |
| }) | |
| # Identify the total range of the subtitles | |
| if start_cs < min_cs: min_cs = start_cs | |
| if end_cs > max_cs: max_cs = end_cs | |
| if not parsed_events: | |
| print(f"Skipping {filename}: No subtitle entries found.") | |
| continue | |
| # Header Construction | |
| ass_lines = [ | |
| "[Script Info]", | |
| f"Title: {base_name}", | |
| "ScriptType: v4.00+", | |
| f"PlayResX: {PLAY_RES_X}", | |
| f"PlayResY: {PLAY_RES_Y}", | |
| "ScaledBorderAndShadow: yes", | |
| "", | |
| STYLES_BLOCK, | |
| "", | |
| "[Events]", | |
| "Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text" | |
| ] | |
| # 1. GENERATE 10-SECOND BACKGROUND SEGMENTS | |
| # This spans from the very first subtitle to the very last. | |
| current_chunk_start = min_cs | |
| while current_chunk_start < max_cs: | |
| current_chunk_end = min(current_chunk_start + SEGMENT_DURATION_CS, max_cs) | |
| t_start = cs_to_ass(current_chunk_start) | |
| t_end = cs_to_ass(current_chunk_end) | |
| # Layer 0 ensures background is behind text | |
| bg_line = f"Dialogue: 0,{t_start},{t_end},Background,,0,0,0,,{BG_DRAWING}" | |
| ass_lines.append(bg_line) | |
| current_chunk_start = current_chunk_end | |
| # 2. ADD DIALOGUE EVENTS | |
| for event in parsed_events: | |
| t_start = cs_to_ass(event['start']) | |
| t_end = cs_to_ass(event['end']) | |
| # Layer 1 ensures text is on top of the background | |
| line = f"Dialogue: 1,{t_start},{t_end},Default,,0,0,0,,{event['text']}" | |
| ass_lines.append(line) | |
| # Write to file | |
| with open(ass_filename, 'w', encoding='utf-8') as out: | |
| out.write('\n'.join(ass_lines)) | |
| print(f"Done: {filename} -> {ass_filename}") | |
| print(f" Background segmented every 10s from {cs_to_ass(min_cs)} to {cs_to_ass(max_cs)}") | |
| if __name__ == "__main__": | |
| process_conversion() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment