abangopera commited on
Commit
43b8f41
·
verified ·
1 Parent(s): 3c905fa
Files changed (1) hide show
  1. app.py +0 -200
app.py CHANGED
@@ -3,8 +3,6 @@ import subprocess
3
  import os
4
  import uuid
5
  import tempfile
6
- import urllib.request
7
- import urllib.parse
8
 
9
  app = Flask(__name__)
10
 
@@ -97,64 +95,6 @@ def probe_media_duration_seconds(input_path):
97
  pass
98
  return None
99
 
100
- def _guess_extension_from_url_or_ct(url, content_type, fallback_ext):
101
- try:
102
- # Try from URL path
103
- path = urllib.parse.urlparse(url).path
104
- ext = os.path.splitext(path)[1]
105
- if ext:
106
- return ext
107
- except Exception:
108
- pass
109
- # Fallback from content-type
110
- if content_type:
111
- ct = content_type.lower().split(';')[0].strip()
112
- mapping = {
113
- 'image/jpeg': '.jpg',
114
- 'image/jpg': '.jpg',
115
- 'image/png': '.png',
116
- 'image/webp': '.webp',
117
- 'image/bmp': '.bmp',
118
- 'image/gif': '.gif',
119
- 'audio/mpeg': '.mp3',
120
- 'audio/mp3': '.mp3',
121
- 'audio/aac': '.aac',
122
- 'audio/wav': '.wav',
123
- 'audio/x-wav': '.wav',
124
- 'audio/flac': '.flac',
125
- 'audio/ogg': '.ogg',
126
- 'audio/webm': '.webm',
127
- }
128
- if ct in mapping:
129
- return mapping[ct]
130
- return fallback_ext
131
-
132
- def download_url_to_file(src_url, default_ext):
133
- """Download a URL to a temp file under OUTPUT_DIR, return the file path.
134
-
135
- This avoids extra deps; uses urllib with a sensible timeout.
136
- """
137
- try:
138
- req = urllib.request.Request(src_url, headers={
139
- 'User-Agent': 'Mozilla/5.0 (compatible; image-audio-fetcher)'
140
- })
141
- with urllib.request.urlopen(req, timeout=60) as resp:
142
- content_type = resp.headers.get('Content-Type', '')
143
- ext = _guess_extension_from_url_or_ct(src_url, content_type, default_ext)
144
- temp_name = f"dl_{uuid.uuid4().hex}{ext}"
145
- out_path = os.path.join(OUTPUT_DIR, temp_name)
146
- with open(out_path, 'wb') as f:
147
- while True:
148
- chunk = resp.read(1024 * 64)
149
- if not chunk:
150
- break
151
- f.write(chunk)
152
- if not os.path.exists(out_path) or os.path.getsize(out_path) == 0:
153
- raise RuntimeError('Downloaded file is empty')
154
- return out_path
155
- except Exception as e:
156
- raise RuntimeError(str(e))
157
-
158
  @app.route('/image-to-video', methods=['POST'])
159
  def image_to_video():
