Ryanus commited on
Commit
0663f9d
·
verified ·
1 Parent(s): e9b75fa

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +312 -404
app.py CHANGED
@@ -5,491 +5,399 @@ import random
5
  import subprocess
6
  import shutil
7
  import zipfile
8
- import threading
9
- import concurrent.futures
10
  from datetime import datetime
11
- import time
12
 
13
- class FastVideoProcessor:
14
- def __init__(self, max_processing_time=30):
15
- self.max_processing_time = max_processing_time
16
- self.start_time = None
 
 
 
 
 
 
 
 
 
17
 
18
- def check_time_limit(self):
19
- """检查是否超过时间限制"""
20
- if self.start_time and time.time() - self.start_time > self.max_processing_time:
 
 
 
21
  return True
 
 
 
 
 
 
22
  return False
23
 
24
- def ffmpeg_cut_video_fast(self, input_path, start_time, duration, output_path):
25
- """高速剪切视频片段"""
26
- command = [
27
- 'ffmpeg',
28
- '-ss', str(start_time),
29
- '-i', input_path,
30
- '-t', str(duration),
31
- '-c:v', 'libx264', # H264编码器
32
- '-preset', 'ultrafast', # 最快编码预设
33
- '-crf', '20', # 高质量(18-24范围)
34
- '-c:a', 'aac',
35
- '-b:a', '128k',
36
- '-threads', '0', # 使用所有CPU核心
37
- '-y',
38
- output_path
39
- ]
40
- process = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
41
- return process.returncode == 0
 
 
 
 
 
 
 
 
 
 
 
 
42
 
43
- def ffmpeg_resize_video_fast(self, input_path, output_path, target_ratio):
44
- """高速调整视频比例"""
45
- if target_ratio == '9:16':
46
- scale_filter = "scale=1080:1920:force_original_aspect_ratio=decrease,pad=1080:1920:(ow-iw)/2:(oh-ih)/2"
47
- else:
48
- scale_filter = "scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  command = [
51
  'ffmpeg',
52
- '-i', input_path,
53
- '-vf', scale_filter,
54
- '-c:v', 'libx264',
55
- '-preset', 'ultrafast',
56
- '-crf', '20',
57
- '-c:a', 'copy', # 音频直接复制,不重编码
58
- '-threads', '0',
59
  '-y',
60
  output_path
61
  ]
62
 
63
- process = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
64
- return process.returncode == 0
65
-
66
- def concat_videos_fast(self, file_list, output_path):
67
- """高速合并视频"""
68
- list_file = tempfile.NamedTemporaryFile(delete=False, mode='w', suffix='.txt')
 
 
 
 
 
 
 
 
69
  try:
70
- for f in file_list:
71
- list_file.write("file '{}'\n".format(f.replace("'", r"'\''")))
72
- list_file.close()
73
-
74
- command = [
75
- 'ffmpeg',
76
- '-f', 'concat',
77
- '-safe', '0',
78
- '-i', list_file.name,
79
- '-c', 'copy', # 直接复制,无重编码
80
- '-threads', '0',
81
- '-y',
82
- output_path
83
- ]
84
- process = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
85
- return process.returncode == 0
86
- finally:
87
  os.unlink(list_file.name)
 
 
88
 
89
- def extract_clips_parallel(self, video_files, clip_duration):
90
- """并行提取视频片段 - 无限制版本"""
91
- self.start_time = time.time()
92
- clips = []
93
- temp_dir = tempfile.mkdtemp()
 
 
 
 
 
 
 
 
94
 
95
- def process_single_video(args):
96
- idx, video_file = args
97
- video_clips = []
 
 
98
 
 
99
  try:
100
- video_path = video_file.name
101
-
102
- # 快速获取视频时长
103
- cmd_duration = [
104
- 'ffprobe', '-v', 'quiet',
105
  '-show_entries', 'format=duration',
106
  '-of', 'default=noprint_wrappers=1:nokey=1',
107
  video_path
108
  ]
109
- result = subprocess.run(cmd_duration, capture_output=True, text=True, timeout=10)
110
  total_duration = float(result.stdout.strip())
 
 
 
 
 
 
 
 
 
 
 
111
 
112
- # 完全按用户设定的时长切片,不限制数量
113
- count = 0
114
- start = 0.0
115
-
116
- while start < total_duration:
117
- if self.check_time_limit():
118
- break
119
-
120
- duration = min(clip_duration, total_duration - start)
121
- clip_path = os.path.join(temp_dir, f"clip_{idx}_{count}.mp4")
122
-
123
- success = self.ffmpeg_cut_video_fast(video_path, start, duration, clip_path)
124
- if success:
125
- video_clips.append(clip_path)
126
-
127
- start += clip_duration # 按用户设定的时长连续切片
128
- count += 1
129
-
130
- return video_clips
131
 
