mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-05 07:27:15 +08:00
feat(meta): 新增 WEB 平台来源识别并支持更多音视频格式。
This commit is contained in:
@@ -10,6 +10,7 @@ from app.core.meta.releasegroup import ReleaseGroupsMatcher
|
|||||||
from app.schemas.types import MediaType
|
from app.schemas.types import MediaType
|
||||||
from app.utils.string import StringUtils
|
from app.utils.string import StringUtils
|
||||||
from app.utils.tokens import Tokens
|
from app.utils.tokens import Tokens
|
||||||
|
from app.core.meta.streamingplatform import StreamingPlatforms
|
||||||
|
|
||||||
|
|
||||||
class MetaVideo(MetaBase):
|
class MetaVideo(MetaBase):
|
||||||
@@ -31,7 +32,7 @@ class MetaVideo(MetaBase):
|
|||||||
_part_re = r"(^PART[0-9ABI]{0,2}$|^CD[0-9]{0,2}$|^DVD[0-9]{0,2}$|^DISK[0-9]{0,2}$|^DISC[0-9]{0,2}$)"
|
_part_re = r"(^PART[0-9ABI]{0,2}$|^CD[0-9]{0,2}$|^DVD[0-9]{0,2}$|^DISK[0-9]{0,2}$|^DISC[0-9]{0,2}$)"
|
||||||
_roman_numerals = r"^(?=[MDCLXVI])M*(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$"
|
_roman_numerals = r"^(?=[MDCLXVI])M*(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$"
|
||||||
_source_re = r"^BLURAY$|^HDTV$|^UHDTV$|^HDDVD$|^WEBRIP$|^DVDRIP$|^BDRIP$|^BLU$|^WEB$|^BD$|^HDRip$|^REMUX$|^UHD$"
|
_source_re = r"^BLURAY$|^HDTV$|^UHDTV$|^HDDVD$|^WEBRIP$|^DVDRIP$|^BDRIP$|^BLU$|^WEB$|^BD$|^HDRip$|^REMUX$|^UHD$"
|
||||||
_effect_re = r"^SDR$|^HDR\d*$|^DOLBY$|^DOVI$|^DV$|^3D$|^REPACK$|^HLG$|^HDR10(\+|Plus)$"
|
_effect_re = r"^SDR$|^HDR\d*$|^DOLBY$|^DOVI$|^DV$|^3D$|^REPACK$|^HLG$|^HDR10(\+|Plus)$|^EDR$|^HQ$"
|
||||||
_resources_type_re = r"%s|%s" % (_source_re, _effect_re)
|
_resources_type_re = r"%s|%s" % (_source_re, _effect_re)
|
||||||
_name_no_begin_re = r"^[\[【].+?[\]】]"
|
_name_no_begin_re = r"^[\[【].+?[\]】]"
|
||||||
_name_no_chinese_re = r".*版|.*字幕"
|
_name_no_chinese_re = r".*版|.*字幕"
|
||||||
@@ -51,7 +52,7 @@ class MetaVideo(MetaBase):
|
|||||||
_resources_pix_re = r"^[SBUHD]*(\d{3,4}[PI]+)|\d{3,4}X(\d{3,4})"
|
_resources_pix_re = r"^[SBUHD]*(\d{3,4}[PI]+)|\d{3,4}X(\d{3,4})"
|
||||||
_resources_pix_re2 = r"(^[248]+K)"
|
_resources_pix_re2 = r"(^[248]+K)"
|
||||||
_video_encode_re = r"^(H26[45])$|^(x26[45])$|^AVC$|^HEVC$|^VC\d?$|^MPEG\d?$|^Xvid$|^DivX$|^AV1$|^HDR\d*$|^AVS(\+|[23])$"
|
_video_encode_re = r"^(H26[45])$|^(x26[45])$|^AVC$|^HEVC$|^VC\d?$|^MPEG\d?$|^Xvid$|^DivX$|^AV1$|^HDR\d*$|^AVS(\+|[23])$"
|
||||||
_audio_encode_re = r"^DTS\d?$|^DTSHD$|^DTSHDMA$|^Atmos$|^TrueHD\d?$|^AC3$|^\dAudios?$|^DDP\d?$|^DD\+\d?$|^DD\d?$|^LPCM\d?$|^AAC\d?$|^FLAC\d?$|^HD\d?$|^MA\d?$|^HR\d?$|^Opus\d?$|^Vorbis\d?$"
|
_audio_encode_re = r"^DTS\d?$|^DTSHD$|^DTSHDMA$|^Atmos$|^TrueHD\d?$|^AC3$|^\dAudios?$|^DDP\d?$|^DD\+\d?$|^DD\d?$|^LPCM\d?$|^AAC\d?$|^FLAC\d?$|^HD\d?$|^MA\d?$|^HR\d?$|^Opus\d?$|^Vorbis\d?$|^AV[3S]A$"
|
||||||
|
|
||||||
def __init__(self, title: str, subtitle: str = None, isfile: bool = False):
|
def __init__(self, title: str, subtitle: str = None, isfile: bool = False):
|
||||||
"""
|
"""
|
||||||
@@ -66,6 +67,8 @@ class MetaVideo(MetaBase):
|
|||||||
original_title = title
|
original_title = title
|
||||||
self._source = ""
|
self._source = ""
|
||||||
self._effect = []
|
self._effect = []
|
||||||
|
self.web_source = None
|
||||||
|
self._index = 0
|
||||||
# 判断是否纯数字命名
|
# 判断是否纯数字命名
|
||||||
if isfile \
|
if isfile \
|
||||||
and title.isdigit() \
|
and title.isdigit() \
|
||||||
@@ -93,9 +96,12 @@ class MetaVideo(MetaBase):
|
|||||||
# 拆分tokens
|
# 拆分tokens
|
||||||
tokens = Tokens(title)
|
tokens = Tokens(title)
|
||||||
self.tokens = tokens
|
self.tokens = tokens
|
||||||
|
# 实例化StreamingPlatforms对象
|
||||||
|
streaming_platforms = StreamingPlatforms()
|
||||||
# 解析名称、年份、季、集、资源类型、分辨率等
|
# 解析名称、年份、季、集、资源类型、分辨率等
|
||||||
token = tokens.get_next()
|
token = tokens.get_next()
|
||||||
while token:
|
while token:
|
||||||
|
self._index += 1 # 更新当前处理的token索引
|
||||||
# Part
|
# Part
|
||||||
self.__init_part(token)
|
self.__init_part(token)
|
||||||
# 标题
|
# 标题
|
||||||
@@ -116,6 +122,9 @@ class MetaVideo(MetaBase):
|
|||||||
# 资源类型
|
# 资源类型
|
||||||
if self._continue_flag:
|
if self._continue_flag:
|
||||||
self.__init_resource_type(token)
|
self.__init_resource_type(token)
|
||||||
|
# 流媒体平台
|
||||||
|
if self._continue_flag:
|
||||||
|
self.__init_web_source(token, streaming_platforms)
|
||||||
# 视频编码
|
# 视频编码
|
||||||
if self._continue_flag:
|
if self._continue_flag:
|
||||||
self.__init_video_encode(token)
|
self.__init_video_encode(token)
|
||||||
@@ -131,6 +140,9 @@ class MetaVideo(MetaBase):
|
|||||||
self.resource_effect = " ".join(self._effect)
|
self.resource_effect = " ".join(self._effect)
|
||||||
if self._source:
|
if self._source:
|
||||||
self.resource_type = self._source.strip()
|
self.resource_type = self._source.strip()
|
||||||
|
# 添加流媒体平台
|
||||||
|
if self.web_source:
|
||||||
|
self.resource_type = f"{self.web_source} {self.resource_type}"
|
||||||
# 提取原盘DIY
|
# 提取原盘DIY
|
||||||
if self.resource_type and "BluRay" in self.resource_type:
|
if self.resource_type and "BluRay" in self.resource_type:
|
||||||
if (self.subtitle and re.findall(r'D[Ii]Y', self.subtitle)) \
|
if (self.subtitle and re.findall(r'D[Ii]Y', self.subtitle)) \
|
||||||
@@ -574,6 +586,57 @@ class MetaVideo(MetaBase):
|
|||||||
self._effect.append(effect)
|
self._effect.append(effect)
|
||||||
self._last_token = effect.upper()
|
self._last_token = effect.upper()
|
||||||
|
|
||||||
|
def __init_web_source(self, token: str, streaming_platforms: StreamingPlatforms):
|
||||||
|
"""
|
||||||
|
识别流媒体平台
|
||||||
|
"""
|
||||||
|
if not self.name:
|
||||||
|
return
|
||||||
|
|
||||||
|
platform_name = None
|
||||||
|
query_range = 1
|
||||||
|
|
||||||
|
prev_token = None
|
||||||
|
prev_idx = self._index - 2
|
||||||
|
if prev_idx >= 0 and prev_idx < len(self.tokens._tokens):
|
||||||
|
prev_token = self.tokens._tokens[prev_idx]
|
||||||
|
|
||||||
|
next_token = self.tokens.peek()
|
||||||
|
|
||||||
|
if streaming_platforms.is_streaming_platform(token):
|
||||||
|
platform_name = streaming_platforms.get_streaming_platform_name(token)
|
||||||
|
else:
|
||||||
|
for adjacent_token, is_next in [(prev_token, False), (next_token, True)]:
|
||||||
|
if not adjacent_token or platform_name:
|
||||||
|
continue
|
||||||
|
|
||||||
|
for separator in [" ", "-"]:
|
||||||
|
if is_next:
|
||||||
|
combined_token = f"{token}{separator}{adjacent_token}"
|
||||||
|
else:
|
||||||
|
combined_token = f"{adjacent_token}{separator}{token}"
|
||||||
|
|
||||||
|
if streaming_platforms.is_streaming_platform(combined_token):
|
||||||
|
platform_name = streaming_platforms.get_streaming_platform_name(combined_token)
|
||||||
|
query_range = 2
|
||||||
|
if is_next:
|
||||||
|
self.tokens.get_next()
|
||||||
|
break
|
||||||
|
|
||||||
|
if not platform_name:
|
||||||
|
return
|
||||||
|
|
||||||
|
web_tokens = ["WEB", "DL", "WEBDL", "WEBRIP"]
|
||||||
|
match_start_idx = self._index - query_range
|
||||||
|
match_end_idx = self._index - 1
|
||||||
|
start_index = max(0, match_start_idx - query_range)
|
||||||
|
end_index = min(len(self.tokens._tokens), match_end_idx + 1 + query_range)
|
||||||
|
tokens_to_check = self.tokens._tokens[start_index:end_index]
|
||||||
|
|
||||||
|
if any(tok and tok.upper() in web_tokens for tok in tokens_to_check):
|
||||||
|
self.web_source = platform_name
|
||||||
|
self._continue_flag = False
|
||||||
|
|
||||||
def __init_video_encode(self, token: str):
|
def __init_video_encode(self, token: str):
|
||||||
"""
|
"""
|
||||||
识别视频编码
|
识别视频编码
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
from typing import Dict, Optional, List, Tuple
|
||||||
|
|
||||||
|
from app.utils.singleton import Singleton
|
||||||
|
|
||||||
|
|
||||||
|
class StreamingPlatforms(metaclass=Singleton):
|
||||||
|
"""
|
||||||
|
流媒体平台简称与全称。
|
||||||
|
"""
|
||||||
|
STREAMING_PLATFORMS: List[Tuple[str, str]] = [
|
||||||
|
("AMZN", "Amazon"),
|
||||||
|
("NF", "Netflix"),
|
||||||
|
("ATVP", "Apple TV+"),
|
||||||
|
("iT", "iTunes"),
|
||||||
|
("DSNP", "Disney+"),
|
||||||
|
("HS", "Hotstar"),
|
||||||
|
("APPS", "Disney+ MENA"),
|
||||||
|
("PMTP", "Paramount+"),
|
||||||
|
("HMAX", "Max"),
|
||||||
|
("", "Max"),
|
||||||
|
("HULU", "Hulu"),
|
||||||
|
("MA", "Movies Anywhere"),
|
||||||
|
("BCORE", "Bravia Core"),
|
||||||
|
("MS", "Microsoft Store"),
|
||||||
|
("SHO", "Showtime"),
|
||||||
|
("STAN", "Stan"),
|
||||||
|
("PCOK", "Peacock"),
|
||||||
|
("SKST", "SkyShowtime"),
|
||||||
|
("NOW", "Now TV"),
|
||||||
|
("FXTL", "Foxtel Now"),
|
||||||
|
("BNGE", "Binge"),
|
||||||
|
("CRKL", "Crackle"),
|
||||||
|
("RKTN", "Rakuten TV"),
|
||||||
|
("ALL4", "All 4"),
|
||||||
|
("AS", "Adult Swim"),
|
||||||
|
("BRTB", "Brtb TV"),
|
||||||
|
("CNLP", "Canal+"),
|
||||||
|
("CRIT", "Criterion Channel"),
|
||||||
|
("DSCP", "Discovery+"),
|
||||||
|
("", "ESPN"),
|
||||||
|
("FOOD", "Food Network"),
|
||||||
|
("MUBI", "Mubi"),
|
||||||
|
("PLAY", "Google Play"),
|
||||||
|
("YT", "YouTube"),
|
||||||
|
("", "friDay"),
|
||||||
|
("", "KKTV"),
|
||||||
|
("", "ofiii"),
|
||||||
|
("", "LiTV"),
|
||||||
|
("", "MyVideo"),
|
||||||
|
("Hami", "Hami Video"),
|
||||||
|
("", "meWATCH"),
|
||||||
|
("CATCHPLAY", "CATCHPLAY+"),
|
||||||
|
("", "LINE TV"),
|
||||||
|
("VIU", "Viu"),
|
||||||
|
("IQ", ""),
|
||||||
|
("", "WeTV"),
|
||||||
|
("ABMA", "Abema"),
|
||||||
|
("ADN", ""),
|
||||||
|
("AT-X", ""),
|
||||||
|
("Baha", ""),
|
||||||
|
("BG", "B-Global"),
|
||||||
|
("CR", "Crunchyroll"),
|
||||||
|
("", "DMM"),
|
||||||
|
("FOD", ""),
|
||||||
|
("FUNi", "Funimation"),
|
||||||
|
("HIDI", "HIDIVE"),
|
||||||
|
("UNXT", "U-NEXT"),
|
||||||
|
]
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""初始化流媒体平台匹配器"""
|
||||||
|
self._lookup_cache = {}
|
||||||
|
self._build_cache()
|
||||||
|
|
||||||
|
def _build_cache(self) -> None:
|
||||||
|
"""
|
||||||
|
构建查询缓存。
|
||||||
|
"""
|
||||||
|
self._lookup_cache.clear()
|
||||||
|
for short_name, full_name in self.STREAMING_PLATFORMS:
|
||||||
|
canonical_name = full_name or short_name
|
||||||
|
if not canonical_name:
|
||||||
|
continue
|
||||||
|
|
||||||
|
aliases = {short_name, full_name}
|
||||||
|
for alias in aliases:
|
||||||
|
if alias:
|
||||||
|
self._lookup_cache[alias.upper()] = canonical_name
|
||||||
|
|
||||||
|
def get_streaming_platform_name(self, platform_code: str) -> Optional[str]:
|
||||||
|
"""
|
||||||
|
根据流媒体平台简称或全称获取标准名称。
|
||||||
|
"""
|
||||||
|
if platform_code is None:
|
||||||
|
return None
|
||||||
|
return self._lookup_cache.get(platform_code.upper())
|
||||||
|
|
||||||
|
def is_streaming_platform(self, name: str) -> bool:
|
||||||
|
"""
|
||||||
|
判断给定的字符串是否为已知的流媒体平台代码或名称。
|
||||||
|
"""
|
||||||
|
if name is None:
|
||||||
|
return False
|
||||||
|
return name.upper() in self._lookup_cache
|
||||||
+7
-7
@@ -153,7 +153,7 @@ meta_cases = [{
|
|||||||
"part": "",
|
"part": "",
|
||||||
"season": "S01",
|
"season": "S01",
|
||||||
"episode": "E02",
|
"episode": "E02",
|
||||||
"restype": "WEB-DL",
|
"restype": "B-Global WEB-DL",
|
||||||
"pix": "1080p",
|
"pix": "1080p",
|
||||||
"video_codec": "x264",
|
"video_codec": "x264",
|
||||||
"audio_codec": "AAC"
|
"audio_codec": "AAC"
|
||||||
@@ -569,7 +569,7 @@ meta_cases = [{
|
|||||||
"part": "",
|
"part": "",
|
||||||
"season": "S02",
|
"season": "S02",
|
||||||
"episode": "E05",
|
"episode": "E05",
|
||||||
"restype": "WEB-DL",
|
"restype": "Crunchyroll WEB-DL",
|
||||||
"pix": "1080p",
|
"pix": "1080p",
|
||||||
"video_codec": "x264",
|
"video_codec": "x264",
|
||||||
"audio_codec": "AAC"
|
"audio_codec": "AAC"
|
||||||
@@ -649,7 +649,7 @@ meta_cases = [{
|
|||||||
"part": "",
|
"part": "",
|
||||||
"season": "",
|
"season": "",
|
||||||
"episode": "",
|
"episode": "",
|
||||||
"restype": "WEBRip",
|
"restype": "Netflix WEBRip",
|
||||||
"pix": "1080p",
|
"pix": "1080p",
|
||||||
"video_codec": "H264",
|
"video_codec": "H264",
|
||||||
"audio_codec": "DDP 5.1"
|
"audio_codec": "DDP 5.1"
|
||||||
@@ -681,7 +681,7 @@ meta_cases = [{
|
|||||||
"part": "",
|
"part": "",
|
||||||
"season": "S01",
|
"season": "S01",
|
||||||
"episode": "E16",
|
"episode": "E16",
|
||||||
"restype": "WEB-DL",
|
"restype": "KKTV WEB-DL",
|
||||||
"pix": "1080p",
|
"pix": "1080p",
|
||||||
"video_codec": "x264",
|
"video_codec": "x264",
|
||||||
"audio_codec": "AAC"
|
"audio_codec": "AAC"
|
||||||
@@ -921,7 +921,7 @@ meta_cases = [{
|
|||||||
"part": "",
|
"part": "",
|
||||||
"season": "S06",
|
"season": "S06",
|
||||||
"episode": "E06",
|
"episode": "E06",
|
||||||
"restype": "WEBRip",
|
"restype": "Max WEBRip",
|
||||||
"pix": "1080p",
|
"pix": "1080p",
|
||||||
"video_codec": "x264",
|
"video_codec": "x264",
|
||||||
"audio_codec": "DD 5.1"
|
"audio_codec": "DD 5.1"
|
||||||
@@ -937,7 +937,7 @@ meta_cases = [{
|
|||||||
"part": "",
|
"part": "",
|
||||||
"season": "S06",
|
"season": "S06",
|
||||||
"episode": "E05",
|
"episode": "E05",
|
||||||
"restype": "WEBRip",
|
"restype": "Max WEBRip",
|
||||||
"pix": "1080p",
|
"pix": "1080p",
|
||||||
"video_codec": "x264",
|
"video_codec": "x264",
|
||||||
"audio_codec": "DD 5.1"
|
"audio_codec": "DD 5.1"
|
||||||
@@ -969,7 +969,7 @@ meta_cases = [{
|
|||||||
"part": "",
|
"part": "",
|
||||||
"season": "S02",
|
"season": "S02",
|
||||||
"episode": "",
|
"episode": "",
|
||||||
"restype": "WEB-DL",
|
"restype": "Netflix WEB-DL",
|
||||||
"pix": "2160p",
|
"pix": "2160p",
|
||||||
"video_codec": "H265",
|
"video_codec": "H265",
|
||||||
"audio_codec": "DDP 5.1 Atmos"
|
"audio_codec": "DDP 5.1 Atmos"
|
||||||
|
|||||||
Reference in New Issue
Block a user