@@ -248,6 +248,8 @@ type ReleaseInfo = {
248248 resolution : string | null ;
249249 codec : string | null ;
250250 source : string | null ;
251+ hdr_formats : string [ ] ;
252+ bit_depth : number | null ;
251253 revision : string | null ;
252254 release_group : string | null ;
253255 matched_rule : string ;
@@ -268,13 +270,24 @@ type MatchDecision = {
268270 explanations : string [ ] ;
269271} ;
270272
273+ type CandidateSortKey = {
274+ resolution_rank : number ;
275+ video_feature_rank : number ;
276+ source_rank : number ;
277+ size_fitness : number ;
278+ size_per_item : number ;
279+ size_target : number ;
280+ codec_rank : number ;
281+ } ;
282+
271283type ResourceCandidate = {
272284 candidateId : string ;
273285 key : string ;
274286 result : IndexerResult ;
275287 release : ReleaseInfo | null ;
276288 parseError : string | null ;
277289 decision : MatchDecision | null ;
290+ sortKey : CandidateSortKey | null ;
278291 rank : number ;
279292 raw : Record < string , unknown > ;
280293} ;
@@ -769,7 +782,15 @@ function compactEpisodeList(episodes: number[], prefix = ""): string {
769782
770783function releaseQualityFields ( release : ReleaseInfo | null | undefined ) : string [ ] {
771784 if ( ! release ) return [ ] ;
772- return [ release . resolution , release . source , release . codec , release . revision , releaseScopeLabel ( release ) ]
785+ return [
786+ release . resolution ,
787+ ...( release . hdr_formats ?? [ ] ) ,
788+ release . bit_depth ? `${ release . bit_depth } bit` : null ,
789+ release . source ,
790+ release . codec ,
791+ release . revision ,
792+ releaseScopeLabel ( release ) ,
793+ ]
773794 . filter ( ( field ) : field is string => Boolean ( field ) ) ;
774795}
775796
@@ -875,6 +896,8 @@ function normalizeCandidate(value: unknown, index: number): ResourceCandidate |
875896 resolution : optionalString ( value . release . resolution ) ,
876897 codec : optionalString ( value . release . codec ) ,
877898 source : optionalString ( value . release . source ) ,
899+ hdr_formats : stringList ( value . release . hdr_formats ) ,
900+ bit_depth : value . release . bit_depth == null ? null : numberValue ( value . release . bit_depth ) ,
878901 revision : optionalString ( value . release . revision ) ,
879902 release_group : optionalString ( value . release . release_group ) ,
880903 matched_rule : optionalString ( value . release . matched_rule ) ?? "unknown" ,
@@ -906,13 +929,27 @@ function normalizeCandidate(value: unknown, index: number): ResourceCandidate |
906929 } ;
907930 }
908931
932+ const sortKeyRaw = isRecord ( value . sort_key ) ? value . sort_key : null ;
933+ const sortKey : CandidateSortKey | null = sortKeyRaw
934+ ? {
935+ resolution_rank : numberValue ( sortKeyRaw . resolution_rank ) ,
936+ video_feature_rank : numberValue ( sortKeyRaw . video_feature_rank ) ,
937+ source_rank : numberValue ( sortKeyRaw . source_rank ) ,
938+ size_fitness : numberValue ( sortKeyRaw . size_fitness ) ,
939+ size_per_item : numberValue ( sortKeyRaw . size_per_item ) ,
940+ size_target : numberValue ( sortKeyRaw . size_target ) ,
941+ codec_rank : numberValue ( sortKeyRaw . codec_rank ) ,
942+ }
943+ : null ;
944+
909945 return {
910946 candidateId,
911947 key : candidateId ,
912948 result,
913949 release,
914950 parseError : value . parse_error == null ? null : describeUnknown ( value . parse_error ) ,
915951 decision,
952+ sortKey,
916953 rank : numberValue ( value . rank , index + 1 ) ,
917954 raw : value ,
918955 } ;
@@ -2151,7 +2188,7 @@ export function MediaPage() {
21512188 < div className = "min-w-0" >
21522189 < div className = "text-sm font-semibold" > 当前会优先寻找</ div >
21532190 < div className = "mt-2 flex flex-wrap gap-2" > < TokenList values = { [ ...splitValues ( qualityForm . resolutionOrder ) . slice ( 0 , 2 ) , ...splitValues ( qualityForm . sourceOrder ) . slice ( 0 , 2 ) , ...splitValues ( qualityForm . codecOrder ) . slice ( 0 , 1 ) ] } /> </ div >
2154- < p className = "mt-2 text-xs leading-5 text-muted" > 原盘 / REMUX、DIY、HDR 、杜比视界、杜比全景声、10bit 等特性仍会保留在资源标题中供你确认;当前自动筛选以分辨率、片源和视频编码为准 。</ p >
2191+ < p className = "mt-2 text-xs leading-5 text-muted" > 自动排序依次比较分辨率、片源、体积充足度 、杜比视界 / HDR / 位深、视频编码和做种数;只有体积达到参考线后才启用视频特性加成 。</ p >
21552192 </ div >
21562193 </ div >
21572194 </ div >
@@ -3374,7 +3411,7 @@ function candidateProcessingOutcome(
33743411 return { label : "未下载" , detail : `标题解析失败:${ candidate . parseError } ` , tone : "negative" } ;
33753412 }
33763413 if ( candidate . decision ?. accepted ) {
3377- return { label : "符合但未选" , detail : "通过全部规则,但综合排序低于最终选择 。" , tone : "neutral" } ;
3414+ return { label : "符合但未选" , detail : "通过全部规则,但在分辨率、片源、体积充足度、视频特性、编码或做种数的逐级排序中低于最终选择 。" , tone : "neutral" } ;
33783415 }
33793416 if ( candidate . decision ) {
33803417 return {
@@ -3746,8 +3783,7 @@ function ReleaseSummary({ candidate }: { candidate: ResourceCandidate }) {
37463783 return < div className = "max-w-56 text-xs text-destructive" > 解析失败:{ candidate . parseError } </ div > ;
37473784 }
37483785 if ( ! candidate . release ) return < span className = "text-xs text-muted" > 无解析数据</ span > ;
3749- const episode = releaseScopeLabel ( candidate . release ) ;
3750- const fields = [ candidate . release . resolution , candidate . release . source , candidate . release . codec , episode ] . filter ( Boolean ) ;
3786+ const fields = releaseQualityFields ( candidate . release ) ;
37513787 return (
37523788 < div className = "max-w-56 text-xs" >
37533789 < div className = "flex flex-wrap gap-1.5" >
@@ -3791,6 +3827,7 @@ function DecisionSummary({ candidate }: { candidate: ResourceCandidate }) {
37913827 < div className = "max-w-64 text-xs" >
37923828 < DecisionBadge candidate = { candidate } />
37933829 { breakdown ? < div className = "mt-1.5 text-muted" > { breakdown } </ div > : null }
3830+ < CandidateRankingSummary candidate = { candidate } />
37943831 </ div >
37953832 ) ;
37963833 }
@@ -3804,6 +3841,26 @@ function DecisionSummary({ candidate }: { candidate: ResourceCandidate }) {
38043841 ) ;
38053842}
38063843
3844+ function CandidateRankingSummary ( { candidate } : { candidate : ResourceCandidate } ) {
3845+ const sortKey = candidate . sortKey ;
3846+ if ( ! sortKey || sortKey . size_target <= 0 ) return null ;
3847+ const fitness = Math . max ( 0 , Math . min ( 100 , Math . floor ( sortKey . size_fitness / 10 ) ) ) ;
3848+ const normalized = sortKey . size_per_item === candidate . result . size ? "体积" : "单集体积" ;
3849+ const hasVideoFeatures = Boolean ( candidate . release ?. hdr_formats . length )
3850+ || ( candidate . release ?. bit_depth ?? 0 ) >= 10 ;
3851+ const featureStatus = hasVideoFeatures
3852+ ? ` · 视频特性加成${ sortKey . video_feature_rank > 0 ? "已启用" : "未启用" } `
3853+ : "" ;
3854+ return (
3855+ < div
3856+ className = "mt-1.5 text-muted"
3857+ title = { `${ normalized } ${ formatBytes ( sortKey . size_per_item ) } ,充足参考 ${ formatBytes ( sortKey . size_target ) } ` }
3858+ >
3859+ { normalized } 充足度 { fitness } %{ featureStatus } · { candidate . result . seeders } 做种
3860+ </ div >
3861+ ) ;
3862+ }
3863+
38073864function decisionBreakdownLabel ( name : string ) : string {
38083865 const labels : Record < string , string > = {
38093866 title : "标题" ,
0 commit comments