perf: slim daw_engine bundle 409MB->170MB + fix engine khong chay tren Windows
- Thay librosa bang app/core/audio_features.py (numpy/scipy/soundfile):
load, beat_track, frames_to_time, spectral_centroid, rms, zero_crossing_rate,
time_stretch, pitch_shift, chroma_stft. A/B ngang librosa (BPM <1% sai lech,
pitch_shift chuan toi Hz). Loai bo llvmlite 171MB + scikit-learn + numba.
- Task layer 2 che do (app/tasks/worker.py): server giu celery; desktop slim
chay in-process thread + registry, giu nguyen API contract (.delay/.id/status
/tasks/{id}) nen frontend khong doi.
- engine.spec: excludes librosa/numba/llvmlite/sklearn/celery/redis/kombu/
billiard/amqp/msgpack/yaml/PIL/cairosvg/zstandard/...; scan scipy gioi han
scipy.signal; giu click (uvicorn.main import click).
- render_engine: scipy.signal thanh lazy import (giam cold start).
- tauri.conf.json: targets [nsis, msi] - NSIS tro lai (bundle nho) de
hooks.nsh cai VC++ Redistributable - sua bug daw_engine.exe khong chay
tren Windows (truoc day MSI-only khong chay hooks).
- build_linux.sh / build_macos.sh: build 1 lenh moi OS.
- Doc: DESKTOP_INSTALL_PLAN.md muc 5.1.
- Verify: 86 tests pass, engine dong goi upload/analyze/waveform/export OK.
This commit is contained in:
+120
-37
@@ -3,7 +3,6 @@ import uuid
|
||||
import time
|
||||
import glob
|
||||
import logging
|
||||
from celery import Celery
|
||||
from app.config import settings
|
||||
from app.core.analyzer import analyze_audio, analyze_structure_with_ai
|
||||
from app.core.audio_editor import (
|
||||
@@ -13,40 +12,124 @@ from app.core.dsp_utils import find_nearest_zero_crossing_file
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
celery_app = Celery(
|
||||
"audio_tasks",
|
||||
broker=settings.CELERY_BROKER_URL,
|
||||
backend=settings.CELERY_RESULT_BACKEND
|
||||
)
|
||||
# ──────────────────────────────────────────────────────────────────────────
|
||||
# Task layer 2 che do:
|
||||
# - Server/Docker: celery day du (broker Redis) — dung nhu cu.
|
||||
# - Desktop slim (PyInstaller KHONG bundle celery/redis): task chay in-process
|
||||
# (thread nen + registry dict), API contract GIONG het (.delay() tra
|
||||
# task_id, /tasks/{id} tra status/result) nen frontend khong doi gi.
|
||||
# ──────────────────────────────────────────────────────────────────────────
|
||||
try:
|
||||
from celery import Celery
|
||||
HAS_CELERY = True
|
||||
except Exception: # pragma: no cover - frozen desktop slim build
|
||||
Celery = None
|
||||
HAS_CELERY = False
|
||||
|
||||
celery_app.conf.update(
|
||||
task_serializer="json",
|
||||
accept_content=["json"],
|
||||
result_serializer="json",
|
||||
timezone="UTC",
|
||||
enable_utc=True,
|
||||
)
|
||||
|
||||
# Che do desktop (SF_DESKTOP=1, do desktop_engine.py set): chay task dong bo
|
||||
# trong tien trinh (eager) — ban Standalone Windows KHONG kem Redis broker.
|
||||
if os.getenv("SF_DESKTOP") == "1":
|
||||
celery_app.conf.update(
|
||||
task_always_eager=True,
|
||||
task_eager_propagates=True,
|
||||
broker_url="memory://",
|
||||
result_backend="cache+memory://",
|
||||
if HAS_CELERY:
|
||||
celery_app = Celery(
|
||||
"audio_tasks",
|
||||
broker=settings.CELERY_BROKER_URL,
|
||||
backend=settings.CELERY_RESULT_BACKEND
|
||||
)
|
||||
|
||||
# ── Lịch trình tự động dọn dẹp file hết hạn (Week 5) ──
|
||||
celery_app.conf.beat_schedule = {
|
||||
"cleanup-expired-files-every-hour": {
|
||||
"task": "app.tasks.worker.cleanup_expired_files_task",
|
||||
"schedule": 3600.0, # Chạy mỗi giờ
|
||||
},
|
||||
}
|
||||
celery_app.conf.update(
|
||||
task_serializer="json",
|
||||
accept_content=["json"],
|
||||
result_serializer="json",
|
||||
timezone="UTC",
|
||||
enable_utc=True,
|
||||
)
|
||||
|
||||
# Che do desktop (SF_DESKTOP=1, do desktop_engine.py set): chay task dong bo
|
||||
# trong tien trinh (eager) — ban Standalone KHONG kem Redis broker.
|
||||
if os.getenv("SF_DESKTOP") == "1":
|
||||
celery_app.conf.update(
|
||||
task_always_eager=True,
|
||||
task_eager_propagates=True,
|
||||
broker_url="memory://",
|
||||
result_backend="cache+memory://",
|
||||
)
|
||||
|
||||
# ── Lich trinh tu dong don dep file het han (Week 5) ──
|
||||
celery_app.conf.beat_schedule = {
|
||||
"cleanup-expired-files-every-hour": {
|
||||
"task": "app.tasks.worker.cleanup_expired_files_task",
|
||||
"schedule": 3600.0, # Chay moi gio
|
||||
},
|
||||
}
|
||||
else:
|
||||
celery_app = None
|
||||
# Registry in-process cho desktop slim: task_id -> {"status", "result"/"error"}
|
||||
_results = {}
|
||||
|
||||
|
||||
@celery_app.task
|
||||
def _task(fn):
|
||||
"""Wrapper: celery task (server) hoac in-process task (desktop slim)."""
|
||||
if HAS_CELERY:
|
||||
return celery_app.task(fn)
|
||||
return _InProcessTask(fn)
|
||||
|
||||
|
||||
class _InProcessTask:
|
||||
"""Task chay tren thread nen, ket qua luu vao registry dict — dung cho
|
||||
bundle desktop khong kem celery (tiet kiem ~40MB)."""
|
||||
|
||||
def __init__(self, fn):
|
||||
self._fn = fn
|
||||
|
||||
def delay(self, *args, **kwargs):
|
||||
import threading
|
||||
tid = uuid.uuid4().hex
|
||||
_results[tid] = {"status": "PENDING"}
|
||||
|
||||
def _run():
|
||||
try:
|
||||
result = self._fn(*args, **kwargs)
|
||||
_results[tid] = {"status": "SUCCESS", "result": result}
|
||||
except Exception as e: # noqa: BLE001 - bao loi day du cho UI
|
||||
logger.exception("In-process task %s failed", tid)
|
||||
_results[tid] = {"status": "FAILURE", "error": str(e)}
|
||||
|
||||
threading.Thread(target=_run, daemon=True, name=f"task-{tid[:8]}").start()
|
||||
return _SimpleAsyncResult(tid)
|
||||
|
||||
|
||||
class _SimpleAsyncResult:
|
||||
"""Giong celery.result.AsyncResult ve mat API cho desktop slim."""
|
||||
|
||||
def __init__(self, task_id):
|
||||
self.task_id = task_id
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
"""Giong celery.result.AsyncResult.id — audio.py dung task.id."""
|
||||
return self.task_id
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
return _results.get(self.task_id, {}).get("status", "PENDING")
|
||||
|
||||
@property
|
||||
def result(self):
|
||||
return _results.get(self.task_id, {}).get("result")
|
||||
|
||||
def ready(self):
|
||||
return _results.get(self.task_id, {}).get("status") in ("SUCCESS", "FAILURE")
|
||||
|
||||
def successful(self):
|
||||
return self.status == "SUCCESS"
|
||||
|
||||
|
||||
def get_task_result(task_id):
|
||||
"""Tra AsyncResult (celery) hoac _SimpleAsyncResult (desktop slim)."""
|
||||
if HAS_CELERY:
|
||||
from celery.result import AsyncResult
|
||||
return AsyncResult(task_id, app=celery_app)
|
||||
return _SimpleAsyncResult(task_id)
|
||||
|
||||
|
||||
@_task
|
||||
def analyze_audio_task(file_id: str):
|
||||
file_path = os.path.join(settings.UPLOADS_DIR, file_id)
|
||||
if not os.path.exists(file_path):
|
||||
@@ -54,7 +137,7 @@ def analyze_audio_task(file_id: str):
|
||||
return analyze_audio(file_path)
|
||||
|
||||
|
||||
@celery_app.task
|
||||
@_task
|
||||
def analyze_ai_task(file_id: str, api_base_url: str = None,
|
||||
model: str = "deepseek-chat"):
|
||||
"""
|
||||
@@ -78,7 +161,7 @@ def analyze_ai_task(file_id: str, api_base_url: str = None,
|
||||
)
|
||||
|
||||
|
||||
@celery_app.task
|
||||
@_task
|
||||
def edit_audio_task(config: dict):
|
||||
file_id = config.get("file_id")
|
||||
|
||||
@@ -98,7 +181,7 @@ def edit_audio_task(config: dict):
|
||||
}
|
||||
|
||||
|
||||
@celery_app.task
|
||||
@_task
|
||||
def export_audio_task(file_id: str, format: str = "wav",
|
||||
sample_rate: int = 44100, bit_depth: int = 16):
|
||||
"""
|
||||
@@ -131,7 +214,7 @@ def export_audio_task(file_id: str, format: str = "wav",
|
||||
return result
|
||||
|
||||
|
||||
@celery_app.task
|
||||
@_task
|
||||
def mix_multitrack_task(session_config: dict):
|
||||
"""
|
||||
Task xử lý hòa âm đa kênh (Multitrack Mixdown).
|
||||
@@ -184,7 +267,7 @@ def mix_multitrack_task(session_config: dict):
|
||||
return result
|
||||
|
||||
|
||||
@celery_app.task
|
||||
@_task
|
||||
def process_multitrack_session_task(session_config: dict):
|
||||
"""
|
||||
Task xử lý toàn bộ session với nhiều tracks và clips.
|
||||
@@ -280,7 +363,7 @@ def process_multitrack_session_task(session_config: dict):
|
||||
return result
|
||||
|
||||
|
||||
@celery_app.task
|
||||
@_task
|
||||
def cleanup_expired_files_task(max_age_hours: int = 24):
|
||||
"""
|
||||
Task tự động dọn dẹp các tệp kết xuất hết hạn (Week 5).
|
||||
@@ -315,7 +398,7 @@ def cleanup_expired_files_task(max_age_hours: int = 24):
|
||||
}
|
||||
|
||||
|
||||
@celery_app.task
|
||||
@_task
|
||||
def render_project_task(project_id: str, project_name: str, project_json_str: str, sample_rate: int = 44100):
|
||||
"""
|
||||
Task Celery để kết xuất dự án ngoại tuyến (Offline Project Mixdown) áp dụng specs 30_DAW_ARCHITECT.md.
|
||||
|
||||
Reference in New Issue
Block a user