import tkinter as tk
|
from tkinter import filedialog, messagebox, ttk,Canvas
|
import os
|
import numpy as np
|
from PIL import Image, ImageTk
|
import vlc
|
import subprocess
|
import functools
|
import re
|
|
|
# Global variables
|
video_files = [] # List of video files
|
current_video_idx = 0 # Index of the current video
|
current_video_path = ""
|
video_player = None
|
thumbnail_images = []
|
split_info = []
|
|
# Create the main window
|
root = tk.Tk()
|
root.title("Video Processing Tool")
|
root.state('zoomed') # Fullscreen
|
|
screen_width = root.winfo_screenwidth()
|
screen_height = root.winfo_screenheight()
|
|
|
# Video playing frame
|
video_frame = tk.Frame(root, width=1080, height=1920)
|
video_frame.pack(side=tk.LEFT, padx=0)
|
|
# Frame for thumbnail images
|
thumbnail_frame = tk.Frame(root, width=1080, height=1920)
|
thumbnail_frame.pack_forget()
|
|
# Frame for right-side info and buttons
|
control_frame = tk.Frame(root, width=100, height=200, bg='yellow')
|
control_frame.pack(side=tk.RIGHT, anchor="ne", padx=5, pady=5)
|
|
# 线程函数来显示视频
|
|
def on_playback_ended(event):
|
# player.play()
|
video_frame.pack_forget() # 如果使用 pack()
|
global current_video_path
|
display_thumbnails(current_video_path)
|
|
|
def toggle_play_pause():
|
if player.is_playing():
|
player.pause() # 暂停播放
|
play_pause_button.config(text="播放视频") # 更新按钮文本为播放
|
else:
|
player.play() # 播放
|
play_pause_button.config(text="暂停视频") # 更新按钮文本为暂停
|
|
# 更新进度条并增加调试信息
|
|
|
def update_progress():
|
# 确保播放器正在播放
|
if player.is_playing():
|
current_time = player.get_time() / 1000 # 毫秒转为秒
|
total_time = player.get_length() / 1000 # 毫秒转为秒
|
if total_time > 0:
|
progress_percentage = (current_time / total_time) * 100
|
progress_var.set(progress_percentage)
|
progress_bar.after(50, update_progress)
|
else:
|
progress_bar.after(50, update_progress)
|
|
|
def get_thumbnail_frames(video_file):
|
# 获取同名文件夹路径
|
base_dir = os.path.dirname(video_file)
|
video_name = os.path.splitext(os.path.basename(video_file))[0]
|
thumbnail_dir = os.path.join(base_dir, video_name)
|
|
# 如果文件夹不存在,调用 bat 文件处理
|
if not os.path.exists(thumbnail_dir):
|
print(f"文件夹 {thumbnail_dir} 不存在,调用 bat 文件进行处理...")
|
subprocess.run(['mp4_generate_thumbnails.bat', video_file], check=True)
|
|
# 重新获取文件夹内的所有 jpg 文件
|
jpg_files = [os.path.join(thumbnail_dir, f) for f in os.listdir(
|
thumbnail_dir) if f.lower().endswith('.jpg')]
|
jpg_files.sort() # 按照文件名顺序排序
|
|
return jpg_files
|
|
selected_labels = [] # 记录当前选中的标签
|
thumbnails = []
|
|
def display_thumbnails(video_file):
|
global thumbnails, selected_labels
|
thumbnails = get_thumbnail_frames(video_file)
|
row, col = 0, 0
|
max_col = (screen_width - 100) // 90 - 1 # 动态地
|
|
# 创建Canvas和垂直滚动条
|
thumbnail_frame.pack(side=tk.LEFT, padx=0)
|
|
canvas = tk.Canvas(thumbnail_frame, width=screen_width - 100, height=screen_height)
|
canvas.grid(row=0, column=0, sticky='nsew')
|
|
scrollbar = ttk.Scrollbar(thumbnail_frame, orient="vertical", command=canvas.yview)
|
scrollbar.grid(row=0, column=1, sticky='ns')
|
|
canvas.configure(yscrollcommand=scrollbar.set)
|
|
# 创建一个frame作为Canvas的子项来添加缩略图
|
thumbnail_container = tk.Frame(canvas)
|
canvas.create_window((0, 0), window=thumbnail_container, anchor="nw")
|
|
# 启用鼠标滚轮事件来滚动Canvas
|
def on_mouse_wheel(event):
|
"""处理鼠标滚轮事件"""
|
canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")
|
|
# 绑定鼠标滚轮事件
|
canvas.bind_all("<MouseWheel>", on_mouse_wheel)
|
|
# 添加缩略图
|
for idx, thumb in enumerate(thumbnails):
|
# 使用PIL加载图片
|
pil_image = Image.open(thumb)
|
|
# 转换为Tkinter可显示的格式
|
photo = ImageTk.PhotoImage(pil_image)
|
|
# 创建一个标签显示图像
|
thumb_label = tk.Label(thumbnail_container, image=photo)
|
thumb_label.image = photo # 必须保持对图像的引用
|
thumb_label.grid(row=row, column=col, padx=1, pady=1)
|
|
# 将toggle_checkmark的调用用 functools.partial包装,传递idx
|
thumb_label.bind("<Button-1>", functools.partial(toggle_checkmark, label=thumb_label, idx=idx))
|
|
checkmark_label = None
|
if idx == 0: # 默认第一个图片加对号
|
checkmark_label = add_checkmark_icon(thumb_label, pil_image)
|
selected_labels.append(idx)
|
|
# 将checkmark_label保存在标签中,后续点击时可以参考
|
thumb_label.checkmark_label = checkmark_label
|
|
# 更新列数和行数
|
col += 1
|
if col >= max_col:
|
col = 0
|
row += 1
|
|
# 更新Canvas的滚动区域
|
thumbnail_container.update_idletasks()
|
canvas.config(scrollregion=canvas.bbox("all"))
|
|
def toggle_checkmark(event, label, idx):
|
"""切换勾选状态"""
|
checkmark_label = label.checkmark_label
|
# 如果已经有勾选图标,移除它;如果没有,添加它
|
if checkmark_label:
|
# 移除勾选图标
|
if idx == 0 :
|
return
|
checkmark_label.destroy()
|
label.checkmark_label = None
|
# 从选中的标签列表中移除
|
if idx in selected_labels:
|
selected_labels.remove(idx)
|
else:
|
# 添加勾选图标
|
if idx ==len(thumbnails)-1:
|
return
|
pil_image = Image.open(thumbnails[idx])
|
checkmark_label = add_checkmark_icon(label, pil_image)
|
label.checkmark_label = checkmark_label
|
selected_labels.append(idx)
|
|
#print(selected_labels)
|
|
|
def add_checkmark_icon(thumb_label, pil_image):
|
"""在图像上添加勾选图标"""
|
checkmark = Image.open("checkmark.png") # 假设你有一个勾选图标
|
checkmark = checkmark.resize((30, 30)) # 调整图标大小
|
checkmark_photo = ImageTk.PhotoImage(checkmark)
|
|
# 创建一个标签来放置勾选图标
|
checkmark_label = tk.Label(thumb_label, image=checkmark_photo, bg='white', width=30, height=30)
|
checkmark_label.image = checkmark_photo # 保持图标的引用
|
# 将勾选图标放在缩略图右上角
|
checkmark_label.place(x=pil_image.width - 15, y=5, anchor="ne") # 确保图标位置在右上角
|
|
return checkmark_label
|
|
|
def show_large_image(image):
|
# 创建一个新的窗口来显示大图
|
top = tk.Toplevel(root)
|
top.title("Large Image")
|
|
# 使用 PIL 创建 Image 对象
|
image_pil = Image.fromarray(image)
|
image_tk = ImageTk.PhotoImage(image_pil)
|
|
# 在新窗口中显示图片
|
label = tk.Label(top, image=image_tk)
|
label.image = image_tk # 保持对图片的引用
|
label.pack()
|
# 跳转到指定进度
|
|
|
def on_progress_change(event):
|
total_time = player.get_length() / 1000 # 视频总时长(秒)
|
new_time = (event.x / progress_bar.winfo_width()) * total_time # 计算新的时间
|
player.set_time(int(new_time * 1000)) # 设置新时间(毫秒)
|
|
|
player = vlc.MediaPlayer()
|
player.event_manager().event_attach(
|
vlc.EventType.MediaPlayerEndReached, on_playback_ended)
|
|
canvas = Canvas(video_frame, width=1080, height=1920) # 设置Canvas大小
|
canvas.pack(side=tk.BOTTOM, pady=10) # Video player area under progress bar
|
|
# 初始化 VLC 播放器
|
# Windows 使用 set_hwnd(embed.winfo_id()),Linux 使用 set_xwindow(embed.winfo_id())
|
player.set_hwnd(canvas.winfo_id())
|
|
|
# 视频进度条
|
progress_var = tk.DoubleVar()
|
progress_bar = ttk.Scale(video_frame, variable=progress_var,
|
from_=0, to=100, orient="horizontal", length=800)
|
progress_bar.pack(side=tk.BOTTOM, pady=10)
|
|
# 绑定进度条点击事件,跳转到指定时间
|
progress_bar.bind("<ButtonRelease-1>", on_progress_change)
|
|
update_progress()
|
# 设置主窗口
|
# Functions
|
|
output_dir = ""
|
|
def open_folder():
|
global output_dir
|
"""Open folder and get all MP4 files"""
|
folder = filedialog.askdirectory()
|
if not folder:
|
return
|
|
global video_files
|
video_files = []
|
for root_dir, dirs, files in os.walk(folder):
|
for file in files:
|
if file.lower().endswith(".mp4"):
|
video_files.append(os.path.join(root_dir, file))
|
|
if video_files:
|
messagebox.showinfo("Info", f"文件数量:{len(video_files)}")
|
|
root.title("文件夹:"+folder+ ",文件数量:"+str(len(video_files)))
|
root.update() # 刷新界面
|
load_video(video_files[0])
|
|
output_dir = os.path.join(folder, "ProcessResults")
|
create_folders(output_dir)
|
else:
|
messagebox.showerror("Error", "没有找到MP4文件")
|
|
|
def load_video(video_path):
|
"""Load video into the player and start playback"""
|
global current_video_path, video_player
|
current_video_path = video_path
|
thumbnail_frame.pack_forget()
|
video_frame.pack(side=tk.LEFT, padx=0)
|
player.set_media(vlc.Media(current_video_path))
|
player.play()
|
|
|
def delete_video():
|
"""Delete current video and play the next"""
|
global current_video_idx
|
update_title_minus_one()
|
if current_video_idx < len(video_files):
|
os.remove(video_files[current_video_idx])
|
current_video_idx += 1
|
if current_video_idx < len(video_files):
|
load_video(video_files[current_video_idx])
|
else:
|
messagebox.showinfo("Info", "处理完毕!")
|
|
|
def split_video():
|
"""Delete current video and play the next"""
|
global current_video_idx
|
update_title_minus_one()
|
if current_video_idx < len(video_files):
|
target_file = video_files[current_video_idx]
|
|
save_hook_= save_hook.get() == 1
|
save_music_= save_music.get() == 1
|
|
cut_video_by_frames(save_hook_,save_music_,target_file,selected_labels,len(thumbnails),output_dir)
|
|
current_video_idx += 1
|
if current_video_idx < len(video_files):
|
load_video(video_files[current_video_idx])
|
else:
|
messagebox.showinfo("Info", "处理完毕!")
|
|
def update_title_minus_one():
|
current_title = root.title()
|
parts = current_title.rsplit(":", 1)
|
if len(parts) == 2 and parts[1].isdigit():
|
new_count = int(parts[1]) - 1
|
new_title = f"{parts[0]}:{new_count}"
|
root.title(new_title)
|
root.update()
|
|
|
def get_filename_without_extension(video_path):
|
# 获取文件名,去除路径
|
filename_with_extension = os.path.basename(video_path)
|
# 去除扩展名
|
filename_without_extension = os.path.splitext(filename_with_extension)[0]
|
return filename_without_extension
|
|
|
def cut_video_by_frames(save_hook,save_music,video_path, frame_segments, total_frames, output_dir):
|
"""
|
将视频按照指定的帧切片进行切割。
|
|
参数:
|
- video_path: 输入视频的路径
|
- frame_segments: 切片的帧序号数组,例如:[0, M1, M2, ..., Mlast]
|
- output_dir: 输出视频文件的目录
|
"""
|
fps = 30 # default fps
|
frame_segments.sort()
|
|
# 获取视频的帧率 (fps)
|
command = [
|
"ffmpeg", "-i", video_path, "-vf", "fps=1", "-f", "null", "-"
|
]
|
result = subprocess.run(command, stdout=subprocess.PIPE,
|
stderr=subprocess.PIPE, text=True, encoding='utf-8')
|
|
if result.returncode != 0:
|
print(f"Error occurred while executing the command: {result.stderr}")
|
else:
|
output = result.stderr
|
|
if output:
|
fps_line = next(line for line in output.splitlines() if 'fps' in line)
|
# 使用正则表达式提取帧率值(假设fps值总是出现在"fps"前面)
|
match = re.search(r'(\d+(\.\d+)?)\s*fps', fps_line)
|
if match:
|
fps = float(match.group(1)) # 提取并转换为 float
|
print(f"FPS: {fps}")
|
else:
|
print("No output from ffmpeg")
|
|
|
print(f"视频的总帧数: {total_frames}, 帧率: {fps}")
|
|
file_name_witout_extension = get_filename_without_extension(video_path)
|
|
if save_music:
|
output_file = os.path.join(os.path.join(output_dir,"music"), f"{file_name_witout_extension}.wav")
|
command = ["ffmpeg", "-i", video_path, "-vn",
|
"-acodec", "pcm_s16le","-ar", "44100","-ac", "2", output_file]
|
subprocess.Popen(command)
|
|
# 遍历切片区间
|
for i in range(len(frame_segments) - 1):
|
start_frame = frame_segments[i]
|
end_frame = frame_segments[i + 1]
|
|
|
start_time = float(start_frame) / fps
|
end_time = float(end_frame) / fps
|
|
if save_hook and i==0:
|
output_file = os.path.join(os.path.join(output_dir,"hook"), f"{file_name_witout_extension}.mp4")
|
else:
|
output_file = os.path.join(os.path.join(output_dir,"slice"), f"{file_name_witout_extension}_{i + 1}.mp4")
|
|
|
# 使用 FFmpeg 命令来切割视频
|
command = [
|
"ffmpeg", "-i", video_path, "-ss", f"{start_time:.10f}", "-to", f"{end_time:.10f}",
|
"-c:v", "libx264", "-crf", "18","-preset", "veryslow", output_file
|
]
|
print(" ".join(command))
|
|
subprocess.Popen(command)
|
|
# 最后一段处理(从最后一个切片到视频结束)
|
last_start_frame = frame_segments[-1]
|
last_start_time = last_start_frame / fps
|
output_file = os.path.join(
|
os.path.join(output_dir,"slice"), f"{file_name_witout_extension}_{len(frame_segments)}.mp4")
|
|
#print(f"正在切割最后一段:从 {last_start_time:.10f}s 到视频结束,保存为 {output_file}")
|
command = [
|
"ffmpeg", "-i", video_path, "-ss", f"{last_start_time:.10f}",
|
"-c:v", "libx264","-crf", "18","-preset", "veryslow", output_file
|
]
|
print(" ".join(command))
|
subprocess.Popen(command)
|
|
def create_folders(base_folder):
|
"""Create a folder structure for saving results"""
|
hook_folder = os.path.join(base_folder, "hook")
|
music_folder = os.path.join(base_folder, "music")
|
category_folder = os.path.join(base_folder, "slice")
|
|
if not os.path.exists(hook_folder):
|
os.makedirs(hook_folder)
|
if not os.path.exists(music_folder):
|
os.makedirs(music_folder)
|
if not os.path.exists(category_folder):
|
os.makedirs(category_folder)
|
|
|
# Buttons
|
open_folder_button = tk.Button(control_frame, text="打开文件夹", command=open_folder)
|
open_folder_button.pack()
|
|
# 播放控制按钮(播放/暂停)
|
play_pause_button = tk.Button(control_frame, text="暂停视频", command=toggle_play_pause)
|
play_pause_button.pack()
|
|
delete_button = tk.Button(control_frame, text="不保留", command=delete_video)
|
delete_button.pack()
|
|
save_hook = tk.IntVar(value=1) # 默认勾选
|
save_music = tk.IntVar(value=1) # 默认勾选
|
|
# 创建两个 Checkbutton
|
checkbox1 = tk.Checkbutton(control_frame, text="保存Hook视频", variable=save_hook)
|
checkbox1.pack()
|
|
checkbox2 = tk.Checkbutton(control_frame, text="保存音频", variable=save_music)
|
checkbox2.pack()
|
|
|
|
delete_button = tk.Button(control_frame, text="拆分", command=split_video)
|
delete_button.pack()
|
|
|
menu_bar = tk.Menu(root)
|
file_menu = tk.Menu(menu_bar, tearoff=0)
|
file_menu.add_command(label="打开文件夹", command=open_folder)
|
menu_bar.add_cascade(label="文件", menu=file_menu)
|
root.config(menu=menu_bar)
|
|
#video_frame.pack_forget()
|
|
#display_thumbnails("E:\抖音-AI风景\新建文件夹\_demoold")
|
# Start the Tkinter loop
|
root.mainloop()
|