MoviePilot元数据清洗工具:修复混乱的影视信息库

MoviePilot元数据清洗工具:修复混乱的影视信息库

【免费下载链接】MoviePilot NAS媒体库自动化管理工具 【免费下载链接】MoviePilot 项目地址: https://gitcode.com/gh_mirrors/mo/MoviePilot

引言:NAS媒体库的元数据痛点

你是否曾经遇到过这样的情况:辛苦下载的电影文件命名混乱,导致媒体服务器(如Plex、Emby、Jellyfin)无法正确识别?或者电视剧集数识别错误,导致播放顺序混乱?这些问题的根源在于元数据(Metadata) 的不规范。元数据是描述媒体文件的关键信息,包括标题、年份、季集、分辨率等。混乱的元数据不仅影响观看体验,还会导致媒体库管理困难。

MoviePilot作为一款NAS媒体库自动化管理工具,提供了强大的元数据清洗功能。本文将深入解析MoviePilot的元数据清洗工具,帮助你理解其工作原理、核心功能以及如何解决实际问题。

读完本文,你将能够:

  • 理解元数据清洗的重要性及常见问题
  • 掌握MoviePilot元数据清洗工具的核心功能
  • 学会使用正则表达式和自定义规则处理复杂命名
  • 解决电影、电视剧、动漫等不同类型媒体的元数据识别问题
  • 通过实际案例优化你的媒体库元数据质量

元数据清洗的核心挑战

在深入技术细节之前,我们首先需要了解元数据清洗面临的主要挑战:

1. 命名格式混乱

影视文件的命名格式千差万别,常见问题包括:

  • 多余信息:如广告、下载站点、字幕组等
  • 缩写不统一:如"Season"缩写为"S"或"季"
  • 年份表示混乱:如"2023-2024"或"23-24"
  • 分辨率和编码信息混杂:如"1080p.H265.DDP5.1"

2. 多语言和特殊字符

  • 中英文混合命名
  • 特殊符号(如[](){}+-)的滥用
  • 拼音和汉字混用

3. 媒体类型多样性

不同类型的媒体(电影、电视剧、动漫、纪录片)有不同的命名习惯和元数据需求。

4. 错误数据传播

一旦元数据识别错误,如果不及时纠正,可能会导致整个媒体库的分类和组织出现问题。

MoviePilot元数据清洗工具架构

MoviePilot的元数据清洗功能主要由app/core/meta/目录下的几个核心模块组成:

mermaid

核心类功能解析

  1. MetaBase: 媒体信息基类,定义了媒体文件的基本属性和通用方法。
  2. MetaVideo: 继承自MetaBase,专注于电影和电视剧的元数据解析。
  3. MetaAnime: 继承自MetaBase,针对动漫内容的特殊元数据解析。
  4. ReleaseGroupsMatcher: 识别发布组/字幕组信息。
  5. CustomizationMatcher: 处理自定义占位符。
  6. StreamingPlatforms: 识别流媒体平台信息。

元数据清洗流程详解

MoviePilot的元数据清洗流程可以分为以下几个关键步骤:

mermaid

1. 预处理阶段

在解析元数据之前,MoviePilot会对原始文件名进行一系列预处理:

# 去掉名称中第1个[]的内容
title = re.sub(r'%s' % self._name_no_begin_re, "", title, count=1)
# 把xxxx-xxxx年份换成前一个年份
title = re.sub(r'([\s.]+)(\d{4})-(\d{4})', r'\1\2', title)
# 把大小去掉
title = re.sub(r'[0-9.]+\s*[MGT]i?B(?![A-Z]+)', "", title, flags=re.IGNORECASE)
# 把年月日去掉
title = re.sub(r'\d{4}[\s._-]\d{1,2}[\s._-]\d{1,2}', "", title)

2. Token拆分

预处理后的标题会被拆分成多个token,便于后续解析:

tokens = Tokens(title)
token = tokens.get_next()
while token:
    # 处理每个token
    token = tokens.get_next()

3. 核心信息识别

名称识别

名称识别是元数据解析的核心,MoviePilot能智能区分中文名称和英文名称:

if StringUtils.is_chinese(token):
    # 含有中文,直接做为标题
    self._last_token_type = "cnname"
    if not self.cn_name:
        self.cn_name = token
    elif not self._stop_cnname_flag:
        if re.search("%s" % self._name_movie_words, token, flags=re.IGNORECASE) \
                or (not re.search("%s" % self._name_no_chinese_re, token, flags=re.IGNORECASE)
                    and not re.search("%s" % self._name_se_words, token, flags=re.IGNORECASE)):
            self.cn_name = "%s %s" % (self.cn_name, token)
        self._stop_cnname_flag = True
else:
    # 英文或者英文+数字,拼装起来
    if self.en_name:
        self.en_name = "%s %s" % (self.en_name, token)
    else:
        self.en_name = token
    self._last_token_type = "enname"
年份识别

年份识别主要通过匹配4位数字(1900-2050之间):

if token.isdigit() and len(token) == 4 and 1900 < int(token) < 2050:
    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)
    self.year = token
    self._last_token_type = "year"
    self._continue_flag = False
    self._stop_name_flag = True
分辨率识别

分辨率识别支持多种格式,如"1080p"、"2160p"、"4K"等:

re_res = re.findall(r"%s" % self._resources_pix_re, token, re.IGNORECASE)
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 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
季集识别

季集识别是电视剧元数据解析的关键,支持多种格式:

# 季识别示例
re_res = re.findall(r"%s" % self._season_re, token, re.IGNORECASE)
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 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

# 集识别示例
re_res = re.findall(r"%s" % self._episode_re, token, re.IGNORECASE)
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 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
资源类型和编码识别

资源类型识别包括媒体来源(如BluRay、WEB-DL)和特效(如HDR、3D):

source_res = re.search(r"(%s)" % self._source_re, token, re.IGNORECASE)
if source_res:
    self._last_token_type = "source"
    self._continue_flag = False
    self._stop_name_flag = True
    if not self._source:
        self._source = source_res.group(1)
        self._last_token = self._source.upper()
    return

effect_res = re.search(r"(%s)" % self._effect_re, token, re.IGNORECASE)
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()

视频和音频编码识别:

# 视频编码识别
re_res = re.search(r"(%s)" % self._video_encode_re, token, re.IGNORECASE)
if re_res:
    # 提取视频编码信息
    if not self.video_encode:
        self.video_encode = re_res.group(1).upper()

# 音频编码识别
re_res = re.search(r"(%s)" % self._audio_encode_re, token, re.IGNORECASE)
if re_res:
    # 提取音频编码信息
    if not self.audio_encode:
        self.audio_encode = re_res.group(1)

4. 元数据合并与输出

解析完成后,MoviePilot会合并所有识别到的信息,并提供标准化的元数据输出:

def merge(self, meta: Self):
    # 类型
    if self.type == MediaType.UNKNOWN and meta.type != MediaType.UNKNOWN:
        self.type = meta.type
    # 名称
    if not self.name:
        self.cn_name = meta.cn_name
        self.en_name = meta.en_name
    # 年份
    if not self.year:
        self.year = meta.year
    # 季
    if (self.type == MediaType.TV and self.begin_season is None):
        self.begin_season = meta.begin_season
        self.end_season = meta.end_season
        self.total_season = meta.total_season
    # 其他属性...

def to_dict(self):
    """转为字典"""
    dicts = vars(self).copy()
    dicts["type"] = self.type.value if self.type else None
    dicts["season_episode"] = self.season_episode
    dicts["edition"] = self.edition
    dicts["name"] = self.name
    dicts["episode_list"] = self.episode_list
    return dicts

实战案例:解决复杂命名问题

案例1:混乱的电影文件名

原始文件名

[华语]【2023】流浪地球2.HD1080P.国语中字.BD中英双字.mkv

清洗过程

  1. 预处理:去除[华语]【2023】.mkv后缀
  2. 名称识别:识别出中文名称"流浪地球2"
  3. 年份识别:从预处理后的字符串中提取"2023"
  4. 分辨率识别:识别出"1080P"
  5. 资源类型识别:识别出"BD"

清洗结果

{
  "cn_name": "流浪地球2",
  "year": "2023",
  "resource_pix": "1080p",
  "resource_type": "BD",
  "type": "MOVIE"
}

案例2:复杂的电视剧命名

原始文件名

The.Greatest.Showman.2017.1080p.BluRay.x264.DTS-HD.MA.5.1-FGT.mkv

清洗过程

  1. 预处理:去除文件后缀
  2. 名称识别:识别出英文名称"The Greatest Showman"
  3. 年份识别:提取"2017"
  4. 分辨率识别:识别出"1080p"
  5. 资源类型识别:识别出"BluRay"
  6. 视频编码识别:识别出"H264"
  7. 音频编码识别:识别出"DTS-HD MA 5.1"
  8. 发布组识别:识别出"FGT"

清洗结果

{
  "en_name": "The Greatest Showman",
  "year": "2017",
  "resource_pix": "1080p",
  "resource_type": "BluRay",
  "video_encode": "x264",
  "audio_encode": "DTS-HD MA 5.1",
  "resource_team": "FGT",
  "type": "MOVIE"
}

