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("", 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("", 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("", 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()