sampleacc-3003 commited on
Commit
bd51402
Β·
verified Β·
1 Parent(s): a7f2779

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +213 -64
app.py CHANGED
@@ -3,29 +3,127 @@ import subprocess
3
  import static_ffmpeg
4
  import os
5
  import tempfile
 
6
  from datetime import datetime
 
7
 
8
  # Add static ffmpeg to PATH
9
  static_ffmpeg.add_paths()
10
 
11
- def stitch_media(video_file, audio_file, subtitle_file, crf_quality=23):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  """
13
- Stitch video, audio, and subtitle files together using ffmpeg
14
- All files are handled through Gradio's temporary file system
 
 
 
 
 
 
 
 
15
  """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  try:
17
- # Validate inputs
18
- if not video_file or not audio_file or not subtitle_file:
19
- return None, "❌ Please upload all three files (video, audio, subtitle)"
 
 
 
 
 
 
 
 
 
 
20
 
21
- # Gradio automatically provides temporary file paths
22
- video_path = video_file.name if hasattr(video_file, 'name') else video_file
23
- audio_path = audio_file.name if hasattr(audio_file, 'name') else audio_file
24
- subtitle_path = subtitle_file.name if hasattr(subtitle_file, 'name') else subtitle_file
 
 
25
 
26
- # Create output file in temp directory
27
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
28
- output_path = os.path.join(tempfile.gettempdir(), f"stitched_{timestamp}.mp4")
 
 
 
 
 
 
 
29
 
30
  # FFmpeg command
31
  cmd = [
@@ -53,12 +151,16 @@ def stitch_media(video_file, audio_file, subtitle_file, crf_quality=23):
53
  # Check if output file was created
54
  if os.path.exists(output_path):
55
  file_size = os.path.getsize(output_path) / (1024 * 1024) # Size in MB
56
- return output_path, f"βœ… Video stitched successfully! (Size: {file_size:.2f} MB)"
 
 
 
 
57
  else:
58
  return None, "❌ Output file was not created"
59
 
60
  except subprocess.CalledProcessError as e:
61
- error_msg = f"❌ FFmpeg error:\n{e.stderr[-500:]}" # Show last 500 chars of error
62
  return None, error_msg
63
  except Exception as e:
64
  return None, f"❌ Error: {str(e)}"
@@ -69,34 +171,59 @@ with gr.Blocks(title="Video Audio Subtitle Stitcher", theme=gr.themes.Soft()) as
69
  """
70
  # 🎬 Video Audio Subtitle Stitcher
71
 
72
- **Upload your files and stitch them together - No local installation needed!**
73
 
74
- This app works entirely in your browser. Just upload, click stitch, and download your result.
 
 
75
  """
76
  )
77
 
78
  with gr.Row():
79
  with gr.Column():
80
- gr.Markdown("### πŸ“€ Upload Files")
 
 
 
 
 
 
 
 
 
 
 
 
81
 
82
- video_input = gr.File(
83
- label="πŸ“Ή Video File",
84
- file_types=[".mp4", ".mov", ".avi", ".mkv"],
85
- type="filepath"
86
- )
87
- audio_input = gr.File(
88
- label="🎡 Audio File",
89
- file_types=[".wav", ".mp3", ".aac", ".m4a"],
90
- type="filepath"
91
- )
92
- subtitle_input = gr.File(
93
- label="πŸ“ Subtitle File (.srt)",
94
- file_types=[".srt"],
95
- type="filepath"
96
- )
97
 
98
- gr.Markdown("### βš™οΈ Settings")
 
 
 
 
 
 
 
 
 
 
 
 
99
 
 
100
  crf_input = gr.Slider(
101
  minimum=18,
102
  maximum=28,
@@ -107,61 +234,83 @@ with gr.Blocks(title="Video Audio Subtitle Stitcher", theme=gr.themes.Soft()) as
107
  )
108
 
109
  stitch_btn = gr.Button("🎬 Stitch Video", variant="primary", size="lg")
110
-
111
- gr.Markdown(
112
- """
113
- **πŸ’‘ Tips:**
114
- - CRF 18-20: Excellent quality
115
- - CRF 23: Balanced (recommended)
116
- - CRF 26-28: Smaller file size
117
- """
118
- )
119
 
