Created
July 27, 2026 21:29
-
-
Save me-suzy/aa3e202d4ad63760a3ae793d1242c5b6 to your computer and use it in GitHub Desktop.
subtitrare 2.py
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
| """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.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.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=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.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 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.stop() | |
| self.file_name.set("Subtitrare: " + path.name) | |
| CONFIG_FILE.write_text(json.dumps({"subtitle_file": str(path)}, ensure_ascii=False, indent=2), encoding="utf-8") | |
| 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.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__": | |
| 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