This commit is contained in:
stirelshka8_BigARM
2025-11-26 16:06:40 +03:00
parent 3f06e4f797
commit 41beee6283
7 changed files with 459 additions and 0 deletions
+167
View File
@@ -0,0 +1,167 @@
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import os
from gui.video_item import VideoItem
from core.video_processor import VideoProcessor
from core.logger import Logger
class MainWindow:
def __init__(self, root):
self.root = root
self.root.title("Video Editor Pro")
self.root.geometry("900x700")
self.video_items = []
self.setup_ui()
self.logger = Logger()
def setup_ui(self):
# Main frame
main_frame = ttk.Frame(self.root, padding="10")
main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# Add files section
files_frame = ttk.LabelFrame(main_frame, text="Видео файлы", padding="5")
files_frame.grid(row=0, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(0, 10))
ttk.Button(files_frame, text="Добавить файлы",
command=self.add_video_files).pack(side=tk.LEFT, padx=(0, 10))
ttk.Button(files_frame, text="Очистить все",
command=self.clear_all).pack(side=tk.LEFT)
# Scrollable frame for video items
canvas = tk.Canvas(main_frame)
scrollbar = ttk.Scrollbar(main_frame, orient="vertical", command=canvas.yview)
self.scrollable_frame = ttk.Frame(canvas)
self.scrollable_frame.bind(
"<Configure>",
lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
)
canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
canvas.configure(yscrollcommand=scrollbar.set)
canvas.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
scrollbar.grid(row=1, column=1, sticky=(tk.N, tk.S))
# Output settings
output_frame = ttk.LabelFrame(main_frame, text="Настройки вывода", padding="5")
output_frame.grid(row=2, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(10, 0))
ttk.Label(output_frame, text="Формат:").grid(row=0, column=0, padx=(0, 5))
self.format_var = tk.StringVar(value="mp4")
format_combo = ttk.Combobox(output_frame, textvariable=self.format_var,
values=["mp4", "avi", "mov", "mkv"], state="readonly")
format_combo.grid(row=0, column=1, padx=(0, 20))
ttk.Label(output_frame, text="Качество:").grid(row=0, column=2, padx=(0, 5))
self.quality_var = tk.StringVar(value="high")
quality_combo = ttk.Combobox(output_frame, textvariable=self.quality_var,
values=["low", "medium", "high"], state="readonly")
quality_combo.grid(row=0, column=3, padx=(0, 20))
ttk.Button(output_frame, text="Выбрать папку для сохранения",
command=self.select_output_dir).grid(row=0, column=4, padx=(0, 10))
self.output_dir_var = tk.StringVar(value=os.getcwd())
ttk.Label(output_frame, textvariable=self.output_dir_var).grid(row=0, column=5)
# Process button
ttk.Button(main_frame, text="Выполнить",
command=self.process_videos, style="Accent.TButton").grid(row=3, column=0, pady=20)
# Progress bar
self.progress_var = tk.DoubleVar()
self.progress_bar = ttk.Progressbar(main_frame, variable=self.progress_var, maximum=100)
self.progress_bar.grid(row=4, column=0, columnspan=2, sticky=(tk.W, tk.E))
# Status label
self.status_var = tk.StringVar(value="Готов к работе")
ttk.Label(main_frame, textvariable=self.status_var).grid(row=5, column=0, columnspan=2)
# Configure grid weights
main_frame.columnconfigure(0, weight=1)
main_frame.rowconfigure(1, weight=1)
def add_video_files(self):
files = filedialog.askopenfilenames(
title="Выберите видео файлы",
filetypes=[
("Видео файлы", "*.mp4 *.avi *.mov *.mkv *.wmv *.flv *.webm"),
("Все файлы", "*.*")
]
)
for file_path in files:
video_item = VideoItem(self.scrollable_frame, file_path)
video_item.pack(fill=tk.X, padx=5, pady=2)
self.video_items.append(video_item)
self.update_status(f"Добавлено файлов: {len(files)}")
def clear_all(self):
for item in self.video_items:
item.destroy()
self.video_items.clear()
self.update_status("Все файлы удалены")
def select_output_dir(self):
directory = filedialog.askdirectory(title="Выберите папку для сохранения")
if directory:
self.output_dir_var.set(directory)
def update_status(self, message):
self.status_var.set(message)
self.root.update()
def process_videos(self):
if not self.video_items:
messagebox.showerror("Ошибка", "Добавьте хотя бы один видео файл")
return
# Collect video data
video_data = []
for item in self.video_items:
time_ranges = item.get_time_ranges()
if not time_ranges: # Use entire video if no ranges specified
time_ranges = [(0, item.get_duration())]
video_data.append({
'path': item.file_path,
'time_ranges': time_ranges
})
output_format = self.format_var.get()
output_quality = self.quality_var.get()
output_dir = self.output_dir_var.get()
try:
processor = VideoProcessor()
def progress_callback(progress, message):
self.progress_var.set(progress * 100)
self.update_status(message)
output_path = processor.process_videos(
video_data=video_data,
output_dir=output_dir,
output_format=output_format,
quality=output_quality,
progress_callback=progress_callback
)
# Save log
log_path = output_path.replace(f".{output_format}", "_log.txt")
self.logger.save_log(log_path)
self.update_status(f"Готово! Файл сохранен: {output_path}")
messagebox.showinfo("Успех", f"Обработка завершена!\nФайл: {output_path}")
except Exception as e:
error_msg = f"Ошибка обработки: {str(e)}"
self.update_status(error_msg)
self.logger.log("ERROR", error_msg)
messagebox.showerror("Ошибка", error_msg)
finally:
self.progress_var.set(0)
+108
View File
@@ -0,0 +1,108 @@
import tkinter as tk
from tkinter import ttk
import os
from moviepy.editor import VideoFileClip
class VideoItem(ttk.Frame):
def __init__(self, parent, file_path):
super().__init__(parent)
self.file_path = file_path
self.time_ranges = []
self.setup_ui()
def setup_ui(self):
# File info
file_name = os.path.basename(self.file_path)
ttk.Label(self, text=file_name, font=('Arial', 9, 'bold')).grid(row=0, column=0, sticky=tk.W)
# Duration info
try:
with VideoFileClip(self.file_path) as clip:
duration = clip.duration
duration_str = self.format_time(duration)
ttk.Label(self, text=f"Длительность: {duration_str}").grid(row=1, column=0, sticky=tk.W)
except:
duration_str = "Неизвестно"
ttk.Label(self, text="Ошибка чтения файла").grid(row=1, column=0, sticky=tk.W)
# Time ranges frame
ranges_frame = ttk.LabelFrame(self, text="Временные диапазоны")
ranges_frame.grid(row=2, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=5)
self.ranges_container = ttk.Frame(ranges_frame)
self.ranges_container.pack(fill=tk.X, padx=5, pady=5)
# Add range button
ttk.Button(ranges_frame, text="+ Добавить диапазон",
command=self.add_time_range).pack(pady=5)
# Buttons
ttk.Button(self, text="Удалить",
command=self.destroy).grid(row=0, column=2, rowspan=2, padx=5)
self.columnconfigure(0, weight=1)
def format_time(self, seconds):
hours = int(seconds // 3600)
minutes = int((seconds % 3600) // 60)
seconds = int(seconds % 60)
return f"{hours:02d}:{minutes:02d}:{seconds:02d}"
def add_time_range(self, start_time=0, end_time=0):
range_frame = ttk.Frame(self.ranges_container)
range_frame.pack(fill=tk.X, pady=2)
ttk.Label(range_frame, text="От:").pack(side=tk.LEFT)
start_var = tk.StringVar(value=self.format_time(start_time))
start_entry = ttk.Entry(range_frame, textvariable=start_var, width=8)
start_entry.pack(side=tk.LEFT, padx=(0, 10))
ttk.Label(range_frame, text="До:").pack(side=tk.LEFT)
end_var = tk.StringVar(value=self.format_time(end_time))
end_entry = ttk.Entry(range_frame, textvariable=end_var, width=8)
end_entry.pack(side=tk.LEFT, padx=(0, 10))
def remove_range():
range_frame.destroy()
self.time_ranges = [r for r in self.time_ranges if r['frame'] != range_frame]
ttk.Button(range_frame, text="×", width=3,
command=remove_range).pack(side=tk.LEFT)
self.time_ranges.append({
'frame': range_frame,
'start_var': start_var,
'end_var': end_var
})
def get_time_ranges(self):
ranges = []
for range_data in self.time_ranges:
try:
start_time = self.parse_time(range_data['start_var'].get())
end_time = self.parse_time(range_data['end_var'].get())
if start_time < end_time:
ranges.append((start_time, end_time))
except:
continue
return ranges
def parse_time(self, time_str):
parts = time_str.split(':')
if len(parts) == 3: # HH:MM:SS
hours, minutes, seconds = map(int, parts)
return hours * 3600 + minutes * 60 + seconds
elif len(parts) == 2: # MM:SS
minutes, seconds = map(int, parts)
return minutes * 60 + seconds
else: # SS
return int(time_str)
def get_duration(self):
try:
with VideoFileClip(self.file_path) as clip:
return clip.duration
except:
return 0