wynai commited on
Commit
3a02865
·
verified ·
1 Parent(s): d5552a5

Upload 5 files

Browse files
Files changed (5) hide show
  1. index.html +298 -0
  2. main.py +46 -0
  3. mock_scraper.py +109 -0
  4. video.py +225 -0
  5. video_scraper_v2.py +291 -0
index.html ADDED
@@ -0,0 +1,298 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="vi">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Video Generator API</title>
7
+ <style>
8
+ body {
9
+ font-family: Arial, sans-serif;
10
+ max-width: 800px;
11
+ margin: 0 auto;
12
+ padding: 20px;
13
+ background-color: #f5f5f5;
14
+ }
15
+ .container {
16
+ background: white;
17
+ padding: 30px;
18
+ border-radius: 10px;
19
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
20
+ }
21
+ h1 {
22
+ color: #333;
23
+ text-align: center;
24
+ }
25
+ .section {
26
+ margin: 30px 0;
27
+ padding: 20px;
28
+ border: 1px solid #ddd;
29
+ border-radius: 5px;
30
+ }
31
+ .section h2 {
32
+ color: #555;
33
+ margin-top: 0;
34
+ }
35
+ input[type="text"] {
36
+ width: 100%;
37
+ padding: 10px;
38
+ border: 1px solid #ddd;
39
+ border-radius: 5px;
40
+ margin: 10px 0;
41
+ }
42
+ button {
43
+ background: #007bff;
44
+ color: white;
45
+ padding: 10px 20px;
46
+ border: none;
47
+ border-radius: 5px;
48
+ cursor: pointer;
49
+ margin: 5px;
50
+ }
51
+ button:hover {
52
+ background: #0056b3;
53
+ }
54
+ .result {
55
+ margin: 20px 0;
56
+ padding: 15px;
57
+ background: #f8f9fa;
58
+ border-radius: 5px;
59
+ border-left: 4px solid #007bff;
60
+ }
61
+ .error {
62
+ border-left-color: #dc3545;
63
+ background: #f8d7da;
64
+ }
65
+ .success {
66
+ border-left-color: #28a745;
67
+ background: #d4edda;
68
+ }
69
+ .code {
70
+ background: #f4f4f4;
71
+ padding: 10px;
72
+ border-radius: 5px;
73
+ font-family: monospace;
74
+ margin: 10px 0;
75
+ }
76
+ .status {
77
+ font-weight: bold;
78
+ padding: 5px 10px;
79
+ border-radius: 3px;
80
+ display: inline-block;
81
+ margin: 5px 0;
82
+ }
83
+ .status.pending { background: #ffc107; color: #212529; }
84
+ .status.processing { background: #17a2b8; color: white; }
85
+ .status.completed { background: #28a745; color: white; }
86
+ .status.failed { background: #dc3545; color: white; }
87
+ </style>
88
+ </head>
89
+ <body>
90
+ <div class="container">
91
+ <h1>🎬 Video Generator API</h1>
92
+ <p>API để tự động tạo video từ prompt sử dụng Hugging Face Self-Forcing</p>
93
+
94
+ <!-- Test API Section -->
95
+ <div class="section">
96
+ <h2>🧪 Test API</h2>
97
+ <input type="text" id="promptInput" placeholder="Nhập prompt để tạo video (ví dụ: A dog running in a park)" value="A cat playing with a ball">
98
+ <button onclick="generateVideo()">Tạo Video</button>
99
+ <button onclick="checkStatus()">Kiểm tra trạng thái</button>
100
+ <button onclick="listJobs()">Danh sách Jobs</button>
101
+
102
+ <div id="result"></div>
103
+ </div>
104
+
105
+ <!-- API Documentation -->
106
+ <div class="section">
107
+ <h2>📖 Hướng dẫn sử dụng API</h2>
108
+
109
+ <h3>1. Tạo video từ prompt</h3>
110
+ <div class="code">
111
+ GET /api/video/generate/&lt;prompt&gt;
112
+ </div>
113
+ <p><strong>Ví dụ:</strong></p>
114
+ <div class="code">
115
+ GET /api/video/generate/A%20dog%20running%20in%20a%20park
116
+ </div>
117
+ <p><strong>Response:</strong></p>
118
+ <div class="code">
119
+ {
120
+ "success": true,
121
+ "job_id": "uuid-here",
122
+ "message": "Video generation started",
123
+ "status_url": "/api/video/status/uuid-here",
124
+ "download_url": "/api/video/download/uuid-here"
125
+ }
126
+ </div>
127
+
128
+ <h3>2. Kiểm tra trạng thái</h3>
129
+ <div class="code">
130
+ GET /api/video/status/&lt;job_id&gt;
131
+ </div>
132
+ <p><strong>Response:</strong></p>
133
+ <div class="code">
134
+ {
135
+ "success": true,
136
+ "job_id": "uuid-here",
137
+ "status": "completed",
138
+ "prompt": "A dog running in a park",
139
+ "video_ready": true,
140
+ "download_url": "/api/video/download/uuid-here"
141
+ }
142
+ </div>
143
+
144
+ <h3>3. Tải xuống video</h3>
145
+ <div class="code">
146
+ GET /api/video/download/&lt;job_id&gt;
147
+ </div>
148
+ <p>Trả về file video để tải xuống.</p>
149
+
150
+ <h3>4. Danh sách tất cả jobs</h3>
151
+ <div class="code">
152
+ GET /api/video/jobs
153
+ </div>
154
+
155
+ <h3>5. Health check</h3>
156
+ <div class="code">
157
+ GET /api/video/health
158
+ </div>
159
+ </div>
160
+
161
+ <!-- Example Usage -->
162
+ <div class="section">
163
+ <h2>💡 Ví dụ sử dụng</h2>
164
+ <p>Để tạo video với prompt "tạo ảnh một chú chó", bạn có thể gọi:</p>
165
+ <div class="code">
166
+ https://your-api.com/api/video/generate/tạo%20ảnh%20một%20chú%20chó
167
+ </div>
168
+ <p>Sau đó kiểm tra trạng thái và tải xuống video khi hoàn thành.</p>
169
+ </div>
170
+ </div>
171
+
172
+ <script>
173
+ let currentJobId = null;
174
+
175
+ function showResult(message, type = 'result') {
176
+ const resultDiv = document.getElementById('result');
177
+ resultDiv.innerHTML = `<div class="result ${type}">${message}</div>`;
178
+ }
179
+
180
+ async function generateVideo() {
181
+ const prompt = document.getElementById('promptInput').value.trim();
182
+ if (!prompt) {
183
+ showResult('Vui lòng nhập prompt!', 'error');
184
+ return;
185
+ }
186
+
187
+ try {
188
+ showResult('Đang gửi yêu cầu tạo video...', 'result');
189
+
190
+ const response = await fetch(`/api/video/generate/${encodeURIComponent(prompt)}`);
191
+ const data = await response.json();
192
+
193
+ if (data.success) {
194
+ currentJobId = data.job_id;
195
+ showResult(`
196
+ <strong>✅ Đã bắt đầu tạo video!</strong><br>
197
+ Job ID: ${data.job_id}<br>
198
+ Status URL: ${data.status_url}<br>
199
+ Download URL: ${data.download_url}
200
+ `, 'success');
201
+
202
+ // Tự động kiểm tra trạng thái sau 5 giây
203
+ setTimeout(checkStatus, 5000);
204
+ } else {
205
+ showResult(`❌ Lỗi: ${data.error}`, 'error');
206
+ }
207
+ } catch (error) {
208
+ showResult(`❌ Lỗi kết nối: ${error.message}`, 'error');
209
+ }
210
+ }
211
+
212
+ async function checkStatus() {
213
+ if (!currentJobId) {
214
+ showResult('Chưa có job nào được tạo!', 'error');
215
+ return;
216
+ }
217
+
218
+ try {
219
+ const response = await fetch(`/api/video/status/${currentJobId}`);
220
+ const data = await response.json();
221
+
222
+ if (data.success) {
223
+ const statusClass = data.status;
224
+ let message = `
225
+ <strong>📊 Trạng thái Job</strong><br>
226
+ Job ID: ${data.job_id}<br>
227
+ Prompt: ${data.prompt}<br>
228
+ Status: <span class="status ${statusClass}">${data.status}</span><br>
229
+ `;
230
+
231
+ if (data.status === 'completed' && data.video_ready) {
232
+ message += `<br><a href="${data.download_url}" target="_blank">
233
+ <button>📥 Tải xuống video</button>
234
+ </a>`;
235
+ } else if (data.status === 'failed') {
236
+ message += `<br>❌ Lỗi: ${data.error || 'Unknown error'}`;
237
+ } else if (data.status === 'processing') {
238
+ message += `<br>⏳ Đang xử lý... Sẽ kiểm tra lại sau 10 giây.`;
239
+ setTimeout(checkStatus, 10000);
240
+ }
241
+
242
+ showResult(message, data.status === 'completed' ? 'success' : 'result');
243
+ } else {
244
+ showResult(`❌ Lỗi: ${data.error}`, 'error');
245
+ }
246
+ } catch (error) {
247
+ showResult(`❌ Lỗi kết nối: ${error.message}`, 'error');
248
+ }
249
+ }
250
+
251
+ async function listJobs() {
252
+ try {
253
+ const response = await fetch('/api/video/jobs');
254
+ const data = await response.json();
255
+
256
+ if (data.success) {
257
+ let message = `<strong>📋 Danh sách Jobs (${data.total})</strong><br><br>`;
258
+
259
+ if (data.jobs.length === 0) {
260
+ message += 'Chưa có job nào.';
261
+ } else {
262
+ data.jobs.forEach(job => {
263
+ message += `
264
+ <div style="border: 1px solid #ddd; padding: 10px; margin: 5px 0; border-radius: 5px;">
265
+ <strong>Job:</strong> ${job.job_id}<br>
266
+ <strong>Prompt:</strong> ${job.prompt}<br>
267
+ <strong>Status:</strong> <span class="status ${job.status}">${job.status}</span><br>
268
+ ${job.download_url ? `<a href="${job.download_url}" target="_blank"><button>📥 Tải xuống</button></a>` : ''}
269
+ </div>
270
+ `;
271
+ });
272
+ }
273
+
274
+ showResult(message, 'result');
275
+ } else {
276
+ showResult(`❌ Lỗi: ${data.error}`, 'error');
277
+ }
278
+ } catch (error) {
279
+ showResult(`❌ Lỗi kết nối: ${error.message}`, 'error');
280
+ }
281
+ }
282
+
283
+ // Health check khi trang load
284
+ window.onload = async function() {
285
+ try {
286
+ const response = await fetch('/api/video/health');
287
+ const data = await response.json();
288
+ if (data.success) {
289
+ console.log('✅ API đang hoạt động:', data);
290
+ }
291
+ } catch (error) {
292
+ console.error('❌ API không hoạt động:', error);
293
+ }
294
+ };
295
+ </script>
296
+ </body>
297
+ </html>
298
+
main.py ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ # DON'T CHANGE THIS !!!
4
+ sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
5
+
6
+ from flask import Flask, send_from_directory
7
+ from flask_cors import CORS
8
+ from src.models.user import db
9
+ from src.routes.user import user_bp
10
+ from src.routes.video import video_bp
11
+
12
+ app = Flask(__name__, static_folder=os.path.join(os.path.dirname(__file__), 'static'))
13
+ app.config['SECRET_KEY'] = 'asdf#FGSgvasgf$5$WGT'
14
+
15
+ # Enable CORS for all routes
16
+ CORS(app)
17
+
18
+ app.register_blueprint(user_bp, url_prefix='/api')
19
+ app.register_blueprint(video_bp, url_prefix='/api/video')
20
+
21
+ # uncomment if you need to use database
22
+ app.config['SQLALCHEMY_DATABASE_URI'] = f"sqlite:///{os.path.join(os.path.dirname(__file__), 'database', 'app.db')}"
23
+ app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
24
+ db.init_app(app)
25
+ with app.app_context():
26
+ db.create_all()
27
+
28
+ @app.route('/', defaults={'path': ''})
29
+ @app.route('/<path:path>')
30
+ def serve(path):
31
+ static_folder_path = app.static_folder
32
+ if static_folder_path is None:
33
+ return "Static folder not configured", 404
34
+
35
+ if path != "" and os.path.exists(os.path.join(static_folder_path, path)):
36
+ return send_from_directory(static_folder_path, path)
37
+ else:
38
+ index_path = os.path.join(static_folder_path, 'index.html')
39
+ if os.path.exists(index_path):
40
+ return send_from_directory(static_folder_path, 'index.html')
41
+ else:
42
+ return "index.html not found", 404
43
+
44
+
45
+ if __name__ == '__main__':
46
+ app.run(host='0.0.0.0', port=5001, debug=True)
mock_scraper.py ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Mock Video Scraper để demo API
4
+ Tạo video giả để test API functionality
5
+ """
6
+
7
+ import time
8
+ import os
9
+ import requests
10
+ import logging
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+ class MockVideoScraper:
15
+ def __init__(self, headless=True):
16
+ """Khởi tạo mock scraper"""
17
+ self.headless = headless
18
+ os.makedirs("/home/ubuntu/downloads", exist_ok=True)
19
+
20
+ def generate_video_simple(self, prompt, timeout=180):
21
+ """
22
+ Mock tạo video từ prompt
23
+ Tải xuống một video mẫu từ internet
24
+ """
25
+ try:
26
+ logger.info(f"Mock: Bắt đầu tạo video với prompt: {prompt}")
27
+
28
+ # Giả lập thời gian xử lý
29
+ time.sleep(5)
30
+
31
+ # Tải xuống video mẫu từ internet
32
+ sample_video_url = "https://sample-videos.com/zip/10/mp4/SampleVideo_1280x720_1mb.mp4"
33
+
34
+ # Tạo tên file
35
+ safe_filename = "".join(c for c in str(prompt) if c.isalnum() or c in (' ', '-', '_')).rstrip()
36
+ safe_filename = safe_filename.replace(' ', '_')[:30]
37
+ filename = f"mock_{safe_filename}_{int(time.time())}.mp4"
38
+ filepath = os.path.join("/home/ubuntu/downloads", filename)
39
+
40
+ logger.info(f"Mock: Đang tải xuống video mẫu...")
41
+
42
+ # Tải xuống video mẫu
43
+ headers = {
44
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
45
+ }
46
+
47
+ try:
48
+ response = requests.get(sample_video_url, headers=headers, stream=True, timeout=30)
49
+ response.raise_for_status()
50
+
51
+ with open(filepath, 'wb') as f:
52
+ for chunk in response.iter_content(chunk_size=8192):
53
+ f.write(chunk)
54
+
55
+ # Kiểm tra file size
56
+ file_size = os.path.getsize(filepath)
57
+ if file_size > 1000: # Ít nhất 1KB
58
+ logger.info(f"Mock: Video đã được tạo thành công: {filepath} ({file_size} bytes)")
59
+ return filepath
60
+ else:
61
+ logger.error(f"Mock: File quá nhỏ: {file_size} bytes")
62
+ os.remove(filepath)
63
+ return None
64
+
65
+ except Exception as e:
66
+ logger.error(f"Mock: Lỗi khi tải video mẫu: {e}")
67
+
68
+ # Fallback: tạo file video giả
69
+ logger.info("Mock: Tạo file video giả...")
70
+ with open(filepath, 'wb') as f:
71
+ # Tạo file MP4 header đơn giản
72
+ f.write(b'\x00\x00\x00\x20ftypmp41\x00\x00\x00\x00mp41isom')
73
+ f.write(b'Mock video content for prompt: ' + prompt.encode('utf-8'))
74
+ f.write(b'\x00' * 1000) # Padding để file có kích thước hợp lý
75
+
76
+ logger.info(f"Mock: File video giả đã được tạo: {filepath}")
77
+ return filepath
78
+
79
+ except Exception as e:
80
+ logger.error(f"Mock: Lỗi khi tạo video: {e}")
81
+ return None
82
+
83
+ def close(self):
84
+ """Đóng scraper"""
85
+ logger.info("Mock: Scraper đã được đóng")
86
+
87
+ def test_mock():
88
+ """Test mock scraper"""
89
+ scraper = MockVideoScraper()
90
+
91
+ try:
92
+ prompt = "A dog running in a park"
93
+ print(f"Testing mock với prompt: {prompt}")
94
+
95
+ video_path = scraper.generate_video_simple(prompt)
96
+
97
+ if video_path:
98
+ print(f"✅ Thành công! Video: {video_path}")
99
+ return True
100
+ else:
101
+ print("❌ Thất bại!")
102
+ return False
103
+
104
+ finally:
105
+ scraper.close()
106
+
107
+ if __name__ == "__main__":
108
+ test_mock()
109
+
video.py ADDED
@@ -0,0 +1,225 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Blueprint, request, jsonify, send_file
2
+ import os
3
+ import threading
4
+ import time
5
+ import uuid
6
+ from src.mock_scraper import MockVideoScraper
7
+ import logging
8
+
9
+ # Cấu hình logging
10
+ logging.basicConfig(level=logging.INFO)
11
+ logger = logging.getLogger(__name__)
12
+
13
+ video_bp = Blueprint('video', __name__)
14
+
15
+ # Dictionary để lưu trạng thái các job
16
+ video_jobs = {}
17
+
18
+ class VideoJob:
19
+ def __init__(self, job_id, prompt):
20
+ self.job_id = job_id
21
+ self.prompt = prompt
22
+ self.status = "pending" # pending, processing, completed, failed
23
+ self.video_path = None
24
+ self.error_message = None
25
+ self.created_at = time.time()
26
+
27
+ def generate_video_async(job_id, prompt):
28
+ """Tạo video bất đồng bộ"""
29
+ job = video_jobs[job_id]
30
+ job.status = "processing"
31
+
32
+ scraper = None
33
+ try:
34
+ logger.info(f"Bắt đầu tạo video cho job {job_id} với prompt: {prompt}")
35
+ scraper = MockVideoScraper(headless=True)
36
+
37
+ video_path = scraper.generate_video_simple(prompt, timeout=300)
38
+
39
+ if video_path and os.path.exists(video_path):
40
+ job.video_path = video_path
41
+ job.status = "completed"
42
+ logger.info(f"Job {job_id} hoàn thành thành công")
43
+ else:
44
+ job.status = "failed"
45
+ job.error_message = "Không thể tạo video"
46
+ logger.error(f"Job {job_id} thất bại: Không thể tạo video")
47
+
48
+ except Exception as e:
49
+ job.status = "failed"
50
+ job.error_message = str(e)
51
+ logger.error(f"Job {job_id} thất bại với lỗi: {e}")
52
+ finally:
53
+ if scraper:
54
+ scraper.close()
55
+
56
+ @video_bp.route('/generate/<path:prompt>', methods=['GET'])
57
+ def generate_video_endpoint(prompt):
58
+ """
59
+ Endpoint để tạo video từ prompt
60
+ URL: /api/video/generate/your_prompt_here
61
+ """
62
+ try:
63
+ # Tạo job ID duy nhất
64
+ job_id = str(uuid.uuid4())
65
+
66
+ # Tạo job mới
67
+ job = VideoJob(job_id, prompt)
68
+ video_jobs[job_id] = job
69
+
70
+ # Bắt đầu tạo video trong thread riêng
71
+ thread = threading.Thread(target=generate_video_async, args=(job_id, prompt))
72
+ thread.daemon = True
73
+ thread.start()
74
+
75
+ return jsonify({
76
+ "success": True,
77
+ "job_id": job_id,
78
+ "message": "Video generation started",
79
+ "status_url": f"/api/video/status/{job_id}",
80
+ "download_url": f"/api/video/download/{job_id}"
81
+ }), 202
82
+
83
+ except Exception as e:
84
+ logger.error(f"Lỗi khi tạo job: {e}")
85
+ return jsonify({
86
+ "success": False,
87
+ "error": str(e)
88
+ }), 500
89
+
90
+ @video_bp.route('/status/<job_id>', methods=['GET'])
91
+ def get_job_status(job_id):
92
+ """Kiểm tra trạng thái job"""
93
+ if job_id not in video_jobs:
94
+ return jsonify({
95
+ "success": False,
96
+ "error": "Job not found"
97
+ }), 404
98
+
99
+ job = video_jobs[job_id]
100
+
101
+ response = {
102
+ "success": True,
103
+ "job_id": job_id,
104
+ "status": job.status,
105
+ "prompt": job.prompt,
106
+ "created_at": job.created_at
107
+ }
108
+
109
+ if job.status == "failed" and job.error_message:
110
+ response["error"] = job.error_message
111
+
112
+ if job.status == "completed" and job.video_path:
113
+ response["download_url"] = f"/api/video/download/{job_id}"
114
+ response["video_ready"] = True
115
+
116
+ return jsonify(response)
117
+
118
+ @video_bp.route('/download/<job_id>', methods=['GET'])
119
+ def download_video(job_id):
120
+ """Tải xuống video"""
121
+ if job_id not in video_jobs:
122
+ return jsonify({
123
+ "success": False,
124
+ "error": "Job not found"
125
+ }), 404
126
+
127
+ job = video_jobs[job_id]
128
+
129
+ if job.status != "completed" or not job.video_path:
130
+ return jsonify({
131
+ "success": False,
132
+ "error": "Video not ready",
133
+ "status": job.status
134
+ }), 400
135
+
136
+ if not os.path.exists(job.video_path):
137
+ return jsonify({
138
+ "success": False,
139
+ "error": "Video file not found"
140
+ }), 404
141
+
142
+ try:
143
+ return send_file(
144
+ job.video_path,
145
+ as_attachment=True,
146
+ download_name=f"video_{job_id}.mp4",
147
+ mimetype='video/mp4'
148
+ )
149
+ except Exception as e:
150
+ logger.error(f"Lỗi khi tải xuống video: {e}")
151
+ return jsonify({
152
+ "success": False,
153
+ "error": "Failed to download video"
154
+ }), 500
155
+
156
+ @video_bp.route('/jobs', methods=['GET'])
157
+ def list_jobs():
158
+ """Liệt kê tất cả jobs"""
159
+ jobs_list = []
160
+ for job_id, job in video_jobs.items():
161
+ job_info = {
162
+ "job_id": job_id,
163
+ "prompt": job.prompt,
164
+ "status": job.status,
165
+ "created_at": job.created_at
166
+ }
167
+ if job.status == "completed":
168
+ job_info["download_url"] = f"/api/video/download/{job_id}"
169
+ jobs_list.append(job_info)
170
+
171
+ return jsonify({
172
+ "success": True,
173
+ "jobs": jobs_list,
174
+ "total": len(jobs_list)
175
+ })
176
+
177
+ @video_bp.route('/health', methods=['GET'])
178
+ def health_check():
179
+ """Health check endpoint"""
180
+ return jsonify({
181
+ "success": True,
182
+ "message": "Video generation API is running",
183
+ "active_jobs": len([j for j in video_jobs.values() if j.status == "processing"]),
184
+ "total_jobs": len(video_jobs)
185
+ })
186
+
187
+ # Cleanup old jobs (older than 1 hour)
188
+ def cleanup_old_jobs():
189
+ """Dọn dẹp các job cũ"""
190
+ current_time = time.time()
191
+ to_remove = []
192
+
193
+ for job_id, job in video_jobs.items():
194
+ if current_time - job.created_at > 3600: # 1 hour
195
+ to_remove.append(job_id)
196
+ # Xóa file video nếu có
197
+ if job.video_path and os.path.exists(job.video_path):
198
+ try:
199
+ os.remove(job.video_path)
200
+ except:
201
+ pass
202
+
203
+ for job_id in to_remove:
204
+ del video_jobs[job_id]
205
+
206
+ logger.info(f"Cleaned up {len(to_remove)} old jobs")
207
+
208
+ # Chạy cleanup mỗi 30 phút
209
+ import atexit
210
+ cleanup_timer = None
211
+
212
+ def start_cleanup_timer():
213
+ global cleanup_timer
214
+ cleanup_timer = threading.Timer(1800, cleanup_old_jobs) # 30 minutes
215
+ cleanup_timer.daemon = True
216
+ cleanup_timer.start()
217
+
218
+ def stop_cleanup_timer():
219
+ global cleanup_timer
220
+ if cleanup_timer:
221
+ cleanup_timer.cancel()
222
+
223
+ atexit.register(stop_cleanup_timer)
224
+ start_cleanup_timer()
225
+
video_scraper_v2.py ADDED
@@ -0,0 +1,291 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Video Scraper v2 cho Hugging Face Self-Forcing
4
+ Phiên bản cải tiến với xử lý lỗi tốt hơn
5
+ """
6
+
7
+ import time
8
+ import os
9
+ import requests
10
+ import json
11
+ from selenium import webdriver
12
+ from selenium.webdriver.common.by import By
13
+ from selenium.webdriver.support.ui import WebDriverWait
14
+ from selenium.webdriver.support import expected_conditions as EC
15
+ from selenium.webdriver.chrome.options import Options
16
+ from selenium.webdriver.chrome.service import Service
17
+ from webdriver_manager.chrome import ChromeDriverManager
18
+ import logging
19
+
20
+ # Cấu hình logging
21
+ logging.basicConfig(level=logging.INFO)
22
+ logger = logging.getLogger(__name__)
23
+
24
+ class VideoScraperV2:
25
+ def __init__(self, headless=True):
26
+ """Khởi tạo web scraper"""
27
+ self.driver = None
28
+ self.headless = headless
29
+ self.setup_driver()
30
+
31
+ def setup_driver(self):
32
+ """Thiết lập Chrome driver với nhiều tùy chọn hơn"""
33
+ try:
34
+ chrome_options = Options()
35
+
36
+ # Các tùy chọn cơ bản
37
+ if self.headless:
38
+ chrome_options.add_argument("--headless=new")
39
+ chrome_options.add_argument("--no-sandbox")
40
+ chrome_options.add_argument("--disable-dev-shm-usage")
41
+ chrome_options.add_argument("--disable-gpu")
42
+ chrome_options.add_argument("--window-size=1920,1080")
43
+ chrome_options.add_argument("--disable-extensions")
44
+ chrome_options.add_argument("--disable-plugins")
45
+ chrome_options.add_argument("--disable-images")
46
+ chrome_options.add_argument("--disable-javascript")
47
+ chrome_options.add_argument("--disable-web-security")
48
+ chrome_options.add_argument("--allow-running-insecure-content")
49
+ chrome_options.add_argument("--ignore-certificate-errors")
50
+ chrome_options.add_argument("--ignore-ssl-errors")
51
+ chrome_options.add_argument("--ignore-certificate-errors-spki-list")
52
+ chrome_options.add_argument("--disable-features=VizDisplayCompositor")
53
+
54
+ # Tạo thư mục downloads nếu chưa có
55
+ os.makedirs("/home/ubuntu/downloads", exist_ok=True)
56
+
57
+ service = Service(ChromeDriverManager().install())
58
+ self.driver = webdriver.Chrome(service=service, options=chrome_options)
59
+ self.driver.set_page_load_timeout(60)
60
+ logger.info("Chrome driver đã được thiết lập thành công")
61
+
62
+ except Exception as e:
63
+ logger.error(f"Lỗi khi thiết lập driver: {e}")
64
+ raise
65
+
66
+ def generate_video_simple(self, prompt, timeout=180):
67
+ """
68
+ Phương pháp đơn giản hơn để tạo video
69
+ Sử dụng requests để gọi API trực tiếp nếu có thể
70
+ """
71
+ try:
72
+ logger.info(f"Bắt đầu tạo video với prompt: {prompt}")
73
+
74
+ # Truy cập trang web
75
+ self.driver.get("https://huggingface.co/spaces/multimodalart/self-forcing")
76
+ logger.info("Đã truy cập trang web")
77
+
78
+ # Chờ một chút để trang tải
79
+ time.sleep(5)
80
+
81
+ # Thử tìm textarea bằng nhiều cách
82
+ textarea = None
83
+ selectors = [
84
+ "textarea",
85
+ "input[type='text']",
86
+ "[placeholder*='prompt']",
87
+ "[placeholder*='Prompt']"
88
+ ]
89
+
90
+ for selector in selectors:
91
+ try:
92
+ elements = self.driver.find_elements(By.CSS_SELECTOR, selector)
93
+ if elements:
94
+ textarea = elements[0]
95
+ logger.info(f"Tìm thấy textarea với selector: {selector}")
96
+ break
97
+ except:
98
+ continue
99
+
100
+ if not textarea:
101
+ logger.error("Không tìm thấy textarea")
102
+ return None
103
+
104
+ # Nhập prompt
105
+ textarea.clear()
106
+ textarea.send_keys(prompt)
107
+ logger.info("Đã nhập prompt")
108
+
109
+ # Tìm nút start
110
+ start_button = None
111
+ button_texts = ["Start Streaming", "Generate", "Submit", "Start"]
112
+
113
+ for text in button_texts:
114
+ try:
115
+ buttons = self.driver.find_elements(By.XPATH, f"//button[contains(text(), '{text}')]")
116
+ if buttons:
117
+ start_button = buttons[0]
118
+ logger.info(f"Tìm thấy nút: {text}")
119
+ break
120
+ except:
121
+ continue
122
+
123
+ if not start_button:
124
+ logger.error("Không tìm thấy nút start")
125
+ return None
126
+
127
+ # Nhấn nút
128
+ start_button.click()
129
+ logger.info("Đã nhấn nút start")
130
+
131
+ # Chờ và theo dõi
132
+ return self._wait_for_video(timeout)
133
+
134
+ except Exception as e:
135
+ logger.error(f"Lỗi khi tạo video: {e}")
136
+ return None
137
+
138
+ def _wait_for_video(self, timeout):
139
+ """Chờ video được tạo và tìm cách tải xuống"""
140
+ start_time = time.time()
141
+
142
+ while time.time() - start_time < timeout:
143
+ try:
144
+ # Kiểm tra các dấu hiệu video đã sẵn sàng
145
+
146
+ # 1. Tìm phần tử video
147
+ videos = self.driver.find_elements(By.TAG_NAME, "video")
148
+ if videos:
149
+ for video in videos:
150
+ src = video.get_attribute("src")
151
+ if src and src.startswith("http"):
152
+ logger.info(f"Tìm thấy video src: {src}")
153
+ return self._download_video(src, "video")
154
+
155
+ # 2. Tìm link download
156
+ links = self.driver.find_elements(By.TAG_NAME, "a")
157
+ for link in links:
158
+ href = link.get_attribute("href")
159
+ if href and any(ext in href for ext in ['.mp4', '.webm', '.mov']):
160
+ logger.info(f"Tìm thấy download link: {href}")
161
+ return self._download_video(href, "video")
162
+
163
+ # 3. Kiểm tra network requests
164
+ video_url = self._check_network_requests()
165
+ if video_url:
166
+ return self._download_video(video_url, "video")
167
+
168
+ # 4. Kiểm tra thông báo hoàn thành
169
+ complete_texts = ["complete", "done", "finished", "ready"]
170
+ for text in complete_texts:
171
+ elements = self.driver.find_elements(By.XPATH, f"//*[contains(translate(text(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), '{text}')]")
172
+ if elements:
173
+ logger.info(f"Tìm thấy thông báo hoàn thành: {text}")
174
+ # Thử tìm video một lần nữa
175
+ time.sleep(2)
176
+ continue
177
+
178
+ time.sleep(3)
179
+ logger.info(f"Đang chờ video... ({int(time.time() - start_time)}s)")
180
+
181
+ except Exception as e:
182
+ logger.debug(f"Lỗi khi chờ video: {e}")
183
+ time.sleep(3)
184
+
185
+ logger.error("Timeout khi chờ video")
186
+ return None
187
+
188
+ def _check_network_requests(self):
189
+ """Kiểm tra network requests để tìm video"""
190
+ try:
191
+ script = """
192
+ var resources = window.performance.getEntriesByType('resource');
193
+ var videoUrls = [];
194
+ for (var i = 0; i < resources.length; i++) {
195
+ var url = resources[i].name;
196
+ if (url.includes('.mp4') || url.includes('.webm') || url.includes('.mov') ||
197
+ url.includes('video') || url.includes('stream')) {
198
+ videoUrls.push(url);
199
+ }
200
+ }
201
+ return videoUrls;
202
+ """
203
+
204
+ urls = self.driver.execute_script(script)
205
+ if urls:
206
+ logger.info(f"Tìm thấy video URLs từ network: {urls}")
207
+ return urls[0]
208
+
209
+ return None
210
+
211
+ except Exception as e:
212
+ logger.debug(f"Lỗi khi kiểm tra network: {e}")
213
+ return None
214
+
215
+ def _download_video(self, video_url, prompt):
216
+ """Tải xuống video từ URL"""
217
+ try:
218
+ logger.info(f"Bắt đầu tải xuống video từ: {video_url}")
219
+
220
+ # Tạo tên file
221
+ safe_filename = "".join(c for c in str(prompt) if c.isalnum() or c in (' ', '-', '_')).rstrip()
222
+ safe_filename = safe_filename.replace(' ', '_')[:30]
223
+
224
+ if '.mp4' in video_url:
225
+ extension = '.mp4'
226
+ elif '.webm' in video_url:
227
+ extension = '.webm'
228
+ else:
229
+ extension = '.mp4'
230
+
231
+ filename = f"{safe_filename}_{int(time.time())}{extension}"
232
+ filepath = os.path.join("/home/ubuntu/downloads", filename)
233
+
234
+ # Tải xuống
235
+ headers = {
236
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
237
+ }
238
+
239
+ response = requests.get(video_url, headers=headers, stream=True, timeout=30)
240
+ response.raise_for_status()
241
+
242
+ with open(filepath, 'wb') as f:
243
+ for chunk in response.iter_content(chunk_size=8192):
244
+ f.write(chunk)
245
+
246
+ # Kiểm tra file size
247
+ file_size = os.path.getsize(filepath)
248
+ if file_size > 1000: # Ít nhất 1KB
249
+ logger.info(f"Video đã được tải xuống thành công: {filepath} ({file_size} bytes)")
250
+ return filepath
251
+ else:
252
+ logger.error(f"File quá nhỏ: {file_size} bytes")
253
+ os.remove(filepath)
254
+ return None
255
+
256
+ except Exception as e:
257
+ logger.error(f"Lỗi khi tải xuống video: {e}")
258
+ return None
259
+
260
+ def close(self):
261
+ """Đóng driver"""
262
+ if self.driver:
263
+ try:
264
+ self.driver.quit()
265
+ logger.info("Driver đã được đóng")
266
+ except:
267
+ pass
268
+
269
+ def test_v2():
270
+ """Test phiên bản 2"""
271
+ scraper = VideoScraperV2(headless=True)
272
+
273
+ try:
274
+ prompt = "A dog running in a park"
275
+ print(f"Testing V2 với prompt: {prompt}")
276
+
277
+ video_path = scraper.generate_video_simple(prompt, timeout=120)
278
+
279
+ if video_path:
280
+ print(f"✅ Thành công! Video: {video_path}")
281
+ return True
282
+ else:
283
+ print("❌ Thất bại!")
284
+ return False
285
+
286
+ finally:
287
+ scraper.close()
288
+
289
+ if __name__ == "__main__":
290
+ test_v2()
291
+