Skip to content

Commit b5ed867

Browse files
committed
✨ feat: enhance video management and danmaku features
- Add danmaku download and preview - Add settings page for favorites/name/size filters - Support multi-part video download - Refactor video management logic
1 parent c94f434 commit b5ed867

29 files changed

+1946
-365
lines changed
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php
2+
3+
namespace App\Console\Commands;
4+
5+
use App\Services\BilibiliService;
6+
use App\Services\DownloadFilterService;
7+
use App\Services\VideoManagerService;
8+
use Illuminate\Console\Command;
9+
use Log;
10+
11+
class DownloadDanmaku extends Command
12+
{
13+
/**
14+
* The name and signature of the console command.
15+
*
16+
* @var string
17+
*/
18+
protected $signature = 'app:download-danmaku';
19+
20+
/**
21+
* The console command description.
22+
*
23+
* @var string
24+
*/
25+
protected $description = '下载弹幕';
26+
27+
/**
28+
* Execute the console command.
29+
*/
30+
public function handle(VideoManagerService $videoManagerService, DownloadFilterService $downloadFilterService)
31+
{
32+
$favList = $videoManagerService->getFavList();
33+
foreach ($favList as $fav) {
34+
if($downloadFilterService->shouldExcludeByFav($fav['id'])){
35+
Log::info('Exclude fav, download danmaku skip', ['id' => $fav['id'], 'title' => $fav['title']]);
36+
continue;
37+
}
38+
39+
40+
$videoList = $videoManagerService->getVideoListByFav($fav['id']);
41+
foreach ($videoList as $video) {
42+
if($videoManagerService->videoIsInvalid($video) || $video['invalid']){
43+
Log::info('Video is invalid, download danmaku skip', ['id' => $video['id'], 'title' => $video['title']]);
44+
continue;
45+
}
46+
47+
//检查名称是否符合
48+
if($downloadFilterService->shouldExcludeByName($video['title'])){
49+
Log::info('Video name not match, download danmaku skip', ['id' => $video['id'], 'title' => $video['title']]);
50+
continue;
51+
}
52+
53+
54+
// 检查上次更新时间是否太短
55+
$lastDownloadTime = $videoManagerService->danmakuDownloadedTime($video['id']);
56+
if($lastDownloadTime && time() - $lastDownloadTime < 3600 * 24 * 7){
57+
Log::info('Danmaku already downloaded, download danmaku skip', ['id' => $video['id'], 'title' => $video['title']]);
58+
continue;
59+
}
60+
61+
$videoManagerService->dispatchDownloadDanmakuJob($video['id']);
62+
}
63+
}
64+
}
65+
}

app/Http/Controllers/CookieController.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public function checkCookieValid()
6868
dispatch($job);
6969

7070
$job = new DownloadAllVideoJob();
71-
dispatch($job)->delay(Carbon::now()->addMinutes(1));
71+
dispatch($job)->delay(Carbon::now()->addMinutes(5));
7272
}
7373

