Head Pic:さとり


描述

原脚本来自B站UP:陪葬品

  • 目前在macOS使用OK,还未在Windows测试。
  • 做了一些小修改,方便排错。

与原代码相比,多了以下功能:

  1. 配置日志记录器: 使用logging模块配置了一个日志记录器,可以将日志输出到名为season_import_log.log的文件中。这个记录器用于记录不同日志级别的信息,包括时间戳、日志名称、日志级别和消息。这使得在执行过程中能够更详细地跟踪日志信息,有助于排查问题和了解程序的执行情况。

  2. 错误处理: 在主程序中使用try-except-finally块捕获异常,并将异常信息记录到错误列表和日志中。这个错误处理功能有助于在处理电视节目信息时检测并记录错误,以便后续分析和处理。

  3. 错误列表记录: 在脚本结束后,将错误列表的内容追加到名为error_list.txt的文件中。这个功能用于将生成季信息失败的电视剧统计起来,方便后续检查及更新。

  4. 日志记录: 在各个关键步骤中使用logger对象记录日志信息,包括开始和结束标记。这有助于在日志中清晰地跟踪脚本的执行流程,以及每个步骤的执行情况。

脚本说明:

数据来源 TMDB : https://www.themoviedb.org/

基本逻辑:

扫描tvshow.nfo文件以及同目录下的季度文件,获取TMDB的ID,再调用TMDB的API获取季度信息,之后在当前季度文件夹内生成seasson.nfo文件

目录结构:

物语系列
├── ....
├── tvshow.nfo //由TMM刮削生成的
├── ....
├── S01 - 化物语
│   └──  season.nfo //由pythen脚本生成的
├── S02 - 伪物语
│   └──  season.nfo
.... 

思路:

通过TMM(TinyMediaManager)刮削后就基本上该有的信息都有了,同时在番剧目录下会有tvshow.nfo文件,但是始终TMM无法生成season.nfo文件。因此在EMBY里季度信息会是:季1,季2,Season 1, Season 2...

手动在emby里改这些信息默认是在emby里的自建数据库维护的,一旦搬迁目录再次扫描就会丢失。

最好的办法就是让emby读取外置文件(season.nfo)。

也可以修改emby媒体库设置,保存元数据到文件夹,但手动修改少数几部影片还行,一旦媒体库电视剧数量上百上千就不现实了

season.nfo 文件内容很简单:

<?xml version="1.0" encoding="utf-8"?>
<season>
    <!-- 季编号 -->
    <seasonnumber>1</seasonnumber>
    <!-- 是否锁住禁止修改 -->
    <lockdata>false</lockdata>
    <!-- 季名称 -->
    <title>化物语</title>
    <!-- 季说明 -->
    <plot>三年同班同学却没有说过一句话,这指得就是阿良良木历(神谷浩史 配音)和战场原黑仪(斋藤千和 配音)。二人虽是同班同学,但可以说没有任何交集。本以为就会这样一直下去的阿良良木历因为一次偶然接住了意外从楼梯上跌下来的黑仪。而这次意外的从天而降,让历知道了黑仪的秘密。而这个秘密牵扯到众多事情,虽然历发誓自己绝不会把这件事告诉任何人,但黑仪还是表示怀疑。而历也向黑仪透露出自己的小秘密。但这个城市并不是只有二人拥有神奇的力量。许多奇怪的生物和事情正在向二人靠拢。</plot>
</season>

安装依赖

pip install beautifulsoup4
pip install lxml
pip install requests

使用方法

创建文件season_import.py,内容如下:

import sys

print("Number of arguments:", len(sys.argv))
print("Argument List:", str(sys.argv))

import logging

