devusman commited on
Commit
65edd82
·
1 Parent(s): 4973717
Files changed (2) hide show
  1. Dockerfile +7 -7
  2. app.py +21 -29
Dockerfile CHANGED
@@ -1,9 +1,10 @@
 
 
1
  # Use an official Python runtime as a parent image
2
  FROM python:3.10-slim-buster
3
 
4
  # 1. Install FFmpeg
5
  # Debian/Ubuntu based images allow easy installation via apt
6
- # ffmpeg is essential for the conversion process
7
  RUN apt-get update && \
8
  apt-get install -y ffmpeg && \
9
  rm -rf /var/lib/apt/lists/*
@@ -17,13 +18,12 @@ COPY requirements.txt /app/requirements.txt
17
  # Install Python dependencies, including Gunicorn for production server
18
  RUN pip install --no-cache-dir -r requirements.txt
19
 
20
- # 3. Copy the application code
21
  COPY ffmpeg_service.py /app/
22
 
23
- # Port 8080 is the standard port for Hugging Face Spaces (hardcoded in CMD)
24
- EXPOSE 5001
25
 
26
  # 4. Run the Flask application using Gunicorn
27
- # Binds to 0.0.0.0:8080 as required by HF Spaces
28
- # Format: 'module_name:app_variable_name' -> 'ffmpeg_service:ffmpeg_app'
29
- CMD exec gunicorn --bind 0.0.0.0:5001 --workers 2 ffmpeg_service:ffmpeg_app
 
1
+ # Dockerfile
2
+
3
  # Use an official Python runtime as a parent image
4
  FROM python:3.10-slim-buster
5
 
6
  # 1. Install FFmpeg
7
  # Debian/Ubuntu based images allow easy installation via apt
 
8
  RUN apt-get update && \
9
  apt-get install -y ffmpeg && \
10
  rm -rf /var/lib/apt/lists/*
 
18
  # Install Python dependencies, including Gunicorn for production server
19
  RUN pip install --no-cache-dir -r requirements.txt
20
 
21
+ # 3. Copy the application code (Ensure this file exists in the same directory!)
22
  COPY ffmpeg_service.py /app/
23
 
24
+ # Port 8080 is the standard port for Hugging Face Spaces Docker deployments
25
+ EXPOSE 8080
26
 
27
  # 4. Run the Flask application using Gunicorn
28
+ # Binds to 0.0.0.0:8080
29
+ CMD exec gunicorn --bind 0.0.0.0:8080 --workers 2 ffmpeg_service:ffmpeg_app
 
app.py CHANGED
@@ -1,4 +1,4 @@
1
- # This is a NEW, separate app deployed to a server that has FFmpeg installed (e.g., Hugging Face Space with Docker)
2
 
3
  from flask import Flask, request, Response, stream_with_context
4
  import requests
@@ -10,8 +10,6 @@ import time
10
 
11
  ffmpeg_app = Flask(__name__)
12
 
13
- # NOTE: Ensure the 'ffmpeg' binary is available in the environment's PATH
14
-
15
  @ffmpeg_app.route('/convert', methods=['GET'])
16
  def convert_media():
17
  """
@@ -26,25 +24,25 @@ def convert_media():
26
  temp_dir = tempfile.mkdtemp()
27
 
28
  # Use the original format's extension for the input file to help FFmpeg
29
- input_ext = media_url.split('.')[-1].split('?')[0] # Heuristic to get file extension
30
  input_file_path = os.path.join(temp_dir, f"input.{input_ext}")
31
  output_file_path = os.path.join(temp_dir, f"output.{target_format}")
32
 
33
  try:
34
  start_time = time.time()
35
- print(f"Starting conversion for {target_format} from URL: {media_url[:100]}...")
36
 
37
  # 1. Download the raw media file to a temporary file
38
  with requests.get(media_url, stream=True, timeout=120) as r:
39
  r.raise_for_status()
40
  content_length = int(r.headers.get('content-length', 0))
41
- print(f"Raw media size: {content_length/1024/1024:.2f} MB")
42
  with open(input_file_path, 'wb') as f:
43
  for chunk in r.iter_content(chunk_size=8192):
44
  f.write(chunk)
45
 
46
  download_time = time.time()
47
- print(f"Download complete. Time taken: {download_time - start_time:.2f} seconds.")
48
 
49
  # 2. Run FFmpeg conversion
50
  command = [
@@ -54,25 +52,19 @@ def convert_media():
54
  # Audio-specific options for quality and codec
55
  '-q:a', '0', # Variable bitrate, highest quality
56
  '-map', 'a', # Only include audio streams
57
- '-c:a', 'libmp3lame' if target_format == 'mp3' else 'aac', # Use libmp3lame for mp3, aac for m4a/wav if necessary
 
 
58
  '-f', target_format,
59
  output_file_path
60
  ]
61
 
62
- # Override codec for WAV (PCM uncompressed)
63
- if target_format == 'wav':
64
- command[9] = 'pcm_s16le' # Use uncompressed PCM 16-bit little-endian
65
- elif target_format == 'm4a':
66
- command[9] = 'aac'
67
-
68
-
69
- process = subprocess.run(command, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=180) # 3-minute timeout
70
 
71
  conversion_time = time.time()
72
- print(f"FFmpeg conversion complete. Time taken: {conversion_time - download_time:.2f} seconds.")
73
- # print(f"FFmpeg Output:\n{process.stderr.decode()}")
74
 
75
- # 3. Stream the converted file back to the Vercel app
76
  def stream_output_file():
77
  with open(output_file_path, 'rb') as f:
78
  chunk = True
@@ -80,28 +72,28 @@ def convert_media():
80
  chunk = f.read(8192)
81
  yield chunk
82
 
83
- mime_type = f'audio/{target_format}' if target_format != 'm4a' else 'audio/mp4' # m4a is audio/mp4
84
 
85
  return Response(stream_with_context(stream_output_file()),
86
- mimetype=mime_type,
87
- # Do NOT set Content-Length as it's not possible before streaming starts
88
- )
89
 
90
  except subprocess.CalledProcessError as e:
91
- print(f"FFmpeg command failed with error: {e.stderr.decode()}")
92
  return f"Conversion failed: {e.stderr.decode()}", 500
93
  except requests.exceptions.Timeout:
94
- return "Media download timed out.", 504
95
  except Exception as e:
96
- print(f"An error occurred in the FFmpeg service: {e}")
97
  return f"Internal Server Error: {e}", 500
98
 
99
  finally:
100
  # 4. Cleanup temporary files
101
  if os.path.exists(temp_dir):
102
  shutil.rmtree(temp_dir)
103
- print(f"Cleaned up temp directory: {temp_dir}")
 
104
 
105
  if __name__ == '__main__':
106
- # You would typically use a WSGI server like Gunicorn in a production Docker deployment
107
- ffmpeg_app.run(host='0.0.0.0', port=5001, debug=True)
 
 
1
+ # ffmpeg_service.py
2
 
3
  from flask import Flask, request, Response, stream_with_context
4
  import requests
 
10
 
11
  ffmpeg_app = Flask(__name__)
12
 
 
 
13
  @ffmpeg_app.route('/convert', methods=['GET'])
14
  def convert_media():
15
  """
 
24
  temp_dir = tempfile.mkdtemp()
25
 
26
  # Use the original format's extension for the input file to help FFmpeg
27
+ input_ext = media_url.split('.')[-1].split('?')[0]
28
  input_file_path = os.path.join(temp_dir, f"input.{input_ext}")
29
  output_file_path = os.path.join(temp_dir, f"output.{target_format}")
30
 
31
  try:
32
  start_time = time.time()
33
+ print(f"[{time.strftime('%H:%M:%S')}] Starting conversion for {target_format} from URL: {media_url[:100]}...")
34
 
35
  # 1. Download the raw media file to a temporary file
36
  with requests.get(media_url, stream=True, timeout=120) as r:
37
  r.raise_for_status()
38
  content_length = int(r.headers.get('content-length', 0))
39
+ print(f"[{time.strftime('%H:%M:%S')}] Raw media size: {content_length/1024/1024:.2f} MB")
40
  with open(input_file_path, 'wb') as f:
41
  for chunk in r.iter_content(chunk_size=8192):
42
  f.write(chunk)
43
 
44
  download_time = time.time()
45
+ print(f"[{time.strftime('%H:%M:%S')}] Download complete. Time taken: {download_time - start_time:.2f} seconds.")
46
 
47
  # 2. Run FFmpeg conversion
48
  command = [
 
52
  # Audio-specific options for quality and codec
53
  '-q:a', '0', # Variable bitrate, highest quality
54
  '-map', 'a', # Only include audio streams
55
+ # Codec selection based on format
56
+ '-c:a', 'libmp3lame' if target_format == 'mp3' else
57
+ ('pcm_s16le' if target_format == 'wav' else 'aac'),
58
  '-f', target_format,
59
  output_file_path
60
  ]
61
 
62
+ subprocess.run(command, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=180) # 3-minute timeout
 
 
 
 
 
 
 
63
 
64
  conversion_time = time.time()
65
+ print(f"[{time.strftime('%H:%M:%S')}] FFmpeg conversion complete. Time taken: {conversion_time - download_time:.2f} seconds.")
 
66
 
67
+ # 3. Stream the converted file back
68
  def stream_output_file():
69
  with open(output_file_path, 'rb') as f:
70
  chunk = True
 
72
  chunk = f.read(8192)
73
  yield chunk
74
 
75
+ mime_type = f'audio/{target_format}' if target_format != 'm4a' else 'audio/mp4'
76
 
77
  return Response(stream_with_context(stream_output_file()),
78
+ mimetype=mime_type)
 
 
79
 
80
  except subprocess.CalledProcessError as e:
81
+ print(f"[{time.strftime('%H:%M:%S')}] FFmpeg command failed with error: {e.stderr.decode()}")
82
  return f"Conversion failed: {e.stderr.decode()}", 500
83
  except requests.exceptions.Timeout:
84
+ return "Media download or conversion timed out.", 504
85
  except Exception as e:
86
+ print(f"[{time.strftime('%H:%M:%S')}] An error occurred in the FFmpeg service: {e}")
87
  return f"Internal Server Error: {e}", 500
88
 
89
  finally:
90
  # 4. Cleanup temporary files
91
  if os.path.exists(temp_dir):
92
  shutil.rmtree(temp_dir)
93
+ print(f"[{time.strftime('%H:%M:%S')}] Cleaned up temp directory: {temp_dir}")
94
+
95
 
96
  if __name__ == '__main__':
97
+ # For local development/testing of the FFmpeg service
98
+ # NOTE: When deployed via Docker/Gunicorn on Hugging Face, this block is ignored.
99
+ ffmpeg_app.run(host='0.0.0.0', port=8080, debug=True)