132
- except Exception as e:
133
- print(f"处理视频 {idx} 出错: {e}")
134
- return []
135
 
136
- # 并行处理所有视频
137
- with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
138
- video_args = [(idx, vf) for idx, vf in enumerate(video_files)]
139
- results = executor.map(process_single_video, video_args)
140
-
141
- for video_clips in results:
142
- clips.extend(video_clips)
143
 
144
- return clips, temp_dir
145
-
146
- def generate_mixed_videos_fast(self, clips, num_output_videos, target_ratio):
147
- """高速生成混剪视频 - 无限制版本"""
148
- temp_dir = tempfile.mkdtemp()
149
- random.shuffle(clips)
150
 
151
- # 按用户要求的数量分配片段,不限制
152
- clips_per_video = max(1, len(clips) // num_output_videos) if num_output_videos > 0 else len(clips)
153
- output_files = []
154
 
155
- def create_single_mixed_video(args):
156
- i, selected_clips = args
157
-
158
- try:
159
- if self.check_time_limit():
160
- return None
161
-
162
- # 先快速合并
163
- temp_video_path = os.path.join(temp_dir, f"temp_mixed_{i+1}.mp4")
164
- success = self.concat_videos_fast(selected_clips, temp_video_path)
165
-
166
- if not success:
167
- return None
168
-
169
- # 再调整比例
170
- timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
171
- output_path = os.path.join(temp_dir, f"混剪视频_{target_ratio.replace(':', 'x')}_{i+1}_{timestamp}.mp4")
172
-
173
- success = self.ffmpeg_resize_video_fast(temp_video_path, output_path, target_ratio)
174
-
175
- if success:
176
- return output_path
177
- return None
178
-
179
- except Exception as e:
180
- print(f"生成混剪视频 {i+1} 出错: {e}")
181
- return None
182
 
183
- # 准备任务 - 支持任意数量
184
- tasks = []
185
  for i in range(num_output_videos):
 
 
186
  start_idx = i * clips_per_video
187
- end_idx = len(clips) if i == num_output_videos - 1 else (start_idx + clips_per_video)
188
- selected_clips = clips[start_idx:end_idx]
189
 
190
- if selected_clips:
191
- tasks.append((i, selected_clips))
192
-
193
- # 并行生成 - 支持更多线程处理大量视频
194
- max_workers = min(8, num_output_videos) # 最多8个线程
195
- with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
196
- results = executor.map(create_single_mixed_video, tasks)
197
 
198
- for result in results:
199
- if result:
200
- output_files.append(result)
201
-
202
- return output_files, temp_dir
203
-
204
- processor = FastVideoProcessor()
205
-
206
- def create_video_package_fast(output_files, original_file_names, target_ratio, processing_time, clip_duration, num_output):
207
- """快速打包视频文件"""
208
- package_dir = tempfile.mkdtemp()
209
- zip_path = os.path.join(package_dir, f"混剪视频包_{target_ratio.replace(':', 'x')}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.zip")
210
-
211
- try:
212
- with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED, compresslevel=1) as zipf: # 最低压缩级别
213
- for video_file in output_files:
214
- arcname = os.path.basename(video_file)
215
- zipf.write(video_file, arcname)
216
 
217
- readme_content = f"""# 无限制高速混剪视频包
218
-
219
- ## 处理信息
220
- - 生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
221
- - 处理耗时: {processing_time:.1f} 秒 ⚡
222
- - 视频数量: {len(output_files)} 个
223
- - 视频比例: {target_ratio}
224
- - 切片时长: {clip_duration} 秒
225
- - 输出数量: {num_output} 个
226
-
227
- ## 🚀 无限制特性
228
- - ✅ 切片时长: 用户自定义,无限制
229
- - ✅ 生成数量: 用户自定义,无限制
230
- - ✅ 文件数量: 支持批量处理,无限制
231
- - ✅ 视频长度: 支持任意长度,无限制
232
- - ✅ 并行处理: 最多8线程同时工作
233
- - ✅ 高速编码: ultrafast + CRF 20
234
-
235
- ## 📁 文件列表
236
- """
237
 
238
- for i, video_file in enumerate(output_files, 1):
239
- file_size = os.path.getsize(video_file) / (1024 * 1024)
240
- readme_content += f"- 混剪视频_{i}.mp4 ({file_size:.1f}MB)\n"
241
 
