Ryanus commited on
Commit
3a34864
·
verified ·
1 Parent(s): d9c311a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +180 -123
app.py CHANGED
@@ -39,10 +39,7 @@ def save_to_storage(file_path, metadata=None):
39
  count += 1
40
 
41
  shutil.copy2(file_path, target_path)
42
-
43
- # 更新配置
44
  update_storage_config()
45
-
46
  return target_path
47
  except Exception as e:
48
  print(f"❌ 储存文件失败: {e}")
@@ -74,7 +71,6 @@ def get_storage_info():
74
  else:
75
  config = {"total_videos": 0, "total_size_mb": 0}
76
 
77
- # 获取视频文件列表
78
  video_files = []
79
  if os.path.exists(STORAGE_DIR):
80
  for f in os.listdir(STORAGE_DIR):
@@ -88,13 +84,105 @@ def get_storage_info():
88
  "modified": mod_time.strftime('%Y-%m-%d %H:%M')
89
  })
90
 
91
- # 按修改时间排序
92
  video_files.sort(key=lambda x: x["modified"], reverse=True)
93
-
94
  return config, video_files
95
  except:
96
  return {"total_videos": 0, "total_size_mb": 0}, []
97
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
  def delete_storage_file(filename):
99
  """从储存空间删除文件"""
100
  try:
@@ -126,20 +214,10 @@ def clear_storage():
126
  def ffmpeg_cut_video(input_path, start_time, duration, output_path):
127
  """稳定的视频切割"""
128
  command = [
129
- 'ffmpeg',
130
- '-i', input_path,
131
- '-ss', str(start_time),
132
- '-t', str(duration),
133
- '-c:v', 'libx264',
134
- '-preset', 'medium',
135
- '-crf', '23',
136
- '-c:a', 'aac',
137
- '-b:a', '128k',
138
- '-avoid_negative_ts', 'make_zero',
139
- '-y',
140
- output_path
141
  ]
142
-
143
  result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
144
  return result.returncode == 0 and os.path.exists(output_path)
145
 
@@ -151,17 +229,9 @@ def ffmpeg_resize_video(input_path, output_path, target_ratio):
151
  filter_complex = "scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2:black"
152
 
153
  command = [
154
- 'ffmpeg',
155
- '-i', input_path,
156
- '-vf', filter_complex,
157
- '-c:v', 'libx264',
158
- '-preset', 'medium',
159
- '-crf', '23',
160
- '-c:a', 'copy',
161
- '-y',
162
- output_path
163
  ]
164
-
165
  result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
166
  return result.returncode == 0 and os.path.exists(output_path)
167
 
@@ -181,19 +251,9 @@ def concat_videos(file_list, output_path):
181
  list_file.write(f"file '{abs_path}'\n")
182
  list_file.close()
183
 
184
- command = [
185
- 'ffmpeg',
186
- '-f', 'concat',
187
- '-safe', '0',
188
- '-i', list_file.name,
189
- '-c', 'copy',
190
- '-y',
191
- output_path
192
- ]
193
-
194
  result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
195
  return result.returncode == 0 and os.path.exists(output_path)
196
-
197
  finally:
198
  try:
199
  os.unlink(list_file.name)
