arikaharada

Untitled

Nov 10th, 2025
93
0
Never
12
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 5.73 KB | None | 0 0
  1. #!/usr/bin/env python3
  2. """
  3. convert_srt_to_ass.py
  4. Converts an .srt subtitle to .ass with:
  5. - Spoken dialogue (normal lines) at bottom
  6. - On-screen captions (lines enclosed in parentheses) above the dialogue area
  7. - Overlapping lines stacked vertically (not drawn on top of each other)
  8. Usage:
  9. python convert_srt_to_ass.py input.srt [output.ass]
  10. """
  11. import re
  12. import sys
  13.  
  14. def srt_time_to_seconds(t):
  15. """Convert SRT time 'HH:MM:SS,ms' to seconds (float)."""
  16. h, m, s_ms = t.split(':')
  17. s, ms = s_ms.split(',')
  18. return int(h) * 3600 + int(m) * 60 + int(s) + int(ms) / 1000.0
  19.  
  20. def seconds_to_ass_time(seconds):
  21. """Convert seconds (float) to ASS time format h:mm:ss.cc (centiseconds)."""
  22. total_cs = int(round(seconds * 100))
  23. cs = total_cs % 100
  24. total_seconds = total_cs // 100
  25. s = total_seconds % 60
  26. total_minutes = total_seconds // 60
  27. m = total_minutes % 60
  28. h = total_minutes // 60
  29. return f"{h}:{m:02d}:{s:02d}.{cs:02d}"
  30.  
  31. def parse_srt(path):
  32. """Parse an SRT file into a list of (start_seconds, end_seconds, text)."""
  33. with open(path, 'r', encoding='utf-8-sig') as f:
  34. raw = f.read()
  35.  
  36. raw = raw.replace('\r\n', '\n').replace('\r', '\n')
  37. blocks = re.split(r'\n\s*\n', raw.strip())
  38.  
  39. subs = []
  40. time_re = re.compile(r'(\d{2}:\d{2}:\d{2},\d{3})\s*-->\s*(\d{2}:\d{2}:\d{2},\d{3})')
  41. for block in blocks:
  42. lines = block.strip().split('\n')
  43. if len(lines) < 2:
  44. continue
  45. for i, ln in enumerate(lines):
  46. if '-->' in ln:
  47. time_line = ln
  48. text_lines = lines[i+1:]
  49. break
  50. else:
  51. continue
  52. m = time_re.search(time_line)
  53. if not m:
  54. continue
  55. start_s = srt_time_to_seconds(m.group(1))
  56. end_s = srt_time_to_seconds(m.group(2))
  57. text = '\n'.join(text_lines).strip()
  58. subs.append((start_s, end_s, text))
  59. return subs
  60.  
  61. def is_caption(text):
  62. """Return True if the entire text is enclosed in parentheses."""
  63. t = text.strip()
  64. return bool(re.match(r'^\(.*\)$', t, re.DOTALL))
  65.  
  66. def assign_tracks(items):
  67. """Assign each (start, end, text) to a track index so overlapping items stack vertically."""
  68. tracks = []
  69. assigned = []
  70. for start, end, text in sorted(items, key=lambda x: x[0]):
  71. for idx, last_end in enumerate(tracks):
  72. if start >= last_end - 1e-6:
  73. tracks[idx] = end
  74. assigned.append(((start, end, text), idx))
  75. break
  76. else:
  77. tracks.append(end)
  78. assigned.append(((start, end, text), len(tracks)-1))
  79. return assigned, len(tracks)
  80.  
  81. def generate_ass(subs, out_path, playresx=1920, playresy=1080):
  82. voice = []
  83. caption = []
  84. for s, e, t in subs:
  85. if is_caption(t):
  86. caption.append((s, e, t))
  87. else:
  88. voice.append((s, e, t))
  89.  
  90. voice_assigned, voice_track_count = assign_tracks(voice)
  91. caption_assigned, caption_track_count = assign_tracks(caption)
  92.  
  93. # Base positions
  94. base_voice_margin = 40
  95. line_height = 56
  96. base_caption_margin = base_voice_margin + (voice_track_count * line_height) + 10
  97.  
  98. header = [
  99. "[Script Info]",
  100. "Title: Converted from SRT",
  101. "ScriptType: v4.00+",
  102. "Collisions: Normal",
  103. f"PlayResX: {playresx}",
  104. f"PlayResY: {playresy}",
  105. "WrapStyle: 2",
  106. "ScaledBorderAndShadow: yes",
  107. "",
  108. "[V4+ Styles]",
  109. "Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, "
  110. "BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, "
  111. "BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding",
  112. # Font sizes increased to 55 for both
  113. "Style: Voice, Arial, 55, &H00FFFFFF, &H000000FF, &H00000000, &H00000000, "
  114. "0, 0, 0, 0, 100, 100, 0, 0, 1, 3, 0, 2, 10, 10, 40, 1",
  115. "Style: Caption, Arial, 55, &H00FFFF99, &H000000FF, &H00000000, &H00000000, "
  116. "0, 0, 0, 0, 100, 100, 0, 0, 1, 3, 0, 2, 10, 10, 200, 1",
  117. "",
  118. "[Events]",
  119. "Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text"
  120. ]
  121.  
  122. events = []
  123.  
  124. # Voice subtitles
  125. for (start, end, text), track in voice_assigned:
  126. margin_v = base_voice_margin + track * line_height
  127. text_esc = text.replace('\r', '').replace('\n', r'\N')
  128. events.append(
  129. f"Dialogue: 0,{seconds_to_ass_time(start)},{seconds_to_ass_time(end)},Voice,,0,0,{margin_v},,{text_esc}"
  130. )
  131.  
  132. # Captions just above voice area
  133. for (start, end, text), track in caption_assigned:
  134. margin_v = base_caption_margin + track * line_height
  135. text_esc = text.replace('\r', '').replace('\n', r'\N')
  136. events.append(
  137. f"Dialogue: 0,{seconds_to_ass_time(start)},{seconds_to_ass_time(end)},Caption,,0,0,{margin_v},,{text_esc}"
  138. )
  139.  
  140. with open(out_path, 'w', encoding='utf-8-sig') as f:
  141. f.write('\n'.join(header) + '\n' + '\n'.join(events) + '\n')
  142.  
  143. print(f"✅ Converted: {out_path}")
  144. print(f" - voice tracks: {voice_track_count}, caption tracks: {caption_track_count}")
  145.  
  146. def main():
  147. if len(sys.argv) < 2:
  148. print("Usage: python convert_srt_to_ass.py input.srt [output.ass]")
  149. sys.exit(1)
  150. input_srt = sys.argv[1]
  151. output_ass = sys.argv[2] if len(sys.argv) > 2 else re.sub(r'\.srt$', '.ass', input_srt, flags=re.I)
  152. subs = parse_srt(input_srt)
  153. if not subs:
  154. print("No subtitles found or failed to parse the SRT.")
  155. sys.exit(2)
  156. generate_ass(subs, output_ass)
  157.  
  158. if __name__ == '__main__':
  159. main()
  160.  
Advertisement
Comments
  • User was banned
  • User was banned
  • User was banned
  • User was banned
  • User was banned
  • User was banned
  • User was banned
  • User was banned
  • User was banned
  • User was banned
  • User was banned
  • User was banned
Add Comment
Please, Sign In to add comment