120
  with gr.Column():
121
- gr.Markdown("### πŸ“₯ Output")
122
 
123
  status_output = gr.Textbox(
124
  label="Status",
125
- placeholder="Upload files and click 'Stitch Video' to begin...",
126
- lines=3
127
  )
128
  video_output = gr.Video(
129
- label="Result",
130
  autoplay=False
131
  )
132
 
133
  gr.Markdown(
134
  """
135
- **Download:** Right-click the video preview and "Save video as..."
136
- or use the download button in the video player.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
137
  """
138
  )
139
 
140
  gr.Markdown(
141
  """
142
  ---
143
- ### πŸ“– How it works:
144
- 1. **Upload** your video, audio, and subtitle files
145
- 2. **Adjust** quality settings if needed
146
- 3. **Click** "Stitch Video" button
147
- 4. **Wait** for processing (may take a few moments)
148
- 5. **Preview** and download your result!
149
-
150
- ### 🎯 Technical Details:
151
- - Video codec: H.264 (libx264)
152
- - Audio codec: AAC
153
- - Output length: Matches shortest stream (video or audio)
154
- - Subtitles: Burned into video
155
-
156
- ### ☁️ Cloud Deployment Ready:
157
- This app can be deployed to Hugging Face Spaces, Google Colab, or any cloud platform!
158
  """
159
  )
160
 
161
  # Connect the button to the function
162
  stitch_btn.click(
163
  fn=stitch_media,
164
- inputs=[video_input, audio_input, subtitle_input, crf_input],
 
 
 
 
 
165
  outputs=[video_output, status_output]
166
  )
167
 
 
3
  import static_ffmpeg
4
  import os
5
  import tempfile
6
+ import requests
7
  from datetime import datetime
8
+ from pathlib import Path
9
 
10
  # Add static ffmpeg to PATH
11
  static_ffmpeg.add_paths()
12
 
13
+ def download_file_from_url(url, output_dir, filename):
14
+ """Download a file from URL and save it to output directory."""
15
+ try:
16
+ response = requests.get(url, stream=True, timeout=30)
17
+ response.raise_for_status()
18
+
19
+ file_path = os.path.join(output_dir, filename)
20
+ with open(file_path, 'wb') as f:
21
+ for chunk in response.iter_content(chunk_size=8192):
22
+ f.write(chunk)
23
+
24
+ return file_path
25
+ except Exception as e:
26
+ raise Exception(f"Failed to download file from URL: {str(e)}")
27
+
28
+ def validate_and_get_file(uploaded_file, url_string, file_type, temp_dir):
29
  """
30
+ Validate that only one input method is used and return the file path.
31
+
32
+ Args:
33
+ uploaded_file: Uploaded file object (or None)
34
+ url_string: URL string (or empty)
35
+ file_type: Type of file (for naming downloaded files)
36
+ temp_dir: Temporary directory to store downloaded files
37
+
38
+ Returns:
39
+ tuple: (file_path, error_message)
40
  """
41
+ has_upload = uploaded_file is not None
42
+ has_url = url_string and url_string.strip()
43
+
44
+ # Validation: Must have exactly one input method
45
+ if not has_upload and not has_url:
46
+ return None, f"❌ Please provide {file_type} either by upload or URL"
47
+
48
+ if has_upload and has_url:
49
+ return None, f"❌ Please use only ONE method for {file_type}: either upload OR URL (not both)"
50
+
51
+ # Handle uploaded file
52
+ if has_upload:
53
+ file_path = uploaded_file.name if hasattr(uploaded_file, 'name') else uploaded_file
54
+ return file_path, None
55
+
56
+ # Handle URL download
57
+ if has_url:
58
+ try:
59
+ # Get file extension from URL or use default
60
+ url_parts = url_string.strip().split('/')
61
+ original_filename = url_parts[-1] if url_parts else f"{file_type}_file"
62
+
63
+ # Add extension if missing
64
+ if '.' not in original_filename:
65
+ ext_map = {
66
+ 'video': '.mp4',
67
+ 'audio': '.wav',
68
+ 'subtitle': '.srt'
69
+ }
70
+ original_filename += ext_map.get(file_type, '.tmp')
71
+
72
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
73
+ filename = f"{file_type}_{timestamp}_{original_filename}"
74
+
75
+ file_path = download_file_from_url(url_string.strip(), temp_dir, filename)
76
+ return file_path, None
77
+
78
+ except Exception as e:
79
+ return None, f"❌ Error downloading {file_type} from URL: {str(e)}"
80
+
81
+ return None, f"❌ Unknown error processing {file_type}"
82
+
83
+ def stitch_media(
84
+ video_file, video_url,
85
+ audio_file, audio_url,
86
+ subtitle_file, subtitle_url,
87
+ crf_quality=23
88
+ ):
89
+ """
90
+ Stitch video, audio, and subtitle files together using ffmpeg.
91
+ Files can be uploaded or fetched from URLs.
92
+ """
93
+ temp_dir = tempfile.mkdtemp()
94
+
95
  try:
