私人影库扫描增加季度支持
This commit is contained in:
@@ -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 到数据库
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// 按更新时间倒序排序
|
||||
|
||||
@@ -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 {
|
||||
// 记录失败的文件夹
|
||||
|
||||
65
src/app/api/tmdb/seasons/route.ts
Normal file
65
src/app/api/tmdb/seasons/route.ts
Normal 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 }
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user