danicor commited on
Commit
549cdf8
Β·
verified Β·
1 Parent(s): 18900df

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +486 -92
app.py CHANGED
@@ -1,127 +1,360 @@
 
1
  import ffmpeg
2
  import os
 
 
 
3
  import tempfile
4
  import shutil
5
- import re
6
- from PIL import Image
7
- from fastapi import FastAPI, File, Form, UploadFile, HTTPException
8
- from fastapi.responses import FileResponse
 
9
  from fastapi.middleware.cors import CORSMiddleware
 
 
 
 
 
 
 
10
 
11
  app = FastAPI(title="Video Conversion API")
12
 
13
- # CORS middleware
14
  app.add_middleware(
15
  CORSMiddleware,
16
- allow_origins=["*"],
17
  allow_credentials=True,
18
  allow_methods=["*"],
19
  allow_headers=["*"],
20
  )
21
 
22
  # Supported formats
23
- supported_formats = [f for f in sorted([
24
- 'AVI','FLV','M2TS','M4V','MKV','MOV','MP4','MPEG','MPG','MTS','TS','VOB','WEBM','WMV'
25
- ])]
26
- audio_formats = [f for f in sorted([
27
- 'MP3','WAV','AAC','FLAC','OGG','M4A','ALAC','WMA','AIFF','OPUS','CAF','SPX','WV'
28
- ])]
29
- image_formats = [f for f in sorted(Image.SAVE.keys() or ['BMP','GIF','JPEG','PNG','TIFF','WEBP'])]
30
  gif_formats = ['GIF']
 
 
 
31
 
32
  CACHE_DIR = tempfile.mkdtemp()
33
 
34
- # Audio & Video codecs
35
  AUDIO_CODECS = {
36
- 'MP3': {'acodec':'libmp3lame','audio_bitrate':'192k'},
37
- 'AAC': {'acodec':'aac','audio_bitrate':'192k'},
38
- 'WAV': {'acodec':'pcm_s16le'},
39
- 'FLAC': {'acodec':'flac'},
40
- 'OGG': {'acodec':'libvorbis','audio_bitrate':'192k'},
41
- 'M4A': {'acodec':'aac','audio_bitrate':'192k'},
42
- 'ALAC': {'acodec':'alac'},
43
- 'WMA': {'acodec':'wmav2','audio_bitrate':'192k'},
44
- 'AIFF': {'acodec':'pcm_s16be'},
45
- 'OPUS': {'acodec':'libopus','audio_bitrate':'128k'},
46
- 'CAF': {'acodec':'alac'},
47
- 'SPX': {'acodec':'libspeex','audio_bitrate':'32k'},
48
- 'WV': {'acodec':'wavpack'}
49
  }
50
 
 
51
  VIDEO_CODECS = {
52
- 'MP4': {'vcodec':'libx264','acodec':'aac','preset':'medium','crf':'23'},
53
- 'AVI': {'vcodec':'libxvid','acodec':'libmp3lame'},
54
- 'MOV': {'vcodec':'libx264','acodec':'aac'},
55
- 'MKV': {'vcodec':'libx264','acodec':'aac'},
56
- 'WEBM': {'vcodec':'libvpx-vp9','acodec':'libopus'},
57
- 'FLV': {'vcodec':'libx264','acodec':'aac'},
58
- 'WMV': {'vcodec':'wmv2','acodec':'wmav2'},
59
- 'M4V': {'vcodec':'libx264','acodec':'aac'},
60
- 'MPG': {'vcodec':'mpeg2video','acodec':'mp2'},
61
- 'MPEG': {'vcodec':'mpeg2video','acodec':'mp2'},
62
- 'VOB': {'vcodec':'mpeg2video','acodec':'mp2'},
63
- 'ASF': {'vcodec':'wmv2','acodec':'wmav2'},
64
- 'TS': {'vcodec':'libx264','acodec':'aac'},
65
- 'M2TS': {'vcodec':'libx264','acodec':'aac'},
66
- 'MTS': {'vcodec':'libx264','acodec':'aac'}
67
  }