96
+ # Validate and get video file
97
+ video_path, video_error = validate_and_get_file(
98
+ video_file, video_url, 'video', temp_dir
99
+ )
100
+ if video_error:
101
+ return None, video_error
102
+
103
+ # Validate and get audio file
104
+ audio_path, audio_error = validate_and_get_file(
105
+ audio_file, audio_url, 'audio', temp_dir
106
+ )
107
+ if audio_error:
108
+ return None, audio_error
109
 
110
+ # Validate and get subtitle file
111
+ subtitle_path, subtitle_error = validate_and_get_file(
112
+ subtitle_file, subtitle_url, 'subtitle', temp_dir
113
+ )
114
+ if subtitle_error:
115
+ return None, subtitle_error
116
 
117
+ # Create output file
118
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
119
+ output_path = os.path.join(temp_dir, f"stitched_{timestamp}.mp4")
120
+
121
+ # Build status message
122
+ status_msg = "πŸ“₯ Processing files:\n"
123
+ status_msg += f" β€’ Video: {'Downloaded from URL' if video_url else 'Uploaded file'}\n"
124
+ status_msg += f" β€’ Audio: {'Downloaded from URL' if audio_url else 'Uploaded file'}\n"
125
+ status_msg += f" β€’ Subtitle: {'Downloaded from URL' if subtitle_url else 'Uploaded file'}\n"
126
+ status_msg += "\n🎬 Stitching video..."
127
 
128
  # FFmpeg command
129
  cmd = [
 
151
  # Check if output file was created
152
  if os.path.exists(output_path):
153
  file_size = os.path.getsize(output_path) / (1024 * 1024) # Size in MB
154
+ success_msg = f"βœ… Video stitched successfully!\n\n"
155
+ success_msg += f"πŸ“Š Output size: {file_size:.2f} MB\n"
156
+ success_msg += f"🎨 Quality: CRF {crf_quality}\n"
157
+ success_msg += status_msg.split("🎬")[0] # Include source info
158
+ return output_path, success_msg
159
  else:
160
  return None, "❌ Output file was not created"
161
 
162
  except subprocess.CalledProcessError as e:
163
+ error_msg = f"❌ FFmpeg error:\n{e.stderr[-500:]}"
164
  return None, error_msg
165
  except Exception as e:
166
  return None, f"❌ Error: {str(e)}"
 
171
  """
172
  # 🎬 Video Audio Subtitle Stitcher
173
 
174
+ **Stitch video, audio, and subtitles together with FFmpeg**
175
 
176
+ πŸ“€ Upload files directly **OR** 🌐 provide URLs to download them
177
+
178
+ ⚠️ **Important:** For each file, use either upload OR URL, not both!
179
  """
180
  )
181
 
182
  with gr.Row():
183
  with gr.Column():
184
+ gr.Markdown("### πŸ“Ή Video Input")
185
+ with gr.Group():
186
+ video_input = gr.File(
187
+ label="Upload Video File",
188
+ file_types=[".mp4", ".mov", ".avi", ".mkv"],
189
+ type="filepath"
190
+ )
191
+ gr.Markdown("**OR**")
192
+ video_url_input = gr.Textbox(
193
+ label="Video URL",
194
+ placeholder="https://example.com/video.mp4",
195
+ lines=1
196
+ )
197
 
