Files
MoviePilot/app/domain/meta/metavideo.py
T

839 lines
34 KiB
Python

import re
from typing import Optional
from Pinyin2Hanzi import is_pinyin
from app.domain.meta.customization import CustomizationMatcher
from app.domain.meta.metabase import MetaBase
from app.domain.meta.releasegroup import ReleaseGroupsMatcher
from app.schemas.types import MediaType
from app.foundation import text as text_tools
from app.domain.tokens import Tokens
from app.domain.meta.streamingplatform import StreamingPlatforms
from app.domain.meta.runtime import get_media_extensions
SEASON_FULL_RE = re.compile(r"^(?:Season\s+|S)(\d{1,3})$", re.IGNORECASE)
FIRST_BRACKET_RE = re.compile(r'^[\[【](.+?)[\]】]')
BRACKET_DOT_TITLE_RE = re.compile(r'[A-Za-z]+\..+(?:19|20)\d{2}')
BRACKET_RESOURCE_RE = re.compile(
r'(?:2160|1080|720|480)[PIpi]|4K|UHD|Blu[\-.]?ray|REMUX|WEB[\-.]?DL|HDTV',
re.IGNORECASE,
)
YEAR_RANGE_RE = re.compile(r'([\s.]+)(\d{4})-(\d{4})')
FILE_SIZE_RE = re.compile(r'[0-9.]+\s*[MGT]i?B(?![A-Z]+)', re.IGNORECASE)
DATE_RE = re.compile(r'\d{4}[\s._-]\d{1,2}[\s._-]\d{1,2}')
DIY_RE = re.compile(r'DIY', re.IGNORECASE)
DIY_TITLE_RE = re.compile(r'-DIY@', re.IGNORECASE)
DESCRIPTION_SPLIT_RE = re.compile(r'[\s/|]+')
SPACE_RE = re.compile(r'\s+')
SEASON_SUFFIX_RE = re.compile(r"SEASON$", re.IGNORECASE)
SOURCE_RE = (
r"^BLURAY$|^HDTV$|^UHDTV$|^HDDVD$|^WEBRIP$|^DVDRIP$|^BDRIP$|"
r"^BLU$|^WEB$|^BD$|^HDRip$|^REMUX$|^UHD$"
)
SOURCE_PATTERN = re.compile(r"(%s)" % SOURCE_RE, re.IGNORECASE)
SOURCE_NAMES = {
"BLURAY": "BluRay",
"HDTV": "HDTV",
"UHDTV": "UHDTV",
"HDDVD": "HDDVD",
"WEBRIP": "WEBRip",
"DVDRIP": "DVDRip",
"BDRIP": "BDRIP",
"BLU": "BLU",
"WEB": "WEB",
"BD": "BD",
"HDRIP": "HDRip",
"REMUX": "REMUX",
"UHD": "UHD",
}
class MetaVideo(MetaBase):
"""
识别电影、电视剧
"""
# 控制标位区
_stop_name_flag = False
_stop_cnname_flag = False
_last_token = ""
_last_token_type = ""
_continue_flag = True
_unknown_name_str = ""
_sources = []
_effect = []
# 正则式区
_season_re = r"S(\d{3})|^S(\d{1,3})$|S(\d{1,3})E"
_episode_re = r"EP?(\d{2,4})$|^EP?(\d{1,4})$|^S\d{1,2}EP?(\d{1,4})$|S\d{2}EP?(\d{2,4})"
_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})$"
_source_re = SOURCE_RE
_effect_re = r"^SDR$|^HDR\d*$|^HDRVIVID$|^DOLBY$|^DOVI$|^DV$|^3D$|^REPACK$|^HLG$|^HDR10(\+|Plus)$|^HDR10P$|^VIVID$|^EDR$|^HQ$"
_resources_type_re = r"%s|%s" % (_source_re, _effect_re)
_name_no_begin_re = r"^[\[【].+?[\]】]"
_name_no_chinese_re = r".*版|.*字幕"
_name_se_words = ['共', '第', '季', '集', '话', '話', '期']
_name_movie_words = ['剧场版', '劇場版', '电影版', '電影版']
_name_nostring_re = r"^PTS|^JADE|^AOD|^CHC|^[A-Z]{1,4}TV[\-0-9UVHDK]*" \
r"|\d{1,2}th|\d{1,2}bit|IMAX|^3D|\s+3D|\s+DC$" \
r"|[第\s共]+[0-9一二三四五六七八九十\-\s]+季" \
r"|[第\s共]+[0-9一二三四五六七八九十百零\-\s]+[集话話]" \
r"|连载|日剧|美剧|电视剧|动画片|动漫|欧美|西德|日韩|超高清|高清|无水印|下载|蓝光|翡翠台|梦幻天堂·龙网|★?\d*月?新番" \
r"|最终季|合集|[多中国英葡法俄日韩德意西印泰台港粤双文语简繁体特效内封官译外挂]+字幕|版本|出品|台版|港版|\w+字幕组|\w+字幕社" \
r"|未删减版|UNCUT$|UNRATE$|WITH EXTRAS$|RERIP$|SUBBED$|PROPER$|REPACK$|SEASON$|EPISODE$|Complete$|Extended$|Extended Version$" \
r"|S\d{2}\s*-\s*S\d{2}|S\d{2}|\s+S\d{1,2}|EP?\d{2,4}\s*-\s*EP?\d{2,4}|EP?\d{2,4}|\s+EP?\d{1,4}" \
r"|CD[\s.]*[1-9]|DVD[\s.]*[1-9]|DISK[\s.]*[1-9]|DISC[\s.]*[1-9]" \
r"|[248]K|\d{3,4}[PIX]+" \
r"|CD[\s.]*[1-9]|DVD[\s.]*[1-9]|DISK[\s.]*[1-9]|DISC[\s.]*[1-9]|\s+GB"
_resources_pix_re = r"^[SBUHD]*(\d{3,4}[PI]+)|\d{3,4}X(\d{3,4})"
_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])$"
_audio_encode_re = r"^DTS\d?$|^DTSHD$|^DTSHDMA$|^Atmos$|^TrueHD\d?$|^AC3$|^EAC3\d?$|^\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$"
_fps_re = r"(\d{2,3})(?=FPS)"
_season_pattern = re.compile(_season_re, re.IGNORECASE)
_episode_pattern = re.compile(_episode_re, re.IGNORECASE)
_part_pattern = re.compile(_part_re, re.IGNORECASE)
_roman_numerals_pattern = re.compile(_roman_numerals)
_source_pattern = SOURCE_PATTERN
_effect_pattern = re.compile(r"(%s)" % _effect_re, re.IGNORECASE)
_resources_type_pattern = re.compile(r"(%s)" % _resources_type_re, re.IGNORECASE)
_name_no_chinese_pattern = re.compile(_name_no_chinese_re, re.IGNORECASE)
_name_movie_words_pattern = re.compile("|".join(_name_movie_words), re.IGNORECASE)
_name_nostring_pattern = re.compile(_name_nostring_re, re.IGNORECASE)
_resources_pix_pattern = re.compile(_resources_pix_re, re.IGNORECASE)
_resources_pix_pattern2 = re.compile(_resources_pix_re2, re.IGNORECASE)
_video_encode_pattern = re.compile(r"(%s)" % _video_encode_re, re.IGNORECASE)
_audio_encode_pattern = re.compile(r"(%s)" % _audio_encode_re, re.IGNORECASE)
_fps_pattern = re.compile(r"(%s)" % _fps_re, re.IGNORECASE)
def __init__(self, title: str, subtitle: str = None, isfile: bool = False):
"""
初始化
:param title: 标题,文件为去掉了后缀
:param subtitle: 副标题
:param isfile: 是否是文件名
"""
super().__init__(title, subtitle, isfile)
if not title:
return
original_title = title
self._sources = []
self._effect = []
self._index = 0
# 判断是否纯数字命名
if isfile \
and title.isdigit() \
and len(title) < 5:
self.begin_episode = int(title)
self.type = MediaType.TV
return
# 全名为Season xx 及 Sxx 直接返回
season_full_res = SEASON_FULL_RE.search(title)
if season_full_res:
self.type = MediaType.TV
season = season_full_res.group(1)
if season:
self.begin_season = int(season)
self.total_season = 1
return
# 去掉名称中第1个[]的内容
_first_bracket = FIRST_BRACKET_RE.match(title)
if _first_bracket:
_bracket_content = _first_bracket.group(1)
# 如果第一个括号内为点分隔的英文发布名格式(含年份+资源类型),保留内容去掉括号
if BRACKET_DOT_TITLE_RE.search(_bracket_content) \
and BRACKET_RESOURCE_RE.search(_bracket_content):
title = _bracket_content + title[_first_bracket.end():]
else:
title = title[_first_bracket.end():]
# 把xxxx-xxxx年份换成前一个年份,常出现在季集上
title = YEAR_RANGE_RE.sub(r'\1\2', title)
# 把大小去掉
title = FILE_SIZE_RE.sub("", title)
# 把年月日去掉
title = DATE_RE.sub("", title)
media_exts = get_media_extensions()
# 拆分tokens
tokens = Tokens(title)
# 实例化StreamingPlatforms对象
streaming_platforms = StreamingPlatforms()
# 解析名称、年份、季、集、资源类型、分辨率等
token = tokens.get_next()
while token:
self._index += 1 # 更新当前处理的token索引
# Part
self.__init_part(token, tokens)
# 标题
if self._continue_flag:
self.__init_name(token, media_exts)
# 年份
if self._continue_flag:
self.__init_year(token)
# 分辨率
if self._continue_flag:
self.__init_resource_pix(token)
# 季
if self._continue_flag:
self.__init_season(token)
# 集
if self._continue_flag:
self.__init_episode(token)
# 资源类型
if self._continue_flag:
self.__init_resource_type(token)
# 流媒体平台
if self._continue_flag:
self.__init_web_source(token, tokens, streaming_platforms)
# 视频编码
if self._continue_flag:
self.__init_video_encode(token)
# 视频位深
if self._continue_flag:
self.__init_video_bit(token)
# 音频编码
if self._continue_flag:
self.__init_audio_encode(token)
# 帧率
if self._continue_flag:
self.__init_fps(token)
# 取下一个,直到没有为卡
token = tokens.get_next()
self._continue_flag = True
# 合成质量
if self._effect:
self._effect.reverse()
self.resource_effect = " ".join(self._effect)
if self._sources:
self.resource_type = " ".join(self._sources)
# 提取原盘DIY
if self.resource_type and "BluRay" in self.resource_type:
if (self.subtitle and DIY_RE.search(self.subtitle)) \
or DIY_TITLE_RE.search(original_title):
self.resource_type = f"{self.resource_type} DIY"
# 解析副标题,只要季和集
self.init_subtitle(self.org_string)
if not self._subtitle_flag and self.subtitle:
self.init_subtitle(self.subtitle)
# 去掉名字中不需要的干扰字符,过短的纯数字不要
self.cn_name = self.__fix_name(self.cn_name)
self.en_name = text_tools.title_case(self.__fix_name(self.en_name))
# 处理part
if self.part and self.part.upper() == "PART":
self.part = None
# 没有中文标题时,尝试中描述中获取中文名
if not self.cn_name and self.en_name and self.subtitle:
if self.__is_pinyin(self.en_name):
# 英文名是拼音
cn_name = self.__get_title_from_description(self.subtitle)
if cn_name and len(cn_name) == len(self.en_name.split()):
# 中文名和拼音单词数相同,认为是中文名
self.cn_name = cn_name
# 制作组/字幕组
self.resource_team = ReleaseGroupsMatcher().match(title=original_title) or None
# 自定义占位符
self.customization = CustomizationMatcher().match(title=original_title) or None
if not self.video_bit:
self.video_bit = self.extract_video_bit(self.video_encode)
@staticmethod
def __get_title_from_description(description: str) -> Optional[str]:
"""
从描述中提取标题
"""
if not description:
return None
titles = DESCRIPTION_SPLIT_RE.split(description)
if text_tools.contains_chinese(titles[0]):
return titles[0]
return None
@staticmethod
def __is_pinyin(name_str: Optional[str]) -> bool:
"""
判断是否拼音
"""
if not name_str:
return False
for n in name_str.lower().split():
if not is_pinyin(n):
return False
return True
def __fix_name(self, name: Optional[str]):
"""
去掉名字中不需要的干扰字符
"""
if not name:
return name
name = self._name_nostring_pattern.sub('', name).strip()
name = SPACE_RE.sub(' ', name)
if name.isdecimal() \
and int(name) < 1800 \
and not self.year \
and self.begin_season is None \
and not self.resource_pix \
and not self.resource_type \
and not self.audio_encode \
and not self.video_encode:
if self.begin_episode is None:
self.begin_episode = int(name)
name = None
elif self.is_in_episode(int(name)) and self.begin_season is None:
name = None
return name
def __init_name(self, token: Optional[str], media_exts: list):
"""
识别名称
"""
if not token:
return
# 回收标题
if self._unknown_name_str:
if not self.cn_name:
if not self.en_name:
self.en_name = self._unknown_name_str
elif self._unknown_name_str != self.year:
self.en_name = "%s %s" % (self.en_name, self._unknown_name_str)
self._last_token_type = "enname"
self._unknown_name_str = ""
if self._stop_name_flag:
return
if token.upper() == "AKA":
self._continue_flag = False
self._stop_name_flag = True
return
if token in self._name_se_words:
self._last_token_type = 'name_se_words'
return
if text_tools.contains_chinese(token):
# 含有中文,直接做为标题(连着的数字或者英文会保留),且不再取用后面出现的中文
self._last_token_type = "cnname"
if not self.cn_name:
self.cn_name = token
elif not self._stop_cnname_flag:
if self._name_movie_words_pattern.search(token) \
or (not self._name_no_chinese_pattern.search(token)
and not any(w in token for w in self._name_se_words)):
self.cn_name = "%s %s" % (self.cn_name, token)
self._stop_cnname_flag = True
else:
is_roman_digit = self._roman_numerals_pattern.search(token)
# 阿拉伯数字或者罗马数字
if token.isdigit() or is_roman_digit:
# 第季集后面的不要
if self._last_token_type == 'name_se_words':
return
if self.name:
# 名字后面以 0 开头的不要,极有可能是集
if token.startswith('0'):
return
# 检查是否真正的数字
if token.isdigit():
try:
int(token)
except ValueError:
return
# 中文名后面跟的数字不是年份的极有可能是集
if not is_roman_digit \
and self._last_token_type == "cnname" \
and int(token) < 1900:
return
if (token.isdigit() and len(token) < 4) or is_roman_digit:
# 4位以下的数字或者罗马数字,拼装到已有标题中
if self._last_token_type == "cnname":
self.cn_name = "%s %s" % (self.cn_name, token)
elif self._last_token_type == "enname":
self.en_name = "%s %s" % (self.en_name, token)
self._continue_flag = False
elif token.isdigit() and len(token) == 4:
# 4位数字,可能是年份,也可能真的是标题的一部分,也有可能是集
if not self._unknown_name_str:
self._unknown_name_str = token
else:
# 名字未出现前的第一个数字,记下来
if not self._unknown_name_str:
self._unknown_name_str = token
elif self._season_pattern.search(token):
# 季的处理
if self.en_name and SEASON_SUFFIX_RE.search(self.en_name):
# 如果匹配到季,英文名结尾为Season,说明Season属于标题,不应在后续作为干扰词去除
self.en_name += ' '
self._stop_name_flag = True
return
elif self._episode_pattern.search(token) \
or self._resources_type_pattern.search(token) \
or self._resources_pix_pattern.search(token):
# 集、来源、版本等不要
self._stop_name_flag = True
return
else:
# 后缀名不要
if ".%s".lower() % token in media_exts:
return
# 英文或者英文+数字,拼装起来
if self.en_name:
self.en_name = "%s %s" % (self.en_name, token)
else:
self.en_name = token
self._last_token_type = "enname"
def __init_part(self, token: str, tokens: Tokens):
"""
识别Part
"""
if not self.name:
return
if not self.year \
and self.begin_season is None \
and not self.begin_episode \
and not self.resource_pix \
and not self.resource_type:
return
re_res = self._part_pattern.search(token)
if re_res:
if not self.part:
self.part = re_res.group(1)
nextv = tokens.cur()
if nextv \
and ((nextv.isdigit() and (len(nextv) == 1 or len(nextv) == 2 and nextv.startswith('0')))
or nextv.upper() in ['A', 'B', 'C', 'I', 'II', 'III']):
self.part = "%s%s" % (self.part, nextv)
tokens.get_next()
self._last_token_type = "part"
self._continue_flag = False
# self._stop_name_flag = False
def __init_year(self, token: str):
"""
识别年份
"""
if not self.name:
return
if not token.isdigit():
return
if len(token) != 4:
return
if not 1900 < int(token) < 2050:
return
if self.year:
if self.en_name:
self.en_name = "%s %s" % (self.en_name.strip(), self.year)
elif self.cn_name:
self.cn_name = "%s %s" % (self.cn_name, self.year)
elif self.en_name and SEASON_SUFFIX_RE.search(self.en_name):
# 如果匹配到年,且英文名结尾为Season,说明Season属于标题,不应在后续作为干扰词去除
self.en_name += ' '
self.year = token
self._last_token_type = "year"
self._continue_flag = False
self._stop_name_flag = True
def __init_resource_pix(self, token: str):
"""
识别分辨率
"""
if not self.name:
return
re_res = self._resources_pix_pattern.findall(token)
if re_res:
self._last_token_type = "pix"
self._continue_flag = False
self._stop_name_flag = True
resource_pix = None
for pixs in re_res:
if isinstance(pixs, tuple):
pix_t = None
for pix_i in pixs:
if pix_i:
pix_t = pix_i
break
if pix_t:
resource_pix = pix_t
else:
resource_pix = pixs
if resource_pix and not self.resource_pix:
self.resource_pix = resource_pix.lower()
break
if self.resource_pix \
and self.resource_pix.isdigit() \
and self.resource_pix[-1] not in 'kpi':
self.resource_pix = "%sp" % self.resource_pix
else:
re_res = self._resources_pix_pattern2.search(token)
if re_res:
self._last_token_type = "pix"
self._continue_flag = False
self._stop_name_flag = True
if not self.resource_pix:
self.resource_pix = re_res.group(1).lower()
def __init_season(self, token: str):
"""
识别季
"""
re_res = self._season_pattern.findall(token)
if re_res:
self._last_token_type = "season"
self.type = MediaType.TV
self._stop_name_flag = True
self._continue_flag = True
for se in re_res:
if isinstance(se, tuple):
se_t = None
for se_i in se:
if se_i and str(se_i).isdigit():
se_t = se_i
break
if se_t:
se = int(se_t)
else:
break
else:
se = int(se)
if self.begin_season is None:
self.begin_season = se
self.total_season = 1
else:
if se > self.begin_season:
self.end_season = se
self.total_season = (self.end_season - self.begin_season) + 1
if self.isfile and self.total_season > 1:
self.end_season = None
self.total_season = 1
elif token.isdigit():
try:
int(token)
except ValueError:
return
if self._last_token_type == "SEASON" \
and self.begin_season is None \
and len(token) < 3:
self.begin_season = int(token)
self.total_season = 1
self._last_token_type = "season"
self._stop_name_flag = True
self._continue_flag = False
self.type = MediaType.TV
elif token.upper() == "SEASON" and self.begin_season is None:
self._last_token_type = "SEASON"
elif self.type == MediaType.TV and self.begin_season is None:
self.begin_season = 1
def __init_episode(self, token: str):
"""
识别集
"""
re_res = self._episode_pattern.findall(token)
if re_res:
self._last_token_type = "episode"
self._continue_flag = False
self._stop_name_flag = True
self.type = MediaType.TV
for se in re_res:
if isinstance(se, tuple):
se_t = None
for se_i in se:
if se_i and str(se_i).isdigit():
se_t = se_i
break
if se_t:
se = int(se_t)
else:
break
else:
se = int(se)
if self.begin_episode is None:
self.begin_episode = se
self.total_episode = 1
else:
if se > self.begin_episode:
self.end_episode = se
self.total_episode = (self.end_episode - self.begin_episode) + 1
if self.isfile and self.total_episode > 2:
self.end_episode = None
self.total_episode = 1
elif token.isdigit():
try:
int(token)
except ValueError:
return
if self.begin_episode is not None \
and self.end_episode is None \
and len(token) < 5 \
and int(token) > self.begin_episode \
and self._last_token_type == "episode":
self.end_episode = int(token)
self.total_episode = (self.end_episode - self.begin_episode) + 1
if self.isfile and self.total_episode > 2:
self.end_episode = None
self.total_episode = 1
self._continue_flag = False
self.type = MediaType.TV
elif self.begin_episode is None \
and 1 < len(token) < 4 \
and self._last_token_type != "year" \
and self._last_token_type != "videoencode" \
and token != self._unknown_name_str:
self.begin_episode = int(token)
self.total_episode = 1
self._last_token_type = "episode"
self._continue_flag = False
self._stop_name_flag = True
self.type = MediaType.TV
elif self._last_token_type == "EPISODE" \
and self.begin_episode is None \
and len(token) < 5:
self.begin_episode = int(token)
self.total_episode = 1
self._last_token_type = "episode"
self._continue_flag = False
self._stop_name_flag = True
self.type = MediaType.TV
elif token.upper() == "EPISODE":
self._last_token_type = "EPISODE"
def __append_resource_source(self, source: str) -> None:
"""
按出现顺序追加资源类型并忽略重复项。
:param source: 原始资源类型标记
"""
source_name = SOURCE_NAMES.get(source.upper(), source)
if source_name.casefold() not in {
item.casefold() for item in self._sources
}:
self._sources.append(source_name)
def __replace_last_resource_source(self, source: str, replacement: str) -> None:
"""
将拆分的资源类型前缀替换为完整规范名称。
:param source: 待替换的末尾资源类型
:param replacement: 完整资源类型
"""
if self._sources and self._sources[-1].casefold() == source.casefold():
self._sources.pop()
self.__append_resource_source(replacement)
def __init_resource_type(self, token):
"""
识别资源类型
"""
if not self.name:
return
if token.upper() == "DL" \
and self._last_token_type == "source" \
and self._last_token == "WEB":
self.__replace_last_resource_source("WEB", "WEB-DL")
self._continue_flag = False
return
elif token.upper() == "RAY" \
and self._last_token_type == "source" \
and self._last_token == "BLU":
self.__replace_last_resource_source("BLU", "BluRay")
self._continue_flag = False
return
elif token.upper() == "WEBDL":
self.__append_resource_source("WEB-DL")
self._continue_flag = False
return
source_res = self._source_pattern.search(token)
if source_res:
self._last_token_type = "source"
self._continue_flag = False
self._stop_name_flag = True
source = source_res.group(1)
self.__append_resource_source(source)
self._last_token = source.upper()
return
effect_res = self._effect_pattern.search(token)
if effect_res:
self._last_token_type = "effect"
self._continue_flag = False
self._stop_name_flag = True
effect = effect_res.group(1)
if effect not in self._effect:
self._effect.append(effect)
self._last_token = effect.upper()
def __init_web_source(self, token: str, tokens: Tokens, streaming_platforms: StreamingPlatforms):
"""
识别流媒体平台
"""
if not self.name:
return
platform_name = None
query_range = 1
prev_token = None
prev_idx = self._index - 2
if 0 <= prev_idx < len(tokens.tokens):
prev_token = tokens.tokens[prev_idx]
next_token = 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:
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(tokens.tokens), match_end_idx + 1 + query_range)
tokens_to_check = 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):
"""
识别视频编码
"""
if not self.name:
return
if not self.year \
and not self.resource_pix \
and not self.resource_type \
and self.begin_season is None \
and not self.begin_episode:
return
re_res = self._video_encode_pattern.search(token)
if re_res:
self._continue_flag = False
self._stop_name_flag = True
self._last_token_type = "videoencode"
if not self.video_encode:
if re_res.group(2):
self.video_encode = re_res.group(2).upper()
elif re_res.group(3):
self.video_encode = re_res.group(3).lower()
else:
self.video_encode = re_res.group(1).upper()
self._last_token = self.video_encode
elif self.video_encode == "10bit":
self.video_encode = f"{re_res.group(1).upper()} 10bit"
self._last_token = re_res.group(1).upper()
elif token.upper() in ['H', 'X']:
self._continue_flag = False
self._stop_name_flag = True
self._last_token_type = "videoencode"
self._last_token = token.upper() if token.upper() == "H" else token.lower()
elif token in ["264", "265"] \
and self._last_token_type == "videoencode" \
and self._last_token in ['H', 'X']:
self.video_encode = "%s%s" % (self._last_token, token)
elif token.isdigit() \
and self._last_token_type == "videoencode" \
and self._last_token in ['VC', 'MPEG']:
self.video_encode = "%s%s" % (self._last_token, token)
elif token.upper() == "10BIT":
self._last_token_type = "videoencode"
if not self.video_encode:
self.video_encode = "10bit"
else:
self.video_encode = f"{self.video_encode} 10bit"
def __init_video_bit(self, token: str):
"""
识别视频位深。
"""
if not self.name:
return
if not self.year \
and not self.resource_pix \
and not self.resource_type \
and self.begin_season is None \
and not self.begin_episode:
return
video_bit = self.extract_video_bit(token)
if not video_bit:
return
self._continue_flag = False
self._stop_name_flag = True
self._last_token_type = "videobit"
if not self.video_bit:
self.video_bit = video_bit
def __init_audio_encode(self, token: str):
"""
识别音频编码
"""
if not self.name:
return
if not self.year \
and not self.resource_pix \
and not self.resource_type \
and self.begin_season is None \
and not self.begin_episode:
return
re_res = self._audio_encode_pattern.search(token)
if re_res:
self._continue_flag = False
self._stop_name_flag = True
self._last_token_type = "audioencode"
self._last_token = re_res.group(1).upper()
if not self.audio_encode:
self.audio_encode = re_res.group(1)
else:
if self.audio_encode.upper() == "DTS":
self.audio_encode = "%s-%s" % (self.audio_encode, re_res.group(1))
else:
self.audio_encode = "%s %s" % (self.audio_encode, re_res.group(1))
elif token.isdigit() \
and self._last_token_type == "audioencode":
if self.audio_encode:
if self._last_token.isdigit():
self.audio_encode = "%s.%s" % (self.audio_encode, token)
elif self.audio_encode[-1].isdigit() and self.audio_encode.upper() not in {"AC3", "EAC3"}:
self.audio_encode = "%s %s.%s" % (self.audio_encode[:-1], self.audio_encode[-1], token)
else:
self.audio_encode = "%s %s" % (self.audio_encode, token)
self._last_token = token
def __init_fps(self, token: str):
"""
识别帧率
"""
if not self.name:
return
re_res = self._fps_pattern.search(token)
if re_res:
self._continue_flag = False
self._stop_name_flag = True
self._last_token_type = "fps"
# 提取帧率数值
fps_value = None
if re_res.group(1): # FPS格式
fps_value = re_res.group(1)
if fps_value and fps_value.isdigit():
# 只存储纯数值
self.fps = int(fps_value)
self._last_token = f"{self.fps}FPS"