|
|
|
@ -1,8 +1,10 @@ |
|
|
|
from __future__ import annotations |
|
|
|
from __future__ import annotations |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import json |
|
|
|
import re |
|
|
|
import re |
|
|
|
import shutil |
|
|
|
import shutil |
|
|
|
import subprocess |
|
|
|
import subprocess |
|
|
|
|
|
|
|
import tempfile |
|
|
|
import threading |
|
|
|
import threading |
|
|
|
import uuid |
|
|
|
import uuid |
|
|
|
from pathlib import Path |
|
|
|
from pathlib import Path |
|
|
|
@ -40,6 +42,7 @@ YOUTUBE_POSTPROCESS_HINTS = ( |
|
|
|
"destination", |
|
|
|
"destination", |
|
|
|
) |
|
|
|
) |
|
|
|
INVALID_FILENAME_CHARS_PATTERN = re.compile(r"[<>:\"/\\|?*\x00-\x1f]") |
|
|
|
INVALID_FILENAME_CHARS_PATTERN = re.compile(r"[<>:\"/\\|?*\x00-\x1f]") |
|
|
|
|
|
|
|
YOUTUBE_JOBS_DIR = Path(tempfile.gettempdir()) / "pysonnerie-youtube-jobs" |
|
|
|
_YOUTUBE_JOBS_LOCK = threading.Lock() |
|
|
|
_YOUTUBE_JOBS_LOCK = threading.Lock() |
|
|
|
_YOUTUBE_JOBS: dict[str, dict[str, object]] = {} |
|
|
|
_YOUTUBE_JOBS: dict[str, dict[str, object]] = {} |
|
|
|
|
|
|
|
|
|
|
|
@ -186,21 +189,40 @@ def _extract_progress_percent(line: str) -> float | None: |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _set_youtube_job_state(job_id: str, **updates: object) -> None: |
|
|
|
def _set_youtube_job_state(job_id: str, **updates: object) -> None: |
|
|
|
|
|
|
|
YOUTUBE_JOBS_DIR.mkdir(parents=True, exist_ok=True) |
|
|
|
|
|
|
|
job_path = YOUTUBE_JOBS_DIR / f"{job_id}.json" |
|
|
|
|
|
|
|
|
|
|
|
with _YOUTUBE_JOBS_LOCK: |
|
|
|
with _YOUTUBE_JOBS_LOCK: |
|
|
|
current = _YOUTUBE_JOBS.get(job_id) |
|
|
|
current = _YOUTUBE_JOBS.get(job_id) |
|
|
|
|
|
|
|
if current is None and job_path.exists(): |
|
|
|
|
|
|
|
try: |
|
|
|
|
|
|
|
current = json.loads(job_path.read_text(encoding="utf-8")) |
|
|
|
|
|
|
|
except Exception: |
|
|
|
|
|
|
|
current = None |
|
|
|
if current is None: |
|
|
|
if current is None: |
|
|
|
return |
|
|
|
current = {} |
|
|
|
current.update(updates) |
|
|
|
current.update(updates) |
|
|
|
|
|
|
|
_YOUTUBE_JOBS[job_id] = current |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
tmp_path = job_path.with_suffix(".json.tmp") |
|
|
|
|
|
|
|
tmp_path.write_text(json.dumps(current, ensure_ascii=False), encoding="utf-8") |
|
|
|
|
|
|
|
tmp_path.replace(job_path) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _create_youtube_job() -> str: |
|
|
|
def _create_youtube_job() -> str: |
|
|
|
job_id = uuid.uuid4().hex |
|
|
|
job_id = uuid.uuid4().hex |
|
|
|
|
|
|
|
YOUTUBE_JOBS_DIR.mkdir(parents=True, exist_ok=True) |
|
|
|
|
|
|
|
payload = { |
|
|
|
|
|
|
|
"status": "running", |
|
|
|
|
|
|
|
"percent": 0.0, |
|
|
|
|
|
|
|
"message": "Préparation du téléchargement...", |
|
|
|
|
|
|
|
} |
|
|
|
with _YOUTUBE_JOBS_LOCK: |
|
|
|
with _YOUTUBE_JOBS_LOCK: |
|
|
|
_YOUTUBE_JOBS[job_id] = { |
|
|
|
_YOUTUBE_JOBS[job_id] = payload |
|
|
|
"status": "running", |
|
|
|
job_path = YOUTUBE_JOBS_DIR / f"{job_id}.json" |
|
|
|
"percent": 0.0, |
|
|
|
tmp_path = job_path.with_suffix(".json.tmp") |
|
|
|
"message": "Préparation du téléchargement...", |
|
|
|
tmp_path.write_text(json.dumps(payload, ensure_ascii=False), encoding="utf-8") |
|
|
|
} |
|
|
|
tmp_path.replace(job_path) |
|
|
|
return job_id |
|
|
|
return job_id |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -721,6 +743,13 @@ def youtube_download_status(job_id: str) -> Response: |
|
|
|
|
|
|
|
|
|
|
|
with _YOUTUBE_JOBS_LOCK: |
|
|
|
with _YOUTUBE_JOBS_LOCK: |
|
|
|
job = _YOUTUBE_JOBS.get(job_id) |
|
|
|
job = _YOUTUBE_JOBS.get(job_id) |
|
|
|
|
|
|
|
if job is None: |
|
|
|
|
|
|
|
job_path = YOUTUBE_JOBS_DIR / f"{job_id}.json" |
|
|
|
|
|
|
|
if job_path.exists(): |
|
|
|
|
|
|
|
try: |
|
|
|
|
|
|
|
job = json.loads(job_path.read_text(encoding="utf-8")) |
|
|
|
|
|
|
|
except Exception: |
|
|
|
|
|
|
|
job = None |
|
|
|
if job is None: |
|
|
|
if job is None: |
|
|
|
return jsonify({"ok": False, "error": "Téléchargement introuvable."}), 404 |
|
|
|
return jsonify({"ok": False, "error": "Téléchargement introuvable."}), 404 |
|
|
|
payload = { |
|
|
|
payload = { |
|
|
|
|