@@ -210,17 +270,11 @@ def process_videos_with_storage(video_files, clip_duration, num_output_videos, t
210
  try:
211
  all_clips = []
212
 
213
- # 切割所有视频
214
  for idx, video_file in enumerate(video_files):
215
  video_path = video_file.name
216
 
217
  try:
218
- cmd = [
219
- 'ffprobe', '-v', 'quiet',
220
- '-show_entries', 'format=duration',
221
- '-of', 'default=noprint_wrappers=1:nokey=1',
222
- video_path
223
- ]
224
  result = subprocess.run(cmd, capture_output=True, text=True, timeout=10)
225
  total_duration = float(result.stdout.strip())
226
  except:
@@ -241,13 +295,11 @@ def process_videos_with_storage(video_files, clip_duration, num_output_videos, t
241
  if not all_clips:
242
  return "❌ 切割失败,请检查视频文件", None, "", ""
243
 
244
- # 随机打乱并分组
245
  random.shuffle(all_clips)
246
  clips_per_video = max(1, len(all_clips) // num_output_videos)
247
  output_files = []
248
  stored_files = []
249
 
250
- # 生成混剪视频
251
  for i in range(num_output_videos):
252
  start_idx = i * clips_per_video
253
  end_idx = len(all_clips) if i == num_output_videos - 1 else (start_idx + clips_per_video)
@@ -256,19 +308,15 @@ def process_videos_with_storage(video_files, clip_duration, num_output_videos, t
256
  if not selected_clips:
257
  continue
258
 
259
- # 合并片段
260
  temp_merged = os.path.join(temp_dir, f"merged_{i+1}.mp4")
261
  if not concat_videos(selected_clips, temp_merged):
262
  continue
263
 
264
- # 调整比例
265
  timestamp = datetime.now().strftime('%H%M%S')
266
  final_output = os.path.join(temp_dir, f"混剪视频_{target_ratio.replace(':', 'x')}_{i+1}_{timestamp}.mp4")
267
 
268
  if ffmpeg_resize_video(temp_merged, final_output, target_ratio):
269
  output_files.append(final_output)
270
-
271
- # 🔥 自动保存到储存空间
272
  stored_path = save_to_storage(final_output)
273
  if stored_path:
274
  stored_files.append(os.path.basename(stored_path))
@@ -276,7 +324,6 @@ def process_videos_with_storage(video_files, clip_duration, num_output_videos, t
276
  if not output_files:
277
  return "❌ 生成混剪视频失败", None, "", ""
278
 
279
- # 打包下载文件
280
  package_dir = tempfile.mkdtemp()
281
  timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
282
  zip_path = os.path.join(package_dir, f"混剪视频包_{target_ratio.replace(':', 'x')}_{timestamp}.zip")
@@ -305,13 +352,8 @@ def process_videos_with_storage(video_files, clip_duration, num_output_videos, t
305
  所有视频已自动保存到本地储存空间:
306
  {', '.join(stored_files)}
307
 
308
- ## 📱 使用说明
309
- 视���已按 {target_ratio} 比例优化,可直接发布到相应平台。
310
-
311
- ---
312
- FFmpeg 自动混剪工具制作
313
  """
314
-
315
  zipf.writestr("README.txt", readme.encode('utf-8'))
316
 
317
  total_size = os.path.getsize(zip_path) / (1024 * 1024)
@@ -347,7 +389,6 @@ FFmpeg 自动混剪工具制作
347
  for i, stored_file in enumerate(stored_files, 1):
348
  details += f"• 已储存: {stored_file}\n"
349
 
350
- # 获取最新储存信息
351
  config, video_list = get_storage_info()
352
  storage_info = f"""📊 **储存空间状态:**
353
 
@@ -358,7 +399,7 @@ FFmpeg 自动混剪工具制作
358
  📋 **最新文件:**
359
  """
360
 
361
- for video in video_list[:5]: # 只显示最新5个
362
  storage_info += f"• {video['name']} ({video['size_mb']}MB) - {video['modified']}\n"
363
 
364
  return success_msg, zip_path, details, storage_info
@@ -389,36 +430,46 @@ def refresh_storage_display():
389
  else:
390
  storage_display += "暂无文件\n"
391
 
392
- # 生成文件选择列表
393
  file_choices = [video['name'] for video in video_list]
394
 
395
- return storage_display, gr.Dropdown(choices=file_choices, label="选择要删除的文件", interactive=True)
 
 
396
 
397
  def handle_delete_file(filename):
398
  """处理文件删除"""
399
  if not filename:
400
- return "⚠️ 请选择要删除的文件", refresh_storage_display()[0], refresh_storage_display()[1]
401
 
402
  result = delete_storage_file(filename)
403
- new_display, new_dropdown = refresh_storage_display()
404
- return result, new_display, new_dropdown
405
 
406
  def handle_clear_storage():
407
  """处理清空储存"""
408
  result = clear_storage()
409
- new_display, new_dropdown = refresh_storage_display()
410
- return result, new_display, new_dropdown
 
 
 
 
 
 
 
 
 
 
411
 
412
- # 初始化储存空间
413
  init_storage()
414
 
415
  def main():
416
- with gr.Blocks(title="FFmpeg混剪+储存管理", theme=gr.themes.Soft()) as demo:
417
 
418
  gr.HTML("""
419
  <div style="text-align: center; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 15px; color: white; margin-bottom: 20px;">
420
- <h1>🎬 FFmpeg 混剪工具 + 储存管理</h1>
421
- <p style="margin: 10px 0 0 0;">长视频切片 → 智能混剪 → 比例调整 → 自动储存 → 永久保存</p>
422
  </div>
423
  """)
424
 
@@ -435,18 +486,8 @@ def main():
435
  )
436
 
437
  with gr.Row():
438
- clip_duration = gr.Number(
439
- value=3,
440
- label="切片时长(秒)",
441
- minimum=1,
442
- maximum=3600
443
- )
444
- num_output = gr.Number(
445
- value=3,
446
- label="生成数量",
447
- minimum=1,
448
- maximum=100
449
- )
450
 
451
  ratio_selection = gr.Radio(
452
  choices=["9:16", "16:9"],
@@ -458,32 +499,20 @@ def main():
458
  process_btn = gr.Button("🎬 开始混剪并储存", variant="primary", size="lg")
459
 
460
  with gr.Column(scale=1):
461
- status_output = gr.Textbox(
462
- label="📊 处理状态",
463
- lines=12,
464
- interactive=False
465
- )
466
 
467
  with gr.Row():
468
  with gr.Column():
469
  download_file = gr.File(label="📦 下载混剪视频包", interactive=False)
470
 
471
  with gr.Column():
472
- details_output = gr.Textbox(
473
- label="📝 处理详情",
474
- lines=12,
475
- interactive=False
476
- )
477
 
478
  with gr.Column():
479
- storage_status = gr.Textbox(
480
- label="💾 储存状态",
481
- lines=12,
482
- interactive=False
483
- )
484
 
485
  # 第二个标签页:储存管理
486
- with gr.TabItem("💾 储存管理"):
487
  with gr.Row():
488
  with gr.Column(scale=2):
489
  storage_display = gr.Textbox(
@@ -496,23 +525,29 @@ def main():
496
  with gr.Column(scale=1):
497
  refresh_btn = gr.Button("🔄 刷新储存状态", variant="secondary")
498
 
499
- gr.Markdown("### 🗑️ 文件管理")
 
 
500
 
501
- file_selector = gr.Dropdown(
502
  choices=[],
503
- label="选择文件",
504
  interactive=True
505
  )
506
 
 
 
 
 
 
 
 
 
507
  with gr.Row():
508
  delete_btn = gr.Button("🗑️ 删除文件", variant="secondary")
509
  clear_btn = gr.Button("🧹 清空储存", variant="stop")
510
 
511
- operation_result = gr.Textbox(
512
- label="操作结果",
513
- lines=3,
514
- interactive=False
515
- )
516
 
517
  # 事件绑定
518
  process_btn.click(
@@ -523,24 +558,35 @@ def main():
523
 
524
  refresh_btn.click(
525
  fn=refresh_storage_display,
526
- outputs=[storage_display, file_selector]
 
 
 
 
 
 
 
 
 
 
 
527
  )
528
 
529
  delete_btn.click(
530
  fn=handle_delete_file,
531
  inputs=[file_selector],
532
- outputs=[operation_result, storage_display, file_selector]
533
  )
534
 
535
  clear_btn.click(
536
  fn=handle_clear_storage,
537
- outputs=[operation_result, storage_display, file_selector]
538
  )
539
 
540
- # 页面加载时自动刷新储存显示
541
  demo.load(
542
  fn=refresh_storage_display,
543
- outputs=[storage_display, file_selector]
544
  )
545
 
546
  gr.Markdown("""
@@ -556,20 +602,31 @@ def main():
556
  **💾 储存管理功能:**
557
  - 📁 所有生成视频自动保存�� `~/video_storage/`
558
  - 🔄 实时查看储存空间使用情况
 
 
 
 
 
 
 
 
 
559
  - 🗑️ 支持单个文件删除
560
  - 🧹 支持清空全部储存文件
561
- - 📊 显示文件详细信息(大小、时间)
562
 
563
- **🔥 储存优势:**
564
- - **永久保存**: 文件不会因临时目录清理而丢失
565
- - 🚀 **快速访问**: 本地储存,无需重新下载
566
- - 📱 **灵活管理**: 随时查看、删除不需要的文件
567
- - 💾 **空间监控**: 实时显示储存使用情况
 
568
 
569
  **⚠️ 注意事项:**
570
- - 储存目录位于用户主目录下的 `video_storage` 文件夹
571
- - 请定期清理不需要的文件以节省磁盘空间
572
- - 删除操作不可恢复,请谨慎操作
 
573
  """)
574
 
575
  demo.launch()
 
39
  count += 1
40
 
41
  shutil.copy2(file_path, target_path)
 
 
42
  update_storage_config()
 
43
  return target_path
44
  except Exception as e:
45
  print(f"❌ 储存文件失败: {e}")
 
71
  else:
72
  config = {"total_videos": 0, "total_size_mb": 0}
73
 
 
74
  video_files = []
75
  if os.path.exists(STORAGE_DIR):
76
  for f in os.listdir(STORAGE_DIR):
 
84
  "modified": mod_time.strftime('%Y-%m-%d %H:%M')
85
  })
86
 
 
87
  video_files.sort(key=lambda x: x["modified"], reverse=True)
 
88
  return config, video_files
89
  except:
90
  return {"total_videos": 0, "total_size_mb": 0}, []
91
 
92
+ def download_all_storage():
93
+ """一键下载储存空间所有视频"""
94
+ try:
95
+ config, video_files = get_storage_info()
96
+
97
+ if not video_files:
98
+ return None, "⚠️ 储存空间为空,没有可下载的文件"
99
+
100
+ # 创建打包文件
101
+ package_dir = tempfile.mkdtemp()
102
+ timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
103
+ zip_path = os.path.join(package_dir, f"储存空间全部视频_{timestamp}.zip")
104
+
105
+ with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
106
+ for video in video_files:
107
+ video_path = os.path.join(STORAGE_DIR, video['name'])
108
+ if os.path.exists(video_path):
109
+ zipf.write(video_path, video['name'])
110
+
111
+ # 添加清单
112
+ manifest = f"""# 储存空间视频清单
113
+
114
+ ## 📊 下载信息
115
+ - 下载时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
116
+ - 视频总数: {len(video_files)} 个
117
+ - 总大小: {config['total_size_mb']}MB
118
+
119
+ ## 📁 文件列表
120
+ """
121
+ for video in video_files:
122
+ manifest += f"- {video['name']} ({video['size_mb']}MB) - {video['modified']}\n"
123
+
124
+ manifest += """
125
+ ## 💡 使用说明
126
+ 这些是您的储存空间中保存的所有混剪视频,可直接使用或进一步编辑。
127
+
128
+ ---
129
+ FFmpeg 储存管理系统
130
+ """
131
+
132
+ zipf.writestr("视频清单.txt", manifest.encode('utf-8'))
133
+
134
+ download_msg = f"✅ 已打包 {len(video_files)} 个视频文件,总大小 {config['total_size_mb']}MB"
135
+ return zip_path, download_msg
136
+
137
+ except Exception as e:
138
+ return None, f"❌ 打包下载失败: {str(e)}"
139
+
140
+ def download_selected_storage(selected_files):
141
+ """下载选中的储存文件"""
142
+ try:
143
+ if not selected_files:
144
+ return None, "⚠️ 请至少选择一个文件进行下载"
145
+
146
+ # 创建打包文件
147
+ package_dir = tempfile.mkdtemp()
148
+ timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
149
+ zip_path = os.path.join(package_dir, f"选中视频_{timestamp}.zip")
150
+
151
+ total_size = 0
152
+ with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
153
+ valid_files = []
154
+ for filename in selected_files:
155
+ video_path = os.path.join(STORAGE_DIR, filename)
156
+ if os.path.exists(video_path):
157
+ zipf.write(video_path, filename)
158
+ size_mb = os.path.getsize(video_path) / (1024 * 1024)
159
+ total_size += size_mb
160
+ valid_files.append({"name": filename, "size_mb": round(size_mb, 1)})
161
+
162
+ if not valid_files:
163
+ return None, "❌ 选中的文件都不存在"
164
+
165
+ # 添加清单
166
+ manifest = f"""# 选中视频下载清单
167
+
168
+ ## 📊 下载信息
169
+ - 下载时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
170
+ - 选中文件: {len(valid_files)} 个
171
+ - 总大小: {round(total_size, 1)}MB
172
+
173
+ ## 📁 文件列表
174
+ """
175
+ for video in valid_files:
176
+ manifest += f"- {video['name']} ({video['size_mb']}MB)\n"
177
+
178
+ zipf.writestr("下载清单.txt", manifest.encode('utf-8'))
179
+
180
+ download_msg = f"✅ 已打包 {len(valid_files)} 个选中文件,总大小 {round(total_size, 1)}MB"
181
+ return zip_path, download_msg
182
+
183
+ except Exception as e:
184
+ return None, f"❌ 选择下载失败: {str(e)}"
185
+
186
  def delete_storage_file(filename):
187
  """从储存空间删除文件"""
188
  try:
 
214
  def ffmpeg_cut_video(input_path, start_time, duration, output_path):
215
  """稳定的视频切割"""
216
  command = [
217
+ 'ffmpeg', '-i', input_path, '-ss', str(start_time), '-t', str(duration),
218
+ '-c:v', 'libx264', '-preset', 'medium', '-crf', '23',
219
+ '-c:a', 'aac', '-b:a', '128k', '-avoid_negative_ts', 'make_zero', '-y', output_path
 
 
 
 
 
 
 
 
 
220
  ]
 
221
  result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
222
  return result.returncode == 0 and os.path.exists(output_path)
223
 
 
229
  filter_complex = "scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2:black"
230
 
231
  command = [
232
+ 'ffmpeg', '-i', input_path, '-vf', filter_complex,
233
+ '-c:v', 'libx264', '-preset', 'medium', '-crf', '23', '-c:a', 'copy', '-y', output_path
 
 
 
 
 
 
 
234
  ]
 
235
  result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
236
  return result.returncode == 0 and os.path.exists(output_path)
237
 
 
251
  list_file.write(f"file '{abs_path}'\n")
252
  list_file.close()
253
 
254
+ command = ['ffmpeg', '-f', 'concat', '-safe', '0', '-i', list_file.name, '-c', 'copy', '-y', output_path]
 
 
 
 
 
 
 
 
 
255
  result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
256
  return result.returncode == 0 and os.path.exists(output_path)
 
257
  finally:
258
  try:
259
  os.unlink(list_file.name)
 
270
  try:
271
  all_clips = []
272
 
 
273
  for idx, video_file in enumerate(video_files):
274
  video_path = video_file.name
275
 
276
  try:
277
+ cmd = ['ffprobe', '-v', 'quiet', '-show_entries', 'format=duration', '-of', 'default=noprint_wrappers=1:nokey=1', video_path]
 
 
 
 
 
278
  result = subprocess.run(cmd, capture_output=True, text=True, timeout=10)
279
  total_duration = float(result.stdout.strip())
280
  except:
 
295
  if not all_clips:
296
  return "❌ 切割失败,请检查视频文件", None, "", ""
297
 
 
298
  random.shuffle(all_clips)
299
  clips_per_video = max(1, len(all_clips) // num_output_videos)
300
  output_files = []
301
  stored_files = []
302
 
 
303
  for i in range(num_output_videos):
304
  start_idx = i * clips_per_video
305
  end_idx = len(all_clips) if i == num_output_videos - 1 else (start_idx + clips_per_video)
 
308
  if not selected_clips:
309
  continue
310
 
 
311
  temp_merged = os.path.join(temp_dir, f"merged_{i+1}.mp4")
312
  if not concat_videos(selected_clips, temp_merged):
313
  continue
314
 
 
315
  timestamp = datetime.now().strftime('%H%M%S')
316
  final_output = os.path.join(temp_dir, f"混剪视频_{target_ratio.replace(':', 'x')}_{i+1}_{timestamp}.mp4")
317
 
318
  if ffmpeg_resize_video(temp_merged, final_output, target_ratio):
319
  output_files.append(final_output)
 
 
320
  stored_path = save_to_storage(final_output)
321
  if stored_path:
322
  stored_files.append(os.path.basename(stored_path))
 
324
  if not output_files:
325
  return "❌ 生成混剪视频失败", None, "", ""
326
 
 
327
  package_dir = tempfile.mkdtemp()
328
  timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
329
  zip_path = os.path.join(package_dir, f"混剪视频包_{target_ratio.replace(':', 'x')}_{timestamp}.zip")
 
352
  所有视频已自动保存到本地储存空间:
353
  {', '.join(stored_files)}
354
 
355
+ 视频已按 {target_ratio} 比例优化,可直接发布。
 
 
 
 
356
  """
 
357
  zipf.writestr("README.txt", readme.encode('utf-8'))
358
 
359
  total_size = os.path.getsize(zip_path) / (1024 * 1024)
 
389
  for i, stored_file in enumerate(stored_files, 1):
390
  details += f"• 已储存: {stored_file}\n"
391
 
 
392
  config, video_list = get_storage_info()
393
  storage_info = f"""📊 **储存空间状态:**
394
 
 
399
  📋 **最新文件:**
400
  """
401
 
402
+ for video in video_list[:5]:
403
  storage_info += f"• {video['name']} ({video['size_mb']}MB) - {video['modified']}\n"
404
 
405
  return success_msg, zip_path, details, storage_info
 
430
  else:
431
  storage_display += "暂无文件\n"
432
 
 
433
  file_choices = [video['name'] for video in video_list]
434
 
435
+ return (storage_display,
436
+ gr.Dropdown(choices=file_choices, label="选择要删除的文件", interactive=True),
437
+ gr.CheckboxGroup(choices=file_choices, label="选择要下载的文件", interactive=True))
438
 
439
  def handle_delete_file(filename):
440
  """处理文件删除"""
441
  if not filename:
442
+ return "⚠️ 请选择要删除的文件", refresh_storage_display()[0], refresh_storage_display()[1], refresh_storage_display()[2]
443
 
444
  result = delete_storage_file(filename)
445
+ new_display, new_dropdown, new_checkbox = refresh_storage_display()
446
+ return result, new_display, new_dropdown, new_checkbox
447
 
448
  def handle_clear_storage():
449
  """处理清空储存"""
450
  result = clear_storage()
451
+ new_display, new_dropdown, new_checkbox = refresh_storage_display()
452
+ return result, new_display, new_dropdown, new_checkbox
453
+
454
+ def handle_download_all():
455
+ """处理一键下载所有"""
456
+ zip_file, message = download_all_storage()
457
+ return zip_file, message
458
+
459
+ def handle_download_selected(selected_files):
460
+ """处理选择下载"""
461
+ zip_file, message = download_selected_storage(selected_files)
462
+ return zip_file, message
463
 
 
464
  init_storage()
465
 
466
  def main():
467
+ with gr.Blocks(title="FFmpeg混剪+储存+下载管理", theme=gr.themes.Soft()) as demo:
468
 
469
  gr.HTML("""
470
  <div style="text-align: center; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 15px; color: white; margin-bottom: 20px;">
471
+ <h1>🎬 FFmpeg 混剪工具 + 储存管理 + 一键下载</h1>
472
+ <p style="margin: 10px 0 0 0;">长视频切片 → 智能混剪 → 比例调整 → 自动储存 → 一键下载</p>
473
  </div>
474
  """)
475
 
 
486
  )
487
 
488
  with gr.Row():
489
+ clip_duration = gr.Number(value=3, label="切片时长(秒)", minimum=1, maximum=3600)
490
+ num_output = gr.Number(value=3, label="生成数量", minimum=1, maximum=100)
 
 
 
 
 
 
 
 
 
 
491
 
492
  ratio_selection = gr.Radio(
493
  choices=["9:16", "16:9"],
 
499
  process_btn = gr.Button("🎬 开始混剪并储存", variant="primary", size="lg")
500
 
501
  with gr.Column(scale=1):
502
+ status_output = gr.Textbox(label="📊 处理状态", lines=12, interactive=False)
 
 
 
 
503
 
504
  with gr.Row():
505
  with gr.Column():
506
  download_file = gr.File(label="📦 下载混剪视频包", interactive=False)
507
 
508
  with gr.Column():
509
+ details_output = gr.Textbox(label="📝 处理详情", lines=12, interactive=False)
 
 
 
 
510
 
511
  with gr.Column():
512
+ storage_status = gr.Textbox(label="💾 储存状态", lines=12, interactive=False)
 
 
 
 
513
 
514
  # 第二个标签页:储存管理
515
+ with gr.TabItem("💾 储存管理 + 下载"):
516
  with gr.Row():
517
  with gr.Column(scale=2):
518
  storage_display = gr.Textbox(
 
525
  with gr.Column(scale=1):
526
  refresh_btn = gr.Button("🔄 刷新储存状态", variant="secondary")
527
 
528
+ gr.Markdown("### ⬇️ 下载管理")
529
+
530
+ download_all_btn = gr.Button("📦 一键下载全部", variant="primary")
531
 
532
+ file_selector_download = gr.CheckboxGroup(
533
  choices=[],
534
+ label="选择要下载的文件",
535
  interactive=True
536
  )
537
 
538
+ download_selected_btn = gr.Button("📥 下载选中文件", variant="secondary")
539
+
540
+ storage_download_file = gr.File(label="📦 储存空间下载", interactive=False)
541
+
542
+ gr.Markdown("### 🗑️ 文件管理")
543
+
544
+ file_selector = gr.Dropdown(choices=[], label="选择文件", interactive=True)
545
+
546
  with gr.Row():
547
  delete_btn = gr.Button("🗑️ 删除文件", variant="secondary")
548
  clear_btn = gr.Button("🧹 清空储存", variant="stop")
549
 
550
+ operation_result = gr.Textbox(label="操作结果", lines=4, interactive=False)
 
 
 
 
551
 
552
  # 事件绑定
553
  process_btn.click(
 
558
 
559
  refresh_btn.click(
560
  fn=refresh_storage_display,
561
+ outputs=[storage_display, file_selector, file_selector_download]
562
+ )
563
+
564
+ download_all_btn.click(
565
+ fn=handle_download_all,
566
+ outputs=[storage_download_file, operation_result]
567
+ )
568
+
569
+ download_selected_btn.click(
570
+ fn=handle_download_selected,
571
+ inputs=[file_selector_download],
572
+ outputs=[storage_download_file, operation_result]
573
  )
574
 
575
  delete_btn.click(
576
  fn=handle_delete_file,
577
  inputs=[file_selector],
578
+ outputs=[operation_result, storage_display, file_selector, file_selector_download]
579
  )
580
 
581
  clear_btn.click(
582
  fn=handle_clear_storage,
583
+ outputs=[operation_result, storage_display, file_selector, file_selector_download]
584
  )
585
 
586
+ # 页面加载时自动刷新
587
  demo.load(
588
  fn=refresh_storage_display,
589
+ outputs=[storage_display, file_selector, file_selector_download]
590
  )
591
 
592
  gr.Markdown("""
 
602
  **💾 储存管理功能:**
603
  - 📁 所有生成视频自动保存�� `~/video_storage/`
604
  - 🔄 实时查看储存空间使用情况
605
+ - 📊 显示文件详细信息(大小、时间)
606
+
607
+ **⬇️ 一键下载功能:**
608
+ - 📦 **一键下载全部**: 打包下载储存空间中所有视频
609
+ - 📥 **选择下载**: 勾选特定文件进行批量下载
610
+ - 🗂️ **自动清单**: 下载包含详细文件清单
611
+ - ⚡ **快速打包**: 自动压缩,节省下载时间
612
+
613
+ **🗑️ 文件管理功能:**
614
  - 🗑️ 支持单个文件删除
615
  - 🧹 支持清空全部储存文件
616
+ - 📱 灵活的文件管理操作
617
 
618
+ **🔥 使用场景:**
619
+ - **批量备份**: 一键下载所有混剪作品
620
+ - **选择性导出**: 只下载需要的特定视频
621
+ - **移动设备**: 下载到手机/平板继续编辑
622
+ - **分享协作**: 打包分享给团队成员
623
+ - **存档管理**: 定期下载备份到云盘
624
 
625
  **⚠️ 注意事项:**
626
+ - 下载文件为ZIP格式,需要解压使用
627
+ - 一键下载包含储存空间中所有视频文件
628
+ - 选择下载可以精确控制需要的文件
629
+ - 下载包自动包含详细的文件清单
630
  """)
631
 
632
  demo.launch()