Update main.py
Browse files
main.py
CHANGED
@@ -386,6 +386,43 @@ async def create_slideshow(request: Request):
|
|
386 |
|
387 |
# Download audio file
|
388 |
audio_file = await download_file(audio_url, ".mp3")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
389 |
|
390 |
# Create a text file with the list of images and their duration
|
391 |
images_list_path = os.path.join(temp_dir, "images_list.txt")
|
@@ -415,31 +452,57 @@ async def create_slideshow(request: Request):
|
|
415 |
print(f"FFmpeg slideshow creation error: {stderr.decode()}")
|
416 |
raise HTTPException(status_code=500, detail=f"FFmpeg slideshow creation failed: {stderr.decode()}")
|
417 |
|
418 |
-
# Add audio to the slideshow
|
419 |
-
|
420 |
-
|
421 |
-
|
422 |
-
|
423 |
-
|
424 |
-
|
425 |
-
|
426 |
-
|
427 |
-
|
428 |
-
|
429 |
-
|
430 |
-
_, stderr = await process.communicate()
|
431 |
|
432 |
-
|
433 |
-
|
434 |
-
|
|
|
|
|
|
|
|
|
435 |
|
436 |
# Clean up temporary files
|
437 |
for file in image_files:
|
438 |
-
os.
|
439 |
-
|
440 |
-
|
441 |
-
os.
|
442 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
443 |
|
444 |
# Return the URL path to the output file
|
445 |
return f"/output/{output_filename}"
|
|
|
386 |
|
387 |
# Download audio file
|
388 |
audio_file = await download_file(audio_url, ".mp3")
|
389 |
+
|
390 |
+
# Pre-process audio file to ensure it's valid
|
391 |
+
normalized_audio = os.path.join(temp_dir, "normalized_audio.wav")
|
392 |
+
audio_check_cmd = (
|
393 |
+
f'ffmpeg -i {audio_file} -af "aformat=sample_fmts=fltp:sample_rates=44100:channel_layouts=stereo" '
|
394 |
+
f'-y {normalized_audio}'
|
395 |
+
)
|
396 |
+
audio_process = await asyncio.create_subprocess_shell(
|
397 |
+
audio_check_cmd,
|
398 |
+
stdout=asyncio.subprocess.PIPE,
|
399 |
+
stderr=asyncio.subprocess.PIPE
|
400 |
+
)
|
401 |
+
_, audio_stderr = await audio_process.communicate()
|
402 |
+
|
403 |
+
# If audio normalization fails, create a silent audio track
|
404 |
+
if audio_process.returncode != 0:
|
405 |
+
print(f"Audio normalization failed: {audio_stderr.decode()}")
|
406 |
+
print("Creating silent audio track instead...")
|
407 |
+
|
408 |
+
# Calculate total video duration based on number of images and duration per image
|
409 |
+
total_duration = len(image_urls) * duration
|
410 |
+
|
411 |
+
silent_cmd = (
|
412 |
+
f'ffmpeg -f lavfi -i anullsrc=r=44100:cl=stereo -t {total_duration} '
|
413 |
+
f'-y {normalized_audio}'
|
414 |
+
)
|
415 |
+
silent_process = await asyncio.create_subprocess_shell(
|
416 |
+
silent_cmd,
|
417 |
+
stdout=asyncio.subprocess.PIPE,
|
418 |
+
stderr=asyncio.subprocess.PIPE
|
419 |
+
)
|
420 |
+
_, silent_stderr = await silent_process.communicate()
|
421 |
+
|
422 |
+
if silent_process.returncode != 0:
|
423 |
+
print(f"Silent audio creation failed: {silent_stderr.decode()}")
|
424 |
+
# Fall back to no audio if even silent audio fails
|
425 |
+
normalized_audio = None
|
426 |
|
427 |
# Create a text file with the list of images and their duration
|
428 |
images_list_path = os.path.join(temp_dir, "images_list.txt")
|
|
|
452 |
print(f"FFmpeg slideshow creation error: {stderr.decode()}")
|
453 |
raise HTTPException(status_code=500, detail=f"FFmpeg slideshow creation failed: {stderr.decode()}")
|
454 |
|
455 |
+
# Add audio to the slideshow only if normalized_audio is available
|
456 |
+
if normalized_audio:
|
457 |
+
final_cmd = (
|
458 |
+
f'ffmpeg -i {intermediate_video} -i {normalized_audio} '
|
459 |
+
f'-c:v copy -c:a aac -b:a 192k -shortest {output_path}'
|
460 |
+
)
|
461 |
+
process = await asyncio.create_subprocess_shell(
|
462 |
+
final_cmd,
|
463 |
+
stdout=asyncio.subprocess.PIPE,
|
464 |
+
stderr=asyncio.subprocess.PIPE
|
465 |
+
)
|
466 |
+
_, stderr = await process.communicate()
|
|
|
467 |
|
468 |
+
if process.returncode != 0:
|
469 |
+
print(f"FFmpeg audio addition error: {stderr.decode()}")
|
470 |
+
# If adding audio fails, use just the video
|
471 |
+
shutil.copy(intermediate_video, output_path)
|
472 |
+
else:
|
473 |
+
# If no normalized audio available, use just the video
|
474 |
+
shutil.copy(intermediate_video, output_path)
|
475 |
|
476 |
# Clean up temporary files
|
477 |
for file in image_files:
|
478 |
+
if os.path.exists(file):
|
479 |
+
os.remove(file)
|
480 |
+
|
481 |
+
if os.path.exists(images_list_path):
|
482 |
+
os.remove(images_list_path)
|
483 |
+
|
484 |
+
if os.path.exists(intermediate_video):
|
485 |
+
os.remove(intermediate_video)
|
486 |
+
|
487 |
+
if os.path.exists(audio_file):
|
488 |
+
os.remove(audio_file)
|
489 |
+
|
490 |
+
if normalized_audio and os.path.exists(normalized_audio):
|
491 |
+
os.remove(normalized_audio)
|
492 |
+
|
493 |
+
try:
|
494 |
+
os.rmdir(temp_dir)
|
495 |
+
except OSError:
|
496 |
+
# Directory might not be empty, try to clean up remaining files
|
497 |
+
for remaining_file in os.listdir(temp_dir):
|
498 |
+
try:
|
499 |
+
os.remove(os.path.join(temp_dir, remaining_file))
|
500 |
+
except:
|
501 |
+
pass
|
502 |
+
try:
|
503 |
+
os.rmdir(temp_dir)
|
504 |
+
except:
|
505 |
+
pass
|
506 |
|
507 |
# Return the URL path to the output file
|
508 |
return f"/output/{output_filename}"
|