Python处理.nfo文件格式的完整教程

Written by

in

文章目录
  • .nfo 文件是媒体文件的元数据容器,通常用于存储电影、电视剧、音乐等多媒体信息的结构化数据。它们本质上是 XML 格式的文本文件,包含如标题、演员、剧情简介等关键信息。
  • class NfoProcessor: “””处理 .nfo 文件的工具类””” def __init__(self, file_path=None): self.file_path = file_path self.tree = None self.root = None if file_path: self.load(file_path) def load(self, file_path): “””加载 .nfo 文件””” self.file_path = file_path try: self.tree = ET.parse(file_path) self.root = self.tree.getroot() except ET.ParseError: # 尝试使用 lxml 解析 try: from lxml import etree parser = etree.XMLParser(recover=True) self.tree = etree.parse(file_path, parser) self.root = self.tree.getroot() except Exception as e: raise ValueError(f”无法解析文件: {e}”) def save(self, file_path=None): “””保存 .nfo 文件””” save_path = file_path or self.file_path if not save_path: raise ValueError(“未指定保存路径”) if self.tree is not None: self.tree.write(save_path, encoding=’utf-8′, xml_declaration=True) else: raise ValueError(“没有可保存的数据”) def get_value(self, path): “””获取指定路径的值””” elem = self.root.find(path) return elem.text if elem is not None else None def set_value(self, path, value): “””设置指定路径的值””” parts = path.split(‘/’) current = self.root # 创建或获取元素 for part in parts: elem = current.find(part) if elem is None: elem = ET.SubElement(current, part) current = elem # 设置值 current.text = str(value) def get_actors(self): “””获取演员列表””” return [ { ‘name’: actor.findtext(‘name’), ‘role’: actor.findtext(‘role’), ‘thumb’: actor.findtext(‘thumb’) } for actor in self.root.findall(‘actor’) ] def add_actor(self, name, role, thumb=None): “””添加演员””” actor = ET.SubElement(self.root, ‘actor’) ET.SubElement(actor, ‘name’).text = name ET.SubElement(actor, ‘role’).text = role if thumb: ET.SubElement(actor, ‘thumb’).text = thumb def to_dict(self): “””转换为字典””” return { ‘title’: self.get_value(‘title’), ‘year’: self.get_value(‘year’), ‘plot’: self.get_value(‘plot’), ‘director’: self.get_value(‘director’), ‘rating’: self.get_value(‘rating’), ‘genres’: [g.text for g in self.root.findall(‘genre’)], ‘actors’: self.get_actors() } # 使用示例 processor = NfoProcessor(“The.Matrix.nfo”) print(processor.get_value(‘title’)) # 输出: 黑客帝国 processor.set_value(‘rating’, 9.0) processor.add_actor(‘凯瑞-安·莫斯’, ‘崔妮蒂’) processor.save()
  • 目录
    • 一、什么是 .nfo 文件?
      • 典型 .nfo 文件结构
    • 二、核心库介绍
      • 1. 标准库解决方案
      • 2. 第三方库推荐
    • 三、完整处理流程
      • 1. 读取 .nfo 文件
      • 2. 提取基本信息
      • 3. 修改 .nfo 文件
      • 4. 创建新的 .nfo 文件
    • 四、高级处理技巧
      • 1. 使用 lxml 处理复杂文件
      • 2. 处理非标准 .nfo 文件
      • 3. 批量处理 .nfo 文件
    • 五、实际应用案例
      • 1. 媒体库元数据同步
      • 2. 生成 HTML 报告
      • 3. 自动下载缺失信息
    • 六、常见问题解决方案
      • 1. 编码问题处理
      • 2. 处理特殊字符
      • 3. 验证 .nfo 文件结构
    • 七、完整工具类实现
      • 八、总结与最佳实践
        • 核心处理流程
        • 最佳实践建议
        • 推荐工具

      掌握媒体元数据文件的操作技巧

      .nfo 文件是媒体文件的元数据容器,通常用于存储电影、电视剧、音乐等多媒体信息的结构化数据。它们本质上是 XML 格式的文本文件,包含如标题、演员、剧情简介等关键信息。

      <?xml version="1.0" encoding="UTF-8"?>
      <movie>
        <title>黑客帝国</title>
        <originaltitle>The Matrix</originaltitle>
        <year>1999</year>
        <plot>一名年轻的网络黑客发现看似正常的现实世界实际上是由名为"矩阵"的计算机人工智能系统控制的...</plot>
        <director>莉莉·沃卓斯基</director>
        <rating>8.7</rating>
        <genre>科幻</genre>
        <genre>动作</genre>
        <actor>
          <name>基努·里维斯</name>
          <role>尼奥</role>
          <thumb>https://example.com/keanu.jpg</thumb>
        </actor>
      </movie>
      

      import xml.etree.ElementTree as ET
      

      pip install lxml beautifulsoup4 pynfo
      

      def read_nfo(file_path):
          """读取并解析 .nfo 文件"""
          try:
              tree = ET.parse(file_path)
              root = tree.getroot()
              return root
          except ET.ParseError as e:
              print(f"解析错误: {e}")
              return None
          except FileNotFoundError:
              print(f"文件不存在: {file_path}")
              return None
      
      # 使用示例
      movie_nfo = read_nfo("The.Matrix.nfo")
      

      def extract_movie_info(root):
          """提取电影基本信息"""
          if root.tag != 'movie':
              return None
          
          info = {
              'title': root.findtext('title'),
              'year': root.findtext('year'),
              'plot': root.findtext('plot'),
              'director': root.findtext('director'),
              'rating': root.findtext('rating'),
              'genres': [genre.text for genre in root.findall('genre')],
              'actors': []
          }
          
          # 提取演员信息
          for actor in root.findall('actor'):
              info['actors'].append({
                  'name': actor.findtext('name'),
                  'role': actor.findtext('role'),
                  'thumb': actor.findtext('thumb')
              })
          
          return info
      
      # 使用示例
      movie_info = extract_movie_info(movie_nfo)
      print(f"电影标题: {movie_info['title']}")
      
      

      def update_nfo_rating(file_path, new_rating):
          """更新电影评分"""
          tree = ET.parse(file_path)
          root = tree.getroot()
          
          # 查找或创建 rating 元素
          rating_elem = root.find('rating')
          if rating_elem is None:
              rating_elem = ET.SubElement(root, 'rating')
          
          rating_elem.text = str(new_rating)
          
          # 保存修改
          tree.write(file_path, encoding='utf-8', xml_declaration=True)
      
      # 使用示例
      update_nfo_rating("The.Matrix.nfo", 9.2)
      

      def create_nfo_file(file_path, movie_data):
          """创建新的 .nfo 文件"""
          # 创建根元素
          movie = ET.Element('movie')
          
          # 添加子元素
          ET.SubElement(movie, 'title').text = movie_data['title']
          ET.SubElement(movie, 'year').text = str(movie_data['year'])
          ET.SubElement(movie, 'plot').text = movie_data['plot']
          
          # 添加类型
          for genre in movie_data['genres']:
              ET.SubElement(movie, 'genre').text = genre
          
          # 添加演员
          for actor in movie_data['actors']:
              actor_elem = ET.SubElement(movie, 'actor')
              ET.SubElement(actor_elem, 'name').text = actor['name']
              ET.SubElement(actor_elem, 'role').text = actor['role']
          
          # 创建 XML 树
          tree = ET.ElementTree(movie)
          
          # 写入文件
          tree.write(file_path, encoding='utf-8', xml_declaration=True)
          print(f"已创建 .nfo 文件: {file_path}")
      
      # 使用示例
      new_movie = {
          'title': '盗梦空间',
          'year': 2010,
          'plot': '一群能够潜入他人梦境窃取思想的盗贼...',
          'genres': ['科幻', '惊悚'],
          'actors': [
              {'name': '莱昂纳多·迪卡普里奥', 'role': '科布'},
              {'name': '约瑟夫·高登-莱维特', 'role': '亚瑟'}
          ]
      }
      create_nfo_file("Inception.nfo", new_movie)
      

      from lxml import etree
      
      def parse_with_lxml(file_path):
          """使用 lxml 解析 .nfo 文件"""
          parser = etree.XMLParser(remove_blank_text=True)
          tree = etree.parse(file_path, parser)
          root = tree.getroot()
          
          # 使用 XPath 查询
          actors = root.xpath('//actor[name="莱昂纳多·迪卡普里奥"]')
          for actor in actors:
              print(f"角色: {actor.xpath('role/text()')[0]}")
          
          return tree
      
      # 添加命名空间支持
      def parse_with_namespace(file_path):
          ns = {'ns': 'http://www.example.com/nfo'}
          tree = etree.parse(file_path)
          title = tree.xpath('//ns:title', namespaces=ns)[0].text
          print(f"带命名空间的标题: {title}")
      

      def handle_non_standard_nfo(file_path):
          """处理非标准格式的 .nfo 文件"""
          from bs4 import BeautifulSoup
          
          with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
              content = f.read()
          
          # 修复常见格式问题
          content = content.replace('&', '&amp;')  # 修复未转义的 & 符号
          
          # 使用 BeautifulSoup 解析
          soup = BeautifulSoup(content, 'lxml-xml')
          
          # 提取信息
          title = soup.find('title').text if soup.find('title') else None
          
          return {
              'title': title,
              'soup': soup  # 返回 BeautifulSoup 对象供进一步处理
          }
      

      import os
      from pathlib import Path
      
      def batch_process_nfo(directory):
          """批量处理目录中的 .nfo 文件"""
          nfo_files = Path(directory).glob('*.nfo')
          results = []
          
          for nfo_file in nfo_files:
              try:
                  tree = ET.parse(nfo_file)
                  root = tree.getroot()
                  info = extract_movie_info(root)
                  results.append((nfo_file.name, info))
              except Exception as e:
                  print(f"处理 {nfo_file} 失败: {e}")
          
          return results
      
      # 使用 pandas 导出结果
      import pandas as pd
      
      def export_to_csv(nfo_dir, output_file):
          """导出 .nfo 信息到 CSV"""
          data = batch_process_nfo(nfo_dir)
          df = pd.DataFrame({
              'file': [item[0] for item in data],
              'title': [item[1]['title'] for item in data],
              'year': [item[1]['year'] for item in data],
              'rating': [item[1]['rating'] for item in data]
          })
          df.to_csv(output_file, index=False)
      

      def sync_with_media_library(nfo_dir, media_dir):
          """将 .nfo 信息同步到媒体文件"""
          for nfo_file in Path(nfo_dir).glob('*.nfo'):
              # 解析 .nfo
              tree = ET.parse(nfo_file)
              root = tree.getroot()
              title = root.findtext('title')
              
              # 查找对应的媒体文件
              media_file = find_media_file(media_dir, title)
              
              if media_file:
                  # 使用 mutagen 更新媒体文件元数据
                  from mutagen import File
                  audio = File(media_file)
                  audio['title'] = title
                  audio['artist'] = root.findtext('director')
                  audio.save()
      

      def generate_html_report(nfo_files, output_file):
          """从 .nfo 文件生成 HTML 报告"""
          html = """
          <html>
          <head>
              <title>媒体库报告</title>
              <style>
                  table { border-collapse: collapse; width: 100%; }
                  th, td { border: 1px solid #ddd; padding: 8px; }
                  tr:nth-child(even) { background-color: #f2f2f2; }
              </style>
          </head>
          <body>
              <h1>媒体库报告</h1>
              <table>
                  <tr>
                      <th>标题</th>
                      <th>年份</th>
                      <th>导演</th>
                      <th>评分</th>
                  </tr>
          """
          
          for nfo_file in nfo_files:
              root = read_nfo(nfo_file)
              if root:
                  html += f"""
                  <tr>
                      <td>{root.findtext('title')}</td>
                      <td>{root.findtext('year')}</td>
                      <td>{root.findtext('director')}</td>
                      <td>{root.findtext('rating')}</td>
                  </tr>
                  """
          
          html += """
              </table>
          </body>
          </html>
          """
          
          with open(output_file, 'w', encoding='utf-8') as f:
              f.write(html)
      

      import requests
      from bs4 import BeautifulSoup
      
      def enrich_nfo_info(file_path):
          """补充缺失的电影信息"""
          tree = ET.parse(file_path)
          root = tree.getroot()
          
          title = root.findtext('title')
          if not title:
              return
          
          # 从豆瓣API获取信息
          url = f"https://api.douban.com/v2/movie/search?q={title}"
          response = requests.get(url)
          data = response.json()
          
          if data['movies']:
              movie_data = data['movies'][0]
              
              # 更新缺失字段
              if not root.findtext('plot'):
                  ET.SubElement(root, 'plot').text = movie_data['summary']
              
              if not root.findtext('rating'):
                  ET.SubElement(root, 'rating').text = str(movie_data['rating']['average'])
              
              # 保存更新
              tree.write(file_path, encoding='utf-8', xml_declaration=True)
      

      def read_nfo_with_encoding(file_path):
          """自动检测编码读取 .nfo 文件"""
          encodings = ['utf-8', 'gbk', 'iso-8859-1']
          
          for enc in encodings:
              try:
                  with open(file_path, 'r', encoding=enc) as f:
                      content = f.read()
                  return ET.fromstring(content)
              except UnicodeDecodeError:
                  continue
          
          # 尝试二进制解析
          with open(file_path, 'rb') as f:
              return ET.fromstring(f.read())
      

      def sanitize_nfo_content(content):
          """清理 .nfo 内容中的特殊字符"""
          replacements = {
              '&': '&amp;',
              '<': '&lt;',
              '>': '&gt;',
              '"': '&quot;',
              "'": '&apos;'
          }
          
          for char, entity in replacements.items():
              content = content.replace(char, entity)
          
          return content
      

      def validate_nfo(file_path, schema_path='nfo_schema.xsd'):
          """使用 XML Schema 验证 .nfo 文件"""
          from lxml import etree
          
          # 解析 XML
          xml_doc = etree.parse(file_path)
          
          # 加载 Schema
          schema_doc = etree.parse(schema_path)
          schema = etree.XMLSchema(schema_doc)
          
          # 验证
          if schema.validate(xml_doc):
              print("文件有效")
              return True
          else:
              print("文件无效:")
              for error in schema.error_log:
                  print(f"行 {error.line}: {error.message}")
              return False
      

      class NfoProcessor:
          """处理 .nfo 文件的工具类"""
          
          def __init__(self, file_path=None):
              self.file_path = file_path
              self.tree = None
              self.root = None
              
              if file_path:
                  self.load(file_path)
          
          def load(self, file_path):
              """加载 .nfo 文件"""
              self.file_path = file_path
              try:
                  self.tree = ET.parse(file_path)
                  self.root = self.tree.getroot()
              except ET.ParseError:
                  # 尝试使用 lxml 解析
                  try:
                      from lxml import etree
                      parser = etree.XMLParser(recover=True)
                      self.tree = etree.parse(file_path, parser)
                      self.root = self.tree.getroot()
                  except Exception as e:
                      raise ValueError(f"无法解析文件: {e}")
          
          def save(self, file_path=None):
              """保存 .nfo 文件"""
              save_path = file_path or self.file_path
              if not save_path:
                  raise ValueError("未指定保存路径")
              
              if self.tree is not None:
                  self.tree.write(save_path, encoding='utf-8', xml_declaration=True)
              else:
                  raise ValueError("没有可保存的数据")
          
          def get_value(self, path):
              """获取指定路径的值"""
              elem = self.root.find(path)
              return elem.text if elem is not None else None
          
          def set_value(self, path, value):
              """设置指定路径的值"""
              parts = path.split('/')
              current = self.root
              
              # 创建或获取元素
              for part in parts:
                  elem = current.find(part)
                  if elem is None:
                      elem = ET.SubElement(current, part)
                  current = elem
              
              # 设置值
              current.text = str(value)
          
          def get_actors(self):
              """获取演员列表"""
              return [
                  {
                      'name': actor.findtext('name'),
                      'role': actor.findtext('role'),
                      'thumb': actor.findtext('thumb')
                  }
                  for actor in self.root.findall('actor')
              ]
          
          def add_actor(self, name, role, thumb=None):
              """添加演员"""
              actor = ET.SubElement(self.root, 'actor')
              ET.SubElement(actor, 'name').text = name
              ET.SubElement(actor, 'role').text = role
              if thumb:
                  ET.SubElement(actor, 'thumb').text = thumb
          
          def to_dict(self):
              """转换为字典"""
              return {
                  'title': self.get_value('title'),
                  'year': self.get_value('year'),
                  'plot': self.get_value('plot'),
                  'director': self.get_value('director'),
                  'rating': self.get_value('rating'),
                  'genres': [g.text for g in self.root.findall('genre')],
                  'actors': self.get_actors()
              }
      
      # 使用示例
      processor = NfoProcessor("The.Matrix.nfo")
      print(processor.get_value('title'))  # 输出: 黑客帝国
      processor.set_value('rating', 9.0)
      processor.add_actor('凯瑞-安·莫斯', '崔妮蒂')
      processor.save()
      

      1. 读取:使用 xml.etree.ElementTreelxml 解析文件
      2. 提取:使用 find()findall() 获取数据
      3. 修改:直接操作 XML 元素树
      4. 创建:使用 ElementSubElement 构建结构
      5. 保存:使用 write() 方法写入文件

      1. 编码处理:始终指定 UTF-8 编码
      2. 错误处理:添加异常捕获处理格式错误
      3. 备份文件:修改前创建备份
      4. 使用 lxml:处理复杂文件时选择 lxml
      5. 验证结构:使用 XML Schema 验证文件

      • 基础处理:Python 标准库 xml.etree.ElementTree
      • 高级需求lxml 库(支持 XPath、Schema 验证)
      • 非标准文件BeautifulSoup 处理格式错误文件
      • 批量操作:结合 ospathlib 遍历目录

      通过本教程,您已掌握使用 Python 处理 .nfo 文件的核心技能,能够高效地读取、修改和创建媒体元数据文件。

      以上就是Python处理.nfo文件格式的完整教程的详细内容,更多关于Python处理.nfo文件格式的资料请关注风君子博客其它相关文章!

      您可能感兴趣的文章:

      • 探索Python中zoneinfo模块处理时区操作实例
      • python获取异常信息exc_info和print_exc的使用
      • python使用DebugInfo模块打印一个条形堆积图
      • pythonfor循环中range与len区别
      • python logging.info在终端没输出的解决

      站内搜索