[Python] 纯文本查看 复制代码
import base64
import concurrent.futures
import io
import json
import os
import subprocess
import threading
import tkinter as tk
from ctypes import windll
from tkinter import filedialog, messagebox, Toplevel
from PIL import Image, ImageTk
from scenedetect import open_video, SceneManager
from scenedetect.detectors import ContentDetector
from scenedetect.video_splitter import split_video_ffmpeg
# 设置应用程序的默认DPI,确保高DPI屏幕上的显示效果
windll.shcore.SetProcessDpiAwareness(2)
font = 'simhei.ttf' # 设置字体 win自带的,不用绝对路径
config_file = 'config.json'
# 多语言支持
languages = {
'zh': {
'title': '分镜探测切割 1.4',
'about': '关于',
'select_video_file': '选择视频文件',
'select_video_folder': '选择视频文件夹',
'threshold': '画面变化阈值:',
'start': '开始切割分镜',
'open_folder': '打开输出文件夹',
'readme': "2024年9月22日 by 吾爱破解:rootcup\n说明:画面变化阈值 数字越小越灵敏,切割的分镜头就越多。\n程序调用Py scenedetect,实现类似达芬奇Resolve和剪映的场景剪切探测/智能镜头分割。\n选择视频文件,是单文件操作。选择视频文件夹,是批量操作。",
'select_error': '错误',
'select_error_message': '请选择一个视频文件或文件夹.',
'threshold_error': '错误',
'threshold_error_message': '阈值必须是一个有效的数字.',
'completion_message': '视频分割已完成!分镜视频位于 {} 文件夹内。\n',
'about_info': '感谢使用本程序!\n'
},
'en': {
'title': 'Scene Detection and Splitting 1.3',
'about': 'About',
'select_video_file': 'Select Video File',
'select_video_folder': 'Select Video Folder',
'threshold': 'Scene Change Threshold:',
'start': 'Start Splitting Scenes',
'open_folder': 'Open Output Folder',
'readme': 'September 22, 2024 52pj: rootcup\n Description: The smaller and more sensitive the number of screen change threshold, the more the shot will be cut. The program calls Py scenedetect to implement scene clipping detection/smart shot segmentation similar to Da Vinci Resolve and clipping. \n Select video file, is a single file operation. Select the video folder in batches',
'select_error': 'Error',
'select_error_message': 'Please select a video file or folder.',
'threshold_error': 'Error',
'threshold_error_message': 'Threshold must be a valid number.',
'completion_message': 'Video splitting is complete! The split scenes are located in the {} folder.\n',
'about_info': 'Thank you for using this program!\n'
}
}
current_lang = 'zh' # 当前语言设置
# 加载配置
def load_config():
if os.path.exists(config_file):
with open(config_file, 'r', encoding='utf-8') as file:
config = json.load(file)
return config
return {'threshold': 27, 'last_folder': '', 'language': 'zh'}
# 保存配置
def save_config(config):
with open(config_file, 'w', encoding='utf-8') as file:
json.dump(config, file, ensure_ascii=False, indent=4)
config = load_config()
current_lang = config.get('language', 'zh')
# 设置应用程序的默认DPI,确保高DPI屏幕上的显示效果
windll.shcore.SetProcessDpiAwareness(2) # DPI_AWARENESS_PER_MONITOR_V2
font = 'simhei.ttf'
readme = languages[current_lang]['readme']
# 52logo图片的Base64编码
jpg1_base64 = '/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAFA3PEY8MlBGQUZaVVBfeMiCeG5uePWvuZHI////////////////////////////////////////////////////2wBDAVVaWnhpeOuCguv/////////////////////////////////////////////////////////////////////////wAARCAAwADADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwBZGkMxCtjngBqX9+Sfm+vzDihQvnuScdcUqoBGXG4BhjHU9fwqTYSVXQlg+QvqckU1XLZ3TMtLMd6sw3D5sYP0ohC9d3Pcbc0D6ajjhSVNwwP0NIkjeeAHLjP+eKmydxJclf7uyqyqBMgVtwyO2KBLX+v+ADttmZl9frS+blWDDrjAHFBUNM+TgAkmnGNSynnDHAXGP8aB6DJJfMGCvToc9KI2Ck8kZGMjqKV1QqxQY2nHXrSRIHPzBueAR0pD0sOBRWDGRmI56U1DunBx1PTNSCNQASmBjJLH9O1RopSZdwxyOlMWhKozcSAE9Oo69qdIQGbcSACP5HpUbJIkjYXIbI/OhjMc/IQSc5UH6UE2uJMPkXBbG0H2ogIIKksM85Hakk85gNytgcUKrqmPLPzHr/SgroSOELgtg56Dd/gKjzm4GeMHGB7U/dKPlERx+J/WkSF/OB24AOetAlof/9k='
# 创建主窗口
root = tk.Tk()
root.title(languages[current_lang]['title'])
root.geometry('550x600')
root.configure(bg='#f0f0f0') # 设置背景色
# 创建一个Frame用于放置关于按钮
top_frame = tk.Frame(root, bg='#f0f0f0')
top_frame.pack(side=tk.TOP, fill=tk.X)
# 在右上角添加关于按钮
btn_about = tk.Button(top_frame, text=languages[current_lang]['about'], fg='black', font=(font, 10, 'bold'),
relief=tk.FLAT, command=lambda: show_about_window())
btn_about.pack(side=tk.RIGHT, padx=10, pady=10)
# 创建语言切换按钮
language_menu = tk.Menubutton(top_frame, text="Language", fg='black', font=(font, 10, 'bold'), relief=tk.FLAT)
language_menu.menu = tk.Menu(language_menu, tearoff=0)
language_menu["menu"] = language_menu.menu
language_menu.menu.add_command(label="中文", command=lambda: change_language('zh'))
language_menu.menu.add_command(label="English", command=lambda: change_language('en'))
language_menu.pack(side=tk.RIGHT, padx=10, pady=10)
# 更新语言
def update_language():
root.title(languages[current_lang]['title'])
btn_about.config(text=languages[current_lang]['about'])
btn_select_file.config(text=languages[current_lang]['select_video_file'])
btn_select_folder.config(text=languages[current_lang]['select_video_folder'])
label_threshold.config(text=languages[current_lang]['threshold'])
btn_start.config(text=languages[current_lang]['start'])
btn_open_folder.config(text=languages[current_lang]['open_folder'])
text_video_paths.delete(1.0, tk.END)
text_video_paths.insert(tk.END, languages[current_lang]['readme'], "tag_1")
# 启动按钮和打开输出文件夹按钮放在同一行的 Frame
button0_frame = tk.Frame(root, bg='#f0f0f0')
button0_frame.pack(pady=20)
# 选择文件按钮
btn_select_file = tk.Button(button0_frame, text=languages[current_lang]['select_video_file'], bg='#4CAF50', fg='white',
font=(font, 12, 'bold'),
relief=tk.FLAT, command=lambda: select_file())
btn_select_file.pack(pady=20, side=tk.LEFT, padx=10)
# 选择文件夹按钮
btn_select_folder = tk.Button(button0_frame, text=languages[current_lang]['select_video_folder'], bg='#4CAF50',
fg='white', font=(font, 12, 'bold'),
relief=tk.FLAT, command=lambda: select_folder())
btn_select_folder.pack(pady=10, side=tk.RIGHT, padx=10)
# 设置阈值标签和输入框
label_threshold = tk.Label(root, text=languages[current_lang]['threshold'], bg='#f0f0f0', font=(font, 10))
label_threshold.pack()
entry_threshold = tk.Entry(root, width=17)
entry_threshold.insert(0, str(config.get('threshold', 27)))
entry_threshold.pack(pady=5)
# 启动按钮和打开输出文件夹按钮放在同一行的 Frame
button_frame = tk.Frame(root, bg='#f0f0f0')
button_frame.pack(pady=20)
# 启动按钮
btn_start = tk.Button(button_frame, text=languages[current_lang]['start'], bg='#2196F3', fg='white',
font=(font, 12, 'bold'),
relief=tk.FLAT, command=lambda: start_detection())
btn_start.pack(side=tk.LEFT, padx=10)
# 打开输出文件夹按钮
btn_open_folder = tk.Button(button_frame, text=languages[current_lang]['open_folder'], bg='#FFC107', fg='white',
font=(font, 12, 'bold'),
relief=tk.FLAT, command=lambda: open_output_folder())
btn_open_folder.pack(side=tk.LEFT, padx=10)
# 视频路径显示文本框
text_video_paths = tk.Text(root, height=4000, width=6000, bg='#f0f0f0', font=(font, 10))
text_video_paths.pack(padx=5, pady=10)
text_video_paths.tag_config("tag_1", foreground="green") # 设置文本颜色为绿色
text_video_paths.tag_config("tag_2", foreground="blue")
text_video_paths.insert(tk.END, readme, "tag_1")
# 全局变量
selected_folder = config.get('last_folder', '')
video_files = []
# 函数定义
def select_file():
global video_files
video_files = [filedialog.askopenfilename(filetypes=[("MP4文件", "*.mp4;*.MP4")])]
if video_files:
update_video_paths()
def select_folder():
global selected_folder, video_files
selected_folder = filedialog.askdirectory()
if selected_folder:
config['last_folder'] = selected_folder
save_config(config)
video_files = [os.path.join(selected_folder, f) for f in os.listdir(selected_folder) if
f.lower().endswith('.mp4')]
update_video_paths()
def update_video_paths():
text_video_paths.delete(1.0, tk.END)
for video_file in video_files:
text_video_paths.insert(tk.END, video_file + '\n')
def start_detection():
if not video_files:
messagebox.showerror(languages[current_lang]['select_error'], languages[current_lang]['select_error_message'])
return
try:
threshold = float(entry_threshold.get())
config['threshold'] = threshold
save_config(config)
except ValueError:
messagebox.showerror(languages[current_lang]['threshold_error'],
languages[current_lang]['threshold_error_message'])
return
detection_thread = threading.Thread(target=run_detection, args=(threshold,))
detection_thread.start()
def run_detection(threshold):
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
futures = [executor.submit(split_video_into_scenes, video_path, threshold) for video_path in video_files]
for future in concurrent.futures.as_completed(futures):
try:
future.result()
except Exception as e:
messagebox.showerror(languages[current_lang]['select_error'],
f"{languages[current_lang]['select_error_message']} {str(e)}")
text_video_paths.insert(tk.END, '已处理完成全部\n', "tag_1")
def split_video_into_scenes(video_path, threshold=27.0):
base_filename = os.path.splitext(os.path.basename(video_path))[0]
save_path = f'./分镜切割/{base_filename}/'
if not os.path.exists(save_path):
os.makedirs(save_path)
output_file_template = f'{save_path}Scene-$SCENE_NUMBER.mp4'
video = open_video(video_path)
scene_manager = SceneManager()
scene_manager.add_detector(ContentDetector(threshold=threshold))
scene_manager.detect_scenes(video, show_progress=True)
scene_list = scene_manager.get_scene_list()
split_video_ffmpeg(video_path, scene_list, output_file_template=output_file_template, show_progress=True,
show_output=True)
update_completion_message(save_path)
def update_completion_message(save_path):
message = languages[current_lang]['completion_message'].format(save_path)
text_video_paths.insert(tk.END, message, 'tag_2')
def open_output_folder():
if video_files:
base_filename = os.path.splitext(os.path.basename(video_files[0]))[0]
save_path = f'./分镜切割/{base_filename}/'
if os.path.exists(save_path):
subprocess.Popen(f'explorer "{os.path.abspath(save_path)}"')
def show_about_window():
about_window = Toplevel(root)
about_window.title(languages[current_lang]['about'])
about_window.geometry("300x300")
# 图片和介绍
info_label = tk.Label(about_window, text=languages[current_lang]['about_info'], font=(font, 12))
info_label.pack(pady=10)
# 显示第一张图片
img1_data = base64.b64decode(jpg1_base64)
img1 = Image.open(io.BytesIO(img1_data))
img1_tk = ImageTk.PhotoImage(img1)
img1_label = tk.Label(about_window, image=img1_tk)
img1_label.image = img1_tk
img1_label.pack(pady=5)
# 切换语言
def change_language(lang):
global current_lang
current_lang = lang
config['language'] = current_lang
save_config(config)
update_language()
# 初始化语言
update_language()
# 运行主循环
root.mainloop()