# 配置日志记录器
logging.basicConfig(filename='season_import_log.log', level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', encoding='utf-8')

# 创建日志记录器
logger = logging.getLogger('season_import')

import os
from bs4 import BeautifulSoup
import requests
import re
import time
import sys
import xml.etree.ElementTree as ET

proxy = None
api_key = ''

def prepare_season_content(season, title, plot=''):
    e_season = ET.Element('season')
    ET.SubElement(e_season, 'seasonnumber').text = str(season)
    ET.SubElement(e_season, 'lockdata').text = 'false'
    ET.SubElement(e_season, 'title').text = title
    ET.SubElement(e_season, 'plot').text = plot
    return BeautifulSoup(ET.tostring(e_season), 'xml').prettify()

def check_overwrite_file(file):
    data = read_file(file)
    if data is None:
        return True
    data = BeautifulSoup(data, 'xml')
    tag_season = data.find('season')
    if tag_season is None:
        return True
    lock = tag_season.find('lockdata')
    return False if lock is not None and lock.text.strip().upper() == 'TRUE' else True

def read_file(file):
    if not os.path.exists(file):
        return None
    with open(file, 'r') as f:
        return f.read()

def get_tmddb_id(tv_nfo):
    print(f'reading file: {os.path.abspath(tv_nfo)}')
    tv_nfo = read_file(tv_nfo)
    if tv_nfo is None:
        return None
    tv_nfo = BeautifulSoup(tv_nfo, 'xml')
    tv_nfo = tv_nfo.find('tvshow')
    if tv_nfo is None:
        return None
    tmdb_id = tv_nfo.find('tmdbid')
    return None if tmdb_id is None else tmdb_id.text

def request_tv_info(tv_id, key):
    print(f'Accessing tmdb to get tv info of {tv_id}')
    response = requests.get(f'https://api.themoviedb.org/3/tv/{tv_id}',
                            timeout=10, params={'api_key': key, 'language': 'zh-CN'},
                            proxies=None if proxy is None else {'https': proxy})
    if response.status_code != 200:
        raise f'''Failed to access tmdb api for get tv {tv_id}.
        status: {response.status_code}
        message: {response.content}'''
    return response.json()

def load_tv_info(file):
    tv_id = get_tmddb_id(file)
    if tv_id is None:
        raise f'Failed to get tmdb id from ${file}'
    print(f'Got tmdb id {tv_id}')
    retry = 5
    tv_info = None
    while retry > 0:
        try:
            retry = retry - 1
            tv_info = request_tv_info(tv_id, api_key)
            retry = 0
        except:
            time.sleep(1)
            print(f'Failed to request tmdb api. rest attempts {retry}')
    if tv_info is None:
        raise 'Failed to get tmdb info'
    new_line="\n\t\t\t"
    print(f'''Got tv info from tmdb
========> tmdb id: {tv_info['id']}
          title: {tv_info['name']}({tv_info['original_name']})
          seasons: {len(tv_info['seasons'])}
{new_line}{new_line.join(list(map( lambda x: str(x['season_number']) + '. '+ x['name'] , tv_info['seasons'] )))}
    ''')
    return tv_info

def get_season_number(folder_name):
    m = re.search('([Ss]eason\\s*[0-9]+)|([第季]+\\s*[0-9]+\\s*季*)|([Ss][0-9]+)|([0-9]+)', folder_name)
    if m is None:
        return None
    s = m.group(0)
    print(f'season: {s}')
    m = re.search('[0-9]+', s)
    return int(m.group(0))

def scan_seasons(directory):
    season_map = None
    for sud_dir in os.listdir(directory):
        if not os.path.isdir(os.path.join(directory, sud_dir)):
            continue
        n = get_season_number(sud_dir)
        if n is not None:
            if season_map is None:
                season_map = {}
            season_map[n] = sud_dir
    return season_map

def write_season_info(file):
    folder = os.path.dirname(os.path.abspath(file))
    tvshow_nfo = os.path.join(folder, 'tvshow.nfo')
    if not os.path.exists(tvshow_nfo):
        print(f'TV show info file not found: {tvshow_nfo}')
        exit(1)
    print(f'Identified the tv folder: {folder}')
    season_folders = scan_seasons(folder)
    print(f'Scanned out the season folders: {season_folders}')
    if season_folders is None:
        print('Not found any season folder')
        exit(0)
    tv_info = load_tv_info(file)
    for season in tv_info['seasons']:
        season_number = int(season['season_number'])
        season_folder = season_folders.get(season_number)
        if season_folder is None:
            continue
        season_folder = os.path.join(folder, season_folder)
        season_file = os.path.join(season_folder, 'season.nfo')
        if not check_overwrite_file(season_file):
            print(f'the file {season_file} has been locked.')
            continue
        if season['name'] is None:
            season['name'] = '第 {: 2d} 季'.format(season_number)

        season_data = prepare_season_content(season_number, season['name'], season['overview'])
        with open(season_file, 'wb') as f:
            print(f"========> writing file '{season_file}'...")
            f.write(season_data.encode('utf-8'))

error_list = []  # 创建一个空的错误列表

if __name__ == '__main__':
    file = str(sys.argv[1])
    api_key = str(sys.argv[2])
    if len(sys.argv) > 3:
        proxy = str(sys.argv[3])
    # 将文件路径转换为UTF-8编码的字符串
    file = os.path.abspath(file).encode('utf-8').decode('utf-8')
    try:
        # 在这里调用 write_season_info(file) 并放在 try 块内
        write_season_info(file)
    except Exception as e:
        # 如果 write_season_info(file) 引发异常,将错误信息添加到错误列表中
        error_list.append({'file': file, 'tmdb_id': get_tmddb_id(file), 'error': str(e)})
        # 同时记录错误信息到日志
        logger.error('Failed to write season for file: %s (tmdb id: %s). Error: %s', file, get_tmddb_id(file), str(e))
    finally:
        print('============== END ==============\n')

# 在脚本结束后,将错误列表累积到文件 error_list.txt 中
with open('error_list.txt', 'a', encoding='utf-8') as error_file:  # 注意这里使用 'a' 模式以追加的方式写入文件
    for error_item in error_list:
        error_file.write(f"Error for file: {error_item['file']} (tmdb id: {error_item['tmdb_id']}), Error Message: {error_item['error']}\n")

导入单部电视剧季信息

python3 season_import.py tvshow.nfo [API_KEY] [PROXY]

导入整个TV媒体库季信息

find ./tv -name "tvshow.nfo" -type f -exec python3 season_import.py "{}" \;

版权属于:本文为原创文章,版权归 AUK CL 所有。
文章地址: https://aukcl.win/archives/586/
所有原创文章由知识共享署名-非商业性使用 4.0 国际许可协议进行许可。
您可以自由转载或修改,但禁止一切形式的商业使用,同时,务必请注明原文地址及作者信息。

Last modification:October 10, 2023
如果觉得我的文章对你有用,请随意赞赏