私人影库扫描增加季度支持

This commit is contained in:
mtvpls
2025-12-25 23:34:54 +08:00
parent 636e6042c8
commit 76b3349aa2
11 changed files with 699 additions and 34 deletions

View File

@@ -27,7 +27,18 @@ export async function POST(request: NextRequest) {
}
const body = await request.json();
const { folder, tmdbId, title, posterPath, releaseDate, overview, voteAverage, mediaType } = body;
const {
folder,
tmdbId,
title,
posterPath,
releaseDate,
overview,
voteAverage,
mediaType,
seasonNumber,
seasonName,
} = body;
if (!folder || !tmdbId) {
return NextResponse.json(
@@ -97,6 +108,8 @@ export async function POST(request: NextRequest) {
media_type: mediaType,
last_updated: Date.now(),
failed: false, // 纠错后标记为成功
season_number: seasonNumber, // 季度编号(可选)
season_name: seasonName, // 季度名称(可选)
};
// 保存 metainfo 到数据库

View File

@@ -125,19 +125,29 @@ export async function GET(request: NextRequest) {
const allVideos = Object.entries(metaInfo.folders)
.filter(([, info]) => includeFailed || !info.failed) // 根据参数过滤失败的视频
.map(
([folderName, info]) => ({
id: folderName,
folder: folderName,
tmdbId: info.tmdb_id,
title: info.title,
poster: getTMDBImageUrl(info.poster_path),
releaseDate: info.release_date,
overview: info.overview,
voteAverage: info.vote_average,
mediaType: info.media_type,
lastUpdated: info.last_updated,
failed: info.failed || false,
})
([folderName, info]) => {
// 构建 id如果是第二季及以后id 也要包含季度信息
let videoId = folderName;
if (info.season_number && info.season_number > 1 && info.season_name) {
videoId = `${folderName} ${info.season_name}`;
}
return {
id: videoId,
folder: folderName,
tmdbId: info.tmdb_id,
title: info.title,
poster: getTMDBImageUrl(info.poster_path),
releaseDate: info.release_date,
overview: info.overview,
voteAverage: info.vote_average,
mediaType: info.media_type,
lastUpdated: info.last_updated,
failed: info.failed || false,
seasonNumber: info.season_number,
seasonName: info.season_name,
};
}
);
// 按更新时间倒序排序

View File

@@ -19,7 +19,8 @@ import {
failScanTask,
updateScanTaskProgress,
} from '@/lib/scan-task';
import { searchTMDB } from '@/lib/tmdb.search';
import { parseSeasonFromTitle } from '@/lib/season-parser';
import { searchTMDB, getTVSeasonDetails } from '@/lib/tmdb.search';
export const runtime = 'nodejs';
@@ -200,17 +201,25 @@ async function performScan(
}
try {
// 搜索 TMDB
// 解析文件夹名称,提取季度信息
const seasonInfo = parseSeasonFromTitle(folder.name);
const searchQuery = seasonInfo.cleanTitle || folder.name;
console.log(`[OpenList Refresh] 处理文件夹: ${folder.name}`);
console.log(`[OpenList Refresh] 清理后标题: ${searchQuery}, 季度: ${seasonInfo.seasonNumber}`);
// 搜索 TMDB使用清理后的标题
const searchResult = await searchTMDB(
tmdbApiKey,
folder.name,
searchQuery,
tmdbProxy
);
if (searchResult.code === 200 && searchResult.result) {
const result = searchResult.result;
metaInfo.folders[folder.name] = {
// 基础信息
const folderInfo: any = {
tmdb_id: result.id,
title: result.title || result.name || folder.name,
poster_path: result.poster_path,
@@ -222,6 +231,51 @@ async function performScan(
failed: false,
};
// 如果是电视剧且识别到季度编号,获取该季度的详细信息
if (result.media_type === 'tv' && seasonInfo.seasonNumber) {
try {
const seasonDetails = await getTVSeasonDetails(
tmdbApiKey,
result.id,
seasonInfo.seasonNumber,
tmdbProxy
);
if (seasonDetails.code === 200 && seasonDetails.season) {
folderInfo.season_number = seasonDetails.season.season_number;
folderInfo.season_name = seasonDetails.season.name;
// 如果是第二季及以后替换标题和ID
if (seasonDetails.season.season_number > 1) {
folderInfo.title = `${folderInfo.title} ${seasonDetails.season.name}`;
folderInfo.tmdb_id = seasonDetails.season.id; // 使用季度的ID
}
// 使用季度的海报(如果有)
if (seasonDetails.season.poster_path) {
folderInfo.poster_path = seasonDetails.season.poster_path;
}
// 使用季度的简介(如果有)
if (seasonDetails.season.overview) {
folderInfo.overview = seasonDetails.season.overview;
}
// 使用季度的首播日期(如果有)
if (seasonDetails.season.air_date) {
folderInfo.release_date = seasonDetails.season.air_date;
}
} else {
console.warn(`[OpenList Refresh] 获取季度 ${seasonInfo.seasonNumber} 详情失败`);
// 即使获取季度详情失败,也保存季度编号
folderInfo.season_number = seasonInfo.seasonNumber;
}
} catch (error) {
console.error(`[OpenList Refresh] 获取季度详情异常:`, error);
// 即使出错,也保存季度编号
folderInfo.season_number = seasonInfo.seasonNumber;
}
}
metaInfo.folders[folder.name] = folderInfo;
newCount++;
} else {
// 记录失败的文件夹

View File

@@ -0,0 +1,65 @@
/* eslint-disable @typescript-eslint/no-explicit-any, no-console */
import { NextRequest, NextResponse } from 'next/server';
import { getAuthInfoFromCookie } from '@/lib/auth';
import { getConfig } from '@/lib/config';
import { getTVSeasons } from '@/lib/tmdb.search';
export const runtime = 'nodejs';
/**
* GET /api/tmdb/seasons?tvId=xxx
* 获取电视剧的季度列表
*/
export async function GET(request: NextRequest) {
try {
const authInfo = getAuthInfoFromCookie(request);
if (!authInfo || !authInfo.username) {
return NextResponse.json({ error: '未授权' }, { status: 401 });
}
const { searchParams } = new URL(request.url);
const tvIdStr = searchParams.get('tvId');
if (!tvIdStr) {
return NextResponse.json({ error: '缺少 tvId 参数' }, { status: 400 });
}
const tvId = parseInt(tvIdStr, 10);
if (isNaN(tvId)) {
return NextResponse.json({ error: 'tvId 必须是数字' }, { status: 400 });
}
const config = await getConfig();
const tmdbApiKey = config.SiteConfig.TMDBApiKey;
const tmdbProxy = config.SiteConfig.TMDBProxy;
if (!tmdbApiKey) {
return NextResponse.json(
{ error: 'TMDB API Key 未配置' },
{ status: 400 }
);
}
const result = await getTVSeasons(tmdbApiKey, tvId, tmdbProxy);
if (result.code === 200 && result.seasons) {
return NextResponse.json({
success: true,
seasons: result.seasons,
});
} else {
return NextResponse.json(
{ error: '获取季度列表失败', code: result.code },
{ status: result.code }
);
}
} catch (error) {
console.error('获取季度列表失败:', error);
return NextResponse.json(
{ error: '获取失败', details: (error as Error).message },
{ status: 500 }
);
}
}