hivecorp commited on
Commit
fad923e
·
verified ·
1 Parent(s): 739584d

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +283 -0
app.py ADDED
@@ -0,0 +1,283 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import requests
3
+ import json
4
+ import subprocess
5
+ import os
6
+ import tempfile
7
+ import shutil
8
+ from urllib.parse import urlparse
9
+ import time
10
+
11
+ def download_file(url, dest_path):
12
+ """Download a file from URL to destination path."""
13
+ try:
14
+ response = requests.get(url, stream=True, timeout=30)
15
+ response.raise_for_status()
16
+ with open(dest_path, 'wb') as f:
17
+ for chunk in response.iter_content(chunk_size=8192):
18
+ f.write(chunk)
19
+ return True
20
+ except Exception as e:
21
+ print(f"Error downloading {url}: {str(e)}")
22
+ return False
23
+
24
+ def extract_metadata(video_path):
25
+ """Extract metadata from video file using ffprobe."""
26
+ try:
27
+ cmd = [
28
+ 'ffprobe', '-v', 'quiet', '-print_format', 'json',
29
+ '-show_format', '-show_streams', video_path
30
+ ]
31
+ result = subprocess.run(cmd, capture_output=True, text=True)
32
+ if result.returncode == 0:
33
+ data = json.loads(result.stdout)
34
+ format_info = data.get('format', {})
35
+
36
+ metadata = {
37
+ 'duration': float(format_info.get('duration', 0)),
38
+ 'filesize': int(format_info.get('size', 0)),
39
+ 'bitrate': int(format_info.get('bit_rate', 0)),
40
+ 'encoder': format_info.get('tags', {}).get('encoder', 'Unknown')
41
+ }
42
+ return metadata
43
+ except Exception as e:
44
+ print(f"Error extracting metadata: {str(e)}")
45
+ return {}
46
+
47
+ def generate_thumbnail(video_path, thumbnail_path):
48
+ """Generate thumbnail from video."""
49
+ try:
50
+ cmd = [
51
+ 'ffmpeg', '-i', video_path, '-ss', '00:00:01',
52
+ '-vframes', '1', '-q:v', '2', thumbnail_path, '-y'
53
+ ]
54
+ subprocess.run(cmd, capture_output=True)
55
+ return os.path.exists(thumbnail_path)
56
+ except:
57
+ return False
58
+
59
+ def process_ffmpeg_job(json_input, api_url=None):
60
+ """Process FFmpeg job from JSON input or API URL."""
61
+ try:
62
+ # Parse JSON input
63
+ if api_url and api_url.strip():
64
+ response = requests.get(api_url.strip(), timeout=30)
65
+ response.raise_for_status()
66
+ job_data = response.json()
67
+ else:
68
+ job_data = json.loads(json_input)
69
+
70
+ job_id = job_data.get('id', 'output')
71
+
72
+ # Create temporary directory for processing
73
+ with tempfile.TemporaryDirectory() as temp_dir:
74
+ # Download all input files
75
+ input_files = []
76
+ for i, input_item in enumerate(job_data.get('inputs', [])):
77
+ file_url = input_item.get('file_url')
78
+ if not file_url:
79
+ continue
80
+
81
+ # Determine file extension
82
+ parsed_url = urlparse(file_url)
83
+ filename = os.path.basename(parsed_url.path)
84
+ if not filename:
85
+ filename = f"input_{i}"
86
+
87
+ dest_path = os.path.join(temp_dir, f"{i}_{filename}")
88
+
89
+ print(f"Downloading {file_url}...")
90
+ if download_file(file_url, dest_path):
91
+ input_files.append(dest_path)
92
+ else:
93
+ return None, f"Failed to download: {file_url}", None
94
+
95
+ if not input_files:
96
+ return None, "No input files to process", None
97
+
98
+ # Build FFmpeg command
99
+ cmd = ['ffmpeg']
100
+
101
+ # Add input files
102
+ for input_file in input_files:
103
+ cmd.extend(['-i', input_file])
104
+
105
+ # Add filters
106
+ filter_complex = []
107
+ for filter_item in job_data.get('filters', []):
108
+ filter_str = filter_item.get('filter', '')
109
+ if filter_str:
110
+ filter_complex.append(filter_str)
111
+
112
+ if filter_complex:
113
+ cmd.extend(['-filter_complex', ';'.join(filter_complex)])
114
+
115
+ # Add output options
116
+ for output in job_data.get('outputs', []):
117
+ for option in output.get('options', []):
118
+ opt = option.get('option', '')
119
+ arg = option.get('argument', '')
120
+ if opt:
121
+ cmd.append(opt)
122
+ if arg:
123
+ cmd.append(arg)
124
+
125
+ # Output file
126
+ output_filename = f"{job_id}_output.mp4"
127
+ output_path = os.path.join(temp_dir, output_filename)
128
+ cmd.append(output_path)
129
+
130
+ # Execute FFmpeg command
131
+ print(f"Executing FFmpeg command...")
132
+ print(' '.join(cmd))
133
+
134
+ result = subprocess.run(cmd, capture_output=True, text=True)
135
+
136
+ if result.returncode != 0:
137
+ return None, f"FFmpeg error: {result.stderr}", None
138
+
139
+ # Check if output file exists
140
+ if not os.path.exists(output_path):
141
+ return None, "Output file was not created", None
142
+
143
+ # Extract metadata if requested
144
+ metadata = {}
145
+ if job_data.get('metadata', {}).get('duration') or \
146
+ job_data.get('metadata', {}).get('filesize') or \
147
+ job_data.get('metadata', {}).get('bitrate') or \
148
+ job_data.get('metadata', {}).get('encoder'):
149
+ metadata = extract_metadata(output_path)
150
+
151
+ # Generate thumbnail if requested
152
+ thumbnail_path = None
153
+ if job_data.get('metadata', {}).get('thumbnail'):
154
+ thumb_filename = f"{job_id}_thumbnail.jpg"
155
+ thumb_path = os.path.join(temp_dir, thumb_filename)
156
+ if generate_thumbnail(output_path, thumb_path):
157
+ # Copy thumbnail to permanent location
158
+ perm_thumb_path = os.path.join(".", thumb_filename)
159
+ shutil.copy2(thumb_path, perm_thumb_path)
160
+ thumbnail_path = perm_thumb_path
161
+
162
+ # Copy output to permanent location
163
+ permanent_output = os.path.join(".", output_filename)
164
+ shutil.copy2(output_path, permanent_output)
165
+
166
+ # Format metadata for display
167
+ metadata_str = ""
168
+ if metadata:
169
+ metadata_str = f"""
170
+ **Metadata:**
171
+ - Duration: {metadata.get('duration', 0):.2f} seconds
172
+ - File size: {metadata.get('filesize', 0) / (1024*1024):.2f} MB
173
+ - Bitrate: {metadata.get('bitrate', 0) / 1000:.0f} kbps
174
+ - Encoder: {metadata.get('encoder', 'Unknown')}
175
+ """
176
+
177
+ return permanent_output, f"Processing complete!\n{metadata_str}", thumbnail_path
178
+
179
+ except json.JSONDecodeError:
180
+ return None, "Invalid JSON format", None
181
+ except requests.RequestException as e:
182
+ return None, f"API request error: {str(e)}", None
183
+ except Exception as e:
184
+ return None, f"Processing error: {str(e)}", None
185
+
186
+ # Create Gradio interface
187
+ def create_interface():
188
+ with gr.Blocks(title="FFmpeg Video Processor") as app:
189
+ gr.Markdown("# FFmpeg Video Processor")
190
+ gr.Markdown("Process videos using FFmpeg with JSON configuration from API or direct input.")
191
+
192
+ with gr.Row():
193
+ with gr.Column():
194
+ api_url_input = gr.Textbox(
195
+ label="API URL (optional)",
196
+ placeholder="https://api.example.com/ffmpeg-job",
197
+ lines=1
198
+ )
199
+
200
+ json_input = gr.Textbox(
201
+ label="JSON Input (used if no API URL provided)",
202
+ placeholder='{"inputs": [...], "filters": [...], "outputs": [...]}',
203
+ lines=15,
204
+ value=json.dumps({
205
+ "inputs": [
206
+ {"file_url": "https://example.com/video1.mp4"},
207
+ {"file_url": "https://example.com/video2.mp4"}
208
+ ],
209
+ "filters": [
210
+ {"filter": "[0:v][1:v]concat=n=2:v=1:a=0[outv]"}
211
+ ],
212
+ "outputs": [
213
+ {
214
+ "options": [
215
+ {"option": "-map", "argument": "[outv]"},
216
+ {"option": "-c:v", "argument": "libx264"}
217
+ ]
218
+ }
219
+ ],
220
+ "metadata": {
221
+ "thumbnail": True,
222
+ "filesize": True,
223
+ "duration": True
224
+ },
225
+ "id": "example_job"
226
+ }, indent=2)
227
+ )
228
+
229
+ process_btn = gr.Button("Process Video", variant="primary")
230
+
231
+ with gr.Column():
232
+ output_video = gr.Video(label="Processed Video")
233
+ output_thumbnail = gr.Image(label="Thumbnail", visible=False)
234
+ status_text = gr.Textbox(label="Status", lines=8)
235
+ download_file = gr.File(label="Download Processed Video", visible=False)
236
+
237
+ def process_and_update(api_url, json_str):
238
+ output_path, status, thumbnail = process_ffmpeg_job(json_str, api_url)
239
+
240
+ if output_path and os.path.exists(output_path):
241
+ return (
242
+ output_path, # video
243
+ status, # status text
244
+ output_path, # download file
245
+ gr.update(visible=True), # show download
246
+ thumbnail, # thumbnail
247
+ gr.update(visible=bool(thumbnail)) # show thumbnail if exists
248
+ )
249
+ else:
250
+ return (
251
+ None, # video
252
+ status, # status text
253
+ None, # download file
254
+ gr.update(visible=False), # hide download
255
+ None, # thumbnail
256
+ gr.update(visible=False) # hide thumbnail
257
+ )
258
+
259
+ process_btn.click(
260
+ fn=process_and_update,
261
+ inputs=[api_url_input, json_input],
262
+ outputs=[output_video, status_text, download_file, download_file, output_thumbnail, output_thumbnail]
263
+ )
264
+
265
+ gr.Markdown("""
266
+ ## Instructions:
267
+ 1. Either provide an API URL that returns the JSON configuration, or paste the JSON directly
268
+ 2. The JSON should contain:
269
+ - `inputs`: Array of input files with `file_url`
270
+ - `filters`: Array of FFmpeg filter strings
271
+ - `outputs`: Array of output options
272
+ - `metadata`: Optional metadata extraction settings
273
+ - `id`: Job identifier
274
+ 3. Click "Process Video" to start processing
275
+ 4. The processed video will be displayed and available for download
276
+ """)
277
+
278
+ return app
279
+
280
+ # Create and launch the app
281
+ if __name__ == "__main__":
282
+ app = create_interface()
283
+ app.launch()