jcnok commited on
Commit
6ae0803
·
verified ·
1 Parent(s): 131c8cf

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +30 -57
app.py CHANGED
@@ -48,19 +48,37 @@ def cleanup_directory(directory_path: str):
48
  shutil.rmtree(directory_path, ignore_errors=True)
49
  print(f"--- CLEANUP: Successfully removed {directory_path} ---")
50
 
51
- async def run_subprocess(command: str):
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  print(f"Executing command: {command}")
53
  process = await asyncio.create_subprocess_shell(
54
- command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
 
 
55
  )
56
  stdout, stderr = await process.communicate()
57
 
58
  if process.returncode != 0:
59
  error_message = stderr.decode(errors='ignore')
60
- print(f"FFmpeg Error: {error_message}")
 
61
  raise RuntimeError(f"FFmpeg command failed: {error_message}")
62
 
63
- print("Command executed successfully.")
 
 
64
 
65
  # ==============================================================================
66
  # 3. API ENDPOINTS
@@ -143,82 +161,37 @@ async def process_video_from_urls(payload: Dict = Body(...)):
143
  async def get_duration(file: UploadFile = File(...)):
144
  """
145
  Calculates the duration of a media file using FFprobe.
146
-
147
- This endpoint accepts a binary file upload, saves it temporarily, runs
148
- ffprobe to get its duration, and returns the duration in seconds.
149
- It ensures temporary files are always cleaned up.
150
-
151
- Args:
152
- file (UploadFile): The media file (audio or video) uploaded by the client.
153
-
154
- Returns:
155
- JSONResponse: A JSON object containing the duration in seconds,
156
- e.g., {"duration_seconds": 146.5}.
157
-
158
- Raises:
159
- HTTPException(400): If the duration cannot be parsed from ffprobe's output.
160
- HTTPException(500): If ffprobe fails or an internal error occurs.
161
  """
162
- # --- Setup Temporary Environment ---
163
- # Create a unique filename to prevent conflicts
164
  unique_id = str(uuid.uuid4())
165
  temp_input_path = os.path.join(TEMP_DIR, f"{unique_id}_{file.filename}")
166
 
167
  try:
168
- # --- Step 1: Save Uploaded File ---
169
- print(f"Saving uploaded file to {temp_input_path}...")
170
- try:
171
- with open(temp_input_path, "wb") as buffer:
172
- shutil.copyfileobj(file.file, buffer)
173
- print("File saved successfully.")
174
- except Exception as e:
175
- # Handle potential file writing errors
176
- raise HTTPException(status_code=500, detail=f"Failed to write uploaded file to disk: {e}")
177
-
178
- # --- Step 2: Construct and Execute FFprobe Command ---
179
- # Using shlex.quote for security against command injection
180
  quoted_path = shlex.quote(temp_input_path)
181
-
182
- # This ffprobe command is optimized to return ONLY the duration number
183
  command = f"ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 {quoted_path}"
184
 
185
- if not await run_subprocess(command):
186
- # The run_subprocess helper now handles logging the error
187
- raise HTTPException(status_code=500, detail="FFprobe command failed. Check server logs.")
188
-
189
- # Re-run the command to capture the output, as the helper only returns success/fail
190
- process = await asyncio.create_subprocess_shell(
191
- command,
192
- stdout=asyncio.subprocess.PIPE,
193
- stderr=asyncio.subprocess.PIPE
194
- )
195
- stdout, stderr = await process.communicate()
196
 
197
- duration_str = stdout.decode().strip()
198
  print(f"FFprobe output (duration): '{duration_str}'")
199
 
200
- # --- Step 3: Parse and Return Duration ---
201
  if not duration_str:
202
- raise ValueError("FFprobe returned an empty string.")
203
 
204
  duration = float(duration_str)
205
  return JSONResponse(content={"duration_seconds": duration})
206
 
207
  except ValueError:
208
- # This catches errors from float() if ffprobe's output is not a number
209
  raise HTTPException(status_code=400, detail="Could not parse a valid duration from the media file.")