242
- readme_content += f"""
243
- ## 🎬 质量保证
244
- - CRF 20: 高质量编码
245
- - 原始音频: 保持音质
246
- - 智能填充: 完美适配比例
247
- - 多核加速: 充分利用CPU
248
-
249
- ## 📱 使用建议
250
- - 适合任意平台发布
251
- - 可进一步编辑处理
252
- - 支持二次剪辑
253
- - 兼容所有播放器
254
-
255
- ---
256
- 🚀 无限制高速混剪工具 - 自由度最大化
257
- """
258
 
259
- zipf.writestr("README.txt", readme_content.encode('utf-8'))
260
-
261
- return zip_path
262
-
263
- except Exception as e:
264
- shutil.rmtree(package_dir, ignore_errors=True)
265
- raise e
266
-
267
- def process_and_package_unlimited(video_files, clip_duration, num_output_videos, target_ratio):
268
- if not video_files or len(video_files) == 0:
269
- return "❌ 请上传至少一个视频文件", None, ""
270
-
271
- start_time = time.time()
272
-
273
- try:
274
- original_names = [os.path.basename(f.name) for f in video_files]
275
-
276
- # 1. 高速提取片段 - 无限制
277
- clips, clips_temp_dir = processor.extract_clips_parallel(video_files, clip_duration)
278
-
279
- if not clips:
280
- return "❌ 未能提取到有效片段", None, ""
281
-
282
- # 2. 高速生成混剪视频 - 无限制
283
- output_files, output_temp_dir = processor.generate_mixed_videos_fast(clips, num_output_videos, target_ratio)
284
-
285
- # 清理切片临时目录
286
- shutil.rmtree(clips_temp_dir, ignore_errors=True)
287
 
288
  if not output_files:
289
- return "❌ 未能生成混剪视频", None, ""
290
-
291
- # 3. 快速打包
292
- processing_time = time.time() - start_time
293
- zip_file_path = create_video_package_fast(output_files, original_names, target_ratio, processing_time, clip_duration, num_output_videos)
294
-
295
- total_size = os.path.getsize(zip_file_path) / (1024 * 1024)
296
 
297
- platform_info = "📱 短视频平台" if target_ratio == '9:16' else "🖥️ 长视频平台"
 
 
 
 
298
 
299
- status_msg = f"""🚀 无限制处理完成!
300
-
301
- 📊 **处理统计:**
302
- 处理时间: {processing_time:.1f} 秒
303
- 🎬 混剪视频: {len(output_files)} 个
304
- 📐 视频比例: {target_ratio}
305
- • ⏱️ 切片时长: {clip_duration} 秒
306
- 🎯 适合平台: {platform_info}
307
- 📦 文件大小: {total_size:.1f}MB
308
 
309
- 🔥 **无限制优势:**
310
- 任意切片时长设定
311
- 任意输出数量设定
312
- 支持大量文件批处理
313
- • 最多8线程并行处理
314
- • 30秒内完成处理
315
 
316
- 💾 **立即下载:**
317
- 点击下方按钮保存到本地
318
  """
319
-
320
- details = f"""🚀 **无限制处理详情:**
 
 
 
 
 
 
 
321
 
322
- 🎬 **视频信息:**
323
- """
324
- for i, video_file in enumerate(output_files, 1):
325
- file_size = os.path.getsize(video_file) / (1024 * 1024)
326
- details += f"• 混剪视频_{i}: {file_size:.1f}MB\n"
327
-
328
- details += f"""
329
- ⚡ **处理参数:**
330
- 切片时长: {clip_duration}秒 (用户自定义)
331
- • 输出数量: {num_output_videos}个 (用户自定义)
332
- • 处理时间: {processing_time:.1f}秒
333
- • 总片段数: {len(clips)}个
334
- • 并行线程: {min(8, num_output_videos)}个
335
 
336
- 🔥 **无限制特性:**
337
- 切片时长: 1秒-3600秒,完全自由
338
- 输出数量: 1个-100个,随意设置
339
- • ✅ 文件处理: 支持大量视频批处理
340
- • ✅ 视频长度: 支持超长视频处理
341
- • ✅ 质量保证: 高速处理不牺牲品质
342
 
343
- 🎯 **适用场景:**
344
  """
