Skip to content

Commit

Permalink
✨ feat: enhance video management and danmaku features
Browse files Browse the repository at this point in the history
- Add danmaku download and preview
- Add settings page for favorites/name/size filters
- Support multi-part video download
- Refactor video management logic
  • Loading branch information
ellermister committed Jan 31, 2025
1 parent c94f434 commit b5ed867
Show file tree
Hide file tree
Showing 29 changed files with 1,946 additions and 365 deletions.
65 changes: 65 additions & 0 deletions app/Console/Commands/DownloadDanmaku.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

namespace App\Console\Commands;

use App\Services\BilibiliService;
use App\Services\DownloadFilterService;
use App\Services\VideoManagerService;
use Illuminate\Console\Command;
use Log;

class DownloadDanmaku extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'app:download-danmaku';

/**
* The console command description.
*
* @var string
*/
protected $description = '下载弹幕';

/**
* Execute the console command.
*/
public function handle(VideoManagerService $videoManagerService, DownloadFilterService $downloadFilterService)
{
$favList = $videoManagerService->getFavList();
foreach ($favList as $fav) {
if($downloadFilterService->shouldExcludeByFav($fav['id'])){
Log::info('Exclude fav, download danmaku skip', ['id' => $fav['id'], 'title' => $fav['title']]);
continue;
}


$videoList = $videoManagerService->getVideoListByFav($fav['id']);
foreach ($videoList as $video) {
if($videoManagerService->videoIsInvalid($video) || $video['invalid']){
Log::info('Video is invalid, download danmaku skip', ['id' => $video['id'], 'title' => $video['title']]);
continue;
}

//检查名称是否符合
if($downloadFilterService->shouldExcludeByName($video['title'])){
Log::info('Video name not match, download danmaku skip', ['id' => $video['id'], 'title' => $video['title']]);
continue;
}


// 检查上次更新时间是否太短
$lastDownloadTime = $videoManagerService->danmakuDownloadedTime($video['id']);
if($lastDownloadTime && time() - $lastDownloadTime < 3600 * 24 * 7){
Log::info('Danmaku already downloaded, download danmaku skip', ['id' => $video['id'], 'title' => $video['title']]);
continue;
}

$videoManagerService->dispatchDownloadDanmakuJob($video['id']);
}
}
}
}
2 changes: 1 addition & 1 deletion app/Http/Controllers/CookieController.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public function checkCookieValid()
dispatch($job);

$job = new DownloadAllVideoJob();
dispatch($job)->delay(Carbon::now()->addMinutes(1));
dispatch($job)->delay(Carbon::now()->addMinutes(5));
}

return response()->json([
Expand Down
12 changes: 8 additions & 4 deletions app/Http/Controllers/FavController.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,22 @@

namespace App\Http\Controllers;

use App\Services\VideoManagerService;
use Illuminate\Http\Request;

class FavController extends Controller
{
public function __construct(public VideoManagerService $videoManagerService)
{

}

/**
* Display a listing of the resource.
*/
public function index()
{
$result = redis()->get('fav_list');
$data = json_decode($result, true);
$data = $this->videoManagerService->getFavList();
if ($data) {
return response()->json($data);
} else {
Expand All @@ -33,8 +38,7 @@ public function store(Request $request)
*/
public function show(string $id)
{
$result = redis()->get(sprintf('fav_list:%d', $id));
$data = json_decode($result, true);
$data = $this->videoManagerService->getVideoListByFav($id);
if ($data && is_array($data)) {
usort($data, function ($a, $b) {
if ($a['fav_time'] == $b['fav_time']) {
Expand Down
72 changes: 72 additions & 0 deletions app/Http/Controllers/SettingsController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

namespace App\Http\Controllers;

use App\Services\SettingsService;
use Illuminate\Http\Request;

class SettingsController extends Controller
{

public function __construct(public SettingsService $settings)
{

}

public function getSettings()
{
$presets = [
'danmakuCache' => 'on',
'favExclude' => [
'enabled' => false,
'selected' => [],
],
'MultiPartitionCache' => 'off',
'nameExclude' => [
'contains' => '',
'regex' => '',
'type' => 'off',
],
'sizeExclude' => [
'customSize' => 0,
'type' => 'off',
],
];

foreach ($presets as $key => $value) {
$got = $this->settings->get($key);
if ($got) {
$presets[$key] = $got;
}
}

return response()->json($presets);
}

public function saveSettings(Request $request)
{
$data = $request->validate([
'MultiPartitionCache' => 'required|string|in:on,off',
'danmakuCache' => 'required|string|in:on,off',

'nameExclude' => 'required|array',
'nameExclude.contains' => 'required_if:nameExclude.type,contains|string',
'nameExclude.regex' => 'required_if:nameExclude.type,regex|string',
'nameExclude.type' => 'required|string|in:off,contains,regex',

'sizeExclude' => 'required|array',
'sizeExclude.customSize' => 'required_if:sizeExclude.type,custom|integer',
'sizeExclude.type' => 'required|string|in:off,1GB,2GB,custom',

'favExclude' => 'required|array',
'favExclude.enabled' => 'required|boolean',
'favExclude.selected' => 'required_if:favExclude.enabled,true|array',
]);

foreach ($data as $key => $value) {
$this->settings->put($key, $value);
}

return response()->json(['message' => 'Settings saved successfully']);
}
}
40 changes: 35 additions & 5 deletions app/Http/Controllers/VideoController.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,23 @@

namespace App\Http\Controllers;

use App\Services\VideoManagerService;
use Illuminate\Http\Request;

class VideoController extends Controller
{

public function __construct(public VideoManagerService $videoManagerService)
{

}

public function show(Request $request, int $id)
{
$result = redis()->get(sprintf('video:%d', $id));
$result = $this->videoManagerService->getVideoInfo($id);
if ($result) {
$data = json_decode($result, true);
if ($data) {
return response()->json($data);
}
$result['parts'] = $this->videoManagerService->getAllPartsVideoForUser($id, $result['page']);
return response()->json($result);
}
abort(404);
}
Expand Down Expand Up @@ -70,4 +75,29 @@ public function progress()

return response()->json($data, 200, [], JSON_UNESCAPED_UNICODE);
}

public function danmaku(Request $request, int $cid)
{
$result = $this->videoManagerService->getDanmaku($cid);
return response()->json($result);
}

public function danmakuV3(Request $request)
{
$cid = $request->input('id');
$result = $this->videoManagerService->getDanmaku($cid);
$result = array_map(function ($item) {
return [
$item['progress'] / 1000,
$item['mode'],
$item['color'],
'',
$item['content'],
];
}, $result['danmaku'] ?? []);
return response()->json([
'code' => 0,
'data' => $result,
]);
}
}
22 changes: 22 additions & 0 deletions app/Http/Middleware/ForceJsonResponse.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class ForceJsonResponse
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
$request->headers->set('Accept', 'application/json');
$request->headers->set('Content-Type', 'application/json');
return $next($request);
}
}
61 changes: 41 additions & 20 deletions app/Jobs/DownloadAllVideoJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,47 +2,68 @@

namespace App\Jobs;

use App\Services\DownloadFilterService;
use App\Services\VideoManagerService;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Log;

class DownloadAllVideoJob implements ShouldQueue
{
use Queueable;


public VideoManagerService $videoManagerService;
public DownloadFilterService $downloadFilterService;
/**
* Create a new job instance.
*/
public function __construct()
{
//
$this->videoManagerService = app(VideoManagerService::class);
$this->downloadFilterService = app(DownloadFilterService::class);
}

/**
* Execute the job.
*/
public function handle(): void
{
$iterator = null;
$keys = [
];
do {
$result = redis()->scan($iterator, 'video:*', 50);
$keys = array_merge($keys, $result);
} while ($iterator != 0);

foreach ($keys as $videoKey) {
$result = redis()->get($videoKey);
$data = json_decode($result, true);
if ($data) {
if (!video_has_invalid($data)) {

$key = sprintf('download_lock:%s', $data['id']);
if (redis()->setnx($key, 1)) {
redis()->expire($key, 60 * 60 * 24);
$job = new DownloadVideoJob($data);
dispatch($job);
$favList = $this->videoManagerService->getFavList();
foreach($favList as $item){
if($this->downloadFilterService->shouldExcludeByFav($item['id'])){
Log::info('Exclude fav', ['id' => $item['id'], 'title' => $item['title']]);
continue;
}

$videoList = $this->videoManagerService->getVideoListByFav($item['id']);
foreach($videoList as $video){

if($this->videoManagerService->videoIsInvalid($video) || $video['invalid']){
Log::info('Video is invalid, skip', ['id' => $video['id'], 'title' => $video['title']]);
continue;
}

if($this->videoManagerService->videoDownloaded($video['id'])){
Log::info('Video already downloaded, try to check parts', ['id' => $video['id'], 'title' => $video['title']]);

// 已经缓存的情况下进一步检查分P是否缓存
$videoTotalParts = intval($video['page'] ?? 1);
$existsParts = $this->videoManagerService->getAllPartsVideo($video['id'], $videoTotalParts);

if(count($existsParts) >= $videoTotalParts){
Log::info('Video all parts already downloaded,skip', ['id' => $video['id'], 'title' => $video['title'], 'parts' => $videoTotalParts]);
continue;
}
}

//检查名称是否符合
if($this->downloadFilterService->shouldExcludeByName($video['title'])){
Log::info('Video name not match, skip', ['id' => $video['id'], 'title' => $video['title']]);
continue;
}

$this->videoManagerService->dispatchDownloadVideoJob($video);
}
}
}
Expand Down
32 changes: 32 additions & 0 deletions app/Jobs/DownloadDanmakuJob.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace App\Jobs;

use App\Services\VideoManagerService;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class DownloadDanmakuJob implements ShouldQueue
{
use Queueable;

/**
* Create a new job instance.
*/
public function __construct(public int $avId)
{
//
}

/**
* Execute the job.
*/
public function handle(): void
{
$videoManagerService = app(VideoManagerService::class);
$videoManagerService->downloadDanmaku($this->avId);
}
}
Loading

0 comments on commit b5ed867

Please sign in to comment.