Files
autoclip/backend/core/celery_app.py
周小舟 6da0a063ab fix: run the processing pipeline locally in desktop mode (no Redis)
Desktop processing never ran — projects stuck at 0% "初始化中" forever.

Root cause: the pipeline is dispatched with Celery (process_video_pipeline.delay()
from /process, /retry, import; and send_task from auto_pipeline). core.celery_app
points at redis://localhost:6379, which doesn't exist in the desktop bundle, and
the only worker started (desktop_celery) uses a *different* (filesystem) broker.
So tasks were queued to a broker nobody consumed.

Fix (no Redis, no broker, non-blocking):
- core/celery_app.py: a DesktopAwareTask base whose apply_async, in desktop mode,
  runs the task via .apply() in a background daemon thread and returns a
  lightweight result. Covers every .delay()/.apply_async() call site at once.
  Production (server) mode is unchanged — it falls through to the real broker.
- task_submission_utils.py: submit_video_pipeline_task (which uses app.send_task,
  not Task.apply_async) gets the same local-thread path in desktop mode, and no
  longer hits the hardcoded redis.Redis(localhost) debug probe.

The pipeline task itself already runs the whole flow inline
(asyncio.run(pipeline_adapter...)), so local execution is a perfect fit;
progress is written to the DB Task record for the UI to poll.

Verified: retrying a subtitle-bearing project now runs Step 1 (大纲, 6 topics via
DashScope) → Step 2 (timeline), with progress events 0%→17%→25%. Processing
works end-to-end in the packaged app for videos that have subtitles.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 00:07:52 +08:00

141 lines
4.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Celery应用配置
任务队列配置和初始化
"""
import os
from celery import Celery
from celery.schedules import crontab
from pathlib import Path
# 设置默认配置模块
# os.environ.setdefault('CELERY_CONFIG_MODULE', 'backend.core.celery_app')
# 创建Celery应用
celery_app = Celery('autoclip')
# 配置Celery
class CeleryConfig:
"""Celery配置类"""
# 任务序列化格式
task_serializer = 'json'
accept_content = ['json']
result_serializer = 'json'
timezone = 'Asia/Shanghai'
enable_utc = True
# Redis配置
broker_url = os.getenv('REDIS_URL', 'redis://localhost:6379/0')
result_backend = os.getenv('REDIS_URL', 'redis://localhost:6379/0')
# 任务配置
task_always_eager = os.getenv('CELERY_ALWAYS_EAGER', 'False').lower() == 'true' # 生产环境异步执行
task_eager_propagates = True
# 工作进程配置
worker_prefetch_multiplier = 1
worker_max_tasks_per_child = 1000
worker_disable_rate_limits = True
worker_concurrency = 1 # 强制设置并发数为1防止重复处理
# 任务路由
task_routes = {
'backend.tasks.processing.*': {'queue': 'processing'},
'backend.tasks.video.*': {'queue': 'video'},
'backend.tasks.notification.*': {'queue': 'notification'},
'backend.tasks.upload.*': {'queue': 'upload'}, # 添加upload任务路由
'backend.tasks.import_processing.*': {'queue': 'processing'}, # 导入任务路由
}
# 定时任务配置
beat_schedule = {
'cleanup-expired-tasks': {
'task': 'backend.tasks.maintenance.cleanup_expired_tasks',
'schedule': crontab(hour=2, minute=0), # 每天凌晨2点
},
'health-check': {
'task': 'backend.tasks.maintenance.health_check',
'schedule': crontab(minute='*/5'), # 每5分钟
},
}
# 结果配置
result_expires = 3600 # 1小时
task_ignore_result = False
# 日志配置
worker_log_format = '[%(asctime)s: %(levelname)s/%(processName)s] %(message)s'
worker_task_log_format = '[%(asctime)s: %(levelname)s/%(processName)s] [%(task_name)s(%(task_id)s)] %(message)s'
# 应用配置
celery_app.config_from_object(CeleryConfig)
def _is_desktop_mode() -> bool:
return os.getenv("AUTOCLIP_DESKTOP_MODE", "").lower() in {"1", "true", "yes"}
class _LocalAsyncResult:
"""轻量级 AsyncResult 替身,桌面模式本地线程执行时返回。"""
def __init__(self, task_id: str):
self.id = task_id
self.task_id = task_id
self.state = "PENDING"
def get(self, *args, **kwargs):
return None
def ready(self) -> bool:
return False
class DesktopAwareTask(celery_app.Task):
"""桌面安装包里没有 Redis broker生产 core.celery_app 又指向 redis://localhost。
所有端点都用 `task.delay(...)` / `apply_async(...)` 派发任务,默认会把任务塞进
Redis 队列 —— 桌面模式下没人消费,于是永远卡在 0%「初始化中」。
这里在桌面模式下把 apply_async 改成「在后台守护线程里同步执行 apply()」:
不依赖任何 broker立即返回进度照常写库供前端轮询。生产模式行为不变。
"""
def apply_async(self, args=None, kwargs=None, task_id=None, **options):
if _is_desktop_mode():
import threading
import uuid
tid = task_id or str(uuid.uuid4())
call_args = list(args) if args else []
call_kwargs = dict(kwargs) if kwargs else {}
def _run():
try:
self.apply(args=call_args, kwargs=call_kwargs, task_id=tid)
except Exception as exc: # noqa: BLE001
import logging
logging.getLogger(__name__).error(
f"桌面模式本地执行任务失败 {self.name} ({tid}): {exc}", exc_info=True
)
threading.Thread(target=_run, name=f"task-{self.name}", daemon=True).start()
return _LocalAsyncResult(tid)
return super().apply_async(args=args, kwargs=kwargs, task_id=task_id, **options)
# 桌面模式下让所有 @celery_app.task 使用上面的本地执行基类
celery_app.Task = DesktopAwareTask
# 自动发现任务
celery_app.autodiscover_tasks([
'backend.tasks.processing',
'backend.tasks.video',
'backend.tasks.notification',
'backend.tasks.maintenance',
'backend.tasks.import_processing' # 添加导入处理任务
])
if __name__ == '__main__':
celery_app.start()