Upload app.py
Browse files
app.py
CHANGED
|
@@ -18,6 +18,7 @@ from fastapi.staticfiles import StaticFiles
|
|
| 18 |
from pydub import AudioSegment
|
| 19 |
import shlex
|
| 20 |
from ffmpeg import probe as ffmpeg_probe # 需要安装ffmpeg-python包
|
|
|
|
| 21 |
|
| 22 |
# 配置日志
|
| 23 |
logging.basicConfig(level=logging.INFO)
|
|
@@ -252,17 +253,33 @@ async def create_audio_file(project_dir, captions, speeches):
|
|
| 252 |
return None, {}, [], []
|
| 253 |
|
| 254 |
def get_video_dimensions(video_path):
|
| 255 |
-
"""获取视频分辨率"""
|
| 256 |
try:
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 260 |
except Exception as e:
|
| 261 |
-
logger.warning(f"
|
| 262 |
return (1920, 1080)
|
| 263 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 264 |
# 创建视频
|
| 265 |
-
def create_video(project_dir, image_paths, caption_subtitle_file, speech_subtitle_file,
|
|
|
|
| 266 |
try:
|
| 267 |
# 创建帧列表文件 - 使用精确的面板持续时间
|
| 268 |
frames_list = os.path.join(project_dir, "frames.txt")
|
|
@@ -288,75 +305,47 @@ def create_video(project_dir, image_paths, caption_subtitle_file, speech_subtitl
|
|
| 288 |
]
|
| 289 |
subprocess.run(cmd1, check=True)
|
| 290 |
|
| 291 |
-
#
|
| 292 |
video_width, video_height = get_video_dimensions(temp_video)
|
| 293 |
base_fontsize = max(24, video_width // 50)
|
| 294 |
-
|
| 295 |
-
# 智能样式计算
|
| 296 |
-
style_config = {
|
| 297 |
-
"caption": {
|
| 298 |
-
"fontsize": int(base_fontsize * 0.9),
|
| 299 |
-
"margin_v": video_height // 10,
|
| 300 |
-
"alignment": 2 # 底部居中
|
| 301 |
-
},
|
| 302 |
-
"speech": {
|
| 303 |
-
"fontsize": base_fontsize,
|
| 304 |
-
"margin_v": video_height // 12,
|
| 305 |
-
"alignment": 8 # 顶部居中
|
| 306 |
-
}
|
| 307 |
-
}
|
| 308 |
-
# 安全处理字幕路径
|
| 309 |
-
def process_sub_path(path):
|
| 310 |
-
return shlex.quote(
|
| 311 |
-
Path(path).resolve().as_posix()
|
| 312 |
-
.replace(':', '\\:')
|
| 313 |
-
)
|
| 314 |
-
# 构建复合滤镜
|
| 315 |
combined_filter = (
|
| 316 |
f"subtitles={process_sub_path(caption_subtitle_file)}:"
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
f"MarginV={style_config['caption']['margin_v']},"
|
| 322 |
-
f"Outline=1,Shadow=1,BackColour=&H40000000,"
|
| 323 |
-
f"BorderStyle=3,WordWrap=1,WrapStyle=2;"
|
| 324 |
f"subtitles={process_sub_path(speech_subtitle_file)}:"
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
f"MarginV={style_config['speech']['margin_v']},"
|
| 330 |
-
f"Outline=1,Shadow=1,BackColour=&H40000000,"
|
| 331 |
-
f"BorderStyle=1,WordWrap=1,WrapStyle=2'"
|
| 332 |
)
|
| 333 |
-
|
|
|
|
| 334 |
cmd_combined = [
|
| 335 |
"ffmpeg", "-y",
|
| 336 |
"-i", temp_video,
|
| 337 |
-
"-vf",
|
| 338 |
"-c:a", "copy",
|
| 339 |
"-c:v", "libx264",
|
| 340 |
"-preset", "fast",
|
| 341 |
"-movflags", "+faststart",
|
| 342 |
-
"-x264-params", "log-level=error",
|
| 343 |
-
"-hide_banner",
|
| 344 |
-
"-loglevel", "error",
|
| 345 |
output_video
|
| 346 |
]
|
| 347 |
-
|
| 348 |
-
# 执行并计时
|
| 349 |
-
logger.info("Starting video processing with optimized subtitles")
|
| 350 |
start_time = time.time()
|
| 351 |
subprocess.run(cmd_combined, check=True)
|
| 352 |
-
logger.info(f"Video
|
| 353 |
# 清理临时文件
|
| 354 |
os.remove(temp_video)
|
| 355 |
-
Path(frames_list).unlink(missing_ok=True)
|
| 356 |
return output_video
|
|
|
|
|
|
|
|
|
|
|
|
|
| 357 |
except Exception as e:
|
| 358 |
-
logger.error(f"
|
| 359 |
-
logger.debug(f"Filter chain used: {combined_filter}")
|
| 360 |
return None
|
| 361 |
|
| 362 |
# 使用本地存储
|
|
|
|
| 18 |
from pydub import AudioSegment
|
| 19 |
import shlex
|
| 20 |
from ffmpeg import probe as ffmpeg_probe # 需要安装ffmpeg-python包
|
| 21 |
+
import time
|
| 22 |
|
| 23 |
# 配置日志
|
| 24 |
logging.basicConfig(level=logging.INFO)
|
|
|
|
| 253 |
return None, {}, [], []
|
| 254 |
|
| 255 |
def get_video_dimensions(video_path):
|
|
|
|
| 256 |
try:
|
| 257 |
+
result = subprocess.run(
|
| 258 |
+
["ffprobe", "-v", "error", "-select_streams", "v:0",
|
| 259 |
+
"-show_entries", "stream=width,height", "-of", "json", video_path],
|
| 260 |
+
capture_output=True,
|
| 261 |
+
text=True
|
| 262 |
+
)
|
| 263 |
+
data = json.loads(result.stdout)
|
| 264 |
+
return (int(data['streams'][0]['width']),
|
| 265 |
+
int(data['streams'][0]['height']))
|
| 266 |
except Exception as e:
|
| 267 |
+
logger.warning(f"Video dimension detection failed: {e}")
|
| 268 |
return (1920, 1080)
|
| 269 |
|
| 270 |
+
def process_sub_path(path):
|
| 271 |
+
# 统一处理所有特殊字符
|
| 272 |
+
return shlex.quote(
|
| 273 |
+
str(Path(path).resolve())
|
| 274 |
+
.replace(':', '\\:')
|
| 275 |
+
.replace(' ', '\\ ')
|
| 276 |
+
.replace('(', '\\(')
|
| 277 |
+
.replace(')', '\\)')
|
| 278 |
+
)
|
| 279 |
+
|
| 280 |
# 创建视频
|
| 281 |
+
def create_video(project_dir, image_paths, caption_subtitle_file, speech_subtitle_file,
|
| 282 |
+
audio_file, output_video, audio_durations, panel_start_times, panel_durations):
|
| 283 |
try:
|
| 284 |
# 创建帧列表文件 - 使用精确的面板持续时间
|
| 285 |
frames_list = os.path.join(project_dir, "frames.txt")
|
|
|
|
| 305 |
]
|
| 306 |
subprocess.run(cmd1, check=True)
|
| 307 |
|
| 308 |
+
# 获取视频尺寸(使用改进后的方法)
|
| 309 |
video_width, video_height = get_video_dimensions(temp_video)
|
| 310 |
base_fontsize = max(24, video_width // 50)
|
| 311 |
+
# 构建滤镜链
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 312 |
combined_filter = (
|
| 313 |
f"subtitles={process_sub_path(caption_subtitle_file)}:"
|
| 314 |
+
"force_style='Fontsize={},Alignment=2,MarginV={},Outline=1'".format(
|
| 315 |
+
int(base_fontsize*0.9),
|
| 316 |
+
video_height//10
|
| 317 |
+
),
|
|
|
|
|
|
|
|
|
|
| 318 |
f"subtitles={process_sub_path(speech_subtitle_file)}:"
|
| 319 |
+
"force_style='Fontsize={},Alignment=8,MarginV={},Outline=1'".format(
|
| 320 |
+
base_fontsize,
|
| 321 |
+
video_height//12
|
| 322 |
+
)
|
|
|
|
|
|
|
|
|
|
| 323 |
)
|
| 324 |
+
filter_chain = ",".join(combined_filter)
|
| 325 |
+
# 优化ffmpeg命令
|
| 326 |
cmd_combined = [
|
| 327 |
"ffmpeg", "-y",
|
| 328 |
"-i", temp_video,
|
| 329 |
+
"-vf", filter_chain,
|
| 330 |
"-c:a", "copy",
|
| 331 |
"-c:v", "libx264",
|
| 332 |
"-preset", "fast",
|
| 333 |
"-movflags", "+faststart",
|
|
|
|
|
|
|
|
|
|
| 334 |
output_video
|
| 335 |
]
|
| 336 |
+
# 添加执行计时
|
|
|
|
|
|
|
| 337 |
start_time = time.time()
|
| 338 |
subprocess.run(cmd_combined, check=True)
|
| 339 |
+
logger.info(f"Video processed in {time.time()-start_time:.2f}s")
|
| 340 |
# 清理临时文件
|
| 341 |
os.remove(temp_video)
|
|
|
|
| 342 |
return output_video
|
| 343 |
+
except subprocess.CalledProcessError as e:
|
| 344 |
+
logger.error(f"FFmpeg failed with cmd: {' '.join(e.cmd)}")
|
| 345 |
+
logger.error(f"FFmpeg stderr: {e.stderr}")
|
| 346 |
+
return None
|
| 347 |
except Exception as e:
|
| 348 |
+
logger.error(f"Unexpected error: {str(e)}")
|
|
|
|
| 349 |
return None
|
| 350 |
|
| 351 |
# 使用本地存储
|