210
-
211
  except Exception as e:
212
- # Catch-all for any other unexpected errors
213
- print(f"An unexpected error occurred in get_duration: {e}")
214
  raise HTTPException(status_code=500, detail=f"An internal server error occurred: {str(e)}")
215
-
216
  finally:
217
- # --- Final Step: Cleanup ---
218
- # This block ALWAYS runs, ensuring no temporary files are left behind.
219
  if os.path.exists(temp_input_path):
220
  os.remove(temp_input_path)
221
- print(f"Cleaned up temporary file: {temp_input_path}")
222
 
223
  # ==============================================================================
224
  # 4. ENDPOINT PARA ADICIONAR LEGENDAS
 
48
  shutil.rmtree(directory_path, ignore_errors=True)
49
  print(f"--- CLEANUP: Successfully removed {directory_path} ---")
50
 
51
+ async def run_subprocess(command: str) -> (str, str):
52
+ """
53
+ Executes a shell command asynchronously and returns its outputs.
54
+ Raises an exception if the command fails.
55
+
56
+ Args:
57
+ command (str): The shell command to execute.
58
+
59
+ Returns:
60
+ tuple[str, str]: A tuple containing (stdout, stderr).
61
+
62
+ Raises:
63
+ RuntimeError: If the command returns a non-zero exit code.
64
+ """
65
  print(f"Executing command: {command}")
66
  process = await asyncio.create_subprocess_shell(
67
+ command,
68
+ stdout=asyncio.subprocess.PIPE,
69
+ stderr=asyncio.subprocess.PIPE
70
  )
71
  stdout, stderr = await process.communicate()
72
 
73
  if process.returncode != 0:
74
  error_message = stderr.decode(errors='ignore')
75
+ print(f"ERROR: Command failed with exit code {process.returncode}\nStderr: {error_message}")
76
+ # Levanta uma exceção para que a função que a chamou possa tratá-la.
77
  raise RuntimeError(f"FFmpeg command failed: {error_message}")
78
 
79
+ print("SUCCESS: Command executed.")
80
+ # Retorna os outputs para a função que a chamou.
81
+ return stdout.decode(errors='ignore'), stderr.decode(errors='ignore')
82
 
83
  # ==============================================================================
84
  # 3. API ENDPOINTS
 
161
  async def get_duration(file: UploadFile = File(...)):
162
  """
163
  Calculates the duration of a media file using FFprobe.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
164
  """
 
 
165
  unique_id = str(uuid.uuid4())
166
  temp_input_path = os.path.join(TEMP_DIR, f"{unique_id}_{file.filename}")
167
 
168
  try:
169
+ with open(temp_input_path, "wb") as buffer:
170
+ shutil.copyfileobj(file.file, buffer)
171
+
 
 
 
 
 
 
 
 
 
172
  quoted_path = shlex.quote(temp_input_path)
 
 
173
  command = f"ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 {quoted_path}"
174
 
175
+ # Agora, run_subprocess retorna o stdout se for bem-sucedido.
176
+ stdout, _ = await run_subprocess(command)
 
 
 
 
 
 
 
 
 
177
 
178
+ duration_str = stdout.strip()
179
  print(f"FFprobe output (duration): '{duration_str}'")
180
 
 
181
  if not duration_str:
182
+ raise ValueError("FFprobe returned an empty string for duration.")
183
 
184
  duration = float(duration_str)
185
  return JSONResponse(content={"duration_seconds": duration})
186
 
187
  except ValueError:
 
188
  raise HTTPException(status_code=400, detail="Could not parse a valid duration from the media file.")
 
189
  except Exception as e:
190
+ # Captura qualquer outro erro, incluindo o RuntimeError do run_subprocess.
 
191
  raise HTTPException(status_code=500, detail=f"An internal server error occurred: {str(e)}")
 
192
  finally:
 
 
193
  if os.path.exists(temp_input_path):
194
  os.remove(temp_input_path)
 
195
 
196
  # ==============================================================================
197
  # 4. ENDPOINT PARA ADICIONAR LEGENDAS