Skip to content

Instantly share code, notes, and snippets.

@me-suzy
Created July 28, 2026 03:45
Show Gist options
  • Select an option

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

Select an option

Save me-suzy/5756c3df800c80db1d3d0daf04476d00 to your computer and use it in GitHub Desktop.
subtitrare 3.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 json
import re
import time
import tkinter as tk
from tkinter import ttk, colorchooser, filedialog
from pathlib import Path
SRT_FILE = Path(r"G:\MedEx\Fin.2012.BRRip.XviD.5rFF.srt")
CONFIG_FILE = Path(__file__).with_name("subtitrare_config.json")
# 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, source_path):
self.cues = cues
self.source_path = source_path
self.started_at = None
self.offset = INITIAL_OFFSET_SECONDS
self.paused_at = self.load_pause_position()
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")
self.overlay.attributes("-transparentcolor", "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(True, True)
self.controls.minsize(480, 460)
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))
self.pause_info = tk.StringVar()
tk.Label(self.controls, textvariable=self.pause_info, fg="#9c5700", font=("Arial", 10, "bold")).pack(padx=12, pady=(0, 6))
self.refresh_pause_info()
self.file_name = tk.StringVar(value="Subtitrare: " + self.source_path.name)
tk.Label(self.controls, textvariable=self.file_name, wraplength=440, fg="#1f4e78").pack(padx=12, pady=(0, 6))
tk.Button(self.controls, text="ÎNCARCĂ SUBTITRARE…", command=self.load_subtitle, bg="#cfe2f3").pack(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=2, pady=(6, 0), padx=2)
self.pause_button = tk.Button(actions, text="PAUZĂ", command=self.pause_resume, bg="#fce5cd")
self.pause_button.grid(row=3, column=2, columnspan=2, pady=(6, 0), padx=2)
if self.paused_at is not None:
self.pause_button.configure(text="CONTINUĂ", bg="#93c47d")
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.Button(style, text="Alege…", command=self.choose_color).grid(row=1, column=1, sticky="e")
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 choose_color(self):
_, hex_color = colorchooser.askcolor(color=self.font_color.get(), parent=self.controls, title="Alege culoarea subtitrării")
if hex_color:
self.font_color.set(hex_color.upper())
self.apply_style()
def set_caption(self, text):
self.label.configure(text=text)
def read_config(self):
try:
return json.loads(CONFIG_FILE.read_text(encoding="utf-8"))
except Exception:
return {}
def save_config(self):
config = self.read_config()
config["subtitle_file"] = str(self.source_path)
pauses = config.setdefault("pause_times", {})
if self.paused_at is None:
pauses.pop(str(self.source_path), None)
else:
pauses[str(self.source_path)] = self.paused_at
CONFIG_FILE.write_text(json.dumps(config, ensure_ascii=False, indent=2), encoding="utf-8")
def load_pause_position(self):
value = self.read_config().get("pause_times", {}).get(str(self.source_path))
return float(value) if value is not None else None
def refresh_pause_info(self):
if self.paused_at is None:
self.pause_info.set("Nu există o pauză salvată")
else:
self.pause_info.set(f"Pauză salvată la: {self.time_text(self.paused_at)}")
def current_subtitle_time(self):
if self.started_at is None:
return self.paused_at or 0.0
return time.perf_counter() - self.started_at + self.offset
def pause_resume(self):
if self.started_at is not None:
self.paused_at = self.current_subtitle_time()
self.started_at = None
self.set_caption("")
self.save_config()
self.refresh_pause_info()
self.pause_button.configure(text="CONTINUĂ", bg="#93c47d")
self.status.set("PAUZAT — timpul a fost salvat")
elif self.paused_at is not None:
self.started_at = time.perf_counter() - self.paused_at - self.offset
self.pause_button.configure(text="PAUZĂ", bg="#fce5cd")
self.status.set("RULEAZĂ DIN TIMPUL SALVAT")
def load_subtitle(self):
selected = filedialog.askopenfilename(
parent=self.controls,
title="Alege fișierul de subtitrare",
filetypes=(("Subtitrări SRT", "*.srt"), ("Toate fișierele", "*.*")),
)
if not selected:
return
path = Path(selected)
try:
cues = load_srt(path)
if not cues:
raise ValueError("Fișierul nu conține replici SRT valide.")
self.cues = cues
self.source_path = path
self.selected_cue_index = 0
self.paused_at = self.load_pause_position()
self.stop()
self.file_name.set("Subtitrare: " + path.name)
self.save_config()
self.refresh_pause_info()
self.status.set(f"Încărcată și memorată: {len(cues)} replici")
except Exception as error:
self.status.set("Nu pot încărca subtitrarea: " + str(error))
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.paused_at = None
self.save_config()
self.refresh_pause_info()
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.pause_button.configure(text="PAUZĂ", bg="#fce5cd")
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__":
subtitle_path = SRT_FILE
if CONFIG_FILE.exists():
try:
saved = Path(json.loads(CONFIG_FILE.read_text(encoding="utf-8")).get("subtitle_file", ""))
if saved.exists():
subtitle_path = saved
except Exception:
pass
if not subtitle_path.exists():
raise SystemExit(f"Nu găsesc subtitrarea: {subtitle_path}")
SubtitleOverlay(load_srt(subtitle_path), subtitle_path).run()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment