From 474b97a40a03e72b03b87fc2a044a39d2a26804f Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Tue, 3 Jan 2023 13:07:45 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E6=8C=81=E4=B8=BB=E5=8A=A8=E5=AF=BC?= =?UTF-8?q?=E5=85=A5=E5=BC=B9=E5=B9=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DanmakuConstructController.php | 71 +++++++++++++++++++ app/Models/VideoDanmakus.php | 6 ++ app/Util/DanmakuUtil.php | 32 +++++++++ composer.json | 1 + resources/views/common/header.blade.php | 4 +- .../danmaku/construct/batch_import.blade.php | 35 +++++++++ resources/views/video/index.blade.php | 3 + routes/web.php | 66 +++++++++-------- 8 files changed, 187 insertions(+), 31 deletions(-) create mode 100644 app/Http/Controllers/DanmakuConstructController.php create mode 100644 app/Util/DanmakuUtil.php create mode 100644 resources/views/danmaku/construct/batch_import.blade.php diff --git a/app/Http/Controllers/DanmakuConstructController.php b/app/Http/Controllers/DanmakuConstructController.php new file mode 100644 index 0000000..6852eef --- /dev/null +++ b/app/Http/Controllers/DanmakuConstructController.php @@ -0,0 +1,71 @@ +<?php + +namespace App\Http\Controllers; + +use App\Models\VideoDanmakus; +use App\Models\Videos; +use App\Util\DanmakuUtil; +use Illuminate\Http\Request; +use Illuminate\Routing\Controller as BaseController; +use Illuminate\Support\Facades\DB; + +class DanmakuConstructController extends BaseController +{ + public function page(Request $request) + { + $view = view("danmaku.construct.batch_import"); + if ($request->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' => ['required'], + 'platform_id' => ['required', 'int'], + 'file' => ['required'] + ]); + $payload = $request->only(["video_bvid", "platform_id"]); + $files = $request->file("file"); + if (!is_array($files)) { + $files = [$files]; + } + $video = Videos::query()->where("bvid", "=", $payload["video_bvid"])->first(); + if ($video == null) { + return back()->withInput()->withErrors([ + "video_bvid" => "系统无此对应视频", + ]); + } + foreach ($files as $file) { + $danmakus = DanmakuUtil::parse_danmaku($file->getFileInfo()); + DB::beginTransaction(); + try { + foreach ($danmakus as &$danmaku) { + $danmaku['video_bvid'] = $video->bvid; + $danmaku['platform_id'] = $payload["platform_id"]; + unset($danmaku); + } + VideoDanmakus::insert($danmakus); + DB::commit(); + } catch (\Exception $e) { + DB::rollBack(); + return back()->withInput()->withErrors([ + "file" => "文件导入异常:" . $e->getMessage(), + ]); + } + } + return redirect("/danmakus/" . $payload["video_bvid"]); + } +} diff --git a/app/Models/VideoDanmakus.php b/app/Models/VideoDanmakus.php index 41af5ad..851fb77 100644 --- a/app/Models/VideoDanmakus.php +++ b/app/Models/VideoDanmakus.php @@ -6,8 +6,14 @@ use Illuminate\Database\Eloquent\Model; class VideoDanmakus extends Model { + protected $guarded = []; protected $table = "video_danmakus"; protected $dateFormat = 'U'; + public $timestamps = false; + protected $casts = [ + 'created_at' => 'datetime:Y-m-d H:i:s', + ]; + protected $fillable = ["from", "from_mid", "content"]; public function video(): \Illuminate\Database\Eloquent\Relations\BelongsTo { return $this->belongsTo(Videos::class, "video_bvid", "bvid"); diff --git a/app/Util/DanmakuUtil.php b/app/Util/DanmakuUtil.php new file mode 100644 index 0000000..5b59ac2 --- /dev/null +++ b/app/Util/DanmakuUtil.php @@ -0,0 +1,32 @@ +<?php + +namespace App\Util; + +use SplFileInfo; + +class DanmakuUtil +{ + public static function parse_danmaku(SplFileInfo $file): array + { + $document = new \DOMDocument(); + $document->load($file->getRealPath()); + $danmaku_items = $document->getElementsByTagName("d"); + $result = []; + /** @var \DOMNode $item */ + foreach ($danmaku_items as $item) { + $paramsNode = $item->attributes->getNamedItem("p"); + $param_list = mb_split(",", $paramsNode->value); + if (sizeof($param_list) < 7) { + throw new \Exception("弹幕格式异常"); + } + $userNode = $item->attributes->getNamedItem("user"); + $result[] = [ + "from" => $userNode->value, + "from_mid" => $param_list[6], + "content" => $item->textContent, + "created_at" => intval($param_list[4])/1000, + ]; + } + return $result; + } +} diff --git a/composer.json b/composer.json index afcb5ef..a7c93d6 100644 --- a/composer.json +++ b/composer.json @@ -6,6 +6,7 @@ "license": "MIT", "require": { "php": "^7.3|^8.0", + "ext-dom": "*", "ext-json": "*", "ext-mbstring": "*", "fruitcake/laravel-cors": "^2.0", diff --git a/resources/views/common/header.blade.php b/resources/views/common/header.blade.php index 534d931..16c71ca 100644 --- a/resources/views/common/header.blade.php +++ b/resources/views/common/header.blade.php @@ -21,7 +21,7 @@ <a class="{{ (request()->is('danmakus', 'danmakus/*')) ? 'bg-gray-900 text-white' : 'text-gray-700 hover:bg-gray-700 hover:text-white' }} px-3 py-2 rounded-md text-sm font-medium" href="/danmakus">稿件查询</a> <a class="{{ (request()->is('programs', '/', 'programs/*/video')) ? 'bg-gray-900 text-white' : 'text-gray-700 hover:bg-gray-700 hover:text-white' }} px-3 py-2 rounded-md text-sm font-medium" href="/programs" title="数据不全,待补充">节目查询</a> @auth("web") - <a class="{{ (request()->is('programs/construct', 'programs/construct/*')) ? 'bg-gray-900 text-white' : 'text-gray-700 hover:bg-gray-700 hover:text-white' }} px-3 py-2 rounded-md text-sm font-medium" href="/programs/construct">节目建设</a> + <a class="{{ (request()->is('construct/*')) ? 'bg-gray-900 text-white' : 'text-gray-700 hover:bg-gray-700 hover:text-white' }} px-3 py-2 rounded-md text-sm font-medium" href="/construct/programs">节目建设</a> @endauth </div> </div> @@ -50,7 +50,7 @@ <a class="{{ (request()->is('danmakus', 'danmakus/*')) ? 'bg-gray-900 text-white' : 'text-gray-700 hover:bg-gray-700 hover:text-white' }} block px-3 py-2 rounded-md text-base font-medium" href="/danmakus">稿件查询</a> <a class="{{ (request()->is('programs', '/', 'programs/*/video')) ? 'bg-gray-900 text-white' : 'text-gray-700 hover:bg-gray-700 hover:text-white' }} block px-3 py-2 rounded-md text-base font-medium" href="/programs" title="数据不全,待补充">节目查询</a> @auth("web") - <a class="{{ (request()->is('programs/construct', 'programs/construct/*')) ? 'bg-gray-900 text-white' : 'text-gray-700 hover:bg-gray-700 hover:text-white' }} block px-3 py-2 rounded-md text-base font-medium" href="/programs/construct">节目建设</a> + <a class="{{ (request()->is('construct/*')) ? 'bg-gray-900 text-white' : 'text-gray-700 hover:bg-gray-700 hover:text-white' }} block px-3 py-2 rounded-md text-base font-medium" href="/construct/programs">节目建设</a> @endauth </div> </div> diff --git a/resources/views/danmaku/construct/batch_import.blade.php b/resources/views/danmaku/construct/batch_import.blade.php new file mode 100644 index 0000000..b03a2ea --- /dev/null +++ b/resources/views/danmaku/construct/batch_import.blade.php @@ -0,0 +1,35 @@ +<html lang="zh"> +<head> + <meta charset="UTF-8"> + <title>弹幕导入</title> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <link href="{{ mix('/css/app.css') }}" rel="stylesheet"/> +</head> +<body> + @include("common.header") + <form class="w-full lg:w-1/2 lg:ml-6 border-2" action="" method="post" enctype="multipart/form-data"> + @csrf + <label class="block my-2"> + BVID + <input class="form-input border-0 border-b-2 w-full" type="text" name="video_bvid" required value="{{ old('video_bvid') }}"> + </label> + <label class="block my-2"> + 弹幕类型 + <span class="block form-input border-0 border-b-2 w-full"> + <input type="radio" value="1" name="platform_id" checked>B站 + <input type="radio" value="2" name="platform_id" disabled>西瓜 + <input type="radio" value="3" name="platform_id" disabled>抖音 + </span> + </label> + <label class="block my-2"> + 弹幕文件 + <input class="form-input border-0 border-b-2 w-full" multiple type="file" name="file[]" accept="text/xml"> + </label> + @include("common.form_error") + <div class="block my-2 text-center"> + <input class="px-6 py-2 inline-block rounded-full bg-cyan-600 text-white" type="submit"> + </div> + </form> + @include("common.footer") +</body> +</html> diff --git a/resources/views/video/index.blade.php b/resources/views/video/index.blade.php index 0ca521d..2e6531f 100644 --- a/resources/views/video/index.blade.php +++ b/resources/views/video/index.blade.php @@ -68,6 +68,9 @@ @if(sizeof($video_pivots) === 0 && $comment) <a href="{{ url(route("program.construct.from_comment", ["comment"=>$comment->id])) }}" class="px-6 py-2 inline-block rounded-full bg-cyan-600 text-white">一键导入评论中的节目单</a> @endif +@if($video->danmakus->count() === 0) + <a href="{{ url(route("danmaku.construct.batch_import", ["video_bvid"=>$video->bvid])) }}" class="px-6 py-2 inline-block rounded-full bg-cyan-600 text-white">导入直播弹幕</a> +@endif @endauth @include("common.footer") </body> diff --git a/routes/web.php b/routes/web.php index a88b052..ab61b96 100644 --- a/routes/web.php +++ b/routes/web.php @@ -31,36 +31,44 @@ Route::post("/login/webauthn/", ["\\App\\Http\\Controllers\\UserWebAuthnControll Route::get('/register', ["\\App\\Http\\Controllers\\UserController", "register_page"])->name("register"); Route::post('/register', ["\\App\\Http\\Controllers\\UserController", "register"])->name("register.submit"); Route::get('/logout', ["\\App\\Http\\Controllers\\UserController", "logout"])->name("logout"); +// 弹幕建设 // 建设部分 -Route::prefix("/programs/construct")->middleware("auth:web")->group(function (Router $router) { - // 节目建设 - $router->get('/', ["\\App\\Http\\Controllers\\ProgramConstructController","index"])->name("program.construct.list"); - $router->get('/add', ["\\App\\Http\\Controllers\\ProgramConstructController","add"])->name("program.construct.add"); - $router->post('/add', ["\\App\\Http\\Controllers\\ProgramConstructController","create"])->name("program.construct.create"); - $router->get('/from_comment/{comment}', ["\\App\\Http\\Controllers\\ProgramConstructController","from_comment"])->name("program.construct.from_comment"); - $router->get('/batch', ["\\App\\Http\\Controllers\\ProgramConstructController","batch_add"])->name("program.construct.batch_add"); - $router->post('/batch', ["\\App\\Http\\Controllers\\ProgramConstructController","batch_create"])->name("program.construct.batch_create"); - $router->get('/{program}', ["\\App\\Http\\Controllers\\ProgramConstructController","edit"])->name("program.construct.edit"); - $router->post('/{program}', ["\\App\\Http\\Controllers\\ProgramConstructController", "submit"])->name("program.construct.submit"); - // 节目关联视频建设 - $router->get("/{program}/video", ["\\App\\Http\\Controllers\\ProgramVideoConstructController","index"])->name("program.construct.video.list"); - $router->get("/{program}/video/add", ["\\App\\Http\\Controllers\\ProgramVideoConstructController","add"])->name("program.construct.video.add"); - $router->post("/{program}/video/add", ["\\App\\Http\\Controllers\\ProgramVideoConstructController","create"])->name("program.construct.video.create"); - $router->get("/video/{program_video}", ["\\App\\Http\\Controllers\\ProgramVideoConstructController","edit"])->name("program.construct.video.edit"); - $router->post("/video/{program_video}", ["\\App\\Http\\Controllers\\ProgramVideoConstructController","submit"])->name("program.construct.video.submit"); - $router->get("/video/{program_video}/manual_fix", ["\\App\\Http\\Controllers\\ProgramVideoConstructController","to_fix_created_at"])->name("program.construct.video.manual_fix_created_at.view"); - $router->post("/video/{program_video}/manual_fix", ["\\App\\Http\\Controllers\\ProgramVideoConstructController","fix_created_at_base_on"])->name("program.construct.video.manual_fix_created_at"); - $router->get("/video/fix/{bvid}", ["\\App\\Http\\Controllers\\ProgramVideoConstructController","auto_fix_created_at"])->name("program.construct.video.auto_fix_created_at"); - // 节目关联点播建设 - $router->get('/{program}/append', ["\\App\\Http\\Controllers\\ProgramAppendConstructController","index"])->name("program.construct.append.list"); - $router->get('/{program}/append/add', ["\\App\\Http\\Controllers\\ProgramAppendConstructController","add"])->name("program.construct.append.add"); - $router->post('/{program}/append/add', ["\\App\\Http\\Controllers\\ProgramAppendConstructController","create"])->name("program.construct.append.create"); - $router->get('/append/from_list', ["\\App\\Http\\Controllers\\ProgramAppendConstructController", "from_list"])->name("program.construct.append.from_list"); - $router->get('/{program}/append/copy', ["\\App\\Http\\Controllers\\ProgramAppendConstructController","copy_view"])->name("program.construct.append.copy"); - $router->post('/{program}/append/copy', ["\\App\\Http\\Controllers\\ProgramAppendConstructController","copy_append"])->name("program.construct.append.copy.submit"); - $router->get('/append/broadcast_list', ["\\App\\Http\\Controllers\\ProgramAppendConstructController", "broadcast_list"])->name("program.construct.append.broadcast_list"); - $router->get('/append/{append}', ["\\App\\Http\\Controllers\\ProgramAppendConstructController","edit"])->name("program.construct.append.edit"); - $router->post('/append/{append}', ["\\App\\Http\\Controllers\\ProgramAppendConstructController","submit"])->name("program.construct.append.submit"); +Route::prefix("/construct")->middleware("auth:web")->group(function (Router $router) { + Route::prefix("/programs")->group(function (Router $router) { + // 节目建设 + $router->get('/', ["\\App\\Http\\Controllers\\ProgramConstructController","index"])->name("program.construct.list"); + $router->get('/add', ["\\App\\Http\\Controllers\\ProgramConstructController","add"])->name("program.construct.add"); + $router->post('/add', ["\\App\\Http\\Controllers\\ProgramConstructController","create"])->name("program.construct.create"); + $router->get('/from_comment/{comment}', ["\\App\\Http\\Controllers\\ProgramConstructController","from_comment"])->name("program.construct.from_comment"); + $router->get('/batch', ["\\App\\Http\\Controllers\\ProgramConstructController","batch_add"])->name("program.construct.batch_add"); + $router->post('/batch', ["\\App\\Http\\Controllers\\ProgramConstructController","batch_create"])->name("program.construct.batch_create"); + $router->get('/{program}', ["\\App\\Http\\Controllers\\ProgramConstructController","edit"])->name("program.construct.edit"); + $router->post('/{program}', ["\\App\\Http\\Controllers\\ProgramConstructController", "submit"])->name("program.construct.submit"); + // 节目关联视频建设 + $router->get("/{program}/video", ["\\App\\Http\\Controllers\\ProgramVideoConstructController","index"])->name("program.construct.video.list"); + $router->get("/{program}/video/add", ["\\App\\Http\\Controllers\\ProgramVideoConstructController","add"])->name("program.construct.video.add"); + $router->post("/{program}/video/add", ["\\App\\Http\\Controllers\\ProgramVideoConstructController","create"])->name("program.construct.video.create"); + $router->get("/video/{program_video}", ["\\App\\Http\\Controllers\\ProgramVideoConstructController","edit"])->name("program.construct.video.edit"); + $router->post("/video/{program_video}", ["\\App\\Http\\Controllers\\ProgramVideoConstructController","submit"])->name("program.construct.video.submit"); + $router->get("/video/{program_video}/manual_fix", ["\\App\\Http\\Controllers\\ProgramVideoConstructController","to_fix_created_at"])->name("program.construct.video.manual_fix_created_at.view"); + $router->post("/video/{program_video}/manual_fix", ["\\App\\Http\\Controllers\\ProgramVideoConstructController","fix_created_at_base_on"])->name("program.construct.video.manual_fix_created_at"); + $router->get("/video/fix/{bvid}", ["\\App\\Http\\Controllers\\ProgramVideoConstructController","auto_fix_created_at"])->name("program.construct.video.auto_fix_created_at"); + // 节目关联点播建设 + $router->get('/{program}/append', ["\\App\\Http\\Controllers\\ProgramAppendConstructController","index"])->name("program.construct.append.list"); + $router->get('/{program}/append/add', ["\\App\\Http\\Controllers\\ProgramAppendConstructController","add"])->name("program.construct.append.add"); + $router->post('/{program}/append/add', ["\\App\\Http\\Controllers\\ProgramAppendConstructController","create"])->name("program.construct.append.create"); + $router->get('/append/from_list', ["\\App\\Http\\Controllers\\ProgramAppendConstructController", "from_list"])->name("program.construct.append.from_list"); + $router->get('/{program}/append/copy', ["\\App\\Http\\Controllers\\ProgramAppendConstructController","copy_view"])->name("program.construct.append.copy"); + $router->post('/{program}/append/copy', ["\\App\\Http\\Controllers\\ProgramAppendConstructController","copy_append"])->name("program.construct.append.copy.submit"); + $router->get('/append/broadcast_list', ["\\App\\Http\\Controllers\\ProgramAppendConstructController", "broadcast_list"])->name("program.construct.append.broadcast_list"); + $router->get('/append/{append}', ["\\App\\Http\\Controllers\\ProgramAppendConstructController","edit"])->name("program.construct.append.edit"); + $router->post('/append/{append}', ["\\App\\Http\\Controllers\\ProgramAppendConstructController","submit"])->name("program.construct.append.submit"); + }); + // 弹幕维护 + Route::prefix("/danmaku")->group(function (Router $router) { + $router->get("/batch_import", ["\\App\\Http\\Controllers\\DanmakuConstructController", "page"])->name("danmaku.construct.batch_import.page"); + $router->post("/batch_import", ["\\App\\Http\\Controllers\\DanmakuConstructController", "do_import"])->name("danmaku.construct.batch_import"); + }); }); Route::prefix("/user")->middleware("auth:web")->group(function (Router $router) { $router->post("/webauthn/options", ["\\App\\Http\\Controllers\\UserWebAuthnController", "register_options"])->name("user.webauthn.bind.options");