支持主动导入弹幕

This commit is contained in:
Jerry Yan 2023-01-03 13:07:45 +08:00
parent cece95a5a4
commit 474b97a40a
8 changed files with 187 additions and 31 deletions

View File

@ -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"]);
}
}

View File

@ -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");

32
app/Util/DanmakuUtil.php Normal file
View File

@ -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;
}
}

View File

@ -6,6 +6,7 @@
"license": "MIT",
"require": {
"php": "^7.3|^8.0",
"ext-dom": "*",
"ext-json": "*",
"ext-mbstring": "*",
"fruitcake/laravel-cors": "^2.0",

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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");