160
  """Create a video from a still image and an audio file.
@@ -299,146 +239,6 @@ def image_to_video():
299
  pass
300
  return response
301
 
302
- @app.route('/image-to-video-url', methods=['POST'])
303
- def image_to_video_url():
304
- """Create a video from image_url + audio_url (JSON body).
305
-
306
- JSON fields:
307
- - image_url (required)
308
- - audio_url (required)
309
- - duration (required unless audio_start/audio_end provided) seconds or HH:MM:SS
310
- - audio_start (optional) seconds or HH:MM:SS
311
- - audio_end (optional) seconds or HH:MM:SS
312
- - resolution (optional) preset or WIDTHxHEIGHT
313
- - fit (optional) 'pad' (default) or 'crop'
314
- """
315
- try:
316
- data = request.get_json(silent=True) or {}
317
- except Exception:
318
- data = {}
319
-
320
- # Support JSON body and/or query params as fallback
321
- image_url = (data.get('image_url') or request.args.get('image_url') or '').strip()
322
- audio_url = (data.get('audio_url') or request.args.get('audio_url') or '').strip()
323
- duration_str = data.get('duration') if 'duration' in data else request.args.get('duration')
324
- audio_start_str = data.get('audio_start') if 'audio_start' in data else request.args.get('audio_start')
325
- audio_end_str = data.get('audio_end') if 'audio_end' in data else request.args.get('audio_end')
326
- resolution = data.get('resolution') if 'resolution' in data else request.args.get('resolution')
327
- fit = (data.get('fit') if 'fit' in data else request.args.get('fit') or 'pad').lower()
328
-
329
- if not image_url or not audio_url:
330
- return jsonify({"error": "Harap kirim image_url dan audio_url"}), 400
331
-
332
- # Resolve timing parameters
333
- audio_start_sec = None
334
- audio_end_sec = None
335
- duration_sec = None
336
-
337
- if audio_start_str:
338
- audio_start_sec = int(float(hms_to_seconds(str(audio_start_str))))
339
- if audio_start_sec < 0:
340
- audio_start_sec = 0
341
- if audio_end_str:
342
- audio_end_sec = int(float(hms_to_seconds(str(audio_end_str))))
343
- if audio_end_sec < 0:
344
- audio_end_sec = 0
345
-
346
- if audio_start_sec is not None or audio_end_sec is not None:
347
- if audio_start_sec is None:
348
- audio_start_sec = 0
349
- if audio_end_sec is not None and audio_end_sec > audio_start_sec:
350
- duration_sec = audio_end_sec - audio_start_sec
351
- elif duration_str:
352
- tmp = int(float(hms_to_seconds(str(duration_str))))
353
- duration_sec = tmp if tmp > 0 else None
354
- if duration_sec is None or duration_sec <= 0:
355
- return jsonify({"error": "Harap berikan rentang audio yang valid (audio_start < audio_end) atau sertakan duration"}), 400
356
- else:
357
- if not duration_str:
358
- return jsonify({"error": "Parameter duration wajib diisi (detik atau HH:MM:SS)"}), 400
359
- tmp = int(float(hms_to_seconds(str(duration_str))))
360
- duration_sec = tmp if tmp > 0 else None
361
- if duration_sec is None:
362
- return jsonify({"error": "Duration tidak valid"}), 400
363
-
364
- width, height = get_resolution_params(resolution)
365
- if (fit in ['crop', 'pad']) and (resolution is not None) and (width is None or height is None):
366
- return jsonify({"error": "Format resolution tidak valid. Gunakan preset (mis. portrait) atau WIDTHxHEIGHT"}), 400
367
- width, height = normalize_even(width, height)
368
-
369
- # Download inputs
370
- try:
371
- image_path = download_url_to_file(image_url, '.png')
372
- audio_path = download_url_to_file(audio_url, '.mp3')
373
- except Exception as e:
374
- return jsonify({"error": f"Gagal download file dari URL: {str(e)}"}), 400
375
-
376
- # Prepare output temp file
377
- temp_out = tempfile.NamedTemporaryFile(delete=False, suffix='.mp4')
378
- temp_out.close()
379
- output_path = temp_out.name
380
-
381
- # Build FFmpeg command (same as upload variant)
382
- ffmpeg_cmd = [
383
- "ffmpeg", "-y",
384
- "-loop", "1", "-i", image_path,
385
- ]
386
- if audio_start_sec is not None:
387
- ffmpeg_cmd += ["-ss", str(audio_start_sec)]
388
- ffmpeg_cmd += ["-i", audio_path]
389
- ffmpeg_cmd += ["-r", "30"]
390
- if width and height:
391
- if fit == 'crop':
392
- vf = f"scale={width}:{height}:force_original_aspect_ratio=increase,crop={width}:{height}"
393
- else:
394
- vf = f"scale={width}:{height}:force_original_aspect_ratio=decrease,pad={width}:{height}:(ow-iw)/2:(oh-ih)/2:black"
395
- ffmpeg_cmd += ["-vf", vf]
396
- ffmpeg_cmd += [
397
- "-t", str(duration_sec),
398
- "-c:v", "libx264", "-pix_fmt", "yuv420p",
399
- "-c:a", "aac", "-b:a", "192k",
400
- "-shortest",
401
- "-movflags", "+faststart",
402
- output_path
403
- ]
404
-
405
- result = run_cmd(ffmpeg_cmd)
406
- # Cleanup downloads
407
- try:
408
- if os.path.exists(image_path):
409
- os.remove(image_path)
410
- if os.path.exists(audio_path):
411
- os.remove(audio_path)
412
- except Exception:
413
- pass
414
-
415
- if result.returncode != 0:
416
- try:
417
- os.remove(output_path)
418
- except Exception:
419
- pass
420
- return jsonify({
421
- "error": "Gagal membuat video dari image dan audio",
422
- "details": result.stderr
423
- }), 500
424
-
425
- if not os.path.exists(output_path) or os.path.getsize(output_path) == 0:
426
- try:
427
- os.remove(output_path)
428
- except Exception:
429
- pass
430
- return jsonify({"error": "File hasil kosong atau tidak ada"}), 500
431
-
432
- from flask import send_file
433
- response = send_file(output_path, as_attachment=True, download_name=f"image2video_{uuid.uuid4().hex}.mp4")
434
- @response.call_on_close
435
- def cleanup():
436
- try:
437
- os.remove(output_path)
438
- except Exception:
439
- pass
440
- return response
441
-
442
  @app.route('/download-split', methods=['GET'])
443
  def download_and_split():
444
  """Download a full YouTube video and split it into fixed-duration parts.
 
3
  import os
4
  import uuid
5
  import tempfile
 
 
6
 
7
  app = Flask(__name__)
8
 
 
95
  pass
96
  return None
97
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
  @app.route('/image-to-video', methods=['POST'])
99
  def image_to_video():
100
  """Create a video from a still image and an audio file.
 
239
  pass
240
  return response
241
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
242
  @app.route('/download-split', methods=['GET'])
243
  def download_and_split():
244
  """Download a full YouTube video and split it into fixed-duration parts.