68
 
 
 
 
 
 
 
 
69
  def sanitize_filename(filename):
 
70
  return re.sub(r'[^a-zA-Z0-9_.-]', '_', filename)
71
 
 
 
 
 
 
 
 
 
72
  def convert_video(video_path, target_format, conversion_type, time_in_seconds=None):
73
- base_name = os.path.splitext(os.path.basename(video_path))[0]
74
- sanitized_base_name = sanitize_filename(base_name)
75
- output_dir = os.path.join(CACHE_DIR, "outputs")
76
- os.makedirs(output_dir, exist_ok=True)
77
-
78
- if conversion_type == 'Video to Video':
79
- output_file = os.path.join(output_dir, f"converted_{sanitized_base_name}.{target_format.lower()}")
80
- codec_settings = VIDEO_CODECS.get(target_format.upper(), {})
81
- input_stream = ffmpeg.input(video_path)
82
- ffmpeg.output(input_stream, output_file, **codec_settings).overwrite_output().run(quiet=True)
83
- return output_file
84
-
85
- elif conversion_type == 'Video to Audio':
86
- output_file = os.path.join(output_dir, f"audio_{sanitized_base_name}.{target_format.lower()}")
87
- codec_settings = AUDIO_CODECS.get(target_format.upper(), {})
88
- input_stream = ffmpeg.input(video_path)
89
- ffmpeg.output(input_stream, output_file, vn=None, **codec_settings).overwrite_output().run(quiet=True)
90
- return output_file
91
-
92
- elif conversion_type == 'Video to GIF':
93
- output_file = os.path.join(output_dir, f"gif_{sanitized_base_name}.gif")
94
- ffmpeg.input(video_path).output(
95
- output_file,
96
- vf="fps=15,scale=480:-1:flags=lanczos,palettegen=stats_mode=diff",
97
- loop=0
98
- ).overwrite_output().run(quiet=True)
99
- return output_file
100
-
101
- elif conversion_type == 'Video to Image':
102
- if time_in_seconds is None:
103
- time_in_seconds = 0
104
- temp_png_file = os.path.join(output_dir, f"temp_{sanitized_base_name}_{time_in_seconds}s.png")
105
- ffmpeg.input(video_path, ss=time_in_seconds).output(temp_png_file, vframes=1, **{'qscale:v': '1'}).overwrite_output().run(quiet=True)
106
- if target_format.upper() == 'PNG':
107
- return temp_png_file
108
- output_file = os.path.join(output_dir, f"image_{sanitized_base_name}_{time_in_seconds}s.{target_format.lower()}")
109
- with Image.open(temp_png_file) as img:
110
- img.convert('RGB').save(output_file, format=target_format.upper())
111
- os.remove(temp_png_file)
112
- return output_file
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
  @app.post("/api/convert")
115
- async def api_convert(file: UploadFile = File(...), conversion_type: str = Form("Video to Video"), target_format: str = Form("MP4"), time_in_seconds: int = Form(0)):
116
- if not file.filename:
117
- raise HTTPException(status_code=400, detail="No file provided")
118
- temp_dir = tempfile.mkdtemp()
119
- file_path = os.path.join(temp_dir, file.filename)
120
- with open(file_path, "wb") as f:
121
- f.write(await file.read())
122
- output_file = convert_video(file_path, target_format, conversion_type, time_in_seconds)
123
- shutil.rmtree(temp_dir, ignore_errors=True)
124
- return FileResponse(output_file, filename=os.path.basename(output_file))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
 
126
  @app.get("/api/formats")
127
  async def get_formats():
@@ -134,6 +367,167 @@ async def get_formats():
134
  "supported_video_codecs": list(VIDEO_CODECS.keys())
135
  }
136
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
137
  @app.get("/")
138
  async def root():
