Chrunos commited on
Commit
5e23e4b
·
verified ·
1 Parent(s): c09c0d6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +48 -43
app.py CHANGED
@@ -3,19 +3,22 @@ import re
3
  import threading
4
  import uuid
5
  import requests
 
6
  from flask import Flask, request, jsonify
7
  from urllib.parse import quote
8
 
9
- # Initialize Flask App
 
 
 
 
10
  app = Flask(__name__)
11
 
12
  # --- Secret Management ---
13
  # Get the base domain of your downloader service from Hugging Face secrets.
14
- # You must set a secret named 'YTDLP_DOMAIN' in your Space settings.
15
- # The value should be, for example, 'https://ytdlp.online'
16
  YTDLP_DOMAIN = os.environ.get("YTDLP_DOMAIN")
17
 
18
- # In-memory "database" to store task status
19
  tasks = {}
20
  tasks_lock = threading.Lock()
21
 
@@ -32,53 +35,64 @@ def get_format_code(quality: str) -> str:
32
  def process_video_task(task_id: str, video_url: str, quality: str):
33
  """
34
  This function runs in a background thread. It calls the external API,
35
- processes the streaming response, extracts the download URL, and updates the task status.
 
36
  """
 
 
37
  if not YTDLP_DOMAIN:
 
38
  with tasks_lock:
39
  tasks[task_id]['status'] = 'error'
40
- tasks[task_id]['message'] = "YTDLP_DOMAIN secret is not set in the Space settings."
41
  return
42
 
43
- found_url = None
44
  try:
45
  format_code = get_format_code(quality)
46
  command = f"{video_url} -f {format_code}"
47
  encoded_command = quote(command)
48
-
49
- # The endpoint that provides the streaming response
50
  stream_api_url = f"{YTDLP_DOMAIN}/stream?command={encoded_command}"
51
 
52
- # Use stream=True to handle the server-sent event stream
 
 
 
53
  with requests.get(stream_api_url, stream=True, timeout=300) as response:
54
  response.raise_for_status()
55
-
56
- # Process each line of the streaming response
57
  for line_bytes in response.iter_lines():
58
  if line_bytes:
59
  line = line_bytes.decode('utf-8')
60
-
61
- # Use regex to find the relative download link
62
- match = re.search(r'href="(/download/download/[^"]+)"', line)
63
- if match:
64
- relative_url = match.group(1)
65
- # Construct the final, absolute URL
66
- final_stream_url = f"{YTDLP_DOMAIN}{relative_url}"
67
- found_url = final_stream_url
68
- # Once found, no need to process the rest of the stream
69
- break
70
 
71
- # After checking the whole stream, update the status
 
 
 
 
 
 
 
 
 
 
 
 
 
72
  with tasks_lock:
73
  if found_url:
74
  tasks[task_id]['status'] = 'completed'
75
  tasks[task_id]['result'] = found_url
 
76
  else:
77
- # If the loop finished but no URL was found
78
  tasks[task_id]['status'] = 'error'
79
- tasks[task_id]['message'] = "Command executed, but no download link was found in the response."
 
 
 
 
80
 
81
  except requests.exceptions.RequestException as e:
 
82
  with tasks_lock:
83
  tasks[task_id]['status'] = 'error'
84
  tasks[task_id]['message'] = str(e)
@@ -86,19 +100,17 @@ def process_video_task(task_id: str, video_url: str, quality: str):
86
 
87
  @app.route('/process', methods=['POST'])
88
  def start_processing():
89
- """
90
- Endpoint to start a new video processing task.
91
- Accepts a JSON payload with 'video_url' and 'quality'.
92
- Returns a task_id immediately.
93
- """
94
  data = request.get_json()
95
  if not data or 'video_url' not in data or 'quality' not in data:
96
  return jsonify({"error": "Missing 'video_url' or 'quality' in request body"}), 400
97
 
 
 
 
98
  video_url = data['video_url']
99
  quality = data.get('quality', 'best')
100
 
101
- task_id = str(uuid.uuid4())
102
  with tasks_lock:
103
  tasks[task_id] = {'status': 'processing'}
104
 
@@ -110,23 +122,15 @@ def start_processing():
110
 
111
  @app.route('/api/<string:task_id>', methods=['GET'])
112
  def get_task_status(task_id: str):
113
- """
114
- Endpoint to check the status and result of a task.
115
- Returns the task's current state. If completed, includes the final URL.
116
- """
117
  with tasks_lock:
118
  task = tasks.get(task_id)
119
 
120
  if task is None:
 
121
  return jsonify({'error': 'Task not found'}), 404
122
-
123
- # If the task completed but has no 'result' key, it's an error
124
- if task['status'] == 'completed' and 'result' not in task:
125
- return jsonify({
126
- 'status': 'error',
127
- 'message': 'Task completed but no result URL was generated.'
128
- }), 500
129
-
130
  return jsonify(task)
131
 
132
  @app.route('/')
@@ -136,5 +140,6 @@ def index():
136
  return "<h1>Video Processing API Error</h1><p>Server is missing the YTDLP_DOMAIN secret.</p>", 500
137
  return "<h1>Video Processing API is running!</h1><p>Use /process and /api/&lt;task_id&gt; endpoints.</p>"
138
 
 
139
  if __name__ == '__main__':
140
  app.run(debug=True, host='0.0.0.0', port=int(os.environ.get("PORT", 7860)))
 
3
  import threading
4
  import uuid
5
  import requests
6
+ import logging
7
  from flask import Flask, request, jsonify
8
  from urllib.parse import quote
9
 
