ShortGenerator/app.py
klop51 c82130ec6e feat: Implement subtitle positioning tool with GUI
- Added app.py for subtitle positioning tool using Tkinter and MoviePy.
- Integrated font selection and adjustable subtitle positioning.
- Implemented loading and saving of presets in JSON format.
- Added functionality to preview subtitles on video clips.
- Enhanced subtitle rendering with highlight effects.
- Created app2.py for advanced subtitle handling with SRT file support.
- Implemented SRT parsing and subtitle navigation in app2.py.
- Added system font detection for better font compatibility.
- Updated shorts_generator2.py to include GUI for shorts generation.
- Enhanced error handling and progress tracking in shorts generation.
- Created subtitle_generator.py for automatic subtitle generation from video.
- Added progress bar and user feedback in subtitle generation GUI.
- Updated subtitle_gui_presets.json and subtitles.srt for testing.
2025-08-06 20:41:10 +02:00

176 lines
5.9 KiB
Python

import tkinter as tk
from tkinter import filedialog
from moviepy import VideoFileClip, TextClip, CompositeVideoClip
import threading
import json
# Global settings with defaults
settings = {
"subtitle_y_px": 1550,
"highlight_offset": -8,
"font_size_subtitle": 65,
"font_size_highlight": 68,
"highlight_x_offset": 0,
"video_path": None,
"selected_font": "Arial" # Default font
}
# Compatible fonts that work across different systems
COMPATIBLE_FONTS = [
"Arial",
"Times-Roman",
"Helvetica",
"Courier",
"Comic-Sans-MS",
"Impact",
"Verdana",
"Tahoma",
"Georgia",
"Trebuchet-MS"
]
preset_file = "subtitle_gui_presets.json"
def save_presets():
with open(preset_file, "w") as f:
json.dump(settings, f)
print("💾 Presets saved!")
def load_presets():
global settings
try:
with open(preset_file, "r") as f:
loaded = json.load(f)
settings.update(loaded)
print("✅ Presets loaded!")
sync_gui()
except FileNotFoundError:
print("⚠️ No presets found.")
def sync_gui():
sub_y_slider.set(settings["subtitle_y_px"])
highlight_slider.set(settings["highlight_offset"])
highlight_x_slider.set(settings["highlight_x_offset"])
sub_font_slider.set(settings["font_size_subtitle"])
highlight_font_slider.set(settings["font_size_highlight"])
font_var.set(settings["selected_font"])
def render_preview():
if not settings["video_path"]:
print("⚠️ No video selected.")
return
clip = VideoFileClip(settings["video_path"]).subclipped(0, 3) # Use first 3 seconds
vertical_clip = clip.resized(height=1920).cropped(width=1080, x_center=clip.w / 2)
subtitle_text = "THIS IS A TEST SUBTITLE"
highlight_word = "SUBTITLE"
base_subtitle = TextClip(
text=subtitle_text,
font_size=settings["font_size_subtitle"],
font=settings["selected_font"],
color='white',
stroke_color='black',
stroke_width=5
).with_duration(3).with_position(('center', settings["subtitle_y_px"]))
# Compute highlight word position
full_text = subtitle_text.upper()
words = full_text.split()
highlight_index = words.index(highlight_word.upper())
chars_before = sum(len(w) + 1 for w in words[:highlight_index])
char_width = 35
total_width = len(full_text) * char_width
x_offset = (chars_before * char_width) - (total_width // 2) + settings["highlight_x_offset"]
highlighted_word = TextClip(
text=highlight_word,
font_size=settings["font_size_highlight"],
font=settings["selected_font"],
color='#FFD700',
stroke_color='#FF6B35',
stroke_width=5
).with_duration(1.5).with_start(0.75).with_position((540 + x_offset, settings["subtitle_y_px"] + settings["highlight_offset"]))
final = CompositeVideoClip([vertical_clip, base_subtitle, highlighted_word], size=(1080, 1920))
# Scale down the preview to fit 1080p monitor (max height ~900px to leave room for taskbar)
preview_scale = 900 / 1920 # Scale factor to fit height
preview_width = int(1080 * preview_scale)
preview_height = int(1920 * preview_scale)
preview_clip = final.resized((preview_width, preview_height))
preview_clip.preview(fps=24, audio=False)
clip.close()
final.close()
preview_clip.close()
def update_setting(var_name, value):
settings[var_name] = int(value)
def update_font(font_name):
settings["selected_font"] = font_name
def open_video():
file_path = filedialog.askopenfilename(filetypes=[("MP4 files", "*.mp4")])
if file_path:
settings["video_path"] = file_path
print(f"📂 Loaded video: {file_path}")
def start_preview_thread():
threading.Thread(target=render_preview).start()
# GUI Setup
root = tk.Tk()
root.title("Subtitle Positioning Tool")
root.geometry("400x600")
load_btn = tk.Button(root, text="🎥 Load Video", command=open_video)
load_btn.pack(pady=10)
tk.Label(root, text="Font Family").pack()
font_var = tk.StringVar(value=settings["selected_font"])
font_dropdown = tk.OptionMenu(root, font_var, *COMPATIBLE_FONTS, command=update_font)
font_dropdown.pack(pady=5)
tk.Label(root, text="Subtitle Y Position").pack()
sub_y_slider = tk.Scale(root, from_=1000, to=1800, orient="horizontal",
command=lambda v: update_setting("subtitle_y_px", v))
sub_y_slider.set(settings["subtitle_y_px"])
sub_y_slider.pack()
tk.Label(root, text="Highlight Y Offset").pack()
highlight_slider = tk.Scale(root, from_=-100, to=100, orient="horizontal",
command=lambda v: update_setting("highlight_offset", v))
highlight_slider.set(settings["highlight_offset"])
highlight_slider.pack()
tk.Label(root, text="Highlight X Offset").pack()
highlight_x_slider = tk.Scale(root, from_=-300, to=300, orient="horizontal",
command=lambda v: update_setting("highlight_x_offset", v))
highlight_x_slider.set(settings["highlight_x_offset"])
highlight_x_slider.pack()
tk.Label(root, text="Subtitle Font Size").pack()
sub_font_slider = tk.Scale(root, from_=30, to=100, orient="horizontal",
command=lambda v: update_setting("font_size_subtitle", v))
sub_font_slider.set(settings["font_size_subtitle"])
sub_font_slider.pack()
tk.Label(root, text="Highlight Font Size").pack()
highlight_font_slider = tk.Scale(root, from_=30, to=100, orient="horizontal",
command=lambda v: update_setting("font_size_highlight", v))
highlight_font_slider.set(settings["font_size_highlight"])
highlight_font_slider.pack()
preview_btn = tk.Button(root, text="▶️ Preview Clip", command=start_preview_thread)
preview_btn.pack(pady=10)
save_btn = tk.Button(root, text="💾 Save Preset", command=save_presets)
save_btn.pack(pady=5)
load_preset_btn = tk.Button(root, text="📂 Load Preset", command=load_presets)
load_preset_btn.pack(pady=5)
root.mainloop()