# -*- coding: utf-8 -*- import tkinter as tk from tkinter import ttk import os from moviepy import VideoFileClip from utils.file_utils import get_file_size from gui.theme import COLORS class VideoItem(ttk.Frame): """Карточка одного видео в списке с диапазонами и стильным оформлением.""" def __init__(self, parent, file_path, on_remove=None): super().__init__(parent) self.file_path = file_path self.time_ranges = [] self._duration = 0 self.on_remove = on_remove self.setup_ui() def setup_ui(self): # Контейнер-карточка с фоном self.configure(style="Card.TFrame") card = tk.Frame(self, bg=COLORS["surface"], highlightbackground=COLORS["border"], highlightthickness=1) card.pack(fill=tk.BOTH, expand=True, padx=0, pady=0) inner = tk.Frame(card, bg=COLORS["surface"], padx=12, pady=10) inner.pack(fill=tk.BOTH, expand=True) # Верхняя строка: имя файла + размер, длительность, кнопка удалить top = tk.Frame(inner, bg=COLORS["surface"]) top.pack(fill=tk.X, pady=(0, 6)) file_name = os.path.basename(self.file_path) try: size_str = get_file_size(self.file_path) file_info = f"{file_name} · {size_str}" except Exception: file_info = file_name lbl_name = tk.Label(top, text=file_info, font=("Tahoma", 10, "bold"), fg=COLORS["text"], bg=COLORS["surface"]) lbl_name.pack(side=tk.LEFT) try: with VideoFileClip(self.file_path) as clip: self._duration = clip.duration duration_str = self.format_time(self._duration) lbl_dur = tk.Label(top, text=f" · {duration_str}", font=("Tahoma", 9), fg=COLORS["subtext"], bg=COLORS["surface"]) lbl_dur.pack(side=tk.LEFT) except Exception: lbl_err = tk.Label(top, text=" · Ошибка чтения", font=("Tahoma", 9), fg=COLORS["red"], bg=COLORS["surface"]) lbl_err.pack(side=tk.LEFT) btn_remove = tk.Button(top, text=" ✕ ", font=("Tahoma", 9), fg=COLORS["text"], bg=COLORS["overlay"], activebackground=COLORS["red"], activeforeground=COLORS["text"], relief=tk.FLAT, cursor="hand2", command=self._on_remove_click) btn_remove.pack(side=tk.RIGHT) # Блок диапазонов на удаление (остальное попадёт в итоговое видео) ranges_lbl = tk.Label(inner, text="Удалить из видео (исключить эти фрагменты)", font=("Tahoma", 9), fg=COLORS["subtext"], bg=COLORS["surface"]) ranges_lbl.pack(anchor=tk.W, pady=(4, 4)) self.ranges_container = tk.Frame(inner, bg=COLORS["surface"]) self.ranges_container.pack(fill=tk.X, pady=(0, 6)) ttk.Button(inner, text="+ Добавить диапазон на удаление", command=self.add_time_range).pack(anchor=tk.W) self.columnconfigure(0, weight=1) def format_time(self, seconds): h = int(seconds // 3600) m = int((seconds % 3600) // 60) s = int(seconds % 60) return f"{h:02d}:{m:02d}:{s:02d}" def add_time_range(self, start_time=0, end_time=0): row = tk.Frame(self.ranges_container, bg=COLORS["surface"]) row.pack(fill=tk.X, pady=2) tk.Label(row, text="Удалить с", font=("Tahoma", 8), fg=COLORS["subtext"], bg=COLORS["surface"]).pack(side=tk.LEFT, padx=(0, 4)) start_var = tk.StringVar(value=self.format_time(start_time)) ent_start = tk.Entry(row, textvariable=start_var, width=10, font=("Consolas", 9), bg=COLORS["overlay"], fg=COLORS["text"], insertbackground=COLORS["text"], relief=tk.FLAT, highlightthickness=1, highlightbackground=COLORS["border"]) ent_start.pack(side=tk.LEFT, padx=(0, 12), ipady=4, ipadx=6) tk.Label(row, text="по", font=("Tahoma", 8), fg=COLORS["subtext"], bg=COLORS["surface"]).pack(side=tk.LEFT, padx=(0, 4)) end_var = tk.StringVar(value=self.format_time(end_time)) ent_end = tk.Entry(row, textvariable=end_var, width=10, font=("Consolas", 9), bg=COLORS["overlay"], fg=COLORS["text"], insertbackground=COLORS["text"], relief=tk.FLAT, highlightthickness=1, highlightbackground=COLORS["border"]) ent_end.pack(side=tk.LEFT, padx=(0, 8), ipady=4, ipadx=6) def remove_range(): row.destroy() self.time_ranges[:] = [r for r in self.time_ranges if r["frame"] != row] btn_del = tk.Button(row, text="×", font=("Tahoma", 9), fg=COLORS["subtext"], bg=COLORS["surface"], activebackground=COLORS["red"], activeforeground=COLORS["text"], relief=tk.FLAT, cursor="hand2", command=remove_range) btn_del.pack(side=tk.LEFT) self.time_ranges.append({"frame": row, "start_var": start_var, "end_var": end_var}) def get_time_ranges(self): out = [] for r in self.time_ranges: try: start = self.parse_time(r["start_var"].get()) end = self.parse_time(r["end_var"].get()) if start < end: out.append((start, end)) except (ValueError, TypeError): continue return out def parse_time(self, time_str): time_str = (time_str or "").strip() if not time_str: return 0 parts = time_str.split(":") if len(parts) == 3: return int(parts[0]) * 3600 + int(parts[1]) * 60 + float(parts[2]) if len(parts) == 2: return int(parts[0]) * 60 + float(parts[1]) return float(parts[0]) def _on_remove_click(self): if self.on_remove: self.on_remove(self) self.destroy() def get_duration(self): if self._duration > 0: return self._duration try: with VideoFileClip(self.file_path) as clip: return clip.duration except Exception: return 0