import os
import random
import json
import threading
import sys
from moviepy.editor import VideoFileClip, concatenate_videoclips, clips_array, AudioFileClip, ImageClip
from moviepy.audio.AudioClip import concatenate_audioclips
import numpy as np
import tkinter as tk
from tkinter import filedialog, scrolledtext, messagebox, ttk
# 全局参数
SCREEN_WIDTH = 1080 # 窗口的宽度
SCREEN_HEIGHT = 1920 # 窗口的高度
FPS = 30 # 固定帧率
DEFAULT_CONFIG = {
"video_folder": "",
"music_folder": "",
"display_time": 5,
"grid_display_time": 2,
"video_count": 12, # 默认视频数量
"grid_count": 3, # 默认网格数量
"current_time_value": 0, # 全屏播放起点
"mode": "标准模式", # 默认模式
"use_unified_music": 1, # 默认使用统一音乐
"mix_count": 1 # 默认混剪数量
}
CONFIG_FILE_PATH = "config.json" # 配置文件路径
# 重定向 print 输出到 Tkinter 文本框
class TextRedirector:
def __init__(self, text_widget):
self.text_space = text_widget
def write(self, str):
self.text_space.insert(tk.END, str)
self.text_space.see(tk.END)
def flush(self):
pass
def get_display_time():
"""获取显示时间并转换为浮点数"""
try:
return float(display_time_entry.get())
except:
return 0.0
def get_grid_display_time():
"""获取网格显示时间并转换为浮点数"""
try:
return float(grid_display_time_entry.get())
except:
return 0.0
def load_config():
"""读取配置文件"""
if os.path.exists(CONFIG_FILE_PATH):
with open(CONFIG_FILE_PATH, 'r', encoding='utf-8') as f:
return json.load(f)
return DEFAULT_CONFIG
def save_config(config):
"""保存配置文件"""
with open(CONFIG_FILE_PATH, 'w', encoding='utf-8') as f:
json.dump(config, f, ensure_ascii=False, indent=4)
def load_videos_from_folder(folder_path, video_count=None):
"""从指定文件夹中加载视频,根据数量选择"""
videos = []
all_videos = [os.path.join(folder_path, filename) for filename in os.listdir(folder_path)
if filename.lower().endswith((".mp4", ".mov", ".avi", ".mkv"))]
if not all_videos:
return videos
# 随机选择视频
if video_count is None or len(all_videos) <= video_count:
selected_videos = all_videos
else:
selected_videos = random.sample(all_videos, video_count)
for video_path in selected_videos:
try:
video = VideoFileClip(video_path).resize((SCREEN_WIDTH, SCREEN_HEIGHT)).set_fps(FPS)
if video.duration > 0:
videos.append(video)
print(f"成功加载视频: {os.path.basename(video_path)}")
else:
print(f"跳过无效的视频文件: {os.path.basename(video_path)}")
except Exception as e:
print(f"无法加载视频文件 {os.path.basename(video_path)}: {e}")
return videos
def get_background_music(music_folder_path, video_folder_path, videos):
"""
三级背景音乐获取策略:
1. 优先从音乐文件夹获取
2. 其次从视频文件夹获取音频文件
3. 最后从视频中提取音频
"""
# 1. 尝试音乐文件夹
if music_folder_path and os.path.exists(music_folder_path):
music_files = [f for f in os.listdir(music_folder_path)
if f.lower().endswith(('.mp3', '.wav'))]
if music_files:
selected_music = random.choice(music_files)
music_path = os.path.join(music_folder_path, selected_music)
print(f"从音乐文件夹随机选择背景音乐: {selected_music}")
return AudioFileClip(music_path)
# 2. 尝试视频文件夹中的音频文件
if video_folder_path and os.path.exists(video_folder_path):
audio_files = [f for f in os.listdir(video_folder_path)
if f.lower().endswith(('.mp3', '.wav'))]
if audio_files:
selected_audio = random.choice(audio_files)
audio_path = os.path.join(video_folder_path, selected_audio)
print(f"从视频文件夹随机选择音频: {selected_audio}")
return AudioFileClip(audio_path)
# 3. 尝试从视频中提取音频
if videos:
# 筛选有音频的视频
videos_with_audio = [v for v in videos if v.audio is not None]
if videos_with_audio:
video = random.choice(videos_with_audio)
print(f"从视频中提取音频: {os.path.basename(video.filename)}")
return video.audio
print("警告:没有找到可用的背景音乐")
return None
def create_dynamic_grid(videos, dynamic_index, grid_display_time, cols):
"""创建动态网格:指定的索引视频播放与网格展示时间一致"""
if not videos:
return None
grid_frames = []
min_duration = min(video.duration for video in videos)
# 计算有效的网格显示时间
if grid_display_time <= 0:
effective_grid_time = min_duration
else:
effective_grid_time = min(grid_display_time, min_duration)
for i in range(0, len(videos), cols):
row_clips = []
for j, video in enumerate(videos[i:i + cols]):
try:
if (i + j) == dynamic_index:
max_start_time = video.duration - effective_grid_time
if max_start_time < 0:
max_start_time = 0
start_time = random.uniform(0, max_start_time)
clip = video.subclip(start_time, start_time + effective_grid_time).resize(
(SCREEN_WIDTH // cols, SCREEN_HEIGHT // cols))
else:
frame = video.get_frame(0)
clip = ImageClip(frame).resize((SCREEN_WIDTH // cols, SCREEN_HEIGHT // cols)).set_duration(
effective_grid_time)
row_clips.append(clip)
except Exception as e:
print(f"创建网格时出错: {e}")
continue
if row_clips:
grid_frames.append(row_clips)
if not grid_frames:
return None
grid_clip = clips_array(grid_frames).resize((SCREEN_WIDTH, SCREEN_HEIGHT)).set_duration(
effective_grid_time).set_fps(FPS)
return grid_clip
def create_fullscreen_clip(video, start_time, display_time):
"""创建一个视频的全屏播放片段"""
try:
if display_time <= 0: # 显示时间为0时播放完整视频
display_time = video.duration
end_time = min(start_time + display_time, video.duration)
return video.subclip(start_time, end_time).resize((SCREEN_WIDTH, SCREEN_HEIGHT)).set_fps(FPS)
except Exception as e:
print(f"创建全屏片段出错: {e}")
return None
def standard_mode_generation(videos, output_path, config, progress_text):
"""标准模式生成:网格图 + 全屏视频交替"""
if not videos:
progress_text.insert(tk.END, "没有有效的视频,终止操作。\n")
return False
result_clips = []
background_music = None
# 处理背景音乐
if config['use_unified_music']:
background_music = get_background_music(
config['music_folder'],
config['video_folder'],
videos
)
current_time = config['current_time_value']
for index, video in enumerate(videos):
# 生成网格图
grid_clip = create_dynamic_grid(videos, index, config['grid_display_time'], config['grid_count'])
if grid_clip:
result_clips.append(grid_clip)
progress_text.insert(tk.END, f"生成网格图: {index + 1}/{len(videos)}\n")
progress_text.yview(tk.END)
# 生成全屏视频
fullscreen_clip = create_fullscreen_clip(video, current_time, config['display_time'])
if fullscreen_clip:
result_clips.append(fullscreen_clip)
current_time += config['display_time'] if config['display_time'] > 0 else video.duration
progress_text.insert(tk.END, f"生成全屏视频: {index + 1}/{len(videos)}\n")
progress_text.yview(tk.END)
if not result_clips:
progress_text.insert(tk.END, "错误:没有生成任何有效视频剪辑\n")
return False
return finalize_video(result_clips, output_path, background_music, progress_text)
def pure_grid_mode_generation(videos, output_path, config, progress_text):
"""纯网格模式生成:仅显示网格图,每个视频依次作为动态视频"""
if not videos:
progress_text.insert(tk.END, "没有有效的视频,终止操作。\n")
return False
result_clips = []
background_music = None
# 处理背景音乐
if config['use_unified_music']:
background_music = get_background_music(
config['music_folder'],
config['video_folder'],
videos
)
# 在纯网格模式中,每个视频依次作为动态视频
display_time = config['display_time']
# 显示提示信息
if display_time <= 0:
progress_text.insert(tk.END, "视频显示时间为0,将播放完整视频\n")
# 为每个视频创建一个网格片段
for index in range(len(videos)):
grid_clip = create_dynamic_grid(videos, index, 0, config['grid_count'])
if grid_clip:
result_clips.append(grid_clip)
progress_text.insert(tk.END, f"生成网格图: {index + 1}/{len(videos)}\n")
progress_text.yview(tk.END)
else:
progress_text.insert(tk.END, f"视频显示时间为{display_time}秒\n")
# 为每个视频创建一个网格片段
for index in range(len(videos)):
grid_clip = create_dynamic_grid(videos, index, display_time, config['grid_count'])
if grid_clip:
result_clips.append(grid_clip)
progress_text.insert(tk.END, f"生成网格图: {index + 1}/{len(videos)}\n")
progress_text.yview(tk.END)
if not result_clips:
progress_text.insert(tk.END, "错误:没有生成任何有效视频剪辑\n")
return False
return finalize_video(result_clips, output_path, background_music, progress_text)
def continuous_action_mode_generation(videos, output_path, config, progress_text):
"""连续动作模式生成:跳过网格,连续全屏片段"""
if not videos:
progress_text.insert(tk.END, "没有有效的视频,终止操作。\n")
return False
result_clips = []
# 处理背景音乐 - 连续动作模式必须使用背景音乐
background_music = get_background_music(
config['music_folder'],
config['video_folder'],
videos
)
# 如果没有背景音乐,使用第一个视频的音频
if not background_music and videos and videos[0].audio:
background_music = videos[0].audio
print("使用第一个视频的音频作为背景音乐")
if not background_music:
progress_text.insert(tk.END, "未找到有效的背景音乐或视频音频,终止操作。\n")
return False
# 计算所需片段数量
required_clip_count = int(background_music.duration // config['display_time']) + 1
progress_text.insert(tk.END, f"背景音乐时长: {background_music.duration:.1f}秒, 需要{required_clip_count}个片段\n")
# 扩展视频列表
if len(videos) < required_clip_count:
while len(videos) < required_clip_count:
videos.append(random.choice(videos))
progress_text.insert(tk.END, f"已扩展视频列表至{len(videos)}个视频\n")
current_time = config['current_time_value']
for i in range(required_clip_count):
video_idx = i % len(videos)
video = videos[video_idx]
# 确保当前时间不超过视频时长
if current_time >= video.duration:
current_time = 0
end_time = min(current_time + config['display_time'], video.duration)
clip = video.subclip(current_time, end_time).resize((SCREEN_WIDTH, SCREEN_HEIGHT)).set_fps(FPS)
result_clips.append(clip)
progress_text.insert(tk.END,
f"生成连续片段 {i+1}/{required_clip_count}: "
f"视频 {video_idx+1} [{current_time:.1f}-{end_time:.1f}秒]\n")
progress_text.yview(tk.END)
current_time = end_time
if current_time >= video.duration:
current_time = 0
if not result_clips:
progress_text.insert(tk.END, "错误:没有生成任何有效视频剪辑\n")
return False
return finalize_video(result_clips, output_path, background_music, progress_text)
def finalize_video(clips, output_path, background_music, progress_text):
"""最终化视频:拼接剪辑并添加音乐"""
try:
final_clip = concatenate_videoclips(clips, method="compose")
# 检测文件名是否重复,自动增加序号
base_name = os.path.splitext(output_path)[0]
ext = os.path.splitext(output_path)[1]
counter = 1
while os.path.exists(output_path):
output_path = f"{base_name}_{counter}{ext}"
counter += 1
# 处理音频
if background_music:
if background_music.duration < final_clip.duration:
repeat_count = int(final_clip.duration // background_music.duration) + 1
repeated_music_clips = [background_music] * repeat_count
background_music = concatenate_audioclips(repeated_music_clips).subclip(0, final_clip.duration)
final_clip = final_clip.set_audio(background_music)
# final_clip.write_videofile(output_path, fps=FPS, codec="libx264", audio_codec="aac", verbose=False)
final_clip.write_videofile(
output_path,
codec='h264_nvenc',
audio_codec="aac",
preset='fast',
bitrate='12M',
threads=8,
ffmpeg_params=[
'-c:v', 'h264_nvenc',
# '-vf', 'scale_cuda=1280:720', # GPU缩放滤镜
# '-vf', 'hwupload_cuda', # 显存上传
'-preset', 'fast',
'-movflags', '+faststart',
'-profile:v', 'high',
'-pix_fmt', 'yuv420p',
'-cq', '23',
'-bf', '3',
'-vsync', '1'
],
)
progress_text.insert(tk.END, f"视频生成完成: {output_path}\n")
return True
except Exception as e:
progress_text.insert(tk.END, f"视频生成失败: {str(e)}\n")
return False
finally:
for clip in clips:
if hasattr(clip, 'close'):
clip.close()
if 'final_clip' in locals():
final_clip.close()
def generate_output_video(mode, videos, output_path, config, progress_text):
"""根据选择的模式生成输出视频"""
if mode == "标准模式":
return standard_mode_generation(
videos, output_path, config, progress_text
)
elif mode == "纯网格模式":
# 在纯网格模式中,网格显示时间强制设为0
config['grid_display_time'] = 0
grid_display_time_entry.delete(0, tk.END)
grid_display_time_entry.insert(0, "0")
return pure_grid_mode_generation(
videos, output_path, config, progress_text
)
elif mode == "连续动作模式":
# 在连续动作模式下,网格显示时间强制设为0
config['grid_display_time'] = 0
grid_display_time_entry.delete(0, tk.END)
grid_display_time_entry.insert(0, "0")
return continuous_action_mode_generation(
videos, output_path, config, progress_text
)
else:
progress_text.insert(tk.END, f"未知模式: {mode}\n")
return False
def start_video_generation(config, progress_text):
"""启动视频生成过程"""
video_folder = config['video_folder']
if not video_folder or not os.path.exists(video_folder):
progress_text.insert(tk.END, "错误:视频文件夹无效\n")
return
mode = config['mode']
video_count = config['video_count'] if mode != "连续动作模式" else None
mix_count = config['mix_count']
finished_folder = os.path.join(video_folder, "成品")
os.makedirs(finished_folder, exist_ok=True)
videos = load_videos_from_folder(video_folder, video_count)
if not videos:
progress_text.insert(tk.END, "没有加载到有效视频,请检查视频文件夹。\n")
return
progress_text.delete(1.0, tk.END)
progress_text.insert(tk.END, f"开始生成视频 - 模式: {mode}\n")
if mode == "连续动作模式":
progress_text.insert(tk.END, f"视频数量: 根据音乐动态选择, 混剪数量: {mix_count}\n")
else:
progress_text.insert(tk.END, f"视频数量: {len(videos)}, 混剪数量: {mix_count}\n")
# 添加模式特定提示
if mode == "纯网格模式":
display_time = config['display_time']
if display_time <= 0:
progress_text.insert(tk.END, "提示: 视频显示时间为0,将播放完整视频\n")
else:
progress_text.insert(tk.END, f"提示: 视频显示时间为{display_time}秒\n")
for i in range(mix_count):
output_path = os.path.join(finished_folder, f"{mode}_混剪_{i+1}.mp4")
progress_text.insert(tk.END, f"\n正在生成第 {i+1}/{mix_count} 个视频...\n")
success = generate_output_video(mode, videos, output_path, config, progress_text)
if success:
progress_text.insert(tk.END, f"第 {i+1} 个视频生成成功!\n")
else:
progress_text.insert(tk.END, f"第 {i+1} 个视频生成失败!\n")
progress_text.yview(tk.END)
progress_text.insert(tk.END, "\n所有视频生成任务完成!\n")
def browse_video_folder(entry):
"""浏览视频文件夹"""
folder = filedialog.askdirectory()
if folder:
entry.delete(0, tk.END)
entry.insert(0, folder)
def browse_music_folder(entry):
"""浏览音频文件夹"""
folder = filedialog.askdirectory()
if folder:
entry.delete(0, tk.END)
entry.insert(0, folder)
def open_output_folder():
"""打开输出文件夹"""
video_folder = video_folder_entry.get()
finished_folder = os.path.join(video_folder, "成品")
if os.path.exists(finished_folder):
os.startfile(finished_folder)
else:
messagebox.showerror("错误", "成品文件夹不存在,请先生成视频。")
def start_generation_thread():
"""启动生成视频的线程"""
config = {
"video_folder": video_folder_entry.get(),
"music_folder": music_folder_entry.get(),
"display_time": get_display_time(),
"grid_display_time": get_grid_display_time(),
"video_count": int(video_count_entry.get()),
"grid_count": int(grid_count_entry.get()),
"current_time_value": float(current_time_entry.get()),
"mode": mode_combobox.get(),
"use_unified_music": use_unified_music_var.get(),
"mix_count": int(mix_count_entry.get())
}
# 验证连续动作模式下的显示时间
if config["mode"] == "连续动作模式" and config["display_time"] <= 0:
messagebox.showerror("错误", "连续动作模式需要设置大于0的视频显示时间")
return
save_config(config)
progress_text.delete(1.0, tk.END)
thread = threading.Thread(target=start_video_generation, args=(config, progress_text))
thread.daemon = True
thread.start()
def update_mode_description(mode):
"""更新模式描述标签"""
descriptions = {
"标准模式": "标准模式: 网格预览 + 全屏播放交替\n"
"• 网格显示时间: 网格中动态视频的播放时长\n"
"• 视频显示时间: 全屏视频的播放时长\n"
"• 网格数量: 网格布局的行列数",
"纯网格模式": "纯网格模式: 仅显示网格布局\n"
"• 视频显示时间: 每个视频片段的播放时长(0=完整视频)\n"
"• 网格数量: 网格布局的行列数\n"
"• 每个视频依次作为动态焦点",
"连续动作模式": "连续动作模式: 跳过网格,连续全屏片段\n"
"• 视频显示时间: 每个片段的播放时长\n"
"• 自动根据音乐时长选择片段数量\n"
"• 视频数量参数无效"
}
if mode in descriptions:
mode_description_label.config(text=descriptions[mode])
else:
mode_description_label.config(text="请选择工作模式")
def on_mode_change(event):
"""当模式改变时更新界面"""
mode = mode_combobox.get()
# 更新模式描述
update_mode_description(mode)
if mode == "连续动作模式":
# 禁用网格显示时间、视频数量和网格数量输入
grid_display_time_entry.config(state='disabled')
video_count_entry.config(state='disabled')
grid_count_entry.config(state='disabled')
# 强制设置网格显示时间为0
grid_display_time_entry.delete(0, tk.END)
grid_display_time_entry.insert(0, "0")
# 更新提示
display_time_entry_tip = "连续动作模式: 每个视频片段的播放时长\n设置为大于0的值"
create_tooltip(display_time_entry, display_time_entry_tip)
elif mode == "纯网格模式":
# 禁用网格显示时间输入
grid_display_time_entry.config(state='disabled')
# 启用视频数量和网格数量输入
video_count_entry.config(state='normal')
grid_count_entry.config(state='normal')
# 强制设置网格显示时间为0
grid_display_time_entry.delete(0, tk.END)
grid_display_time_entry.insert(0, "0")
# 更新提示
display_time_entry_tip = "纯网格模式: 每个视频片段的播放时长\n设置为0表示播放完整视频\n设置为大于0的值使用指定时长"
create_tooltip(display_time_entry, display_time_entry_tip)
else:
# 标准模式:启用所有输入
grid_display_time_entry.config(state='normal')
video_count_entry.config(state='normal')
grid_count_entry.config(state='normal')
# 恢复标准提示
display_time_entry_tip = "标准模式: 每个视频片段的播放时长\n设置为0表示播放完整视频"
create_tooltip(display_time_entry, display_time_entry_tip)
def create_tooltip(widget, text):
"""创建工具提示"""
# 如果已经存在工具提示,先销毁
if hasattr(widget, '_tooltip'):
widget._tooltip.destroy()
# 创建新工具提示
tooltip = tk.Toplevel(widget)
tooltip.wm_overrideredirect(True)
tooltip.wm_geometry("+0+0")
tooltip.withdraw()
tooltip.attributes('-topmost', True)
label = tk.Label(tooltip, text=text, background="#ffffe0", relief="solid", borderwidth=1,
padx=5, pady=2, justify=tk.LEFT)
label.pack()
# 存储引用以便后续销毁
widget._tooltip = tooltip
widget._tooltip_label = label
def enter(event):
x = widget.winfo_rootx() + widget.winfo_width() + 5
y = widget.winfo_rooty()
tooltip.wm_geometry(f"+{x}+{y}")
tooltip.deiconify()
def leave(event):
tooltip.withdraw()
widget.bind("<Enter>", enter)
widget.bind("<Leave>", leave)
def frame_main_window():
global video_folder_entry, music_folder_entry, display_time_entry, grid_display_time_entry
global video_count_entry, grid_count_entry, use_unified_music_var, progress_text
global current_time_entry, mix_count_entry, mode_combobox, grid_display_time_entry
global mode_description_label
root = tk.Tk()
root.title("视频混剪生成工具")
root.geometry("800x700") # 增加高度以容纳模式描述
# 加载配置
config = load_config()
# 创建主框架
main_frame = ttk.Frame(root, padding="10")
main_frame.pack(fill=tk.BOTH, expand=True)
# 模式选择
mode_frame = ttk.LabelFrame(main_frame, text="模式选择", padding="5")
mode_frame.grid(row=0, column=0, columnspan=3, sticky="ew", pady=5)
ttk.Label(mode_frame, text="工作模式:").grid(row=0, column=0, padx=5, pady=2, sticky='w')
mode_combobox = ttk.Combobox(mode_frame, values=["标准模式", "纯网格模式", "连续动作模式"], width=15)
mode_combobox.set(config.get("mode", "标准模式"))
mode_combobox.grid(row=0, column=1, padx=5, pady=2, sticky='w')
mode_combobox.bind("<<ComboboxSelected>>", on_mode_change)
ttk.Label(mode_frame, text="使用统一音乐:").grid(row=0, column=2, padx=5, pady=2, sticky='w')
use_unified_music_var = tk.IntVar(value=config.get("use_unified_music", 1))
ttk.Checkbutton(mode_frame, variable=use_unified_music_var).grid(row=0, column=3, padx=5, pady=2, sticky='w')
# 模式描述标签
mode_description_frame = ttk.Frame(main_frame)
mode_description_frame.grid(row=1, column=0, columnspan=3, sticky="ew", pady=5)
mode_description_label = ttk.Label(
mode_description_frame,
text="请选择工作模式",
background="#f0f0f0",
relief="solid",
borderwidth=1,
padding=5,
wraplength=700,
justify=tk.LEFT
)
mode_description_label.pack(fill=tk.BOTH, expand=True)
# 文件夹设置
folder_frame = ttk.LabelFrame(main_frame, text="文件夹设置", padding="5")
folder_frame.grid(row=2, column=0, columnspan=3, sticky="ew", pady=5)
ttk.Label(folder_frame, text="视频文件夹:").grid(row=0, column=0, padx=5, pady=2, sticky='w')
video_folder_entry = ttk.Entry(folder_frame, width=50)
video_folder_entry.insert(0, config.get("video_folder", ""))
video_folder_entry.grid(row=0, column=1, padx=5, pady=2, sticky='ew')
ttk.Button(folder_frame, text="浏览", width=10,
command=lambda: browse_video_folder(video_folder_entry)).grid(row=0, column=2, padx=5, pady=2)
ttk.Label(folder_frame, text="音乐文件夹:").grid(row=1, column=0, padx=5, pady=2, sticky='w')
music_folder_entry = ttk.Entry(folder_frame, width=50)
music_folder_entry.insert(0, config.get("music_folder", ""))
music_folder_entry.grid(row=1, column=1, padx=5, pady=2, sticky='ew')
ttk.Button(folder_frame, text="浏览", width=10,
command=lambda: browse_music_folder(music_folder_entry)).grid(row=1, column=2, padx=5, pady=2)
# 参数设置
param_frame = ttk.LabelFrame(main_frame, text="参数设置", padding="5")
param_frame.grid(row=3, column=0, columnspan=3, sticky="ew", pady=5)
ttk.Label(param_frame, text="视频显示时间 (秒):").grid(row=0, column=0, padx=5, pady=2, sticky='w')
display_time_entry = ttk.Entry(param_frame, width=10)
display_time_entry.insert(0, config.get("display_time", "5"))
display_time_entry.grid(row=0, column=1, padx=5, pady=2, sticky='w')
ttk.Label(param_frame, text="网格显示时间 (秒):").grid(row=0, column=2, padx=5, pady=2, sticky='w')
grid_display_time_entry = ttk.Entry(param_frame, width=10)
grid_display_time_entry.insert(0, config.get("grid_display_time", "2"))
grid_display_time_entry.grid(row=0, column=3, padx=5, pady=2, sticky='w')
ttk.Label(param_frame, text="全屏播放起点 (秒):").grid(row=1, column=0, padx=5, pady=2, sticky='w')
current_time_entry = ttk.Entry(param_frame, width=10)
current_time_entry.insert(0, config.get("current_time_value", "0"))
current_time_entry.grid(row=1, column=1, padx=5, pady=2, sticky='w')
ttk.Label(param_frame, text="视频数量:").grid(row=1, column=2, padx=5, pady=2, sticky='w')
video_count_entry = ttk.Entry(param_frame, width=10)
video_count_entry.insert(0, config.get("video_count", "12"))
video_count_entry.grid(row=1, column=3, padx=5, pady=2, sticky='w')
ttk.Label(param_frame, text="网格数量:").grid(row=2, column=0, padx=5, pady=2, sticky='w')
grid_count_entry = ttk.Entry(param_frame, width=10)
grid_count_entry.insert(0, config.get("grid_count", "3"))
grid_count_entry.grid(row=2, column=1, padx=5, pady=2, sticky='w')
ttk.Label(param_frame, text="混剪数量:").grid(row=2, column=2, padx=5, pady=2, sticky='w')
mix_count_entry = ttk.Entry(param_frame, width=10)
mix_count_entry.insert(0, config.get("mix_count", "1"))
mix_count_entry.grid(row=2, column=3, padx=5, pady=2, sticky='w')
# 按钮区域
button_frame = ttk.Frame(main_frame)
button_frame.grid(row=4, column=0, columnspan=3, pady=10)
ttk.Button(button_frame, text="生成视频", width=15, command=start_generation_thread).pack(side=tk.LEFT, padx=10)
ttk.Button(button_frame, text="打开输出文件夹", width=15, command=open_output_folder).pack(side=tk.LEFT, padx=10)
# 输出区域
output_frame = ttk.LabelFrame(main_frame, text="输出信息", padding="5")
output_frame.grid(row=5, column=0, columnspan=3, sticky="nsew", pady=5)
# 配置权重使输出区域可扩展
main_frame.columnconfigure(0, weight=1)
main_frame.rowconfigure(5, weight=1)
output_frame.columnconfigure(0, weight=1)
output_frame.rowconfigure(0, weight=1)
progress_text = scrolledtext.ScrolledText(output_frame, wrap=tk.WORD)
progress_text.grid(row=0, column=0, sticky="nsew")
# 重定向 print 输出到 Tkinter 文本框
redirector = TextRedirector(progress_text)
sys.stdout = redirector
# 创建初始工具提示
create_tooltip(display_time_entry, "标准模式: 每个视频片段的播放时长\n设置为0表示播放完整视频")
create_tooltip(grid_display_time_entry, "网格布局中动态视频的播放时长\n设置为0表示播放完整视频")
create_tooltip(current_time_entry, "每个视频开始播放的时间点")
create_tooltip(video_count_entry, "从文件夹中随机选择的视频数量")
create_tooltip(grid_count_entry, "网格布局的行列数(3表示3x3网格)")
create_tooltip(mix_count_entry, "生成的视频数量")
# 初始模式处理
on_mode_change(None)
return root
if __name__ == "__main__":
root = frame_main_window()
root.mainloop()
最新发布