import
os
import
tkinter
import
moviepy
from
tkinter
import
Tk
from
tkinterdnd2
import
TkinterDnD, DND_FILES
import
threading
from
tkinter.ttk
import
Progressbar
from
tkinter
import
messagebox
from
datetime
import
datetime
video_files
=
[]
def
convert_video_to_gif(input_video_path, segment_duration
=
5
, fps
=
15
, resolution
=
(
640
,
480
), lock_aspect_ratio
=
True
,
progress_callback
=
None
, log_callback
=
None
, stop_event
=
None
):
try
:
video
=
moviepy.VideoFileClip(input_video_path)
video_duration
=
video.duration
video_width, video_height
=
video.size
default_resolution
=
(video_width, video_height)
segments
=
[]
start_time
=
0
while
start_time < video_duration:
if
stop_event
and
stop_event.is_set():
log_callback(
"处理已停止"
)
return
end_time
=
min
(start_time
+
segment_duration, video_duration)
segments.append((start_time, end_time))
start_time
=
end_time
if
lock_aspect_ratio:
aspect_ratio
=
video_width
/
video_height
resolution
=
(resolution[
0
],
int
(resolution[
0
]
/
aspect_ratio))
output_dir
=
"output_gifs"
os.makedirs(output_dir, exist_ok
=
True
)
base_filename
=
os.path.splitext(os.path.basename(input_video_path))[
0
]
total_segments
=
len
(segments)
for
i, (start_time, end_time)
in
enumerate
(segments):
if
stop_event
and
stop_event.is_set():
log_callback(
"处理已停止"
)
return
log_msg
=
f
"正在处理第 {i + 1} 个分段: {start_time} 秒到 {end_time} 秒"
if
log_callback:
log_callback(log_msg)
segment_clip
=
video.subclipped(start_time, end_time)
segment_clip
=
segment_clip.resized(new_size
=
resolution)
segment_clip
=
segment_clip.with_fps(fps)
output_filename
=
f
"{base_filename}_segment_{i + 1}_{int(start_time)}-{int(end_time)}.gif"
output_path
=
os.path.join(output_dir, output_filename)
segment_clip.write_gif(output_path)
log_msg
=
f
"GIF已保存为: {output_path}"
if
log_callback:
log_callback(log_msg)
if
progress_callback:
progress_callback(i
+
1
, total_segments)
log_msg
=
"转换完成!"
if
log_callback:
log_callback(log_msg)
except
Exception as e:
log_msg
=
f
"处理视频 {input_video_path} 时出错: {e}"
if
log_callback:
log_callback(log_msg)
def
on_drop(event):
global
video_files
video_files
=
event.data.split()
video_file
=
video_files[
0
]
video
=
moviepy.VideoFileClip(video_file)
video_width, video_height
=
video.size
resolution_width.delete(
0
, tkinter.END)
resolution_width.insert(tkinter.END,
str
(video_width))
resolution_height.delete(
0
, tkinter.END)
resolution_height.insert(tkinter.END,
str
(video_height))
log_callback(
"视频文件已加载,请点击开始按钮进行转换"
)
def
on_start_processing():
stop_event.clear()
if
not
video_files:
log_callback(
"请拖动视频文件到窗口中"
)
return
resolution_width_value
=
int
(resolution_width.get())
resolution_height_value
=
int
(resolution_height.get())
if
resolution_width_value <
=
0
or
resolution_height_value <
=
0
:
messagebox.showerror(
"输入错误"
,
"分辨率必须大于零"
)
return
try
:
fps_value
=
int
(fps_entry.get())
if
fps_value <
=
0
:
raise
ValueError(
"帧率必须大于零"
)
except
ValueError:
messagebox.showerror(
"输入错误"
,
"请输入有效的帧率值(整数)"
)
return
try
:
segment_duration_value
=
int
(segment_duration_entry.get())
if
segment_duration_value <
=
0
:
raise
ValueError(
"分段秒数必须大于零"
)
except
ValueError:
messagebox.showerror(
"输入错误"
,
"请输入有效的分段秒数(整数)"
)
return
lock_aspect_ratio
=
lock_aspect_ratio_var.get()
progressbar[
"value"
]
=
0
threading.Thread(target
=
process_videos,
args
=
(video_files, resolution_width_value, resolution_height_value, fps_value, lock_aspect_ratio,
segment_duration_value, stop_event)).start()
def
process_videos(video_files, resolution_width, resolution_height, fps_value, lock_aspect_ratio, segment_duration_value, stop_event):
for
video_file
in
video_files:
if
stop_event.is_set():
log_callback(
"处理已停止"
)
break
log_callback(f
"开始处理视频: {video_file}"
)
convert_video_to_gif(
video_file,
segment_duration
=
segment_duration_value,
fps
=
fps_value,
resolution
=
(resolution_width, resolution_height),
lock_aspect_ratio
=
lock_aspect_ratio,
progress_callback
=
update_progress,
log_callback
=
log_callback,
stop_event
=
stop_event
)
def
update_progress(current, total):
progressbar[
"value"
]
=
(current
/
total)
*
100
root.update_idletasks()
def
log_callback(log_msg):
log_text.config(state
=
tkinter.NORMAL)
log_text.insert(tkinter.END, log_msg
+
"\n"
)
log_text.yview(tkinter.END)
log_text.config(state
=
tkinter.DISABLED)
def
stop_processing():
stop_event.
set
()
root
=
TkinterDnD.Tk()
root.title(
"批量视频转换为GIF"
)
root.geometry(
"600x600"
)
progressbar
=
Progressbar(root, orient
=
"horizontal"
, length
=
300
, mode
=
"determinate"
)
progressbar.pack(padx
=
10
, pady
=
10
)
label
=
tkinter.Label(root, text
=
"请拖动视频文件到这里"
, width
=
40
, height
=
10
)
label.pack(padx
=
10
, pady
=
10
)
frame_controls
=
tkinter.Frame(root)
frame_controls.pack(padx
=
10
, pady
=
10
)
tkinter.Label(frame_controls, text
=
"宽度:"
).grid(row
=
0
, column
=
0
)
resolution_width
=
tkinter.Entry(frame_controls)
resolution_width.grid(row
=
0
, column
=
1
)
tkinter.Label(frame_controls, text
=
"高度:"
).grid(row
=
1
, column
=
0
)
resolution_height
=
tkinter.Entry(frame_controls)
resolution_height.grid(row
=
1
, column
=
1
)
lock_aspect_ratio_var
=
tkinter.IntVar(value
=
1
)
lock_aspect_ratio_check
=
tkinter.Checkbutton(frame_controls, text
=
"保持宽高比"
, variable
=
lock_aspect_ratio_var)
lock_aspect_ratio_check.grid(row
=
2
, column
=
0
, columnspan
=
2
)
tkinter.Label(frame_controls, text
=
"帧率:"
).grid(row
=
3
, column
=
0
)
fps_entry
=
tkinter.Entry(frame_controls)
fps_entry.grid(row
=
3
, column
=
1
)
fps_entry.insert(tkinter.END,
"15"
)
tkinter.Label(frame_controls, text
=
"分段秒数:"
).grid(row
=
4
, column
=
0
)
segment_duration_entry
=
tkinter.Entry(frame_controls)
segment_duration_entry.grid(row
=
4
, column
=
1
)
segment_duration_entry.insert(tkinter.END,
"5"
)
log_text
=
tkinter.Text(root, width
=
70
, height
=
10
)
log_text.pack(padx
=
10
, pady
=
10
)
log_text.config(state
=
tkinter.DISABLED)
button_frame
=
tkinter.Frame(root)
button_frame.pack(padx
=
10
, pady
=
10
)
start_button
=
tkinter.Button(button_frame, text
=
"开始"
, command
=
on_start_processing)
start_button.grid(row
=
0
, column
=
0
, padx
=
5
)
stop_button
=
tkinter.Button(button_frame, text
=
"停止"
, command
=
stop_processing)
stop_button.grid(row
=
0
, column
=
1
, padx
=
5
)
def
on_drop_with_default_resolution(event):
global
video_files
video_file
=
event.data.split()[
0
]
video
=
moviepy.VideoFileClip(video_file)
video_width, video_height
=
video.size
resolution_width.delete(
0
, tkinter.END)
resolution_width.insert(tkinter.END,
str
(video_width))
resolution_height.delete(
0
, tkinter.END)
resolution_height.insert(tkinter.END,
str
(video_height))
video_files.append(video_file)
log_callback(
"视频文件已加载,请点击开始按钮进行转换"
)
stop_event
=
threading.Event()
root.drop_target_register(DND_FILES)
root.dnd_bind(
'<<Drop>>'
, on_drop_with_default_resolution)
root.mainloop()