sam12345324 commited on
Commit
46bdb10
·
verified ·
1 Parent(s): 59fea23

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +102 -36
main.py CHANGED
@@ -1,5 +1,5 @@
1
- from fastapi import FastAPI, UploadFile, File, Form
2
- from fastapi.responses import FileResponse
3
  import tempfile
4
  import shutil
5
  import os
@@ -10,13 +10,28 @@ from moviepy.editor import (
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("main")
19
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  # Safe audio clip loader
21
  def safe_audio_clip(path):
22
  try:
@@ -28,40 +43,58 @@ def safe_audio_clip(path):
28
  except Exception as e:
29
  raise ValueError(f"Failed to load audio file '{os.path.basename(path)}': {str(e)}")
30
 
31
- def merge_videos_and_audios(video_paths, audio_paths, output_path, temp_dir):
 
 
32
  # Load video clips
33
- video_clips = [VideoFileClip(vp) for vp in video_paths]
 
 
 
 
 
 
 
 
 
34
 
35
- # Load audio clips, only if valid files
36
  audio_clips = []
37
  for ap in audio_paths:
38
  try:
39
- audio_clip = safe_audio_clip(ap)
40
- audio_clips.append(audio_clip)
 
41
  except Exception as e:
42
  logger.warning(f"Skipping invalid audio file {ap}: {e}")
43
 
44
- # Combine all audio clips into one composite audio clip (optional)
45
- if audio_clips:
46
- final_audio = CompositeAudioClip(audio_clips)
47
- else:
48
- final_audio = None
49
-
50
  # Concatenate video clips
51
  final_video = concatenate_videoclips(video_clips, method="compose")
52
 
53
- # Set the audio if available
54
- if final_audio is not None:
 
55
  final_video = final_video.set_audio(final_audio)
 
 
 
 
56
 
57
- # Write output — FIX: use temp_audiofile in /tmp
58
- final_video.write_videofile(
59
- output_path,
60
- codec="libx264",
61
- audio_codec="aac",
62
- temp_audiofile=os.path.join(temp_dir, "temp-audio.m4a"),
63
- remove_temp=True
64
- )
 
 
 
 
 
 
 
65
 
66
  # Close clips to release resources
67
  final_video.close()
@@ -77,45 +110,78 @@ async def merge_endpoint(
77
  files: list[UploadFile] = File(...),
78
  orig_vol: float = Form(1.0),
79
  music_vol: float = Form(0.5),
 
80
  ):
81
  temp_dir = tempfile.mkdtemp()
 
 
82
  try:
83
  saved_files = []
84
 
85
  # Save uploaded files
86
  for uploaded_file in files:
 
 
 
87
  file_path = os.path.join(temp_dir, uploaded_file.filename)
88
  with open(file_path, "wb") as out_file:
89
  content = await uploaded_file.read()
 
 
 
90
  out_file.write(content)
91
  saved_files.append(file_path)
 
92
 
93
  # Separate video and audio files
94
  video_files = [f for f in saved_files if f.lower().endswith(".mp4")]
95
  audio_files = [
96
- f
97
- for f in saved_files
98
- if f.lower().endswith((".mp3", ".wav", ".aac", ".m4a", ".ogg"))
99
  ]
 
 
100
 
101
- if len(saved_files) < 2:
102
- return {"error": "Please upload at least 2 files (videos or audios)."}
103
 
104
  # Prepare output path
105
  output_path = os.path.join(temp_dir, "merged_output.mp4")
106
 
107
  # Merge videos and audios
108
- merge_videos_and_audios(video_files, audio_files, output_path, temp_dir)
109
-
110
- # Return merged file
111
- return FileResponse(output_path, media_type="video/mp4", filename="merged_output.mp4")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
 
113
  except Exception as e:
114
  error_msg = f"Error: {str(e)}\n\n{traceback.format_exc()}"
115
  logger.error(error_msg)
116
- return {"error": error_msg}
117
 
118
  finally:
 
 
119
  shutil.rmtree(temp_dir, ignore_errors=True)
120
 
121
  # Optional: log public URL
@@ -126,4 +192,4 @@ def log_api_url():
126
  else:
127
  logger.info("SPACE_PUBLIC_URL environment variable not found")
128
 
129
- log_api_url()
 
1
+ from fastapi import FastAPI, UploadFile, File, Form, HTTPException, BackgroundTasks
2
+ from fastapi.responses import StreamingResponse
3
  import tempfile
4
  import shutil
5
  import os
 
10
  concatenate_videoclips,
11
  AudioFileClip,
12
  CompositeAudioClip,
 
13
  )
14
+ import io
15
 
16
  # Configure logging
17
+ logging.basicConfig(level=logging.DEBUG) # Set to DEBUG for detailed output
18
  logger = logging.getLogger("main")
19
 
20
+ # Enable MoviePy verbose logging
21
+ from moviepy.config import change_settings
22
+ change_settings({"VERBOSE": True})
23
+
24
+ # Safe video clip loader
25
+ def safe_video_clip(path):
26
+ try:
27
+ clip = VideoFileClip(path)
28
+ if clip.reader is None or clip.duration is None or clip.duration <= 0:
29
+ clip.close()
30
+ raise ValueError(f"Invalid or empty video file: {os.path.basename(path)}")
31
+ return clip
32
+ except Exception as e:
33
+ raise ValueError(f"Failed to load video file '{os.path.basename(path)}': {str(e)}")
34
+
35
  # Safe audio clip loader
36
  def safe_audio_clip(path):
37
  try:
 
43
  except Exception as e:
44
  raise ValueError(f"Failed to load audio file '{os.path.basename(path)}': {str(e)}")
45
 