139
  return {"message": "Video Conversion API is running", "docs": "/docs"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #import streamlit as st
2
  import ffmpeg
3
  import os
4
+ import time
5
+ from PIL import Image
6
+ import re
7
  import tempfile
8
  import shutil
9
+ import threading
10
+ import requests
11
+ import json
12
+ from fastapi import FastAPI, File, Form, UploadFile, HTTPException, Request
13
+ from fastapi.responses import FileResponse, JSONResponse
14
  from fastapi.middleware.cors import CORSMiddleware
15
+ import uvicorn
16
+ import asyncio
17
+ from threading import Thread
18
+ import nest_asyncio
19
+
20
+ # برای اجرای Ω‡Ω…Ψ²Ω…Ψ§Ω† FastAPI و Streamlit
21
+ nest_asyncio.apply()
22
 
23
  app = FastAPI(title="Video Conversion API")
24
 
25
+ # Add CORS middleware to handle cross-origin requests
26
  app.add_middleware(
27
  CORSMiddleware,
28
+ allow_origins=["*"], # In production, replace with specific domains
29
  allow_credentials=True,
30
  allow_methods=["*"],
31
  allow_headers=["*"],
32
  )
33
 
34
  # Supported formats
35
+ supported_formats = [data.upper() for data in (sorted(['3GP', 'ASF', 'AVI', 'DIVX', 'FLV', 'M2TS', 'M4V', 'MKV', 'MOV', 'MP4', 'MPEG', 'MPG', 'MTS', 'TS', 'VOB', 'WEBM', 'WMV', 'XVID'])) if data not in ['3GP', 'DIVX', 'XVID']]
36
+ audio_formats = [data.upper() for data in (sorted(['MP3', 'WAV', 'AAC', 'FLAC', 'OGG', 'M4A', 'ALAC', 'WMA', 'AIFF', 'OPUS', 'APE', 'CAF', 'PCM', 'DTS', 'TTA', 'AMR', 'MID', 'SPX', 'WV', 'RA', 'TAK'])) if data not in ['ALC', 'AMR', 'APE', 'DTS', 'MID', 'PCM', 'RA', 'TAK']]
 
 
 
 
 
37
  gif_formats = ['GIF']
38
+ image_formats = [data.upper() for data in sorted(Image.SAVE.keys() or ['BLP', 'BMP', 'BUFR', 'DDS', 'DIB', 'EPS', 'GIF', 'GRIB', 'HDF5', 'ICNS', 'ICO', 'IM',
39
+ 'JPEG', 'JPEG2000', 'MPO', 'MSP', 'PALM', 'PCX', 'PDF', 'PNG', 'PPM', 'SGI', 'SPIDER',
40
+ 'TGA', 'TIFF', 'WEBP', 'WMX', 'XBM'])]
41
 
42
  CACHE_DIR = tempfile.mkdtemp()
43
 
44
+ # Audio codec mapping
45
  AUDIO_CODECS = {
46
+ 'MP3': {'acodec': 'libmp3lame', 'audio_bitrate': '192k'},
47
+ 'AAC': {'acodec': 'aac', 'audio_bitrate': '192k'},
48
+ 'WAV': {'acodec': 'pcm_s16le'},
49
+ 'FLAC': {'acodec': 'flac'},
50
+ 'OGG': {'acodec': 'libvorbis', 'audio_bitrate': '192k'},
51
+ 'M4A': {'acodec': 'aac', 'audio_bitrate': '192k'},
52
+ 'ALAC': {'acodec': 'alac'},
53
+ 'WMA': {'acodec': 'wmav2', 'audio_bitrate': '192k'},
54
+ 'AIFF': {'acodec': 'pcm_s16be'},
55
+ 'OPUS': {'acodec': 'libopus', 'audio_bitrate': '128k'},
56
+ 'CAF': {'acodec': 'alac'},
57
+ 'SPX': {'acodec': 'libspeex', 'audio_bitrate': '32k'},
58
+ 'WV': {'acodec': 'wavpack'},
59
  }
60
 
61
+ # Video codec mapping
62
  VIDEO_CODECS = {
63
+ 'MP4': {'vcodec': 'libx264', 'acodec': 'aac', 'preset': 'medium', 'crf': '23'},
64
+ 'AVI': {'vcodec': 'libxvid', 'acodec': 'libmp3lame'},
65
+ 'MOV': {'vcodec': 'libx264', 'acodec': 'aac'},
66
+ 'MKV': {'vcodec': 'libx264', 'acodec': 'aac'},
67
+ 'WEBM': {'vcodec': 'libvpx-vp9', 'acodec': 'libopus'},
68
+ 'FLV': {'vcodec': 'libx264', 'acodec': 'aac'},
69
+ 'WMV': {'vcodec': 'wmv2', 'acodec': 'wmav2'},
70
+ 'M4V': {'vcodec': 'libx264', 'acodec': 'aac'},
71
+ 'MPG': {'vcodec': 'mpeg2video', 'acodec': 'mp2'},
72
+ 'MPEG': {'vcodec': 'mpeg2video', 'acodec': 'mp2'},
73
+ 'VOB': {'vcodec': 'mpeg2video', 'acodec': 'mp2'},
74
+ 'ASF': {'vcodec': 'wmv2', 'acodec': 'wmav2'},
75
+ 'TS': {'vcodec': 'libx264', 'acodec': 'aac'},
76
+ 'M2TS': {'vcodec': 'libx264', 'acodec': 'aac'},
77
+ 'MTS': {'vcodec': 'libx264', 'acodec': 'aac'},
78
  }
79
 
80
+ def delete_temp_dir(directory, delay=900):
81
+ """Delete temporary directory after delay"""
82
+ timer = threading.Timer(delay, shutil.rmtree, [directory])
83
+ timer.start()
84
+
85
+ delete_temp_dir(CACHE_DIR, delay=900)
86
+
87
  def sanitize_filename(filename):
88
+ """Sanitize filename by removing special characters and spaces."""
89
  return re.sub(r'[^a-zA-Z0-9_.-]', '_', filename)
90
 
91
+ def get_video_duration(video_path):
92
+ """Get video duration in seconds using ffmpeg."""
93
+ try:
94
+ probe = ffmpeg.probe(video_path, v='error', select_streams='v:0', show_entries='stream=duration')
95
+ return float(probe['streams'][0]['duration'])
96
+ except:
97
+ return 60 # Fallback duration
98
+
99
  def convert_video(video_path, target_format, conversion_type, time_in_seconds=None):
100
+ try:
101
+ base_name = os.path.splitext(os.path.basename(video_path))[0]
102
+ sanitized_base_name = sanitize_filename(base_name)
103
+
104
+ output_dir = os.path.join(CACHE_DIR, "outputs")
105
+ os.makedirs(output_dir, exist_ok=True)
106
+
107
+ if conversion_type == 'Video to Video':
108
+ output_file = os.path.join(output_dir, f"converted_{sanitized_base_name}.{target_format.lower()}")
109
+
110
+ # Get codec settings for the target format
111
+ codec_settings = VIDEO_CODECS.get(target_format.upper(), {})
112
+
113
+ if codec_settings:
114
+ # Build ffmpeg command with specific codecs
115
+ input_stream = ffmpeg.input(video_path)
116
+ output_kwargs = {}
117
+
118
+ # Add video codec if specified
119
+ if 'vcodec' in codec_settings:
120
+ output_kwargs['vcodec'] = codec_settings['vcodec']
121
+
122
+ # Add audio codec if specified
123
+ if 'acodec' in codec_settings:
124
+ output_kwargs['acodec'] = codec_settings['acodec']
125
+
126
+ # Add other settings
127
+ for key, value in codec_settings.items():
128
+ if key not in ['vcodec', 'acodec']:
129
+ output_kwargs[key] = value
130
+
131
+ ffmpeg.output(input_stream, output_file, **output_kwargs).overwrite_output().run(quiet=True)
132
+ else:
133
+ # Fallback for unsupported formats
134
+ ffmpeg.input(video_path).output(output_file).overwrite_output().run(quiet=True)
135
+
136
+ return output_file
137
+
138
+ elif conversion_type == 'Video to Audio':
139
+ output_file = os.path.join(output_dir, f"audio_{sanitized_base_name}.{target_format.lower()}")
140
+
141
+ # Get codec settings for the target audio format
142
+ codec_settings = AUDIO_CODECS.get(target_format.upper(), {})
143
+
144
+ if codec_settings:
145
+ # Build ffmpeg command with specific audio codec
146
+ input_stream = ffmpeg.input(video_path)
147
+ output_kwargs = {
148
+ 'vn': None # Disable video stream (audio only)
149
+ }
150
+
151
+ # Add audio codec
152
+ if 'acodec' in codec_settings:
153
+ output_kwargs['acodec'] = codec_settings['acodec']
154
+
155
+ # Add audio bitrate if specified
156
+ if 'audio_bitrate' in codec_settings:
157
+ output_kwargs['audio_bitrate'] = codec_settings['audio_bitrate']
158
+
159
+ # Add other audio settings
160
+ for key, value in codec_settings.items():
161
+ if key not in ['acodec', 'audio_bitrate']:
162
+ output_kwargs[key] = value
163
+
164
+ ffmpeg.output(input_stream, output_file, **output_kwargs).overwrite_output().run(quiet=True)
165
+ else:
166
+ # Fallback for unsupported audio formats - but still audio only
167
+ ffmpeg.input(video_path).output(output_file, vn=None).overwrite_output().run(quiet=True)
168
+
169
+ return output_file
170
 
171
+ elif conversion_type == 'Video to GIF':
172
+ output_file = os.path.join(output_dir, f"gif_{sanitized_base_name}.gif")
173
+ # Create high-quality GIF with palette optimization
174
+ (
175
+ ffmpeg
176
+ .input(video_path)
177
+ .output(output_file,
178
+ vf="fps=15,scale=480:-1:flags=lanczos,palettegen=stats_mode=diff",
179
+ loop=0)
180
+ .overwrite_output()
181
+ .run(quiet=True)
182
+ )
183
+ return output_file
184
+
185
+ elif conversion_type == 'Video to Image':
186
+ if time_in_seconds is None:
187
+ time_in_seconds = 0
188
+
189
+ # First extract frame as PNG using ffmpeg
190
+ temp_png_file = os.path.join(output_dir, f"temp_image_{sanitized_base_name}_{time_in_seconds}s.png")
191
+
192
+ # Extract frame using ffmpeg with high quality
193
+ (
194
+ ffmpeg
195
+ .input(video_path, ss=time_in_seconds)
196
+ .output(temp_png_file, vframes=1, **{'qscale:v': '1'})
197
+ .overwrite_output()
198
+ .run(quiet=True, capture_stdout=True, capture_stderr=True)
199
+ )
200
+
201
+ # If target format is PNG, return directly
202
+ if target_format.upper() == 'PNG':
203
+ return temp_png_file
204
+
205
+ # For other image formats, convert using PIL
206
+ output_file = os.path.join(output_dir, f"image_{sanitized_base_name}_{time_in_seconds}s.{target_format.lower()}")
207
+
208
+ try:
209
+ # Open the extracted PNG and convert to desired format
210
+ with Image.open(temp_png_file) as img:
211
+ # Handle different formats with optimal settings
212
+ if target_format.upper() == 'PDF':
213
+ # Convert to RGB first for PDF
214
+ if img.mode != 'RGB':
215
+ img = img.convert('RGB')
216
+ img.save(output_file, format='PDF', save_all=True, quality=95)
217
+ elif target_format.upper() in ['JPEG', 'JPG']:
218
+ # Convert to RGB for JPEG
219
+ if img.mode != 'RGB':
220
+ img = img.convert('RGB')
221
+ img.save(output_file, format='JPEG', quality=95, optimize=True)
222
+ elif target_format.upper() == 'TIFF':
223
+ img.save(output_file, format='TIFF', compression='tiff_lzw')
224
+ elif target_format.upper() == 'WEBP':
225
+ img.save(output_file, format='WEBP', quality=95, method=6)
226
+ elif target_format.upper() == 'BMP':
227
+ # Convert to RGB for BMP
228
+ if img.mode != 'RGB':
229
+ img = img.convert('RGB')
230
+ img.save(output_file, format='BMP')
231
+ else:
232
+ # For other formats, try to save directly
233
+ try:
234
+ img.save(output_file, format=target_format.upper())
235
+ except:
236
+ # If format not supported, convert to RGB and try again
237
+ if img.mode != 'RGB':
238
+ img = img.convert('RGB')
239
+ img.save(output_file, format=target_format.upper())
240
+
241
+ # Remove temporary PNG file
242
+ os.remove(temp_png_file)
243
+ return output_file
244
+
245
+ except Exception as pil_error:
246
+ # If PIL conversion fails, return the PNG file as fallback
247
+ print(f"PIL conversion error: {pil_error}, returning PNG instead")
248
+ return temp_png_file
249
+
250
+ except ffmpeg.Error as e:
251
+ error_message = f"FFmpeg error: {e.stderr.decode() if e.stderr else str(e)}"
252
+ raise Exception(error_message)
253
+ except Exception as e:
254
+ raise Exception(f"Conversion error: {str(e)}")
255
+
256
+ # API Endpoints
257
  @app.post("/api/convert")
258
+ async def api_convert(
259
+ file: UploadFile = File(...),
260
+ conversion_type: str = Form("Video to Video"),
261
+ target_format: str = Form("MP4"),
262
+ time_in_seconds: int = Form(0)
263
+ ):
264
+ try:
265
+ print(f"Received conversion request:")
266
+ print(f"- File: {file.filename} ({file.content_type})")
267
+ print(f"- Conversion type: {conversion_type}")
268
+ print(f"- Target format: {target_format}")
269
+ print(f"- Time: {time_in_seconds}")
270
+
271
+ # Validate inputs
272
+ if not file.filename:
273
+ raise HTTPException(status_code=400, detail="No file provided")
274
+
275
+ # Validate format support
276
+ if conversion_type == 'Video to Audio' and target_format.upper() not in [f.upper() for f in audio_formats]:
277
+ raise HTTPException(status_code=400, detail=f"Unsupported audio format: {target_format}")
278
+ elif conversion_type == 'Video to Video' and target_format.upper() not in [f.upper() for f in supported_formats]:
279
+ raise HTTPException(status_code=400, detail=f"Unsupported video format: {target_format}")
280
+ elif conversion_type == 'Video to Image' and target_format.upper() not in [f.upper() for f in image_formats]:
281
+ raise HTTPException(status_code=400, detail=f"Unsupported image format: {target_format}")
282
+
283
+ # Save uploaded file
284
+ temp_dir = tempfile.mkdtemp()
285
+ file_path = os.path.join(temp_dir, file.filename)
286
+
287
+ print(f"Saving file to: {file_path}")
288
+
289
+ with open(file_path, "wb") as f:
290
+ content = await file.read()
291
+ f.write(content)
292
+ print(f"File saved, size: {len(content)} bytes")
293
+
294
+ # Verify file exists and is readable
295
+ if not os.path.exists(file_path) or os.path.getsize(file_path) == 0:
296
+ raise HTTPException(status_code=400, detail="Failed to save uploaded file")
297
+
298
+ # Convert video
299
+ print(f"Starting conversion...")
300
+ output_file = convert_video(file_path, target_format, conversion_type, time_in_seconds)
301
+
302
+ # Verify output file exists
303
+ if not os.path.exists(output_file):
304
+ raise HTTPException(status_code=500, detail="Conversion failed - output file not created")
305
+
306
+ print(f"Conversion successful. Output file: {output_file} (size: {os.path.getsize(output_file)} bytes)")
307
+
308
+ # Determine content type based on format
309
+ content_type = "application/octet-stream"
310
+ if conversion_type == 'Video to Video':
311
+ content_type = f"video/{target_format.lower()}"
312
+ elif conversion_type == 'Video to Audio':
313
+ if target_format.upper() == 'OGG':
314
+ content_type = "audio/ogg"
315
+ elif target_format.upper() == 'M4A':
316
+ content_type = "audio/mp4"
317
+ else:
318
+ content_type = f"audio/{target_format.lower()}"
319
+ elif conversion_type == 'Video to GIF':
320
+ content_type = "image/gif"
321
+ elif conversion_type == 'Video to Image':
322
+ # Set appropriate content type for image formats
323
+ if target_format.upper() == 'PDF':
324
+ content_type = "application/pdf"
325
+ elif target_format.upper() in ['JPEG', 'JPG']:
326
+ content_type = "image/jpeg"
327
+ elif target_format.upper() == 'PNG':
328
+ content_type = "image/png"
329
+ elif target_format.upper() == 'TIFF':
330
+ content_type = "image/tiff"
331
+ elif target_format.upper() == 'WEBP':
332
+ content_type = "image/webp"
333
+ else:
334
+ content_type = f"image/{target_format.lower()}"
335
+
336
+ # Clean up input file
337
+ try:
338
+ os.unlink(file_path)
339
+ os.rmdir(temp_dir)
340
+ except:
341
+ pass
342
+
343
+ return FileResponse(
344
+ output_file,
345
+ media_type=content_type,
346
+ filename=os.path.basename(output_file),
347
+ headers={
348
+ "Content-Disposition": f"attachment; filename=\"{os.path.basename(output_file)}\"",
349
+ "Cache-Control": "no-cache"
350
+ }
351
+ )
352
+
353
+ except HTTPException:
354
+ raise
355
+ except Exception as e:
356
+ print(f"Conversion error: {str(e)}")
357
+ raise HTTPException(status_code=500, detail=f"Conversion failed: {str(e)}")
358
 
359
  @app.get("/api/formats")
360
  async def get_formats():
 
367
  "supported_video_codecs": list(VIDEO_CODECS.keys())
368
  }
