Skip to content

Instantly share code, notes, and snippets.

@me-suzy
Created July 27, 2026 21:19
Show Gist options
  • Select an option

  • Save me-suzy/2c91b64c0eecb91aa8eaa51020b8f91d to your computer and use it in GitHub Desktop.

Select an option

Save me-suzy/2c91b64c0eecb91aa8eaa51020b8f91d to your computer and use it in GitHub Desktop.
subtitrare.py
"""Overlay pentru subtitrari SRT, sincronizat manual cu un film redat in browser.
Pornire: dublu-click pe acest fisier sau: python subtitrare.py
Apasa START exact cand pornesti/redai filmul. Foloseste -/+ pentru corectie fina.
"""
import ctypes
import re
import time
import tkinter as tk
from tkinter import ttk
from pathlib import Path
SRT_FILE = Path(r"G:\MedEx\Fin.2012.BRRip.XviD.5rFF.srt")
# Schimbă această valoare dacă subtitrarea este mereu înainte sau în urmă.
# Exemplu: 2.0 = subtitrarea apare cu 2 secunde mai târziu; -2.0 = mai devreme.
INITIAL_OFFSET_SECONDS = 0.0
def timestamp(value: str) -> float:
hours, minutes, seconds, milliseconds = re.split(r"[:,]", value.strip())
return int(hours) * 3600 + int(minutes) * 60 + int(seconds) + int(milliseconds) / 1000
def load_srt(path: Path):
text = path.read_text(encoding="utf-8-sig", errors="replace").replace("\r\n", "\n")
cues = []
for block in re.split(r"\n\s*\n", text.strip()):
lines = [line.strip("\ufeff") for line in block.split("\n")]
timing_index = next((i for i, line in enumerate(lines) if "-->" in line), None)
if timing_index is None:
continue
start, end = [part.strip() for part in lines[timing_index].split("-->", 1)]
caption = "\n".join(lines[timing_index + 1:]).strip()
if caption:
cues.append((timestamp(start), timestamp(end), caption))
return cues
class SubtitleOverlay:
def __init__(self, cues):
self.cues = cues
self.started_at = None
self.offset = INITIAL_OFFSET_SECONDS
self.preview_until = 0.0
self.selected_cue_index = 0
self.root = tk.Tk()
self.root.withdraw()
self.font_name = tk.StringVar(master=self.root, value="Arial")
self.font_size = tk.IntVar(master=self.root, value=26)
self.font_color = tk.StringVar(master=self.root, value="#FFFFFF")
self.bold = tk.BooleanVar(master=self.root, value=True)
self.italic = tk.BooleanVar(master=self.root, value=False)
self.bottom_offset = tk.IntVar(master=self.root, value=265)
self.screen_w = self.root.winfo_screenwidth()
self.screen_h = self.root.winfo_screenheight()
self.overlay = tk.Toplevel(self.root)
self.overlay.overrideredirect(True)
self.overlay.attributes("-topmost", True)
self.overlay.configure(bg="black")
overlay_width = min(1500, self.screen_w - 80)
overlay_x = (self.screen_w - overlay_width) // 2
self.overlay_width = overlay_width
self.overlay_x = overlay_x
self.overlay.geometry(f"{overlay_width}x150+{overlay_x}+{self.screen_h - self.bottom_offset.get()}")
self.label = tk.Label(self.overlay, text="", fg="white", bg="black", justify="center", wraplength=overlay_width - 80, padx=28, pady=14)
self.label.pack(fill="both", expand=True)
self.apply_style()
self.controls = tk.Toplevel(self.root)
self.controls.title("Subtitrare")
self.controls.attributes("-topmost", True)
self.controls.resizable(False, False)
self.controls.geometry("480x460")
tk.Label(self.controls, text="Sincronizare subtitrare", font=("Arial", 11, "bold")).pack(padx=12, pady=(10, 4))
self.status = tk.StringVar(value="Apasă START când pornești filmul")
tk.Label(self.controls, textvariable=self.status).pack(padx=12, pady=(0, 8))
actions = tk.Frame(self.controls)
actions.pack(padx=10, pady=(0, 10))
self.start_button = tk.Button(actions, text="START — SINCRONIZEAZĂ ACUM", command=self.sync_first_cue, width=29, bg="#9fc5e8")
self.start_button.grid(row=0, column=0, columnspan=4, pady=(0, 6))
self.test_button = tk.Button(actions, text="TESTEAZĂ FONTUL ȘI POZIȚIA", command=self.preview_first_cue, bg="#d9d2e9")
self.test_button.grid(row=1, column=0, columnspan=4, pady=(0, 6))
self.adjust_buttons = []
for column, (caption, seconds) in enumerate((("− 1 sec", -1.0), ("− 0,1 sec", -0.1), ("+ 0,1 sec", 0.1), ("+ 1 sec", 1.0))):
button = tk.Button(actions, text=caption, bg="#eeeeee")
button.configure(command=lambda value=seconds, source=button: self.adjust(value, source))
button.grid(row=2, column=column, padx=2)
self.adjust_buttons.append(button)
self.stop_button = tk.Button(actions, text="Oprește", command=self.stop, bg="#f4cccc")
self.stop_button.grid(row=3, column=0, columnspan=4, pady=(6, 0))
style = tk.LabelFrame(self.controls, text="Aspect subtitrare")
style.pack(fill="x", padx=10, pady=(0, 10))
tk.Label(style, text="Font:").grid(row=0, column=0, sticky="w", padx=6, pady=4)
ttk.Combobox(style, textvariable=self.font_name, values=("Arial", "Calibri", "Verdana", "Tahoma", "Times New Roman"), width=18).grid(row=0, column=1, sticky="w")
tk.Label(style, text="Mărime:").grid(row=0, column=2, sticky="w", padx=(12, 4))
tk.Spinbox(style, from_=14, to=54, textvariable=self.font_size, width=5).grid(row=0, column=3, sticky="w")
tk.Label(style, text="Culoare:").grid(row=1, column=0, sticky="w", padx=6, pady=4)
tk.Entry(style, textvariable=self.font_color, width=10).grid(row=1, column=1, sticky="w")
tk.Checkbutton(style, text="Bold", variable=self.bold).grid(row=1, column=2, sticky="w", padx=(12, 0))
tk.Checkbutton(style, text="Italic", variable=self.italic).grid(row=1, column=3, sticky="w")
tk.Label(style, text="Distanță de jos:").grid(row=2, column=0, sticky="w", padx=6, pady=4)
tk.Spinbox(style, from_=120, to=500, increment=10, textvariable=self.bottom_offset, width=7).grid(row=2, column=1, sticky="w")
tk.Button(style, text="Aplică aspectul", command=self.apply_style, bg="#cfe2f3").grid(row=2, column=2, columnspan=2, pady=4)
tk.Button(self.controls, text="Închide", command=self.close).pack(pady=(0, 10))
self.controls.protocol("WM_DELETE_WINDOW", self.close)
def apply_style(self):
style = []
if self.bold.get(): style.append("bold")
if self.italic.get(): style.append("italic")
self.label.configure(font=(self.font_name.get(), self.font_size.get(), *style), fg=self.font_color.get() or "#FFFFFF")
self.overlay.geometry(f"{self.overlay_width}x150+{self.overlay_x}+{self.screen_h - self.bottom_offset.get()}")
def set_caption(self, text):
self.label.configure(text=text)
def start(self):
self.started_at = time.perf_counter()
self.offset = INITIAL_OFFSET_SECONDS
self.start_button.configure(bg="#93c47d", activebackground="#93c47d")
for button in self.adjust_buttons:
button.configure(bg="#eeeeee")
self.status.set("RULEAZĂ — așteaptă prima replică")
def sync_first_cue(self):
"""Align the first cue in whatever SRT file is currently configured."""
if not self.cues:
return
self.started_at = time.perf_counter() - self.cues[0][0]
self.offset = 0.0
self.start_button.configure(bg="#93c47d", activebackground="#93c47d")
self.start_button.configure(bg="#76a5af", activebackground="#76a5af")
for button in self.adjust_buttons:
button.configure(bg="#eeeeee")
self.status.set("SINCRONIZAT CU PRIMA FRAZĂ DIN SUBTITRARE")
def adjust(self, seconds, source=None):
if self.started_at is None:
self.start()
self.offset += seconds
for button in self.adjust_buttons:
button.configure(bg="#eeeeee")
if source is not None:
source.configure(bg="#ffe599", activebackground="#ffe599")
direction = "+" if self.offset >= 0 else ""
self.status.set(f"CORECȚIE APLICATĂ: {direction}{self.offset:.1f} sec")
def preview_first_cue(self):
if not self.cues:
return
self.set_caption(self.cues[0][2])
self.preview_until = time.perf_counter() + 4
self.test_button.configure(bg="#b4a7d6", activebackground="#b4a7d6")
self.status.set("TEST ACTIV — textul trebuie să fie vizibil jos pe ecran")
def stop(self):
self.started_at = None
self.set_caption("")
self.start_button.configure(bg="#d9d9d9", activebackground="#d9d9d9")
self.start_button.configure(bg="#9fc5e8", activebackground="#9fc5e8")
self.status.set("OPRIT")
@staticmethod
def time_text(seconds):
seconds = max(0, int(seconds))
return f"{seconds // 60:02d}:{seconds % 60:02d}"
def tick(self):
self.overlay.lift()
if self.preview_until and time.perf_counter() >= self.preview_until:
self.preview_until = 0.0
self.set_caption("")
self.test_button.configure(bg="#d9d2e9", activebackground="#d9d2e9")
if self.started_at is not None:
current = time.perf_counter() - self.started_at + self.offset
caption = next((text for start, end, text in self.cues if start <= current <= end), "")
if not self.preview_until:
self.set_caption(caption)
if not caption:
self.status.set(f"RULEAZĂ — subtitrare: {self.time_text(current)}")
self.root.after(80, self.tick)
def close(self):
self.root.destroy()
def run(self):
self.tick()
self.root.mainloop()
if __name__ == "__main__":
if not SRT_FILE.exists():
raise SystemExit(f"Nu găsesc subtitrarea: {SRT_FILE}")
SubtitleOverlay(load_srt(SRT_FILE)).run()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment