denizaybey commited on
Commit
04c3abb
·
2 Parent(s): 9989a8a b5e3557

Merge branch 'content-moderation-implementation' into 'main'

Browse files

Add video processing functionality with OpenCV and Modal integration

See merge request sonne-technology/bsod-tv/waveform-matching-gradio-front-end!21

Files changed (2) hide show
  1. app.py +89 -21
  2. requirements.txt +2 -1
app.py CHANGED
@@ -6,8 +6,11 @@
6
 
7
 
8
  import os
 
9
  import time
 
10
  import modal
 
11
  import logging
12
  import gradio as gr
13
 
@@ -20,11 +23,11 @@ def process_audio(original_audio_path, dubbed_audio_path, email, company_name, t
20
  file upload to presigned URLs, and triggering the processing.
21
  """
22
  # 1. Check the duration of both audio files.
23
- app = modal.App("Waveform-Matching")
24
  modal_token_id = os.environ['MODAL_TOKEN_ID']
25
  modal_token_secret = os.environ['MODAL_TOKEN_SECRET']
26
  modal_environment = os.environ['MODAL_ENVIRONMENT']
27
- modal_volume = os.environ['MODAL_VOLUME']
28
  processing_id = str(int(time.time()))
29
  try:
30
  bsodtv_storage = modal.Volume.from_name(modal_volume)
@@ -50,29 +53,94 @@ def process_audio(original_audio_path, dubbed_audio_path, email, company_name, t
50
  return "Processing started. Results will be emailed to you shortly."
51
 
52
 
53
- def process_video(video_path, notes, email, company_name):
54
  """
55
- Process the input video for content moderation.
56
- Inputs:
57
- - video_path: path to the uploaded video file (str)
58
- - notes: department notes (str)
59
- - email: contact email (str)
60
- - company_name: company name (str)
61
- Output:
62
- - path to the output video file (str)
63
- For now, this is a placeholder that returns the input video unchanged.
64
  """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  try:
66
- # In a real implementation, you would trigger your backend processing here.
67
- # For this demo, we simply return the original video path as the output.
68
- if not video_path:
69
- # Gradio expects None to clear the output component
70
- return None
71
- return video_path
72
- except Exception as e:
73
- logger.error(f"Error during video processing: {e}")
74
- return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
 
 
 
 
76
 
77
  # Create a professional Gradio interface using the Golden ratio (1.618) for proportions
78
  # Define custom CSS for a professional look
 
6
 
7
 
8
  import os
9
+ import cv2
10
  import time
11
+ import uuid
12
  import modal
13
+ import shutil
14
  import logging
15
  import gradio as gr
16
 
 
23
  file upload to presigned URLs, and triggering the processing.
24
  """
25
  # 1. Check the duration of both audio files.
26
+ waveform_app = modal.App("Waveform-Matching")
27
  modal_token_id = os.environ['MODAL_TOKEN_ID']
28
  modal_token_secret = os.environ['MODAL_TOKEN_SECRET']
29
  modal_environment = os.environ['MODAL_ENVIRONMENT']
30
+ modal_volume = os.environ['WAVEFORM_MODAL_VOLUME']
31
  processing_id = str(int(time.time()))
32
  try:
33
  bsodtv_storage = modal.Volume.from_name(modal_volume)
 
53
  return "Processing started. Results will be emailed to you shortly."
54
 
55
 
56
+ def process_video(video_path, notes, email, company_name) -> str:
57
  """
58
+ Process the input video for content moderation using Modal.
59
+ Steps:
60
+ 1. Upload the provided video to the configured Modal Volume.
61
+ 2. Obtain the video dimensions (width, height).
62
+ 3. Call the Content-Moderation reception_function via Modal (synchronously with .remote).
63
+ 4. Download the processed video returned by the function to /tmp with a random UUID filename.
64
+ 5. Return the local path to the downloaded video.
 
 
65
  """
66
+ # Validate inputs
67
+ if not video_path or not os.path.exists(video_path):
68
+ logger.error("Invalid video path provided to process_video.")
69
+ return "Invalid video path."
70
+
71
+ # Helper to obtain width and height
72
+ def _get_video_dimensions(path: str):
73
+ try:
74
+ # type: ignore
75
+ cap = cv2.VideoCapture(path)
76
+ if cap.isOpened():
77
+ width = int(cap.get(3))
78
+ height = int(cap.get(4))
79
+ cap.release()
80
+ except Exception as e:
81
+ logger.debug(f"OpenCV not available or failed to read video dimensions: {e}")
82
+ return width, height
83
+
84
  try:
85
+ # 1. Setup Modal app and volume
86
+ moderation_app = modal.App("Content-Moderation")
87
+ _ = os.environ.get('MODAL_TOKEN_ID') # Read to ensure environment readiness (kept for parity with process_audio)
88
+ _ = os.environ.get('MODAL_TOKEN_SECRET')
89
+ _ = os.environ.get('MODAL_ENVIRONMENT')
90
+ modal_volume_name = os.environ['MODERATION_MODAL_VOLUME']
91
+
92
+ # Unique processing folder and paths
93
+ processing_id = str(int(time.time()))
94
+ ext = os.path.splitext(video_path)[1]
95
+ remote_input_path = f"/{processing_id}/input_video{ext}"
96
+
97
+ # 2. Upload video to Modal Volume
98
+ volume = modal.Volume.from_name(modal_volume_name)
99
+ try:
100
+ with volume.batch_upload() as batch:
101
+ batch.put_file(video_path, remote_input_path)
102
+ except Exception as e:
103
+ logger.error(f"Error uploading video to Modal Storage: {e}")
104
+ return "Error uploading video to Cloud Storage."
105
+
106
+ # 3. Obtain video dimensions
107
+ width, height = _get_video_dimensions(video_path)
108
+
109
+ # 4. Call Modal function synchronously
110
+ try:
111
+ moderation_function = modal.Function.from_name("Content-Moderation", "reception_function")
112
+ processed_remote_path = moderation_function.remote(
113
+ input_text=str(notes) if notes is not None else "",
114
+ video_path=remote_input_path,
115
+ size=(int(width), int(height)),
116
+ )
117
+ except Exception as e:
118
+ logger.error(f"Error calling Modal reception_function: {e}")
119
+ return "Error calling Outpost to trigger processing."
120
+
121
+ if not processed_remote_path or not isinstance(processed_remote_path, str):
122
+ logger.error("Modal function did not return a valid path to the processed video.")
123
+ return "Processing failed to return an output path."
124
+
125
+ # 5. Download the processed video to /tmp with UUID filename
126
+ local_ext = os.path.splitext(processed_remote_path)[1] or ext or ".mp4"
127
+ local_output_path = f"/tmp/{uuid.uuid4().hex}{local_ext}"
128
+ try:
129
+ # Use Modal Volume.read_file to stream the remote file to the local path
130
+ with open(local_output_path, "wb") as dst:
131
+ for chunk in volume.read_file(processed_remote_path):
132
+ if chunk:
133
+ dst.write(chunk)
134
+ except Exception as e:
135
+ logger.error(f"Error downloading processed video from Modal Storage using read_file: {e}")
136
+ return "Error downloading processed video from Cloud Storage."
137
+
138
+ # 6. Return local path
139
+ return local_output_path
140
 
141
+ except Exception as e:
142
+ logger.error(f"Unexpected error in process_video: {e}")
143
+ return "Unexpected error during video processing."
144
 
145
  # Create a professional Gradio interface using the Golden ratio (1.618) for proportions
146
  # Define custom CSS for a professional look
requirements.txt CHANGED
@@ -1,2 +1,3 @@
1
  modal
2
- gradio
 
 
1
  modal
2
+ gradio
3
+ opencv-python-headless