345
-
346
- if target_ratio == '9:16':
347
- details += """抖音、快手批量内容制作
348
- • 小红书多样化内容输出
349
- Instagram Stories系列发布
350
- TikTok内容矩阵建设"""
351
- else:
352
- details += """• YouTube频道批量更新
353
- B站UP主内容制作
354
- 长视频平台系列发布
355
- 教育培训批量制作"""
356
-
357
- shutil.rmtree(output_temp_dir, ignore_errors=True)
358
-
359
- return status_msg, zip_file_path, details
360
-
361
  except Exception as e:
362
- processing_time = time.time() - start_time
363
- return f" 处理失败 (耗时{processing_time:.1f}秒): {str(e)}", None, ""
 
 
 
 
 
 
 
 
 
 
364
 
365
  def main():
366
- with gr.Blocks(
367
- title="🚀无限制FFmpeg剪辑工具",
368
- theme=gr.themes.Soft()
369
- ) as demo:
370
 
371
  gr.HTML("""
372
- <div style="text-align: center; padding: 20px; background: linear-gradient(135deg, #FF6B6B 0%, #4ECDC4 100%); border-radius: 15px; color: white; margin-bottom: 20px;">
373
- <h1>🚀 无���制 FFmpeg 自动剪辑工具</h1>
374
- <p style="margin: 10px 0 0 0; font-size: 18px;">🔥 完全自由 → 无任何限制 → 批量处理 → 高速完成</p>
375
  </div>
376
  """)
377
 
378
  gr.Markdown("""
379
- ### 🔥 无限制特性
380
- - 🚀 **切片时长**: 1秒-3600秒,完全自由设定
381
- - 🎬 **输出数量**: 1个-100个,任意数量生成
382
- - 📁 **文件数量**: 支持大量视频批处理
383
- - ⚡ **处理速度**: 30秒内完成,多线程并行
384
- - 💎 **品质保证**: CRF 20高质量,不妥协
385
  """)
386
 
387
  with gr.Row():
388
- with gr.Column(scale=2):
389
  video_input = gr.File(
390
- label="📤 上传视频文件 (支持批量,无数量限制)",
391
- file_types=[".mp4", ".mov", ".avi", ".mkv"],
392
- file_count="multiple",
393
- height=120
394
  )
395
 
396
  with gr.Row():
397
- clip_duration = gr.Number(
398
- value=3,
399
- label="切片时长(秒)",
400
- minimum=1,
401
- maximum=3600,
402
- info="🔥 完全无限制:1-3600秒任意设置"
403
- )
404
- num_output = gr.Number(
405
- value=3,
406
- label="生成数量",
407
- minimum=1,
408
- maximum=100,
409
- info="🎬 无限制:1-100个随意生成"
410
- )
411
 
412
- ratio_selection = gr.Radio(
413
- choices=["9:16", "16:9"],
414
- value="9:16",
415
- label="📐 视频比例",
416
- info="9:16抖音快手 | 16:9 YouTube B站"
417
- )
418
 
419
- process_btn = gr.Button(
420
- "🚀 无限制高速处理",
421
- variant="primary",
422
- size="lg"
423
- )
424
-
425
- with gr.Column(scale=1):
426
- status_output = gr.Textbox(
427
- label="📊 处理状态",
428
- lines=15,
429
- interactive=False,
430
- show_copy_button=True
431
- )
432
 
433
  with gr.Row():
434
- with gr.Column(scale=1):
435
- download_file = gr.File(
436
- label="🚀 无限制下载视频包",
437
- interactive=False
438
- )
439
 
440
- with gr.Column(scale=1):
441
- details_output = gr.Textbox(
442
- label="📝 处理详情",
443
- lines=20,
444
- interactive=False,
445
- show_copy_button=True
446
- )
447
 
448
  process_btn.click(
449
- fn=process_and_package_unlimited,
450
- inputs=[video_input, clip_duration, num_output, ratio_selection],
451
- outputs=[status_output, download_file, details_output]
452
  )
453
 
