has("video_bvid")) { $bvid = $request->get("video_bvid"); $video = Videos::query()->where("bvid", "=", $bvid)->first(); if ($video == null) { $view->withErrors([ "video_bvid" => "系统无此对应视频", ]); } else { $request->session()->flashInput([ "video_bvid" => $bvid ]); } } return $view; } public function do_import(Request $request) { $request->validate([ 'video_bvid' => ['nullable', 'string'], 'part_num' => ['nullable', 'int', 'min:1'], 'file.*' => ['required', 'file'] ]); $manualBvid = trim((string)$request->get("video_bvid", "")); $manualPartNum = $request->get("part_num"); $files = $request->file("file"); if (!is_array($files)) { $files = [$files]; } if ($manualBvid !== "" && sizeof($files) > 1) { return back()->withInput()->withErrors([ "file" => "手工指定稿件时一次只能上传一个文件", ]); } $results = []; foreach ($files as $file) { $results[] = $this->import_one($file, $manualBvid, $manualPartNum); } return back()->withInput()->with("import_results", $results); } private function import_one($file, string $manualBvid, $manualPartNum): array { $name = $file->getClientOriginalName(); try { [$bvid, $partNum] = $this->resolve_target($name, $manualBvid, $manualPartNum); $records = VideoOcrUtil::parse_jsonl_records($file->getRealPath()); $count = VideoOcrUtil::import_records($bvid, $partNum, $records); $message = "导入 {$count} 条 → {$bvid} P{$partNum}"; try { VideoOcrUtil::export_to_cdn($bvid, $partNum); } catch (\Exception $e) { $message .= "(CDN 同步失败:{$e->getMessage()})"; } return ["file" => $name, "ok" => true, "message" => $message]; } catch (\Exception $e) { return ["file" => $name, "ok" => false, "message" => $e->getMessage()]; } } /** * 确定导入目标 [bvid, part_num]:手工指定优先,否则按文件名时间戳自动匹配。 */ private function resolve_target(string $filename, string $manualBvid, $manualPartNum): array { if ($manualBvid !== "") { $video = Videos::query()->where("bvid", "=", $manualBvid)->first(); if ($video == null) { throw new \InvalidArgumentException("系统无此对应视频:{$manualBvid}"); } if ($manualPartNum !== null && $manualPartNum !== "") { return [$manualBvid, (int)$manualPartNum]; } $parts = $video->parts; if (sizeof($parts) === 1) { return [$manualBvid, (int)$parts[0]->part_num]; } throw new \InvalidArgumentException("该稿件有 " . sizeof($parts) . " 个分P,请手工填写分P号"); } $recordedAt = VideoOcrUtil::parse_jsonl_filename($filename); if ($recordedAt === null) { throw new \InvalidArgumentException("文件名无法解析录制时间,请手工指定 BVID 与分P"); } $matches = VideoOcrUtil::match_parts($recordedAt); if (sizeof($matches) === 0) { throw new \InvalidArgumentException("按 {$recordedAt->format("Y-m-d H:i")} 未匹配到分P,请手工指定 BVID 与分P"); } if (sizeof($matches) > 1) { $desc = $matches->map(fn(VideoParts $p) => "{$p->bvid} P{$p->part_num}")->implode("、"); throw new \InvalidArgumentException("匹配到多个分P({$desc}),请手工指定"); } return [$matches[0]->bvid, (int)$matches[0]->part_num]; } }