198
+ gr.Markdown("### 🎡 Audio Input")
199
+ with gr.Group():
200
+ audio_input = gr.File(
201
+ label="Upload Audio File",
202
+ file_types=[".wav", ".mp3", ".aac", ".m4a"],
203
+ type="filepath"
204
+ )
205
+ gr.Markdown("**OR**")
206
+ audio_url_input = gr.Textbox(
207
+ label="Audio URL",
208
+ placeholder="https://example.com/audio.wav",
209
+ lines=1
210
+ )
 
 
211
 
212
+ gr.Markdown("### πŸ“ Subtitle Input")
213
+ with gr.Group():
214
+ subtitle_input = gr.File(
215
+ label="Upload Subtitle File (.srt)",
216
+ file_types=[".srt"],
217
+ type="filepath"
218
+ )
219
+ gr.Markdown("**OR**")
220
+ subtitle_url_input = gr.Textbox(
221
+ label="Subtitle URL",
222
+ placeholder="https://example.com/subtitles.srt",
223
+ lines=1
224
+ )
225
 
226
+ gr.Markdown("### βš™οΈ Settings")
227
  crf_input = gr.Slider(
228
  minimum=18,
229
  maximum=28,
 
234
  )
235
 
236
  stitch_btn = gr.Button("🎬 Stitch Video", variant="primary", size="lg")
 
 
 
 
 
 
 
 
 
237
 
238
  with gr.Column():
239
+ gr.Markdown("### πŸ“Š Status & Output")
240
 
241
  status_output = gr.Textbox(
242
  label="Status",
243
+ placeholder="Provide inputs and click 'Stitch Video' to begin...",
244
+ lines=8
245
  )
246
  video_output = gr.Video(
247
+ label="Result Video",
248
  autoplay=False
249
  )
250
 
251
  gr.Markdown(
252
  """
253
+ **πŸ’Ύ Download:** Click the download button on the video player
254
+ or right-click and "Save video as..."
255
+ """
256
+ )
257
+
258
+ with gr.Row():
259
+ with gr.Column():
260
+ gr.Markdown(
261
+ """
262
+ ### πŸ“– Instructions:
263
+
264
+ **For each file (video, audio, subtitle):**
265
+ 1. **Either** upload a file from your computer
266
+ 2. **OR** paste a URL to download it
267
+ 3. ⚠️ **Don't use both!** Choose one method per file
268
+
269
+ ### πŸ’‘ Quality Settings:
270
+ - **CRF 18-20:** Excellent quality (larger files)
271
+ - **CRF 23:** Balanced (recommended) ⭐
272
+ - **CRF 26-28:** Good quality (smaller files)
273
+ """
274
+ )
275
+
276
+ with gr.Column():
277
+ gr.Markdown(
278
+ """
279
+ ### 🎯 Example URLs:
280
+ You can use direct links to files:
281
+ - Video: `.mp4`, `.mov`, `.avi`, `.mkv`
282
+ - Audio: `.wav`, `.mp3`, `.aac`, `.m4a`
283
+ - Subtitle: `.srt`
284
+
285
+ ### πŸ”§ Technical Details:
286
+ - **Video codec:** H.264 (libx264)
287
+ - **Audio codec:** AAC
288
+ - **Output length:** Matches shortest stream
289
+ - **Subtitles:** Burned into video
290
  """
291
  )
292
 
293
  gr.Markdown(
294
  """
295
  ---
296
+ ### ⚑ Features:
297
+ βœ… Upload files or fetch from URLs
298
+ βœ… Automatic validation (no mixing methods)
299
+ βœ… Progress tracking
300
+ βœ… Instant preview
301
+ βœ… Cloud deployment ready
 
 
 
 
 
 
 
 
 
302
  """
303
  )
304
 
305
  # Connect the button to the function
306
  stitch_btn.click(
307
  fn=stitch_media,
308
+ inputs=[
309
+ video_input, video_url_input,
310
+ audio_input, audio_url_input,
311
+ subtitle_input, subtitle_url_input,
312
+ crf_input
313
+ ],
314
  outputs=[video_output, status_output]
315
  )
316