Update app.py
Browse files
app.py
CHANGED
|
@@ -264,15 +264,19 @@ def build_ffmpeg_command(input_path, start_time, duration, output_path, operatio
|
|
| 264 |
threads = get_optimal_threads()
|
| 265 |
encoder = HARDWARE_ACCEL['encoder']
|
| 266 |
|
| 267 |
-
|
|
|
|
|
|
|
| 268 |
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
elif HARDWARE_ACCEL['
|
| 275 |
-
|
|
|
|
|
|
|
| 276 |
|
| 277 |
base_command.extend(['-i', input_path])
|
| 278 |
|
|
@@ -281,19 +285,18 @@ def build_ffmpeg_command(input_path, start_time, duration, output_path, operatio
|
|
| 281 |
|
| 282 |
# 编码参数
|
| 283 |
if encoder == 'h264_nvenc':
|
| 284 |
-
# NVIDIA GPU编码参数
|
| 285 |
base_command.extend([
|
| 286 |
'-c:v', 'h264_nvenc',
|
| 287 |
-
'-preset', '
|
| 288 |
-
'-
|
| 289 |
-
'-cq', '28', # 质量参数
|
| 290 |
'-c:a', 'aac',
|
| 291 |
'-b:a', '128k',
|
| 292 |
'-movflags', '+faststart',
|
| 293 |
'-y', output_path
|
| 294 |
])
|
| 295 |
elif encoder == 'h264_amf':
|
| 296 |
-
# AMD GPU编码参数
|
| 297 |
base_command.extend([
|
| 298 |
'-c:v', 'h264_amf',
|
| 299 |
'-quality', 'speed',
|
|
@@ -306,7 +309,7 @@ def build_ffmpeg_command(input_path, start_time, duration, output_path, operatio
|
|
| 306 |
'-y', output_path
|
| 307 |
])
|
| 308 |
elif encoder == 'h264_qsv':
|
| 309 |
-
# Intel Quick Sync编码参数
|
| 310 |
base_command.extend([
|
| 311 |
'-c:v', 'h264_qsv',
|
| 312 |
'-preset', 'veryfast',
|
|
@@ -323,7 +326,6 @@ def build_ffmpeg_command(input_path, start_time, duration, output_path, operatio
|
|
| 323 |
'-preset', 'ultrafast', # 最快预设
|
| 324 |
'-crf', '28', # 质量参数
|
| 325 |
'-tune', 'fastdecode', # 优化解码速度
|
| 326 |
-
'-x264opts', 'no-scenecut', # 禁用场景检测以加快速度
|
| 327 |
'-c:a', 'aac',
|
| 328 |
'-b:a', '128k',
|
| 329 |
'-movflags', '+faststart',
|
|
@@ -336,8 +338,7 @@ def build_ffmpeg_command(input_path, start_time, duration, output_path, operatio
|
|
| 336 |
if encoder == 'h264_nvenc':
|
| 337 |
base_command.extend([
|
| 338 |
'-c:v', 'h264_nvenc',
|
| 339 |
-
'-preset', '
|
| 340 |
-
'-tune', 'ull',
|
| 341 |
'-cq', '28',
|
| 342 |
'-c:a', 'copy',
|
| 343 |
'-movflags', '+faststart',
|
|
@@ -349,7 +350,6 @@ def build_ffmpeg_command(input_path, start_time, duration, output_path, operatio
|
|
| 349 |
'-preset', 'ultrafast',
|
| 350 |
'-crf', '28',
|
| 351 |
'-tune', 'fastdecode',
|
| 352 |
-
'-x264opts', 'no-scenecut',
|
| 353 |
'-c:a', 'copy',
|
| 354 |
'-movflags', '+faststart',
|
| 355 |
'-y', output_path
|
|
@@ -359,24 +359,52 @@ def build_ffmpeg_command(input_path, start_time, duration, output_path, operatio
|
|
| 359 |
|
| 360 |
def ffmpeg_cut_video(input_path, start_time, duration, output_path):
|
| 361 |
"""[优化] 快速的视频切割,使用硬件加速和ultrafast预设"""
|
| 362 |
-
|
| 363 |
-
|
| 364 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 365 |
|
| 366 |
def ffmpeg_resize_video(input_path, output_path, target_ratio):
|
| 367 |
"""[优化] 快速的比例调整,使用硬件���速和ultrafast预设"""
|
| 368 |
-
|
| 369 |
-
|
| 370 |
-
|
| 371 |
-
|
| 372 |
-
|
| 373 |
-
|
| 374 |
-
|
| 375 |
-
|
| 376 |
-
|
| 377 |
-
|
| 378 |
-
|
| 379 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 380 |
|
| 381 |
def concat_videos(file_list, output_path):
|
| 382 |
"""稳定的视频合并"""
|
|
@@ -397,8 +425,19 @@ def concat_videos(file_list, output_path):
|
|
| 397 |
# 使用多线程进行合并
|
| 398 |
threads = get_optimal_threads()
|
| 399 |
command = ['ffmpeg', '-threads', str(threads), '-f', 'concat', '-safe', '0', '-i', list_file.name, '-c', 'copy', '-y', output_path]
|
| 400 |
-
|
| 401 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 402 |
finally:
|
| 403 |
try:
|
| 404 |
os.unlink(list_file.name)
|
|
@@ -414,6 +453,7 @@ def process_single_video(video_file, clip_duration, temp_dir):
|
|
| 414 |
cmd = ['ffprobe', '-v', 'quiet', '-show_entries', 'format=duration', '-of', 'default=noprint_wrappers=1:nokey=1', video_path]
|
| 415 |
result = subprocess.run(cmd, capture_output=True, text=True, timeout=10)
|
| 416 |
total_duration = float(result.stdout.strip())
|
|
|
|
| 417 |
except Exception as e:
|
| 418 |
print(f"处理视频 {video_path} 时获取时长失败: {e}")
|
| 419 |
return clips
|
|
@@ -425,10 +465,13 @@ def process_single_video(video_file, clip_duration, temp_dir):
|
|
| 425 |
# 修改文件名,包含原始文件名和计数,避免不同视频的切片重名
|
| 426 |
clip_path = os.path.join(temp_dir, f"clip_{os.path.splitext(os.path.basename(video_path))[0]}_{count}.mp4")
|
| 427 |
|
|
|
|
|
|
|
| 428 |
if ffmpeg_cut_video(video_path, start, duration, clip_path):
|
| 429 |
clips.append(clip_path)
|
|
|
|
| 430 |
else:
|
| 431 |
-
print(f"
|
| 432 |
|
| 433 |
start += clip_duration
|
| 434 |
count += 1
|
|
@@ -451,9 +494,9 @@ def process_videos_with_storage(video_files, clip_duration, num_output_videos, t
|
|
| 451 |
|
| 452 |
all_clips = []
|
| 453 |
|
| 454 |
-
#
|
| 455 |
-
print(f"🚀 启动并行切片({min(
|
| 456 |
-
with concurrent.futures.
|
| 457 |
future_to_video = {
|
| 458 |
executor.submit(process_single_video, vf, clip_duration, temp_dir): vf
|
| 459 |
for vf in video_files
|
|
@@ -809,35 +852,4 @@ def main():
|
|
| 809 |
**⬇️ 一键下载功能:**
|
| 810 |
- 📦 **一键下载全部**: 打包下载储存空间中所有视频
|
| 811 |
- 📥 **选择下载**: 勾选特定文件进行批量下载
|
| 812 |
-
|
| 813 |
-
- ⚡ **快速打包**: 自动压缩,节省下载时间
|
| 814 |
-
|
| 815 |
-
**🗑️ 文件管理功能:**
|
| 816 |
-
- 🗑️ 支持单个文件删除
|
| 817 |
-
- 🧹 支持清空全部储存文件
|
| 818 |
-
- 📱 灵活的文件管理操作
|
| 819 |
-
|
| 820 |
-
**🔥 使用场景:**
|
| 821 |
-
- **批量备份**: 一键下载所有混剪作品
|
| 822 |
-
- **选择性导出**: 只下载需要的特定视频
|
| 823 |
-
- **移动设备**: 下载到手机/平板继续编辑
|
| 824 |
-
- **分享协作**: 打包分享给团队成员
|
| 825 |
-
- **存档管理**: 定期下载备份到云盘
|
| 826 |
-
|
| 827 |
-
**⚡ 性能优化:**
|
| 828 |
-
- **硬件加速**: 自动检测并使用GPU加速
|
| 829 |
-
- **多线程处理**: 充分利用多核CPU
|
| 830 |
-
- **快速预设**: 使用ultrafast预设提升速度
|
| 831 |
-
- **并行处理**: 多个视频同时处理
|
| 832 |
-
|
| 833 |
-
**⚠️ 注意事项:**
|
| 834 |
-
- 下载文件为ZIP格式,需要解压使用
|
| 835 |
-
- 一键下载包含储存空间中所有视频文件
|
| 836 |
-
- 选择下载可以精确控制需要的文件
|
| 837 |
-
- 下载包自动包含详细的文件清单
|
| 838 |
-
""")
|
| 839 |
-
|
| 840 |
-
demo.launch()
|
| 841 |
-
|
| 842 |
-
if __name__ == "__main__":
|
| 843 |
-
main()
|
|
|
|
| 264 |
threads = get_optimal_threads()
|
| 265 |
encoder = HARDWARE_ACCEL['encoder']
|
| 266 |
|
| 267 |
+
# 处理中文文件名 - 使用绝对路径并确保路径正确
|
| 268 |
+
input_path = os.path.abspath(input_path)
|
| 269 |
+
output_path = os.path.abspath(output_path)
|
| 270 |
|
| 271 |
+
base_command = ['ffmpeg']
|
| 272 |
+
|
| 273 |
+
# 硬件加速解码 - 简化处理,避免兼容性问题
|
| 274 |
+
# if HARDWARE_ACCEL['nvidia']:
|
| 275 |
+
# base_command.extend(['-hwaccel', 'cuda'])
|
| 276 |
+
# elif HARDWARE_ACCEL['amd']:
|
| 277 |
+
# base_command.extend(['-hwaccel', 'amf'])
|
| 278 |
+
# elif HARDWARE_ACCEL['intel']:
|
| 279 |
+
# base_command.extend(['-hwaccel', 'qsv'])
|
| 280 |
|
| 281 |
base_command.extend(['-i', input_path])
|
| 282 |
|
|
|
|
| 285 |
|
| 286 |
# 编码参数
|
| 287 |
if encoder == 'h264_nvenc':
|
| 288 |
+
# NVIDIA GPU编码参数 - 简化版本
|
| 289 |
base_command.extend([
|
| 290 |
'-c:v', 'h264_nvenc',
|
| 291 |
+
'-preset', 'fast', # 使用fast而不是p1,更兼容
|
| 292 |
+
'-cq', '28', # 质量参数
|
|
|
|
| 293 |
'-c:a', 'aac',
|
| 294 |
'-b:a', '128k',
|
| 295 |
'-movflags', '+faststart',
|
| 296 |
'-y', output_path
|
| 297 |
])
|
| 298 |
elif encoder == 'h264_amf':
|
| 299 |
+
# AMD GPU编码参数 - 简化版本
|
| 300 |
base_command.extend([
|
| 301 |
'-c:v', 'h264_amf',
|
| 302 |
'-quality', 'speed',
|
|
|
|
| 309 |
'-y', output_path
|
| 310 |
])
|
| 311 |
elif encoder == 'h264_qsv':
|
| 312 |
+
# Intel Quick Sync编码参数 - 简化版本
|
| 313 |
base_command.extend([
|
| 314 |
'-c:v', 'h264_qsv',
|
| 315 |
'-preset', 'veryfast',
|
|
|
|
| 326 |
'-preset', 'ultrafast', # 最快预设
|
| 327 |
'-crf', '28', # 质量参数
|
| 328 |
'-tune', 'fastdecode', # 优化解码速度
|
|
|
|
| 329 |
'-c:a', 'aac',
|
| 330 |
'-b:a', '128k',
|
| 331 |
'-movflags', '+faststart',
|
|
|
|
| 338 |
if encoder == 'h264_nvenc':
|
| 339 |
base_command.extend([
|
| 340 |
'-c:v', 'h264_nvenc',
|
| 341 |
+
'-preset', 'fast',
|
|
|
|
| 342 |
'-cq', '28',
|
| 343 |
'-c:a', 'copy',
|
| 344 |
'-movflags', '+faststart',
|
|
|
|
| 350 |
'-preset', 'ultrafast',
|
| 351 |
'-crf', '28',
|
| 352 |
'-tune', 'fastdecode',
|
|
|
|
| 353 |
'-c:a', 'copy',
|
| 354 |
'-movflags', '+faststart',
|
| 355 |
'-y', output_path
|
|
|
|
| 359 |
|
| 360 |
def ffmpeg_cut_video(input_path, start_time, duration, output_path):
|
| 361 |
"""[优化] 快速的视频切割,使用硬件加速和ultrafast预设"""
|
| 362 |
+
try:
|
| 363 |
+
command = build_ffmpeg_command(input_path, start_time, duration, output_path, 'cut')
|
| 364 |
+
print(f"执行FFmpeg命令: {' '.join(command)}") # 调试输出
|
| 365 |
+
|
| 366 |
+
# 创建输出目录(如果不存在)
|
| 367 |
+
os.makedirs(os.path.dirname(output_path), exist_ok=True)
|
| 368 |
+
|
| 369 |
+
result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
| 370 |
+
|
| 371 |
+
if result.returncode != 0:
|
| 372 |
+
print(f"FFmpeg错误: {result.stderr}") # 输出错误信息
|
| 373 |
+
return False
|
| 374 |
+
|
| 375 |
+
return os.path.exists(output_path)
|
| 376 |
+
except Exception as e:
|
| 377 |
+
print(f"切割视频异常: {e}")
|
| 378 |
+
return False
|
| 379 |
|
| 380 |
def ffmpeg_resize_video(input_path, output_path, target_ratio):
|
| 381 |
"""[优化] 快速的比例调整,使用硬件���速和ultrafast预设"""
|
| 382 |
+
try:
|
| 383 |
+
if target_ratio == '9:16':
|
| 384 |
+
filter_complex = "scale=1080:1920:force_original_aspect_ratio=decrease,pad=1080:1920:(ow-iw)/2:(oh-ih)/2:black"
|
| 385 |
+
else:
|
| 386 |
+
filter_complex = "scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2:black"
|
| 387 |
+
|
| 388 |
+
command = build_ffmpeg_command(input_path, 0, 0, output_path, 'resize')
|
| 389 |
+
# 在resize操作中插入滤镜
|
| 390 |
+
command.insert(-5, '-vf') # 在-y之前插入
|
| 391 |
+
command.insert(-4, filter_complex)
|
| 392 |
+
|
| 393 |
+
print(f"执行FFmpeg调整命令: {' '.join(command)}") # 调试输出
|
| 394 |
+
|
| 395 |
+
# 创建输出目录(如果不存在)
|
| 396 |
+
os.makedirs(os.path.dirname(output_path), exist_ok=True)
|
| 397 |
+
|
| 398 |
+
result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
| 399 |
+
|
| 400 |
+
if result.returncode != 0:
|
| 401 |
+
print(f"FFmpeg调整错误: {result.stderr}") # 输出错误信息
|
| 402 |
+
return False
|
| 403 |
+
|
| 404 |
+
return os.path.exists(output_path)
|
| 405 |
+
except Exception as e:
|
| 406 |
+
print(f"调整视频异常: {e}")
|
| 407 |
+
return False
|
| 408 |
|
| 409 |
def concat_videos(file_list, output_path):
|
| 410 |
"""稳定的视频合并"""
|
|
|
|
| 425 |
# 使用多线程进行合并
|
| 426 |
threads = get_optimal_threads()
|
| 427 |
command = ['ffmpeg', '-threads', str(threads), '-f', 'concat', '-safe', '0', '-i', list_file.name, '-c', 'copy', '-y', output_path]
|
| 428 |
+
|
| 429 |
+
print(f"执行FFmpeg合并命令: {' '.join(command)}") # 调试输出
|
| 430 |
+
|
| 431 |
+
# 创建输出目录(如果不存在)
|
| 432 |
+
os.makedirs(os.path.dirname(output_path), exist_ok=True)
|
| 433 |
+
|
| 434 |
+
result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
| 435 |
+
|
| 436 |
+
if result.returncode != 0:
|
| 437 |
+
print(f"FFmpeg合并错误: {result.stderr}") # 输出错误信息
|
| 438 |
+
return False
|
| 439 |
+
|
| 440 |
+
return os.path.exists(output_path)
|
| 441 |
finally:
|
| 442 |
try:
|
| 443 |
os.unlink(list_file.name)
|
|
|
|
| 453 |
cmd = ['ffprobe', '-v', 'quiet', '-show_entries', 'format=duration', '-of', 'default=noprint_wrappers=1:nokey=1', video_path]
|
| 454 |
result = subprocess.run(cmd, capture_output=True, text=True, timeout=10)
|
| 455 |
total_duration = float(result.stdout.strip())
|
| 456 |
+
print(f"视频总时长: {total_duration} 秒")
|
| 457 |
except Exception as e:
|
| 458 |
print(f"处理视频 {video_path} 时获取时长失败: {e}")
|
| 459 |
return clips
|
|
|
|
| 465 |
# 修改文件名,包含原始文件名和计数,避免不同视频的切片重名
|
| 466 |
clip_path = os.path.join(temp_dir, f"clip_{os.path.splitext(os.path.basename(video_path))[0]}_{count}.mp4")
|
| 467 |
|
| 468 |
+
print(f"正在切割片段 {count}: {start}-{start+duration} 秒 -> {clip_path}")
|
| 469 |
+
|
| 470 |
if ffmpeg_cut_video(video_path, start, duration, clip_path):
|
| 471 |
clips.append(clip_path)
|
| 472 |
+
print(f"✅ 切片成功: {clip_path}")
|
| 473 |
else:
|
| 474 |
+
print(f"❌ 切片失败: {clip_path}")
|
| 475 |
|
| 476 |
start += clip_duration
|
| 477 |
count += 1
|
|
|
|
| 494 |
|
| 495 |
all_clips = []
|
| 496 |
|
| 497 |
+
# 使用线程池进行并行处理(避免GIL限制)
|
| 498 |
+
print(f"🚀 启动并行切片({min(4, os.cpu_count() or 1)} 线程)...")
|
| 499 |
+
with concurrent.futures.ThreadPoolExecutor(max_workers=min(4, os.cpu_count() or 1)) as executor:
|
| 500 |
future_to_video = {
|
| 501 |
executor.submit(process_single_video, vf, clip_duration, temp_dir): vf
|
| 502 |
for vf in video_files
|
|
|
|
| 852 |
**⬇️ 一键下载功能:**
|
| 853 |
- 📦 **一键下载全部**: 打包下载储存空间中所有视频
|
| 854 |
- 📥 **选择下载**: 勾选特定文件进行批量下载
|
| 855 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|