10
+ # --- Logging Configuration ---
11
+ # This will print logs to the console, which you can view in your Space's "Logs" tab.
12
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
13
+
14
+ # --- Flask App Initialization ---
15
  app = Flask(__name__)
16
 
17
  # --- Secret Management ---
18
  # Get the base domain of your downloader service from Hugging Face secrets.
 
 
19
  YTDLP_DOMAIN = os.environ.get("YTDLP_DOMAIN")
20
 
21
+ # --- In-memory Task Storage ---
22
  tasks = {}
23
  tasks_lock = threading.Lock()
24
 
 
35
  def process_video_task(task_id: str, video_url: str, quality: str):
36
  """
37
  This function runs in a background thread. It calls the external API,
38
+ waits for the stream to complete, then parses the full response to find the
39
+ download URL and updates the task status.
40
  """
41
+ app.logger.info(f"Task {task_id}: Starting for URL '{video_url}' with quality '{quality}'.")
42
+
43
  if not YTDLP_DOMAIN:
44
+ app.logger.error(f"Task {task_id}: Failed. YTDLP_DOMAIN secret is not set in the Space settings.")
45
  with tasks_lock:
46
  tasks[task_id]['status'] = 'error'
47
+ tasks[task_id]['message'] = "Server configuration error: YTDLP_DOMAIN secret is not set."
48
  return
49
 
 
50
  try:
51
  format_code = get_format_code(quality)
52
  command = f"{video_url} -f {format_code}"
53
  encoded_command = quote(command)
 
 
54
  stream_api_url = f"{YTDLP_DOMAIN}/stream?command={encoded_command}"
55
 
56
+ app.logger.info(f"Task {task_id}: Calling stream API: {stream_api_url}")
57
+
58
+ # Collect the full response from the stream
59
+ full_response_lines = []
60
  with requests.get(stream_api_url, stream=True, timeout=300) as response:
61
  response.raise_for_status()
 
 
62
  for line_bytes in response.iter_lines():
63
  if line_bytes:
64
  line = line_bytes.decode('utf-8')
65
+ full_response_lines.append(line)
 
 
 
 
 
 
 
 
 
66
 
67
+ app.logger.info(f"Task {task_id}: Stream completed. Searching for download link in the response.")
68
+
69
+ # Now, parse the complete response to find the link
70
+ found_url = None
71
+ for line in full_response_lines:
72
+ match = re.search(r'href="(/download/download/[^"]+)"', line)
73
+ if match:
74
+ relative_url = match.group(1)
75
+ final_stream_url = f"{YTDLP_DOMAIN}{relative_url}"
76
+ found_url = final_stream_url
77
+ app.logger.info(f"Task {task_id}: Found download link: {final_stream_url}")
78
+ break # Found the link, no need to keep searching
79
+
80
+ # After checking the whole response, update the task status
81
  with tasks_lock:
82
  if found_url:
83
  tasks[task_id]['status'] = 'completed'
84
  tasks[task_id]['result'] = found_url
85
+ app.logger.info(f"Task {task_id}: Marked as COMPLETED.")
86
  else:
 
87
  tasks[task_id]['status'] = 'error'
88
+ tasks[task_id]['message'] = "Command executed, but no download link was found in the API response."
89
+ app.logger.warning(f"Task {task_id}: Marked as ERROR, no download link found.")
90
+ # Log the last few lines for debugging, in case of an error message from the service
91
+ app.logger.info(f"Task {task_id}: Last 5 lines of response: {full_response_lines[-5:]}")
92
+
93
 
94
  except requests.exceptions.RequestException as e:
95
+ app.logger.error(f"Task {task_id}: A request exception occurred. Error: {e}")
96
  with tasks_lock:
97
  tasks[task_id]['status'] = 'error'
98
  tasks[task_id]['message'] = str(e)
 
100
 
101
  @app.route('/process', methods=['POST'])
102
  def start_processing():
103
+ """Endpoint to start a new video processing task."""
 
 
 
 
104
  data = request.get_json()
105
  if not data or 'video_url' not in data or 'quality' not in data:
106
  return jsonify({"error": "Missing 'video_url' or 'quality' in request body"}), 400
107
 
108
+ task_id = str(uuid.uuid4())
109
+ app.logger.info(f"Received new job. Assigning Task ID: {task_id}")
110
+
111
  video_url = data['video_url']
112
  quality = data.get('quality', 'best')
113
 
 
114
  with tasks_lock:
115
  tasks[task_id] = {'status': 'processing'}
116
 
 
122
 
123
  @app.route('/api/<string:task_id>', methods=['GET'])
124
  def get_task_status(task_id: str):
125
+ """Endpoint to check the status and result of a task."""
126
+ app.logger.info(f"Status check for Task ID: {task_id}")
 
 
127
  with tasks_lock:
128
  task = tasks.get(task_id)
129
 
130
  if task is None:
131
+ app.logger.warning(f"Status check for non-existent Task ID: {task_id}")
132
  return jsonify({'error': 'Task not found'}), 404
133
+
 
 
 
 
 
 
 
134
  return jsonify(task)
135
 
136
  @app.route('/')
 
140
  return "<h1>Video Processing API Error</h1><p>Server is missing the YTDLP_DOMAIN secret.</p>", 500
141
  return "<h1>Video Processing API is running!</h1><p>Use /process and /api/&lt;task_id&gt; endpoints.</p>"
142
 
143
+ # This part is for local testing. Gunicorn runs the app in your Space.
144
  if __name__ == '__main__':
145
  app.run(debug=True, host='0.0.0.0', port=int(os.environ.get("PORT", 7860)))