454
  gr.Markdown("""
455
  ---
456
- ### 🚀 无限制使用指南
457
-
458
- **🔥 完全自由设定:**
459
- - **切片时长**: 1秒到1小时(3600秒),完全由你决定
460
- - **输出数量**: 1个到100个混剪视频,批量生成
461
- - **文件处理**: 支持同时处理大量视频文件
462
- - **视频长度**: 支持任意长度的源视频
463
-
464
- **⚡ 高速处理保证:**
465
- - **并行切片**: 最多4线程同时切割视频
466
- - **并行生成**: 最多8线程同时生成混剪
467
- - **ultrafast编码**: FFmpeg最快预设
468
- - **智能优化**: 30秒内完成所有处理
469
-
470
- **💎 品质不妥协:**
471
- - **CRF 20编码**: 视觉无损高质量
472
- - **原音保持**: 音频零损失处理
473
- - **完美比例**: 智能适配目标尺寸
474
- - **兼容性**: H.264编码,全平台支持
475
 
476
- **🎯 适用场景:**
477
- - **内容创作者**: 批量制作短视频/长视频
478
- - **营销团队**: 大量素材快速处理
479
- - **教育机构**: 课程内容批量剪辑
480
- - **自媒体**: 多平台内容矩阵建设
481
 
482
- **📊 处理能力:**
483
- - **单次处理**: 最多100个混剪视频
484
- - **文件支持**: MP4、MOV、AVI、MKV
485
- - **系统要求**: 需安装FFmpeg
486
- - **推荐配置**: 多核CPU,8GB+内存
487
 
488
- **💡 使用技巧:**
489
- - 切片时长越短,混剪节奏越快
490
- - 生成数量越多,内容变化越丰富
491
- - 建议先小批量测试,再大量处理
492
- - CPU核心越多,处理速度越快
493
  """)
494
 
495
  demo.launch()
 
5
  import subprocess
6
  import shutil
7
  import zipfile
 
 
8
  from datetime import datetime
9
+ import traceback
10
 
11
+ def run_ffmpeg_with_debug(command, operation_name):
12
+ """运行FFmpeg命令并提供详细调试信息"""
13
+ try:
14
+ print(f"🔧 执行 {operation_name}...")
15
+ print(f"📝 命令: {' '.join(command)}")
16
+
17
+ result = subprocess.run(
18
+ command,
19
+ stdout=subprocess.PIPE,
20
+ stderr=subprocess.PIPE,
21
+ text=True,
22
+ timeout=60 # 60秒超时
23
+ )
24
 
25
+ if result.returncode != 0:
26
+ print(f"❌ {operation_name} 失败")
27
+ print(f"错误输出: {result.stderr}")
28
+ return False
29
+ else:
30
+ print(f"✅ {operation_name} 成功")
31
  return True
32
+
33
+ except subprocess.TimeoutExpired:
34
+ print(f"⏰ {operation_name} 超时")
35
+ return False
36
+ except Exception as e:
37
+ print(f"💥 {operation_name} 异常: {str(e)}")
38
  return False
39
 
40
+ def ffmpeg_cut_video_safe(input_path, start_time, duration, output_path):
41
+ """安全的视频切割,使用兼容性最好的参数"""
42
+ if not os.path.exists(input_path):
43
+ print(f"❌ 输入文件不存在: {input_path}")
44
+ return False
45
+
46
+ command = [
47
+ 'ffmpeg',
48
+ '-i', input_path,
49
+ '-ss', str(start_time),
50
+ '-t', str(duration),
51
+ '-c:v', 'libx264',
52
+ '-preset', 'medium', # 平衡速度和质量
53
+ '-crf', '23', # 适中的质量
54
+ '-c:a', 'aac',
55
+ '-b:a', '128k',
56
+ '-avoid_negative_ts', 'make_zero',
57
+ '-y',
58
+ output_path
59
+ ]
60
+
61
+ success = run_ffmpeg_with_debug(command, f"切割视频 ({os.path.basename(input_path)})")
62
+
63
+ if success and os.path.exists(output_path):
64
+ file_size = os.path.getsize(output_path) / (1024 * 1024)
65
+ print(f"📁 生成文件: {os.path.basename(output_path)} ({file_size:.1f}MB)")
66
+ return True
67
+ else:
68
+ print(f"❌ 切割后文件不存在: {output_path}")
69
+ return False
70
 
71
+ def ffmpeg_resize_video_safe(input_path, output_path, target_ratio):
72
+ """安全的视频比例调整"""
73
+ if not os.path.exists(input_path):
74
+ print(f"❌ 输入文件不存在: {input_path}")
75
+ return False
76
+
77
+ # 简化的比例调整,使用pad而不是crop
78
+ if target_ratio == '9:16':
79
+ filter_complex = "scale=1080:1920:force_original_aspect_ratio=decrease,pad=1080:1920:(ow-iw)/2:(oh-ih)/2:black"
80
+ else:
81
+ filter_complex = "scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2:black"
82
+
83
+ command = [
84
+ 'ffmpeg',
85
+ '-i', input_path,
86
+ '-vf', filter_complex,
87
+ '-c:v', 'libx264',
88
+ '-preset', 'medium',
89
+ '-crf', '23',
90
+ '-c:a', 'copy', # 音频直接复制
91
+ '-y',
92
+ output_path
93
+ ]
94
+
95
+ success = run_ffmpeg_with_debug(command, f"调整比例 ({target_ratio})")
96
+
97
+ if success and os.path.exists(output_path):
98
+ file_size = os.path.getsize(output_path) / (1024 * 1024)
99
+ print(f"📁 调整后文件: {os.path.basename(output_path)} ({file_size:.1f}MB)")
100
+ return True
101
+ else:
102
+ print(f"❌ 比例调整后文件不存在: {output_path}")
103
+ return False
104
 