46
+ def merge_videos_and_audios(video_paths, audio_paths, output_path, temp_dir, orig_vol=1.0, music_vol=0.5):
47
+ logger.debug(f"Merging {len(video_paths)} videos and {len(audio_paths)} audios to {output_path}")
48
+
49
  # Load video clips
50
+ video_clips = []
51
+ for vp in video_paths:
52
+ try:
53
+ clip = safe_video_clip(vp)
54
+ video_clips.append(clip)
55
+ except Exception as e:
56
+ logger.warning(f"Skipping invalid video file {vp}: {e}")
57
+
58
+ if not video_clips:
59
+ raise ValueError("No valid video files provided")
60
 
61
+ # Load audio clips
62
  audio_clips = []
63
  for ap in audio_paths:
64
  try:
65
+ clip = safe_audio_clip(ap)
66
+ clip = clip.volumex(music_vol) # Apply volume adjustment
67
+ audio_clips.append(clip)
68
  except Exception as e:
69
  logger.warning(f"Skipping invalid audio file {ap}: {e}")
70
 
 
 
 
 
 
 
71
  # Concatenate video clips
72
  final_video = concatenate_videoclips(video_clips, method="compose")
73
 
74
+ # Combine audio clips if available
75
+ if audio_clips:
76
+ final_audio = CompositeAudioClip(audio_clips)
77
  final_video = final_video.set_audio(final_audio)
78
+ else:
79
+ # Optionally adjust original video audio volume
80
+ if final_video.audio:
81
+ final_video.audio = final_video.audio.volumex(orig_vol)
82
 
83
+ # Write output
84
+ logger.debug(f"Writing output to {output_path}")
85
+ try:
86
+ final_video.write_videofile(
87
+ output_path,
88
+ codec="libx264",
89
+ audio_codec="aac",
90
+ temp_audiofile=os.path.join(temp_dir, "temp-audio.m4a"),
91
+ remove_temp=True,
92
+ verbose=True,
93
+ logger='bar'
94
+ )
95
+ except Exception as e:
96
+ logger.error(f"MoviePy failed to write output: {str(e)}")
97
+ raise
98
 
99
  # Close clips to release resources
100
  final_video.close()
 
110
  files: list[UploadFile] = File(...),
111
  orig_vol: float = Form(1.0),
112
  music_vol: float = Form(0.5),
113
+ background_tasks: BackgroundTasks = BackgroundTasks(),
114
  ):
115
  temp_dir = tempfile.mkdtemp()
116
+ logger.debug(f"Created temporary directory: {temp_dir}")
117
+
118
  try:
119
  saved_files = []
120
 
121
  # Save uploaded files
122
  for uploaded_file in files:
123
+ if not uploaded_file.filename:
124
+ logger.warning("Skipping file with no filename")
125
+ continue
126
  file_path = os.path.join(temp_dir, uploaded_file.filename)
127
  with open(file_path, "wb") as out_file:
128
  content = await uploaded_file.read()
129
+ if not content:
130
+ logger.warning(f"Empty file uploaded: {uploaded_file.filename}")
131
+ continue
132
  out_file.write(content)
133
  saved_files.append(file_path)
134
+ logger.debug(f"Saved file: {file_path}")
135
 
136
  # Separate video and audio files
137
  video_files = [f for f in saved_files if f.lower().endswith(".mp4")]
138
  audio_files = [
139
+ f for f in saved_files if f.lower().endswith((".mp3", ".wav", ".aac", ".m4a", ".ogg"))
 
 
140
  ]
141
+ logger.debug(f"Video files: {video_files}")
142
+ logger.debug(f"Audio files: {audio_files}")
143
 
144
+ if len(video_files) < 1 and len(audio_files) < 1:
145
+ raise HTTPException(status_code=400, detail="Please upload at least one valid video or audio file")
146
 
147
  # Prepare output path
148
  output_path = os.path.join(temp_dir, "merged_output.mp4")
149
 
150
  # Merge videos and audios
151
+ merge_videos_and_audios(video_files, audio_files, output_path, temp_dir, orig_vol, music_vol)
152
+
153
+ # Verify the output file exists
154
+ if not os.path.exists(output_path):
155
+ logger.error(f"Output file not found: {output_path}")
156
+ raise HTTPException(status_code=500, detail="Failed to create merged video file")
157
+
158
+ # Read the file into memory
159
+ logger.debug(f"Reading output file: {output_path}")
160
+ with open(output_path, "rb") as file:
161
+ content = file.read()
162
+ if not content:
163
+ logger.error(f"Output file is empty: {output_path}")
164
+ raise HTTPException(status_code=500, detail="Output file is empty")
165
+
166
+ # Schedule cleanup as a background task
167
+ background_tasks.add_task(shutil.rmtree, temp_dir, ignore_errors=True)
168
+
169
+ # Return the file as a streaming response
170
+ logger.debug("Sending streaming response")
171
+ return StreamingResponse(
172
+ io.BytesIO(content),
173
+ media_type="video/mp4",
174
+ headers={"Content-Disposition": 'attachment; filename="merged_output.mp4"'}
175
+ )
176
 
177
  except Exception as e:
178
  error_msg = f"Error: {str(e)}\n\n{traceback.format_exc()}"
179
  logger.error(error_msg)
180
+ raise HTTPException(status_code=500, detail=error_msg)
181
 
182
  finally:
183
+ # Fallback cleanup in case background task fails
184
+ logger.debug(f"Cleaning up temporary directory: {temp_dir}")
185
  shutil.rmtree(temp_dir, ignore_errors=True)
186
 
187
  # Optional: log public URL
 
192
  else:
193
  logger.info("SPACE_PUBLIC_URL environment variable not found")
194
 
195
+ log_api_url()