sam12345324 commited on
Commit
bf60d22
·
verified ·
1 Parent(s): 7c25f19

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +64 -26
main.py CHANGED
@@ -7,46 +7,81 @@ import logging
7
  import traceback
8
  from moviepy.editor import (
9
  VideoFileClip,
 
10
  AudioFileClip,
11
  CompositeAudioClip,
12
- concatenate_videoclips,
13
  )
14
 
15
  # Configure logging
16
  logging.basicConfig(level=logging.INFO)
17
  logger = logging.getLogger(__name__)
18
 
19
- def merge_videos_and_audios(video_paths, audio_paths, output_path):
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  try:
21
  # Load video clips
22
- video_clips = [VideoFileClip(vp) for vp in video_paths]
23
 
24
- # Load audio clips, only if valid files
25
  audio_clips = []
26
- for ap in audio_paths:
27
  try:
28
- audio_clip = AudioFileClip(ap)
29
- audio_clips.append(audio_clip)
30
  except Exception as e:
31
- logger.warning(f"Could not load audio file {ap}: {e}")
32
 
33
- # Combine all audio clips into one composite audio clip (plays simultaneously)
34
  if audio_clips:
35
- final_audio = CompositeAudioClip(audio_clips)
36
  else:
37
  final_audio = None
38
 
39
- # Concatenate video clips
40
  final_video = concatenate_videoclips(video_clips, method="compose")
41
 
42
- # Set the audio if available
43
- if final_audio is not None:
44
- final_video = final_video.set_audio(final_audio)
45
-
46
- # Write output video file
47
- final_video.write_videofile(output_path, codec="libx264", audio_codec="aac")
48
 
49
- # Close clips to release resources
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  final_video.close()
51
  for vc in video_clips:
52
  vc.close()
@@ -65,6 +100,8 @@ app = FastAPI()
65
  @app.post("/merge")
66
  async def merge_endpoint(
67
  files: list[UploadFile] = File(...),
 
 
68
  ):
69
  temp_dir = tempfile.mkdtemp()
70
  try:
@@ -85,20 +122,21 @@ async def merge_endpoint(
85
  return {"error": "Please upload at least 2 files (videos or audios)."}
86
 
87
  output_file = os.path.join(temp_dir, "merged_output.mp4")
88
- result_path = merge_videos_and_audios(video_files, audio_files, output_file)
 
 
 
89
 
90
  if isinstance(result_path, str) and result_path.startswith("Error"):
91
  return {"error": result_path}
92
 
93
- return FileResponse(result_path, media_type="video/mp4", filename="merged_output.mp4")
 
 
 
94
 
95
  finally:
96
- # Delay deleting temp_dir to allow FileResponse to send file (FastAPI streams file)
97
- # So do NOT delete temp_dir immediately here if you want to serve the file correctly.
98
- # Instead, you might want a background task or periodic cleanup.
99
- # For now, comment out or remove this line if the file disappears before sending:
100
- # shutil.rmtree(temp_dir)
101
- pass
102
 
103
  # Optional: log space public URL
104
  def log_api_url():
 
7
  import traceback
8
  from moviepy.editor import (
9
  VideoFileClip,
10
+ concatenate_videoclips,
11
  AudioFileClip,
12
  CompositeAudioClip,
13
+ concatenate_audioclips,
14
  )
15
 
16
  # Configure logging
17
  logging.basicConfig(level=logging.INFO)
18
  logger = logging.getLogger(__name__)
19
 
20
+ def safe_audio_clip(path):
21
+ try:
22
+ clip = AudioFileClip(path)
23
+ if clip.reader is None or clip.duration is None or clip.duration <= 0:
24
+ clip.close()
25
+ raise ValueError(f"Invalid or empty audio file: {os.path.basename(path)}")
26
+ return clip
27
+ except Exception as e:
28
+ raise ValueError(f"Failed to load audio file '{os.path.basename(path)}': {str(e)}")
29
+
30
+ def merge_videos_and_audios(
31
+ video_files, audio_files, output_path, temp_dir,
32
+ orig_vol=1.0, music_vol=0.5,
33
+ ):
34
  try:
35
  # Load video clips
36
+ video_clips = [VideoFileClip(v) for v in video_files]
37
 
38
+ # Load audio clips safely
39
  audio_clips = []
40
+ for a in audio_files:
41
  try:
42
+ clip = safe_audio_clip(a)
43
+ audio_clips.append(clip)
44
  except Exception as e:
45
+ logger.warning(f"Skipping invalid audio file: {a} {str(e)}")
46
 
47
+ # Prepare final audio composite if audio clips exist
48
  if audio_clips:
49
+ final_audio = concatenate_audioclips(audio_clips).volumex(music_vol)
50
  else:
51
  final_audio = None
52
 
53
+ # Concatenate video clips with method 'compose' for safety
54
  final_video = concatenate_videoclips(video_clips, method="compose")
55
 
56
+ # Combine video audio with provided original volume
57
+ original_audio = final_video.audio.volumex(orig_vol) if final_video.audio else None
 
 
 
 
58
 
59
+ # Combine audios if both exist
60
+ if original_audio and final_audio:
61
+ composite_audio = CompositeAudioClip([original_audio, final_audio])
62
+ final_video = final_video.set_audio(composite_audio)
63
+ elif final_audio:
64
+ final_video = final_video.set_audio(final_audio)
65
+ elif original_audio:
66
+ final_video = final_video.set_audio(original_audio)
67
+ else:
68
+ final_video = final_video.set_audio(None)
69
+
70
+ # Explicit writable temp audio file path inside temp_dir
71
+ temp_audiofile_path = os.path.join(temp_dir, "temp_audiofile.m4a")
72
+
73
+ # Write output video with explicit temp audio file to avoid permission errors
74
+ final_video.write_videofile(
75
+ output_path,
76
+ codec="libx264",
77
+ audio_codec="aac",
78
+ temp_audiofile=temp_audiofile_path,
79
+ remove_temp=True,
80
+ verbose=False,
81
+ logger=None,
82
+ )
83
+
84
+ # Cleanup
85
  final_video.close()
86
  for vc in video_clips:
87
  vc.close()
 
100
  @app.post("/merge")
101
  async def merge_endpoint(
102
  files: list[UploadFile] = File(...),
103
+ orig_vol: float = Form(1.0),
104
+ music_vol: float = Form(0.5),
105
  ):
106
  temp_dir = tempfile.mkdtemp()
107
  try:
 
122
  return {"error": "Please upload at least 2 files (videos or audios)."}
123
 
124
  output_file = os.path.join(temp_dir, "merged_output.mp4")
125
+
126
+ result_path = merge_videos_and_audios(
127
+ video_files, audio_files, output_file, temp_dir, orig_vol, music_vol
128
+ )
129
 
130
  if isinstance(result_path, str) and result_path.startswith("Error"):
131
  return {"error": result_path}
132
 
133
+ media_type = "video/mp4" if result_path.lower().endswith(".mp4") else "audio/mpeg"
134
+ filename = os.path.basename(result_path)
135
+
136
+ return FileResponse(result_path, media_type=media_type, filename=filename)
137
 
138
  finally:
139
+ shutil.rmtree(temp_dir)
 
 
 
 
 
140
 
141
  # Optional: log space public URL
142
  def log_api_url():