案例3:多集电视剧识别

原始文件名

权力的游戏.Game.of.Thrones.S01E01-E05.1080p.BluRay.x264.双语字幕.mkv

清洗过程

  1. 预处理:去除多余信息
  2. 名称识别:同时识别中文名称"权力的游戏"和英文名称"Game of Thrones"
  3. 季集识别:识别出第1季第1-5集
  4. 分辨率识别:识别出"1080p"
  5. 资源类型识别:识别出"BluRay"
  6. 视频编码识别:识别出"H264"

清洗结果

{
  "cn_name": "权力的游戏",
  "en_name": "Game of Thrones",
  "begin_season": 1,
  "end_season": 1,
  "begin_episode": 1,
  "end_episode": 5,
  "resource_pix": "1080p",
  "resource_type": "BluRay",
  "video_encode": "x264",
  "type": "TV"
}

案例4:动漫特殊处理

原始文件名

[KTXP][进击的巨人 最终季][Attack on Titan Final Season][S04][1080p][HEVC-10bit AAC][CHS].mp4

清洗过程

  1. 预处理:去除[KTXP][CHS]等标签
  2. 名称识别:识别中文名称"进击的巨人 最终季"和英文名称"Attack on Titan Final Season"
  3. 季识别:识别出第4季
  4. 分辨率识别:识别出"1080p"
  5. 视频编码识别:识别出"HEVC-10bit"
  6. 音频编码识别:识别出"AAC"
  7. 发布组识别:识别出"KTXP"

清洗结果

{
  "cn_name": "进击的巨人 最终季",
  "en_name": "Attack on Titan Final Season",
  "begin_season": 4,
  "end_season": 4,
  "resource_pix": "1080p",
  "video_encode": "HEVC-10bit",
  "audio_encode": "AAC",
  "resource_team": "KTXP",
  "type": "TV"
}

高级应用:自定义元数据规则

MoviePilot允许用户通过自定义规则来处理特殊的命名格式。这主要通过CustomizationMatcher类实现:

class CustomizationMatcher:
    def __init__(self):
        # 初始化自定义规则
        pass
        
    def match(self, title=None):
        # 应用自定义规则匹配元数据
        pass

用户可以通过配置文件定义自己的匹配规则,例如处理特定站点的特殊命名格式。

性能优化与批量处理

对于大型媒体库,元数据清洗的性能至关重要。MoviePilot通过以下方式优化性能:

  1. 缓存机制:对流媒体平台等信息进行缓存,避免重复处理
  2. 增量更新:只处理新增或修改的文件
  3. 多线程处理:批量处理时利用多线程提高效率

总结与展望

MoviePilot的元数据清洗工具通过强大的正则表达式匹配和智能识别算法,能够解决绝大多数媒体文件的元数据混乱问题。其核心优势在于:

  1. 多语言支持:同时识别中文和英文名称
  2. 灵活的识别规则:支持多种命名格式
  3. 丰富的元数据提取:不仅提取基本信息,还包括分辨率、编码等详细属性
  4. 可扩展性:通过自定义规则处理特殊情况

未来,MoviePilot的元数据清洗功能可以进一步增强:

  1. AI辅助识别:引入机器学习模型提高复杂命名的识别准确率
  2. 用户贡献规则:建立社区驱动的规则库,共同优化识别能力
  3. 跨平台同步:与各大媒体服务器深度集成,实现元数据的自动同步和更新

通过掌握MoviePilot的元数据清洗工具,你可以轻松解决NAS媒体库的元数据混乱问题,打造一个规范、整洁、易于管理的个人媒体中心。

附录:常见问题与解决方案

Q1: 某些特殊命名的文件无法正确识别怎么办?

A1: 可以通过自定义规则(CustomizationMatcher)来处理特殊情况,或提交issue反馈给开发团队。

Q2: 如何手动修改已识别的元数据?

A2: MoviePilot提供了Web界面,允许用户手动编辑和修正元数据信息。

Q3: 元数据清洗会修改原始文件吗?

A3: 不会,元数据清洗只处理元数据信息,不会修改原始文件。

Q4: 支持哪些媒体服务器的元数据同步?

A4: 目前支持Plex、Emby、Jellyfin等主流媒体服务器的元数据同步。

Q5: 如何批量处理已有的媒体库?

A5: MoviePilot提供了"媒体库扫描"功能,可以批量处理已有文件的元数据。

【免费下载链接】MoviePilot NAS媒体库自动化管理工具 【免费下载链接】MoviePilot 项目地址: https://gitcode.com/gh_mirrors/mo/MoviePilot

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值