$id, 'original_name' => 'youtube_video', 'upload_path' => $uploadPath, 'extension' => 'mp4', 'status' => 'downloading', 'created_at' => time(), 'source_video_exposed' => false, 'youtube_url' => $youtubeUrl ]; write_json_file(RECEIPT_DIR . "/$id.job.json", $job); // Fork background download $logFile = RECEIPT_DIR . "/$id.download.log"; $cmd = sprintf( 'nohup bash -c %s > %s 2>&1 &', shell_arg(sprintf( 'cd /app && yt-dlp -f "best[ext=mp4]/best" -o %s %s 2>&1 && ' . 'TITLE=$(yt-dlp --get-title %s 2>/dev/null) && ' . 'echo "DONE|$TITLE" >> %s', shell_arg($uploadPath), shell_arg($youtubeUrl), shell_arg($youtubeUrl), shell_arg($logFile) )), shell_arg($logFile) ); exec($cmd); json_response([ 'ok' => true, 'id' => $id, 'job' => $job, 'status_url' => "/api/status.php?id=$id" ]); } // ─── File upload mode ─── if (!isset($_FILES['video'])) { fail_json('No video uploaded or YouTube URL provided'); } $file = $_FILES['video']; if ($file['error'] !== UPLOAD_ERR_OK) { fail_json('Upload failed', 400, ['upload_error' => $file['error']]); } $original = basename($file['name']); $ext = strtolower(pathinfo($original, PATHINFO_EXTENSION)); $allowed = ['mp4', 'mov', 'm4v', 'webm', 'mkv']; if (!in_array($ext, $allowed, true)) { fail_json('Unsupported video type', 400, ['extension' => $ext]); } $id = safe_id(); $uploadPath = UPLOAD_DIR . "/$id.$ext"; if (!move_uploaded_file($file['tmp_name'], $uploadPath)) { fail_json('Could not save upload'); } $job = [ 'id' => $id, 'original_name' => $original, 'upload_path' => $uploadPath, 'extension' => $ext, 'status' => 'uploaded', 'created_at' => time(), 'source_video_exposed' => false ]; write_json_file(RECEIPT_DIR . "/$id.job.json", $job); json_response([ 'ok' => true, 'id' => $id, 'job' => $job ]);