105
+ def concat_videos_safe(file_list, output_path):
106
+ """安全的视频合并"""
107
+ if not file_list:
108
+ print("❌ 没有文件需要合并")
109
+ return False
110
+
111
+ # 检查所有输入文件是否存在
112
+ valid_files = []
113
+ for f in file_list:
114
+ if os.path.exists(f):
115
+ valid_files.append(f)
116
+ print(f"✅ 文件存在: {os.path.basename(f)}")
117
+ else:
118
+ print(f"❌ 文件不存在: {f}")
119
+
120
+ if not valid_files:
121
+ print("❌ 没有有效的输入文件")
122
+ return False
123
+
124
+ # 创建文件列表
125
+ list_file = tempfile.NamedTemporaryFile(delete=False, mode='w', suffix='.txt', encoding='utf-8')
126
+ try:
127
+ for f in valid_files:
128
+ # 使用绝对路径,避免路径问题
129
+ abs_path = os.path.abspath(f)
130
+ list_file.write(f"file '{abs_path}'\n")
131
+ list_file.close()
132
+
133
+ print(f"📝 文件列表: {list_file.name}")
134
+ with open(list_file.name, 'r', encoding='utf-8') as lf:
135
+ print("📋 合并列表内容:")
136
+ print(lf.read())
137
+
138
  command = [
139
  'ffmpeg',
140
+ '-f', 'concat',
141
+ '-safe', '0',
142
+ '-i', list_file.name,
143
+ '-c', 'copy',
 
 
 
144
  '-y',
145
  output_path
146
  ]
147
 
148
+ success = run_ffmpeg_with_debug(command, "合并视频")
149
+
150
+ if success and os.path.exists(output_path):
151
+ file_size = os.path.getsize(output_path) / (1024 * 1024)
152
+ print(f"📁 合并后文件: {os.path.basename(output_path)} ({file_size:.1f}MB)")
153
+ return True
154
+ else:
155
+ print(f"❌ 合并后文件不存在: {output_path}")
156
+ return False
157
+
158
+ except Exception as e:
159
+ print(f"💥 合并过程异常: {str(e)}")
160
+ return False
161
+ finally:
162
  try:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
163
  os.unlink(list_file.name)
164
+ except:
165
+ pass
166
 
167
+ def process_videos_with_debug(video_files, clip_duration, num_output_videos, target_ratio):
168
+ """带调试信息的视频处理"""
169
+ if not video_files:
170
+ return "❌ 请上传视频文件", None, "没有上传文件"
171
+
172
+ print("🚀 开始处理视频...")
173
+ print(f"📊 参数: 切片{clip_duration}秒, 生成{num_output_videos}个, 比例{target_ratio}")
174
+
175
+ temp_dir = tempfile.mkdtemp()
176
+ print(f"📁 临时目录: {temp_dir}")
177
+
178
+ try:
179
+ all_clips = []
180
 
181
+ # 第一步:切割视频
182
+ print("📹 第一步:切割视频")
183
+ for idx, video_file in enumerate(video_files):
184
+ video_path = video_file.name
185
+ print(f"🎬 处理视频 {idx+1}: {os.path.basename(video_path)}")
186
 
187
+ # 获取视频时长
188
  try:
189
+ cmd = [
190
+ 'ffprobe', '-v', 'quiet',
 
 
 
191
  '-show_entries', 'format=duration',
192
  '-of', 'default=noprint_wrappers=1:nokey=1',
193
  video_path
194
  ]
195
+ result = subprocess.run(cmd, capture_output=True, text=True, timeout=10)
196
  total_duration = float(result.stdout.strip())
197
+ print(f"⏱️ 视频时长: {total_duration:.1f}秒")
198
+ except Exception as e:
199
+ print(f"❌ 无法获取视频时长: {e}")
200
+ continue
201
+
202
+ # 切割视频片段
203
+ start = 0.0
204
+ count = 0
205
+ while start < total_duration:
206
+ duration = min(clip_duration, total_duration - start)
207
+ clip_path = os.path.join(temp_dir, f"clip_{idx}_{count}.mp4")
208
 