369
 
370
+ # Health check endpoints for Hugging Face Spaces
371
+ @app.get("/_stcore/health")
372
+ async def stcore_health():
373
+ return {"status": "healthy"}
374
+
375
+ @app.get("/_stcore/host-config")
376
+ async def stcore_host_config():
377
+ return {
378
+ "version": "1.0",
379
+ "config": {
380
+ "enableCors": False,
381
+ "enableXsrfProtection": False
382
+ }
383
+ }
384
+
385
+ @app.get("/health/_stcore/health")
386
+ async def health_stcore_health():
387
+ return {"status": "healthy"}
388
+
389
+ @app.get("/health/_stcore/host-config")
390
+ async def health_stcore_host_config():
391
+ return {
392
+ "version": "1.0",
393
+ "config": {
394
+ "enableCors": False,
395
+ "enableXsrfProtection": False
396
+ }
397
+ }
398
+
399
+ @app.get("/convert/_stcore/health")
400
+ async def convert_stcore_health():
401
+ return {"status": "healthy"}
402
+
403
+ @app.get("/convert/_stcore/host-config")
404
+ async def convert_stcore_host_config():
405
+ return {
406
+ "version": "1.0",
407
+ "config": {
408
+ "enableCors": False,
409
+ "enableXsrfProtection": False
410
+ }
411
+ }
412
+
413
  @app.get("/")
