147 lines
6.4 KiB
Python
147 lines
6.4 KiB
Python
# -*- 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
|