7474
return response()->json([

app/Http/Controllers/FavController.php

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,22 @@
22

33
namespace App\Http\Controllers;
44

5+
use App\Services\VideoManagerService;
56
use Illuminate\Http\Request;
67

78
class FavController extends Controller
89
{
10+
public function __construct(public VideoManagerService $videoManagerService)
11+
{
12+
13+
}
14+
915
/**
1016
* Display a listing of the resource.
1117
*/
1218
public function index()
1319
{
14-
$result = redis()->get('fav_list');
15-
$data = json_decode($result, true);
20+
$data = $this->videoManagerService->getFavList();
1621
if ($data) {
1722
return response()->json($data);
1823
} else {
@@ -33,8 +38,7 @@ public function store(Request $request)
3338
*/
3439
public function show(string $id)
3540
{
36-
$result = redis()->get(sprintf('fav_list:%d', $id));
37-
$data = json_decode($result, true);
41+
$data = $this->videoManagerService->getVideoListByFav($id);
3842
if ($data && is_array($data)) {
3943
usort($data, function ($a, $b) {
4044
if ($a['fav_time'] == $b['fav_time']) {
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?php
2+
3+
namespace App\Http\Controllers;
4+
5+
use App\Services\SettingsService;
6+
use Illuminate\Http\Request;
7+
8+
class SettingsController extends Controller
9+
{
10+
11+
public function __construct(public SettingsService $settings)
12+
{
13+
14+
}
15+
16+
public function getSettings()
17+
{
18+
$presets = [
19+
'danmakuCache' => 'on',
20+
'favExclude' => [
21+
'enabled' => false,
22+
'selected' => [],
23+
],
24+
'MultiPartitionCache' => 'off',
25+
'nameExclude' => [
26+
'contains' => '',
27+
'regex' => '',
28+
'type' => 'off',
29+
],
30+
'sizeExclude' => [
31+
'customSize' => 0,
32+
'type' => 'off',
33+
],
34+
];
35+
36+
foreach ($presets as $key => $value) {
37+
$got = $this->settings->get($key);
38+
if ($got) {
39+
$presets[$key] = $got;
40+
}
41+
}
42+
43+
return response()->json($presets);
44+
}
45+
46+
public function saveSettings(Request $request)
47+
{
48+
$data = $request->validate([
49+
'MultiPartitionCache' => 'required|string|in:on,off',
50+
'danmakuCache' => 'required|string|in:on,off',
51+
52+
'nameExclude' => 'required|array',
53+
'nameExclude.contains' => 'required_if:nameExclude.type,contains|string',
54+
'nameExclude.regex' => 'required_if:nameExclude.type,regex|string',
55+
'nameExclude.type' => 'required|string|in:off,contains,regex',
56+
57+
'sizeExclude' => 'required|array',
58+
'sizeExclude.customSize' => 'required_if:sizeExclude.type,custom|integer',
59+
'sizeExclude.type' => 'required|string|in:off,1GB,2GB,custom',
60+
61+
'favExclude' => 'required|array',
62+
'favExclude.enabled' => 'required|boolean',
63+
'favExclude.selected' => 'required_if:favExclude.enabled,true|array',
64+
]);
65+
66+
foreach ($data as $key => $value) {
67+
$this->settings->put($key, $value);
68+
}
69+
70+
return response()->json(['message' => 'Settings saved successfully']);
71+
}
72+
}

app/Http/Controllers/VideoController.php

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,23 @@
22

33
namespace App\Http\Controllers;
44

5+
use App\Services\VideoManagerService;
56
use Illuminate\Http\Request;
67

78
class VideoController extends Controller
89
{
10+
11+
public function __construct(public VideoManagerService $videoManagerService)
12+
{
13+
14+
}
15+
916
public function show(Request $request, int $id)
1017
{
11-
$result = redis()->get(sprintf('video:%d', $id));
18+
$result = $this->videoManagerService->getVideoInfo($id);
1219
if ($result) {
13-
$data = json_decode($result, true);
14-
if ($data) {
15-
return response()->json($data);
16-
}
20+
$result['parts'] = $this->videoManagerService->getAllPartsVideoForUser($id, $result['page']);
21+
return response()->json($result);
1722
}
1823
abort(404);
1924
}
@@ -70,4 +75,29 @@ public function progress()
7075

7176
return response()->json($data, 200, [], JSON_UNESCAPED_UNICODE);
7277
}
78+
79+
public function danmaku(Request $request, int $cid)
80+
{
81+
$result = $this->videoManagerService->getDanmaku($cid);
82+
return response()->json($result);
83+
}
84+
85+
public function danmakuV3(Request $request)
86+
{
87+
$cid = $request->input('id');
88+
$result = $this->videoManagerService->getDanmaku($cid);
89+
$result = array_map(function ($item) {
90+
return [
91+
$item['progress'] / 1000,
92+
$item['mode'],
93+
$item['color'],
94+
'',
95+
$item['content'],
96+
];
97+
}, $result['danmaku'] ?? []);
98+
return response()->json([
99+
'code' => 0,
100+
'data' => $result,
101+
]);
102+
}
73103
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
namespace App\Http\Middleware;
4+
5+
use Closure;
6+
use Illuminate\Http\Request;
7+
use Symfony\Component\HttpFoundation\Response;
8+
9+
class ForceJsonResponse
10+
{
11+
/**
12+
* Handle an incoming request.
13+
*
14+
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
15+
*/
16+
public function handle(Request $request, Closure $next): Response
17+
{
18+
$request->headers->set('Accept', 'application/json');
19+
$request->headers->set('Content-Type', 'application/json');
20+
return $next($request);
21+
}
22+
}

app/Jobs/DownloadAllVideoJob.php

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,47 +2,68 @@
22

33
namespace App\Jobs;
44

5+
use App\Services\DownloadFilterService;
6+
use App\Services\VideoManagerService;
57
use Illuminate\Contracts\Queue\ShouldQueue;
68
use Illuminate\Foundation\Queue\Queueable;
9+
use Log;
710

811
class DownloadAllVideoJob implements ShouldQueue
912
{
1013
use Queueable;
1114

15+
16+
public VideoManagerService $videoManagerService;
17+
public DownloadFilterService $downloadFilterService;
1218
/**
1319
* Create a new job instance.
1420
*/
1521
public function __construct()
1622
{
17-
//
23+
$this->videoManagerService = app(VideoManagerService::class);
24+
$this->downloadFilterService = app(DownloadFilterService::class);
1825
}
1926

2027
/**
2128
* Execute the job.
2229
*/
2330
public function handle(): void
2431
{
25-
$iterator = null;
26-
$keys = [
27-
];
28-
do {
29-
$result = redis()->scan($iterator, 'video:*', 50);
30-
$keys = array_merge($keys, $result);
31-
} while ($iterator != 0);
32-
33-
foreach ($keys as $videoKey) {
34-
$result = redis()->get($videoKey);
35-
$data = json_decode($result, true);
36-
if ($data) {
37-
if (!video_has_invalid($data)) {
38-
39-
$key = sprintf('download_lock:%s', $data['id']);
40-
if (redis()->setnx($key, 1)) {
41-
redis()->expire($key, 60 * 60 * 24);
42-
$job = new DownloadVideoJob($data);
43-
dispatch($job);
32+
$favList = $this->videoManagerService->getFavList();
33+
foreach($favList as $item){
34+
if($this->downloadFilterService->shouldExcludeByFav($item['id'])){
35+
Log::info('Exclude fav', ['id' => $item['id'], 'title' => $item['title']]);
36+
continue;
37+
}
38+
39+
$videoList = $this->videoManagerService->getVideoListByFav($item['id']);
40+
foreach($videoList as $video){
41+
42+
if($this->videoManagerService->videoIsInvalid($video) || $video['invalid']){
43+
Log::info('Video is invalid, skip', ['id' => $video['id'], 'title' => $video['title']]);
44+
continue;
45+
}
46+
47+
if($this->videoManagerService->videoDownloaded($video['id'])){
48+
Log::info('Video already downloaded, try to check parts', ['id' => $video['id'], 'title' => $video['title']]);
49+
50+
// 已经缓存的情况下进一步检查分P是否缓存
51+
$videoTotalParts = intval($video['page'] ?? 1);
52+
$existsParts = $this->videoManagerService->getAllPartsVideo($video['id'], $videoTotalParts);
53+
54+
if(count($existsParts) >= $videoTotalParts){
55+
Log::info('Video all parts already downloaded,skip', ['id' => $video['id'], 'title' => $video['title'], 'parts' => $videoTotalParts]);
56+
continue;
4457
}
4558
}
59+
60+
//检查名称是否符合
61+
if($this->downloadFilterService->shouldExcludeByName($video['title'])){
62+
Log::info('Video name not match, skip', ['id' => $video['id'], 'title' => $video['title']]);
63+
continue;
64+
}
65+
66+
$this->videoManagerService->dispatchDownloadVideoJob($video);
4667
}
4768
}
4869
}

app/Jobs/DownloadDanmakuJob.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
namespace App\Jobs;
4+
5+
use App\Services\VideoManagerService;
6+
use Illuminate\Contracts\Queue\ShouldQueue;
7+
use Illuminate\Foundation\Bus\Dispatchable;
8+
use Illuminate\Foundation\Queue\Queueable;
9+
use Illuminate\Queue\InteractsWithQueue;
10+
use Illuminate\Queue\SerializesModels;
11+
12+
class DownloadDanmakuJob implements ShouldQueue
13+
{
14+
use Queueable;
15+
16+
/**
17+
* Create a new job instance.
18+
*/
19+
public function __construct(public int $avId)
20+
{
21+
//
22+
}
23+
24+
/**
25+
* Execute the job.
26+
*/
27+
public function handle(): void
28+
{
29+
$videoManagerService = app(VideoManagerService::class);
30+
$videoManagerService->downloadDanmaku($this->avId);
31+
}
32+
}

0 commit comments

Comments
 (0)