414
  async def root():
415
  return {"message": "Video Conversion API is running", "docs": "/docs"}
416
+
417
+ # # Streamlit UI
418
+ # def streamlit_app():
419
+ # st.set_page_config(layout="wide", page_title="Video Conversion Tool")
420
+
421
+ # st.title("🎬 Video Conversion Tool")
422
+ # st.write("Convert videos to audio, GIFs, images, or other formats with optimized codecs for best quality.")
423
+
424
+ # # Create two columns
425
+ # col1, col2 = st.columns([1, 1])
426
+
427
+ # with col1:
428
+ # video_file = st.file_uploader("Upload a Video", type=[ext.lower() for ext in supported_formats])
429
+ # if video_file:
430
+ # st.video(video_file)
431
+
432
+ # with col2:
433
+ # if video_file:
434
+ # # Save uploaded video to cache
435
+ # temp_video_path = os.path.join(CACHE_DIR, video_file.name)
436
+ # with open(temp_video_path, "wb") as f:
437
+ # f.write(video_file.getbuffer())
438
+
439
+ # # Get video duration
440
+ # video_duration = get_video_duration(temp_video_path)
441
+
442
+ # # Select conversion type
443
+ # conversion_type = st.selectbox(
444
+ # "Select Conversion Type",
445
+ # ['Video to Video', 'Video to Audio', 'Video to GIF', 'Video to Image']
446
+ # )
447
+
448
+ # # Update format choices
449
+ # def update_format_choices(conversion_type):
450
+ # if conversion_type == 'Video to Video':
451
+ # return supported_formats
452
+ # elif conversion_type == 'Video to Audio':
453
+ # return audio_formats
454
+ # elif conversion_type == 'Video to GIF':
455
+ # return gif_formats
456
+ # elif conversion_type == 'Video to Image':
457
+ # return image_formats
458
+ # return []
459
+
460
+ # target_format_choices = update_format_choices(conversion_type)
461
+ # target_format = st.selectbox("Select Target Format", target_format_choices)
462
+
463
+ # # Show codec info for supported formats
464
+ # if conversion_type == 'Video to Audio' and target_format.upper() in AUDIO_CODECS:
465
+ # codec_info = AUDIO_CODECS[target_format.upper()]
466
+ # st.info(f"🎡 Audio Codec: {codec_info.get('acodec', 'default')}")
467
+ # elif conversion_type == 'Video to Video' and target_format.upper() in VIDEO_CODECS:
468
+ # codec_info = VIDEO_CODECS[target_format.upper()]
469
+ # st.info(f"🎬 Video Codec: {codec_info.get('vcodec', 'default')} | Audio Codec: {codec_info.get('acodec', 'default')}")
470
+
471
+ # if conversion_type == 'Video to Image':
472
+ # time_in_seconds = st.slider(
473
+ # "Time (in seconds) for image extraction",
474
+ # 0, int(video_duration), 0, 1
475
+ # )
476
+ # else:
477
+ # time_in_seconds = None
478
+
479
+ # if st.button("πŸš€ Convert", type="primary"):
480
+ # with st.spinner("Converting with optimized codecs..."):
481
+ # try:
482
+ # output_file = convert_video(temp_video_path, target_format, conversion_type, time_in_seconds)
483
+
484
+ # st.success("βœ… Conversion Successful!")
485
+
486
+ # # Show file size
487
+ # file_size = os.path.getsize(output_file) / (1024 * 1024) # MB
488
+ # st.info(f"πŸ“ File size: {file_size:.2f} MB")
489
+
490
+ # with open(output_file, "rb") as f:
491
+ # st.download_button(
492
+ # "⬇️ Download Converted File",
493
+ # f,
494
+ # file_name=os.path.basename(output_file),
495
+ # type="primary"
496
+ # )
497
+ # except Exception as e:
498
+ # st.error(f"❌ Error: {str(e)}")
499
+
500
+ # API Info & Supported Codecs
501
+ st.sidebar.header("πŸ”§ API Information")
502
+ st.sidebar.info("""
503
+ **API Endpoints:**
504
+ - `POST /api/convert` - Convert video files
505
+ - `GET /api/formats` - Get supported formats
506
+
507
+ **WordPress Integration:**
508
+ Configure the Hugging Face Space URL in WordPress settings.
509
+
510
+ **Test API:**
511
+ Visit `/docs` for interactive API documentation.
512
+ """)
513
+
514
+ # Show supported codecs
515
+ with st.sidebar.expander("🎡 Supported Audio Codecs"):
516
+ for format_name, codec_info in AUDIO_CODECS.items():
517
+ st.write(f"**{format_name}:** {codec_info.get('acodec', 'default')}")
518
+
519
+ with st.sidebar.expander("🎬 Supported Video Codecs"):
520
+ for format_name, codec_info in VIDEO_CODECS.items():
521
+ st.write(f"**{format_name}:** {codec_info.get('vcodec', 'default')}")
522
+
523
+ def run_fastapi():
524
+ uvicorn.run(app, host="0.0.0.0", port=7860)
525
+
526
+ # Run both applications
527
+ if __name__ == "__main__":
528
+ # Run FastAPI in background
529
+ fastapi_thread = Thread(target=run_fastapi, daemon=True)
530
+ fastapi_thread.start()
531
+
532
+ # # Run Streamlit
533
+ # streamlit_app()