v4.3: file-based media storage + manual VACUUM maintenance
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
# routes/generation_routes.py - Combined Endpoint with Correct Task Polling
|
||||
# routes/generation_routes.py - Combined Endpoint with Correct Task Polling (v4.3)
|
||||
|
||||
import json
|
||||
import time
|
||||
@@ -40,8 +40,6 @@ def poll_beam_task(task_id):
|
||||
print(f" URL: {task_url}")
|
||||
|
||||
start_time = time.time()
|
||||
|
||||
# প্রথম কয়েকটা attempt এ 404 আসতে পারে — task register হতে delay
|
||||
initial_delay = True
|
||||
|
||||
while True:
|
||||
@@ -51,17 +49,14 @@ def poll_beam_task(task_id):
|
||||
print(f"❌ Polling timeout after {POLL_MAX_WAIT}s")
|
||||
return None, f'Task timed out after {int(POLL_MAX_WAIT)} seconds'
|
||||
|
||||
# প্রথম ২ সেকেন্ড wait করি task register হতে
|
||||
if initial_delay and elapsed < 2:
|
||||
time.sleep(2)
|
||||
initial_delay = False
|
||||
continue
|
||||
|
||||
try:
|
||||
# ★ Bearer token দিয়ে try
|
||||
resp = requests.get(task_url, headers=get_beam_auth_headers(), timeout=30)
|
||||
|
||||
# Bearer fail হলে Basic try করি
|
||||
if resp.status_code in (401, 403):
|
||||
print(f" Bearer auth failed, trying Basic...")
|
||||
basic_headers = {
|
||||
@@ -73,20 +68,14 @@ def poll_beam_task(task_id):
|
||||
print(f" [{int(elapsed)}s] HTTP {resp.status_code} | Body: {len(resp.text)} chars")
|
||||
|
||||
if resp.status_code == 404:
|
||||
# Task এখনও register হয়নি — wait
|
||||
if elapsed < 30:
|
||||
print(f" Task not found yet, waiting...")
|
||||
time.sleep(POLL_INTERVAL)
|
||||
continue
|
||||
else:
|
||||
# ৩০ সেকেন্ড পরেও 404 — সমস্যা
|
||||
print(f"❌ Task not found after {int(elapsed)}s")
|
||||
|
||||
# ★ Debug: response body দেখি
|
||||
print(f" 404 body: {resp.text[:300]}")
|
||||
|
||||
# ★ Alternative: Beam API base URL ভিন্ন হতে পারে
|
||||
# কিছু Beam setup এ URL format ভিন্ন
|
||||
alt_urls = [
|
||||
f"https://api.beam.cloud/v2/task/{task_id}/status/",
|
||||
f"https://api.beam.cloud/v2/task/{task_id}",
|
||||
@@ -121,25 +110,14 @@ def poll_beam_task(task_id):
|
||||
if status in ('COMPLETE', 'COMPLETED', 'SUCCESS'):
|
||||
print(f"✅ Task complete!")
|
||||
|
||||
# ★ Result বের করা — Beam বিভিন্ন জায়গায় result রাখে
|
||||
# 1. 'output' key
|
||||
# 2. 'result' key
|
||||
# 3. 'outputs' list (file-based)
|
||||
# 4. response body তেই (endpoint mode)
|
||||
|
||||
actual_result = None
|
||||
|
||||
# Check 'output' (endpoint mode — function return value)
|
||||
if data.get('output') and isinstance(data['output'], dict):
|
||||
actual_result = data['output']
|
||||
print(f" Result found in 'output' key")
|
||||
|
||||
# Check 'result'
|
||||
elif data.get('result') and isinstance(data['result'], dict):
|
||||
actual_result = data['result']
|
||||
print(f" Result found in 'result' key")
|
||||
|
||||
# Check if top-level has audio_base64 (unlikely but possible)
|
||||
elif data.get('audio_base64'):
|
||||
actual_result = data
|
||||
print(f" Result found in top-level data")
|
||||
@@ -149,16 +127,12 @@ def poll_beam_task(task_id):
|
||||
elif actual_result and actual_result.get('success'):
|
||||
return actual_result, None
|
||||
|
||||
# ★ Outputs (file-based) — need to download
|
||||
outputs = data.get('outputs', [])
|
||||
if outputs:
|
||||
print(f" Task has {len(outputs)} output files")
|
||||
# For our use case, result should be in 'output' not files
|
||||
# But log it for debug
|
||||
for out in outputs:
|
||||
print(f" Output: {out.get('name', '?')} → {out.get('url', '?')}")
|
||||
|
||||
# No usable result found
|
||||
print(f" ⚠️ Task complete but no audio in response")
|
||||
print(f" Response keys: {list(data.keys())}")
|
||||
print(f" Full response (first 500): {json.dumps(data, default=str)[:500]}")
|
||||
@@ -177,7 +151,7 @@ def poll_beam_task(task_id):
|
||||
return None, f'Task {status.lower()} on Beam. Container may not have started in time.'
|
||||
|
||||
elif status in ('PENDING', 'RUNNING', 'RETRY'):
|
||||
pass # Keep polling
|
||||
pass
|
||||
|
||||
else:
|
||||
print(f" Unknown status: {status}")
|
||||
@@ -221,16 +195,12 @@ def call_beam_and_get_result(text, voice='af_heart', speed=1.0):
|
||||
|
||||
task_id = response.headers.get('X-Task-Id', '')
|
||||
|
||||
# ========================================
|
||||
# CASE 1: Task ID + empty/no body → Async → Poll
|
||||
# ========================================
|
||||
# CASE 1: Task ID + empty body → Async → Poll
|
||||
if task_id and (not response.text or not response.text.strip() or response.headers.get('Content-Length') == '0'):
|
||||
print(f"📋 Async mode — Task ID: {task_id}")
|
||||
return poll_beam_task(task_id)
|
||||
|
||||
# ========================================
|
||||
# CASE 2: Task ID + body
|
||||
# ========================================
|
||||
if task_id and response.text and response.text.strip():
|
||||
print(f"📋 Task ID: {task_id} + body ({len(response.text)} chars)")
|
||||
try:
|
||||
@@ -238,20 +208,15 @@ def call_beam_and_get_result(text, voice='af_heart', speed=1.0):
|
||||
if result.get('success') and result.get('audio_base64'):
|
||||
print(f"✅ Direct sync result")
|
||||
return _extract(result), None
|
||||
# Body isn't the final result — poll
|
||||
return poll_beam_task(task_id)
|
||||
except Exception:
|
||||
return poll_beam_task(task_id)
|
||||
|
||||
# ========================================
|
||||
# CASE 3: No task_id + empty body → Error
|
||||
# ========================================
|
||||
if not response.text or not response.text.strip():
|
||||
return None, 'Empty response from Beam with no task ID'
|
||||
|
||||
# ========================================
|
||||
# CASE 4: Synchronous response
|
||||
# ========================================
|
||||
if response.status_code != 200:
|
||||
try:
|
||||
err = response.json().get('error', response.text[:200])
|
||||
@@ -326,15 +291,26 @@ def generate_audio():
|
||||
if source_format != 'mp3':
|
||||
audio_base64 = convert_to_mp3(audio_base64, source_format)
|
||||
|
||||
# block_id থাকলে সরাসরি ফাইলে সেভ করি (v4.3)
|
||||
if block_id:
|
||||
from media_storage import save_audio
|
||||
db = get_db()
|
||||
cursor = db.cursor()
|
||||
cursor.execute('''
|
||||
UPDATE markdown_blocks
|
||||
SET audio_data = ?, audio_format = 'mp3', transcription = ?
|
||||
WHERE id = ?
|
||||
''', (audio_base64, json.dumps(transcription), block_id))
|
||||
db.commit()
|
||||
SELECT c.project_id FROM markdown_blocks mb
|
||||
JOIN chapters c ON mb.chapter_id = c.id
|
||||
WHERE mb.id = ?
|
||||
''', (block_id,))
|
||||
row = cursor.fetchone()
|
||||
if row:
|
||||
project_id = row['project_id']
|
||||
rel_path = save_audio(project_id, block_id, audio_base64, 'mp3')
|
||||
cursor.execute('''
|
||||
UPDATE markdown_blocks
|
||||
SET audio_path = ?, audio_data = '', audio_format = 'mp3', transcription = ?
|
||||
WHERE id = ?
|
||||
''', (rel_path, json.dumps(transcription), block_id))
|
||||
db.commit()
|
||||
|
||||
print(f"✅ DONE: audio={len(audio_base64)} bytes, words={len(transcription)}")
|
||||
print(f"{'='*60}")
|
||||
@@ -377,7 +353,7 @@ def generate_chapter_audio():
|
||||
cursor = db.cursor()
|
||||
|
||||
cursor.execute('''
|
||||
SELECT id, content, tts_text, block_type FROM markdown_blocks
|
||||
SELECT id, content, tts_text, block_type, chapter_id FROM markdown_blocks
|
||||
WHERE chapter_id = ? ORDER BY block_order
|
||||
''', (chapter_id,))
|
||||
blocks = cursor.fetchall()
|
||||
@@ -385,6 +361,11 @@ def generate_chapter_audio():
|
||||
if not blocks:
|
||||
return jsonify({'error': 'No blocks found'}), 404
|
||||
|
||||
# project_id বের করি (ফাইল সেভের জন্য)
|
||||
cursor.execute('SELECT project_id FROM chapters WHERE id = ?', (chapter_id,))
|
||||
ch_row = cursor.fetchone()
|
||||
project_id = ch_row['project_id'] if ch_row else None
|
||||
|
||||
results = []
|
||||
success_count = 0
|
||||
error_count = 0
|
||||
@@ -394,6 +375,8 @@ def generate_chapter_audio():
|
||||
print(f"📖 CHAPTER: {total} blocks, voice={voice}")
|
||||
print(f"{'='*60}")
|
||||
|
||||
from media_storage import save_audio
|
||||
|
||||
for idx, block in enumerate(blocks):
|
||||
block_id = block['id']
|
||||
block_type = block['block_type'] if 'block_type' in block.keys() else 'paragraph'
|
||||
@@ -437,11 +420,14 @@ def generate_chapter_audio():
|
||||
if source_format != 'mp3':
|
||||
audio_base64 = convert_to_mp3(audio_base64, source_format)
|
||||
|
||||
# v4.3: ফাইলে সেভ
|
||||
rel_path = save_audio(project_id, block_id, audio_base64, 'mp3') if project_id else None
|
||||
|
||||
cursor.execute('''
|
||||
UPDATE markdown_blocks
|
||||
SET audio_data = ?, audio_format = 'mp3', transcription = ?
|
||||
SET audio_path = ?, audio_data = '', audio_format = 'mp3', transcription = ?
|
||||
WHERE id = ?
|
||||
''', (audio_base64, json.dumps(transcription), block_id))
|
||||
''', (rel_path, json.dumps(transcription), block_id))
|
||||
|
||||
results.append({
|
||||
'block_id': block_id,
|
||||
|
||||
Reference in New Issue
Block a user