209
+ success = ffmpeg_cut_video_safe(video_path, start, duration, clip_path)
210
+ if success:
211
+ all_clips.append(clip_path)
212
+ else:
213
+ print(f"⚠️ 跳过失败的片段: {count}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
214
 
215
+ start += clip_duration
216
+ count += 1
 
217
 
218
+ print(f"📊 总共切割了 {len(all_clips)} 个片段")
 
 
 
 
 
 
219
 
220
+ if not all_clips:
221
+ return "❌ 没有成功切割任何片段", None, "切割阶段失败"
 
 
 
 
222
 
223
+ # 第二步:生成混剪视频
224
+ print("🎭 第二步:生成混剪视频")
225
+ random.shuffle(all_clips)
226
 
227
+ output_files = []
228
+ clips_per_video = max(1, len(all_clips) // num_output_videos)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
229
 
 
 
230
  for i in range(num_output_videos):
231
+ print(f"🎬 生成第 {i+1} 个混剪视频")
232
+
233
  start_idx = i * clips_per_video
234
+ end_idx = len(all_clips) if i == num_output_videos - 1 else (start_idx + clips_per_video)
235
+ selected_clips = all_clips[start_idx:end_idx]
236
 
237
+ if not selected_clips:
238
+ print(f"⚠️ 第 {i+1} 个视频没有片段,跳过")
239
+ continue
 
 
 
 
240
 
241
+ print(f"📝 {i+1} 个视频使用 {len(selected_clips)} 个片段")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
242
 
243
+ # 先合并片段
244
+ temp_merged = os.path.join(temp_dir, f"merged_{i+1}.mp4")
245
+ merge_success = concat_videos_safe(selected_clips, temp_merged)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
246
 
247
+ if not merge_success:
248
+ print(f"❌ {i+1} 个视频合并失败")
249
+ continue
250
 
251
+ # 再调整比例
252
+ timestamp = datetime.now().strftime('%H%M%S')
253
+ final_output = os.path.join(temp_dir, f"混剪视频_{target_ratio.replace(':', 'x')}_{i+1}_{timestamp}.mp4")
254
+ resize_success = ffmpeg_resize_video_safe(temp_merged, final_output, target_ratio)
 
 
 
 
 
 
 
 
 
 
 
 
255
 
256
+ if resize_success:
257
+ output_files.append(final_output)
258
+ print(f"✅ 第 {i+1} 个混剪视频完成")
259
+ else:
260
+ print(f"❌ {i+1} 个视频比例调整失败")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
261
 
262
  if not output_files:
263
+ error_info = f"生成阶段失败。切割了{len(all_clips)}个片段,但无法生成最终视频。"
264
+ return "❌ 未能生成任何混剪视频", None, error_info
 
 
 
 
 
265
 
266
+ # 第三步:打包
267
+ print("📦 第三步:打包文件")
268
+ package_dir = tempfile.mkdtemp()
269
+ timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
270
+ zip_path = os.path.join(package_dir, f"混剪视频包_{target_ratio.replace(':', 'x')}_{timestamp}.zip")
271
 
272
+ try:
273
+ with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
274
+ for video_file in output_files:
275
+ arcname = os.path.basename(video_file)
276
+ zipf.write(video_file, arcname)
277
+ print(f"📦 打包文件: {arcname}")
278
+
279
+ # 添加说明
280
+ readme = f"""调试版混剪视频包
281
 
282
+ 生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
283
+ 成功生成: {len(output_files)} 个视频
284
+ 视频比例: {target_ratio}
285
+ 切片时长: {clip_duration} 秒
 
 
286
 
287
+ 文件列表:
 
288
  """
289
+ for i, vf in enumerate(output_files, 1):
290
+ size_mb = os.path.getsize(vf) / (1024 * 1024)
291
+ readme += f"- 混剪视频_{i}.mp4 ({size_mb:.1f}MB)\n"
292
+
293
+ zipf.writestr("README.txt", readme.encode('utf-8'))
294
+
295
+ total_size = os.path.getsize(zip_path) / (1024 * 1024)
296
+
297
+ success_msg = f"""✅ 处理成功完成!
298
 
299
+ 📊 最终统计:
300
+ • 成功生成: {len(output_files)} 个混剪视频
301
+ 视频比例: {target_ratio}
302
+ 总文件大小: {total_size:.1f}MB
303
+ 切片时长: {clip_duration}
304
+
305
+ 💾 点击下方下载按钮获取视频包"""
306
+
307
+ detail_info = f"""详细处理信息:
 
 
 
 
308
 
309
+ 📹 输入处理:
310
+ 输入文件数: {len(video_files)}
311
+ 成功切片数: {len(all_clips)}
 
 
 
312
 
313
+ 🎬 输出统计:
314
  """
315
+ for i, vf in enumerate(output_files, 1):
316
+ size_mb = os.path.getsize(vf) / (1024 * 1024)
317
+ detail_info += f"• 混剪视频_{i}: {size_mb:.1f}MB\n"
318
+
319
+ print("🎉 所有处理完成!")
320
+ return success_msg, zip_path, detail_info
321
+
322
+ except Exception as e:
323
+ error_msg = f"打包失败: {str(e)}"
324
+ print(f"❌ {error_msg}")
325
+ return f"❌ {error_msg}", None, str(e)
326
+
 
 
 
 
327
  except Exception as e:
328
+ error_trace = traceback.format_exc()
329
+ print(f"💥 处理过程发生异常:")
330
+ print(error_trace)
331
+ return f"❌ 处理异常: {str(e)}", None, error_trace
332
+
333
+ finally:
334
+ # 清理临时文件
335
+ try:
336
+ shutil.rmtree(temp_dir, ignore_errors=True)
337
+ print("🧹 临时文件清理完成")
338
+ except:
339
+ pass
340
 
341
  def main():
342
+ with gr.Blocks(title="调试版FFmpeg剪辑工具") as demo:
 
 
 
343
 
344
  gr.HTML("""
345
+ <div style="text-align: center; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 15px; color: white; margin-bottom: 20px;">
346
+ <h1>🔧 调试版 FFmpeg 自动剪辑工具</h1>
347
+ <p style="margin: 10px 0 0 0;">🚀 带详细调试信息,帮助解决处理问题</p>
348
  </div>
349
  """)
350
 
351
  gr.Markdown("""
352
+ ### 🔧 调试版特性
353
+ - 📝 **详细日志**: 显示每步处理的详细信息
354
+ - 🛠️ **错误诊断**: 精确定位问题所在
355
+ - 🔍 **文件检查**: 验证每个步骤的文件状态
356
+ - ⚡ **稳定处理**: 使用最兼容的FFmpeg参数
 
357
  """)
358
 
359
  with gr.Row():
360
+ with gr.Column():
361
  video_input = gr.File(
362
+ label="📤 上传视频文件",
363
+ file_types=[".mp4", ".mov", ".avi"],
364
+ file_count="multiple"
 
365
  )
366
 
367
  with gr.Row():
368
+ clip_duration = gr.Number(value=3, label="切片时长(秒)", minimum=1, maximum=30)
369
+ num_output = gr.Number(value=2, label="生成数量", minimum=1, maximum=5)
 
 
 
 
 
 
 
 
 
 
 
 
370
 
371
+ target_ratio = gr.Radio(choices=["9:16", "16:9"], value="9:16", label="视频比例")
 
 
 
 
 
372
 
373
+ process_btn = gr.Button("🔧 开始调试处理", variant="primary")
 
 
 
 
 
 
 
 
 
 
 
 
374
 
375
  with gr.Row():
376
+ with gr.Column():
377
+ status_output = gr.Textbox(label="处理状态", lines=8, interactive=False)
378
+ download_file = gr.File(label="下载结果", interactive=False)
 
 
379
 
380
+ with gr.Column():
381
+ debug_output = gr.Textbox(label="调试信息", lines=12, interactive=False)
 
 
 
 
 
382
 
383
  process_btn.click(
384
+ fn=process_videos_with_debug,
385
+ inputs=[video_input, clip_duration, num_output, target_ratio],
386
+ outputs=[status_output, download_file, debug_output]
387
  )
388
 
389
  gr.Markdown("""
390
  ---
391
+ ### 🔧 调试说明
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
392
 
393
+ 这个调试版本会显示详细的处理过程,帮助识别问题:
 
 
 
 
394
 
395
+ - ✅ **成功标志**: 显示每步成功完成
396
+ - **失败标志**: 显示具体失败原因
397
+ - 📁 **文件信息**: 显示文件大小和路径
398
+ - 🔧 **命令日志**: 显示执行的FFmpeg命令
 
399
 
400
+ 如果仍然失败,请查看"调试信息"栏中的详细错误信息。
 
 
 
 
401
  """)
402
 
403
  demo.launch()