290 lines
10 KiB
Python
290 lines
10 KiB
Python
# tasks.py
|
|
import asyncio
|
|
import json
|
|
import requests
|
|
from celery import shared_task, group, chord, chain
|
|
from celery.utils.log import get_task_logger
|
|
from django.utils import timezone
|
|
from django.utils.translation import gettext_lazy as _
|
|
from .process import ScannerProcess, ReplayProcess, redisDB
|
|
from .export_tasks import shm_download_video, export_images_zip, export_video_mp4
|
|
from .constants import ScannerConstants
|
|
from . import models
|
|
|
|
logger = get_task_logger(__name__)
|
|
|
|
|
|
@shared_task
|
|
def scanner_start():
|
|
scanner = ScannerProcess()
|
|
scanner.start()
|
|
return str(_("Scanner démarré."))
|
|
|
|
@shared_task
|
|
def replay_start():
|
|
replay = ReplayProcess(latency=5.0)
|
|
replay.start()
|
|
return str(_("Replay démarré."))
|
|
|
|
|
|
@shared_task(bind=True)
|
|
def download_video(self, uuid, start_ts, end_ts, frame_rate=5, opencv_fourcc_format='mp4v', opencv_video_type='mp4'):
|
|
"""
|
|
Exécuter directement la fonction async DANS la tâche Celery.
|
|
Sans passer par une wrapper.
|
|
"""
|
|
try:
|
|
logger.info(f"Task {self.request.id}: Processing {uuid} from {start_ts} to {end_ts}")
|
|
loop = asyncio.new_event_loop()
|
|
asyncio.set_event_loop(loop)
|
|
try:
|
|
result = loop.run_until_complete(
|
|
shm_download_video(uuid, start_ts, end_ts, frame_rate, opencv_fourcc_format, opencv_video_type) )
|
|
return result
|
|
finally:
|
|
loop.close()
|
|
except Exception as e:
|
|
logger.error(f"shm_download_video_task error: {e}", exc_info=True)
|
|
return { 'status': 'error', 'error': str(e), 'uuid': uuid}
|
|
|
|
@shared_task
|
|
def export_images(
|
|
uuid: str,
|
|
start_ts: float | None = None,
|
|
end_ts: float | None = None,
|
|
jpeg_quality: int = 90,
|
|
max_zip_size_mb: int = 0,
|
|
max_image_width: int = 0,
|
|
max_image_height: int = 0 ):
|
|
try:
|
|
return asyncio.run(export_images_zip(uuid, start_ts, end_ts, jpeg_quality, max_zip_size_mb, max_image_width, max_image_height))
|
|
except Exception as e:
|
|
logger.error(f"export_images: {e}")
|
|
|
|
|
|
@shared_task
|
|
def export_all_images(session_id=None):
|
|
try:
|
|
conf = ScannerConstants().get()
|
|
|
|
if session_id is None:
|
|
sessions = [s.pk for s in models.Session.objects.filter(active=False)]
|
|
else:
|
|
sessions = [session_id]
|
|
job_zip = []
|
|
for session_id in sessions:
|
|
uuid_list = models.SessionExperiment.uuid_from_session(session_id)
|
|
for uuid in uuid_list:
|
|
job = export_images.delay( # @UndefinedVariable
|
|
uuid,
|
|
start_ts=None,
|
|
end_ts=None,
|
|
jpeg_quality=conf.video_jpeg_quality, # Qualité JPEG
|
|
)
|
|
job_zip.append(job.id)
|
|
|
|
return job_zip
|
|
except Exception as e:
|
|
logger.error(f"export_images: {e}")
|
|
|
|
|
|
@shared_task
|
|
def export_videos(uuid: str, start_ts: float | None = None, end_ts: float | None = None, frame_rate: int = 5,
|
|
opencv_fourcc_format='mp4v',
|
|
opencv_video_type='mp4',
|
|
max_video_size_mb: int = 0,
|
|
max_width: int = 0,
|
|
max_height: int = 0, ):
|
|
try:
|
|
return asyncio.run(export_video_mp4(uuid, start_ts, end_ts, frame_rate, opencv_fourcc_format, opencv_video_type, max_video_size_mb, max_width, max_height))
|
|
except Exception as e:
|
|
logger.error(f"export_videos: {e}")
|
|
return {"success": False, "error": str(e)}
|
|
|
|
|
|
@shared_task
|
|
def export_all_videos(session_id=None):
|
|
try:
|
|
conf = ScannerConstants().get()
|
|
if session_id is None:
|
|
sessions = [s.pk for s in models.Session.objects.filter(active=False)]
|
|
else:
|
|
sessions = [session_id]
|
|
job_mp4 = []
|
|
for session_id in sessions:
|
|
uuid_list = models.SessionExperiment.uuid_from_session(session_id)
|
|
for uuid in uuid_list:
|
|
|
|
job = export_videos.delay( # @UndefinedVariable
|
|
uuid,
|
|
start_ts=None,
|
|
end_ts=None,
|
|
frame_rate=conf.video_frame_rate, # Frame rate de la vidéo exportée
|
|
opencv_fourcc_format=conf.opencv_fourcc_format, # Format de compression vidéo (ex: 'mp4v' pour MP4),
|
|
opencv_video_type=conf.opencv_video_type, # Type de vidéo exportée (ex: 'mp4', 'avi', 'mkv'),
|
|
)
|
|
job_mp4.append(job.id)
|
|
|
|
return job_mp4
|
|
except Exception as e:
|
|
logger.error(f"export_all_videos: {e}")
|
|
|
|
|
|
@shared_task(bind=True)
|
|
def run_session_exports(self, session_id: str):
|
|
"""
|
|
Orchestre l'export images + vidéo en parallèle via chord.
|
|
Lance en parallèle l'export images et l'export vidéo de la session.
|
|
Le callback on_exports_done est appelé quand les 2 sont terminés.
|
|
"""
|
|
try:
|
|
session = models.Session.objects.get(pk=session_id)
|
|
except models.Session.DoesNotExist:
|
|
logger.error("run_session_exports: session %s introuvable", session_id)
|
|
return {"status": "error", "message": "Session introuvable"}
|
|
|
|
session.export_status = models.Session.Status.RUNNING
|
|
session.save(update_fields=["export_status"])
|
|
logger.info(f"run_session_exports [{session_id}]: export démarré")
|
|
chord(
|
|
group(
|
|
export_all_images.s(session_id),
|
|
export_all_videos.s(session_id),
|
|
),
|
|
on_exports_done.s(session_id=session_id)
|
|
).apply_async()
|
|
|
|
|
|
@shared_task
|
|
def on_exports_done(results: list, session_id: str):
|
|
"""
|
|
Callback appelé par chord quand export_all_images ET export_all_videos
|
|
sont tous les deux terminés.
|
|
results = [résultat_images, résultat_vidéo]
|
|
"""
|
|
session = models.Session.objects.get(pk=session_id)
|
|
|
|
errors = [r for r in results if isinstance(r, dict) and r.get("export_status") == "error"]
|
|
if errors:
|
|
session.export_status = models.Session.Status.ERROR
|
|
logger.error(f"on_exports_done [{session_id}]: échec avec {len(errors)} erreurs")
|
|
else:
|
|
session.export_status = models.Session.Status.DONE
|
|
logger.warning(f"on_exports_done [{session_id}]: succès export terminé")
|
|
|
|
session.export_exported_at = timezone.now()
|
|
session.save(update_fields=["export_status", "export_exported_at"])
|
|
return {"status": session.export_status}
|
|
|
|
|
|
@shared_task
|
|
def scanning(session_id: str):
|
|
"""
|
|
Scanning différé.
|
|
"""
|
|
try:
|
|
logger.warning("==== scanning session %s start", session_id)
|
|
group = 'scanner_proc'
|
|
data = { "type": "scanner", "topic": "scan", "session": session_id }
|
|
redisDB.publish(group, json.dumps(data))
|
|
except Exception as e:
|
|
logger.error(f"scanning session: {session_id} error {e}")
|
|
return {"status": "error", "message": str(e)}
|
|
|
|
@shared_task(bind=True)
|
|
def run_scanning(self, session_id: str):
|
|
try:
|
|
session = models.Session.objects.get(pk=session_id)
|
|
if not session.active:
|
|
raise Exception("La session n'est plus active")
|
|
except models.Session.DoesNotExist:
|
|
logger.error("run_session_exports: session %s introuvable", session_id)
|
|
return {"status": "error", "message": "Session introuvable"}
|
|
|
|
session.scanning_status = models.Session.Status.RUNNING
|
|
session.save(update_fields=["scanning_status"])
|
|
chain(
|
|
scanning.s(session_id),
|
|
on_scanning_done.s(session_id=session_id),
|
|
).apply_async()
|
|
|
|
|
|
@shared_task
|
|
def on_scanning_done(result: dict, session_id: str):
|
|
"""
|
|
Callback appelé automatiquement à la fin de scanning().
|
|
Met à jour scanning_status en base — détectable par polling.
|
|
"""
|
|
try:
|
|
session = models.Session.objects.get(pk=session_id)
|
|
except models.Session.DoesNotExist:
|
|
logger.error("on_scanning_done: session %s introuvable", session_id)
|
|
return
|
|
|
|
session.scanning_status = models.Session.Status.ERROR if result.get("status") == "error" else models.Session.Status.DONE
|
|
session.scanning_finished_at = timezone.now()
|
|
session.save(update_fields=["scanning_status", "scanning_finished_at"])
|
|
|
|
logger.info("==== Scanning done: session %s", session_id)
|
|
|
|
|
|
@shared_task
|
|
def http_request(req_type='json_post', url=None, user=None, passwd=None, headers={}, **kwargs):
|
|
|
|
def json_post():
|
|
return requests.post(url, json=kwargs, headers=headers, auth=(user, passwd), timeout=3)
|
|
|
|
def post():
|
|
return requests.post(url, params=kwargs, headers=headers, auth=(user, passwd), timeout=3)
|
|
|
|
def get():
|
|
return requests.get(url, params=kwargs, headers=headers, auth=(user, passwd), timeout=3)
|
|
|
|
request = {
|
|
'json_post': json_post,
|
|
'post': post,
|
|
'get':get
|
|
}
|
|
retries = 3
|
|
for attempt in range(retries):
|
|
try:
|
|
response = request[req_type]()
|
|
response.raise_for_status()
|
|
try:
|
|
data = response.json()
|
|
except ValueError:
|
|
data = response.text
|
|
|
|
resp = {"success": True, "data": data}
|
|
logger.info(data)
|
|
return resp
|
|
|
|
except requests.exceptions.Timeout:
|
|
logger.error(f"{attempt + 1}/{retries}: Délai d'attente dépassé")
|
|
except requests.exceptions.ConnectionError:
|
|
logger.error(f"{attempt + 1}/{retries}: Erreur de connexion")
|
|
except requests.exceptions.HTTPError as e:
|
|
raise
|
|
except Exception as e:
|
|
logger.error(f"Erreur inattendue: {str(e)}")
|
|
|
|
@shared_task
|
|
def supervisor_restart_service(params):
|
|
"""
|
|
Redémarre un service.
|
|
|
|
"""
|
|
try:
|
|
logger.warning("==== Supervisor scanner restart all services")
|
|
http_request(
|
|
req_type='get',
|
|
url=f'http://localhost:9001/{params}',
|
|
user='root',
|
|
passwd='toor',
|
|
headers={}
|
|
)
|
|
except Exception as e:
|
|
logger.error(f"supervisor_restart_all_services error {e}")
|
|
|
|
|
|
|