mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-12 00:06:37 +08:00
fix(media): 修复emby电影库混入剧集/特别篇识别/剧集合集 + 新增手动编辑元数据
emby 兼容层(hills/yamby 等客户端): - 电影库不再混入剧集:filterLikelyEpisodicPathsFromMovieQuery 按路径 特征(Season/S0x/Specials/电视剧/日番/国漫等)剔除误入电影库的剧集 - itemPayload 改用 mediaShouldBeEpisode:有季集号且(属剧集库或路径像剧集) 才标记为 Episode,修正电影库里剧集被当成 Movie 特别篇识别: - episode_parser 新增 patSpecialSeason,识别 S0/Season 0/Specials/SP/OVA/ OAD/番外/特别篇/特典 文件夹 → 第0季;seasonFromParents 返回 found 标志, 区分"明确第0季"与"未识别默认第1季" - emby seasonDir 正则同步支持 Specials/SP/OVA/特别篇 等 剧集合集: - groupSeries 抽出 isSeriesCard,卡片链接按是否剧集判定,修复剧集卡片 跳转;每集分离的根因(季集号/分组key)随入库识别修复一并改善 手动编辑元数据(自采集视频无TMDB信息时可自定义封面/标题/简介/分类): - 新增 MediaService.UpdateMetadata + PATCH /media/:id/metadata(需admin) - 前端 MetadataEditDialog 接入 LibraryPage 与 MediaDetailPage 其他:scanner/scraper/nfo/local+cloud_metadata/adult 元数据识别配套改进; .gitignore 忽略本地 .dev-cache/.dev-data 运行产物。
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { motion } from 'framer-motion'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Link, useNavigate, useParams } from 'react-router-dom'
|
||||
import { FileText, Heart, Play, RefreshCw, Sparkles, Trash2, Calendar, Database, Search } from 'lucide-react'
|
||||
import { FileText, Heart, Play, RefreshCw, Sparkles, Trash2, Calendar, Database, Search, Pencil } from 'lucide-react'
|
||||
import toast from 'react-hot-toast'
|
||||
|
||||
import { mediaAPI } from '../api/library'
|
||||
@@ -14,6 +14,7 @@ import type { Media } from '../types'
|
||||
import { confirmAction } from '../components/ConfirmDialog'
|
||||
import { ExternalPlayerButton } from '../components/ExternalPlayerButton'
|
||||
import { ManualScrapeDialog } from '../components/ManualScrapeDialog'
|
||||
import { MetadataEditDialog } from '../components/MetadataEditDialog'
|
||||
|
||||
function fmtDuration(sec: number): string {
|
||||
if (!sec || sec <= 0) return '—'
|
||||
@@ -47,6 +48,7 @@ export function MediaDetailPage() {
|
||||
const [favourite, setFavourite] = useState(false)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [manualScrapeOpen, setManualScrapeOpen] = useState(false)
|
||||
const [metadataEditOpen, setMetadataEditOpen] = useState(false)
|
||||
|
||||
const refresh = async () => {
|
||||
if (!id) return
|
||||
@@ -136,9 +138,9 @@ export function MediaDetailPage() {
|
||||
<div className="relative overflow-hidden rounded-3xl bg-white border border-gray-200/90 shadow-[0_1px_3px_rgba(0,0,0,0.01),0_1px_2px_rgba(0,0,0,0.015)]">
|
||||
{/* ── Cinematic Blurred Backdrop Glow ── */}
|
||||
<div className="absolute inset-0 h-[480px] z-0 overflow-hidden">
|
||||
{media.poster_url ? (
|
||||
{(media.backdrop_url || media.poster_url) ? (
|
||||
<img
|
||||
src={imageURL(media.poster_url)}
|
||||
src={imageURL(media.backdrop_url || media.poster_url || '')}
|
||||
alt=""
|
||||
className="w-full h-full object-cover opacity-[0.04] scale-110 blur-2xl"
|
||||
referrerPolicy="no-referrer"
|
||||
@@ -321,6 +323,10 @@ export function MediaDetailPage() {
|
||||
<Search size={13} className="text-[#c9954a]" />
|
||||
<span>手动匹配刮削</span>
|
||||
</button>
|
||||
<button onClick={() => setMetadataEditOpen(true)} className="btn-outline py-2 px-3.5 text-xs gap-1.5 border-gray-200 hover:border-brand-500/50 hover:bg-brand-50">
|
||||
<Pencil size={13} className="text-gray-600" />
|
||||
<span>编辑元数据</span>
|
||||
</button>
|
||||
<button onClick={reprobe} className="btn-outline py-2 px-3.5 text-xs gap-1.5 border-gray-200 hover:border-brand-500/50 hover:bg-brand-50">
|
||||
<Database size={13} className="text-gray-600" />
|
||||
<span>探测媒体轨 (ffprobe)</span>
|
||||
@@ -351,6 +357,15 @@ export function MediaDetailPage() {
|
||||
onClose={() => setManualScrapeOpen(false)}
|
||||
onApplied={refresh}
|
||||
/>
|
||||
<MetadataEditDialog
|
||||
open={metadataEditOpen}
|
||||
media={media}
|
||||
onClose={() => setMetadataEditOpen(false)}
|
||||
onSaved={async (next) => {
|
||||
setMedia(next)
|
||||
await refresh()
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user