diff --git a/app/run.py b/app/run.py index 0583be1..5a737f6 100644 --- a/app/run.py +++ b/app/run.py @@ -196,7 +196,7 @@ def enrich_tasks_with_calendar_meta(tasks_info: list) -> list: else: try: # 仅用到静态映射,不触发网络请求 - _svc = TMDBService(config_data.get('tmdb_api_key', '')) + _svc = TMDBService(config_data.get('tmdb_api_key', ''), get_poster_language_setting()) status = _svc.map_show_status_cn(raw) except Exception: status = raw @@ -732,6 +732,10 @@ def get_data(): else: if 'calendar_refresh_interval_seconds' not in perf: data['performance']['calendar_refresh_interval_seconds'] = DEFAULT_REFRESH_SECONDS + + # 确保海报语言有默认值 + if 'poster_language' not in data: + data['poster_language'] = 'zh-CN' # 处理插件配置中的多账号支持字段,将数组格式转换为逗号分隔的字符串用于显示 if "plugins" in data: @@ -899,6 +903,9 @@ def update(): old_task_map[name] = { "tmdb_id": match.get("tmdb_id") or cal.get("tmdb_id") } + + # 记录旧的海报语言设置 + old_poster_language = config_data.get('poster_language', 'zh-CN') dont_save_keys = ["task_plugins_config_default", "api_token"] for key, value in request.json.items(): if key not in dont_save_keys: @@ -940,7 +947,18 @@ def update(): prev_tmdb, new_tmdb = '', None import threading - def _post_update_tasks(old_task_map_snapshot, prev_tmdb_value, new_tmdb_value): + def _post_update_tasks(old_task_map_snapshot, prev_tmdb_value, new_tmdb_value, old_poster_lang): + # 检查海报语言设置是否改变 + new_poster_language = config_data.get('poster_language', 'zh-CN') + if old_poster_lang != new_poster_language: + try: + # 清空现有海报 + clear_all_posters() + # 重新下载所有海报 + redownload_all_posters() + except Exception as e: + logging.error(f"重新下载海报失败: {e}") + # 在保存配置时自动进行任务信息提取与TMDB匹配(若缺失则补齐) try: changed = ensure_calendar_info_for_tasks() @@ -1010,7 +1028,7 @@ def update(): except Exception as e: logging.warning(f"自动初始化 bootstrap 失败: {e}") - threading.Thread(target=_post_update_tasks, args=(old_task_map, prev_tmdb, new_tmdb), daemon=True).start() + threading.Thread(target=_post_update_tasks, args=(old_task_map, prev_tmdb, new_tmdb, old_poster_language), daemon=True).start() # 确保性能配置包含秒级字段 if not isinstance(config_data.get('performance'), dict): @@ -1343,7 +1361,8 @@ def ensure_calendar_info_for_tasks() -> bool: extractor = TaskExtractor() tmdb_api_key = config_data.get('tmdb_api_key', '') - tmdb_service = TMDBService(tmdb_api_key) if tmdb_api_key else None + poster_language = get_poster_language_setting() + tmdb_service = TMDBService(tmdb_api_key, poster_language) if tmdb_api_key else None changed = False # 简单去重缓存,避免同一名称/年份重复请求 TMDB @@ -3851,7 +3870,8 @@ def get_calendar_episodes(): tasks_info = [task for task in tasks_info if show_name.lower() in task['show_name'].lower()] # 获取剧集信息 - tmdb_service = TMDBService(tmdb_api_key) + poster_language = get_poster_language_setting() + tmdb_service = TMDBService(tmdb_api_key, poster_language) all_episodes = [] for task_info in tasks_info: @@ -3903,7 +3923,8 @@ def process_new_tasks_async(): if not tmdb_api_key: return - tmdb_service = TMDBService(tmdb_api_key) + poster_language = get_poster_language_setting() + tmdb_service = TMDBService(tmdb_api_key, poster_language) cal_db = CalendarDB() tasks = config_data.get('tasklist', []) @@ -4042,7 +4063,8 @@ def process_single_task_async(task, tmdb_service, cal_db): status = localized_status except Exception: status = raw_status - poster_path = details.get('poster_path') or '' + # 使用海报语言设置获取海报路径 + poster_path = tmdb_service.get_poster_path_with_language(int(tmdb_id)) if tmdb_service else (details.get('poster_path') or '') latest_season_number = updated_match.get('latest_season_number') or 1 logging.debug(f"process_single_task_async - 最终使用的季数: {latest_season_number}") @@ -4131,7 +4153,8 @@ def do_calendar_bootstrap() -> tuple: if not tmdb_api_key: return False, 'TMDB API未配置' - tmdb_service = TMDBService(tmdb_api_key) + poster_language = get_poster_language_setting() + tmdb_service = TMDBService(tmdb_api_key, poster_language) cal_db = CalendarDB() tasks = config_data.get('tasklist', []) @@ -4155,7 +4178,8 @@ def do_calendar_bootstrap() -> tuple: status = localized_status except Exception: status = raw_status - poster_path = details.get('poster_path') or '' + # 使用海报语言设置获取海报路径 + poster_path = tmdb_service.get_poster_path_with_language(int(tmdb_id)) if tmdb_service else (details.get('poster_path') or '') latest_season_number = match.get('latest_season_number') or 1 poster_local_path = '' @@ -4237,6 +4261,70 @@ def details_of_season_episode_count(tmdb_service: TMDBService, tmdb_id: int, sea return 0 +def get_poster_language_setting(): + """获取海报语言设置""" + return config_data.get('poster_language', 'zh-CN') + +def clear_all_posters(): + """清空所有海报文件""" + try: + import shutil + if os.path.exists(CACHE_IMAGES_DIR): + shutil.rmtree(CACHE_IMAGES_DIR) + os.makedirs(CACHE_IMAGES_DIR, exist_ok=True) + return True + except Exception as e: + logging.error(f"清空海报文件失败: {e}") + return False + +def redownload_all_posters(): + """重新下载所有海报""" + try: + from app.sdk.db import CalendarDB + cal_db = CalendarDB() + + # 获取所有节目 + shows = cal_db.get_all_shows() + if not shows: + return True + + tmdb_api_key = config_data.get('tmdb_api_key', '') + if not tmdb_api_key: + logging.warning("TMDB API未配置,无法重新下载海报") + return False + + poster_language = get_poster_language_setting() + tmdb_service = TMDBService(tmdb_api_key, poster_language) + + success_count = 0 + total_count = len(shows) + + for show in shows: + try: + tmdb_id = show.get('tmdb_id') + if not tmdb_id: + continue + + # 获取新的海报路径 + new_poster_path = tmdb_service.get_poster_path_with_language(int(tmdb_id)) + if new_poster_path: + # 下载新海报 + poster_local_path = download_poster_local(new_poster_path) + if poster_local_path: + # 更新数据库中的海报路径 + cal_db.update_show_poster(int(tmdb_id), poster_local_path) + success_count += 1 + + except Exception as e: + logging.error(f"重新下载海报失败 (TMDB ID: {show.get('tmdb_id')}): {e}") + continue + + return success_count > 0 + + except Exception as e: + logging.error(f"重新下载所有海报失败: {e}") + return False + def download_poster_local(poster_path: str) -> str: """下载 TMDB 海报到本地 static/cache/images 下,等比缩放为宽400px,返回相对路径。""" try: @@ -4285,7 +4373,8 @@ def calendar_refresh_latest_season(): if not tmdb_api_key: return jsonify({"success": False, "message": "TMDB API 未配置"}) - tmdb_service = TMDBService(tmdb_api_key) + poster_language = get_poster_language_setting() + tmdb_service = TMDBService(tmdb_api_key, poster_language) cal_db = CalendarDB() show = cal_db.get_show(int(tmdb_id)) if not show: @@ -4358,7 +4447,8 @@ def calendar_refresh_episode(): if not tmdb_api_key: return jsonify({"success": False, "message": "TMDB API 未配置"}) - tmdb_service = TMDBService(tmdb_api_key) + poster_language = get_poster_language_setting() + tmdb_service = TMDBService(tmdb_api_key, poster_language) cal_db = CalendarDB() # 验证节目是否存在 @@ -4426,7 +4516,8 @@ def calendar_refresh_season(): if not tmdb_api_key: return jsonify({"success": False, "message": "TMDB API 未配置"}) - tmdb_service = TMDBService(tmdb_api_key) + poster_language = get_poster_language_setting() + tmdb_service = TMDBService(tmdb_api_key, poster_language) cal_db = CalendarDB() # 验证节目是否存在 @@ -4505,7 +4596,8 @@ def calendar_refresh_show(): if not tmdb_api_key: return jsonify({"success": False, "message": "TMDB API 未配置"}) - tmdb_service = TMDBService(tmdb_api_key) + poster_language = get_poster_language_setting() + tmdb_service = TMDBService(tmdb_api_key, poster_language) cal_db = CalendarDB() details = tmdb_service.get_tv_show_details(int(tmdb_id)) or {} @@ -4545,8 +4637,8 @@ def calendar_refresh_show(): except Exception: latest_season_number = 1 - # 海报 - poster_path = details.get('poster_path') or '' + # 海报 - 使用海报语言设置获取海报路径 + poster_path = tmdb_service.get_poster_path_with_language(int(tmdb_id)) if tmdb_service else (details.get('poster_path') or '') poster_local_path = '' try: if poster_path: @@ -4642,7 +4734,8 @@ def calendar_edit_metadata(): did_rematch = False cal_db = CalendarDB() tmdb_api_key = config_data.get('tmdb_api_key', '') - tmdb_service = TMDBService(tmdb_api_key) if tmdb_api_key else None + poster_language = get_poster_language_setting() + tmdb_service = TMDBService(tmdb_api_key, poster_language) if tmdb_api_key else None # 场景一:提供 new_tmdb_id(重绑节目,可同时指定季数) if new_tmdb_id: try: @@ -4935,7 +5028,8 @@ def run_calendar_refresh_all_internal(): tmdb_api_key = config_data.get('tmdb_api_key', '') if not tmdb_api_key: return - tmdb_service = TMDBService(tmdb_api_key) + poster_language = get_poster_language_setting() + tmdb_service = TMDBService(tmdb_api_key, poster_language) db = CalendarDB() shows = [] # 简单读取所有已初始化的剧目 diff --git a/app/sdk/db.py b/app/sdk/db.py index a1620dc..ec79658 100644 --- a/app/sdk/db.py +++ b/app/sdk/db.py @@ -578,3 +578,17 @@ class CalendarDB: cursor = self.conn.cursor() cursor.execute('UPDATE shows SET latest_season_number=? WHERE tmdb_id=?', (latest_season_number, tmdb_id)) self.conn.commit() + + def update_show_poster(self, tmdb_id: int, poster_local_path: str): + """更新节目的海报路径""" + cursor = self.conn.cursor() + cursor.execute('UPDATE shows SET poster_local_path=? WHERE tmdb_id=?', (poster_local_path, tmdb_id)) + self.conn.commit() + + def get_all_shows(self): + """获取所有节目""" + cursor = self.conn.cursor() + cursor.execute('SELECT * FROM shows') + columns = [description[0] for description in cursor.description] + rows = cursor.fetchall() + return [dict(zip(columns, row)) for row in rows] diff --git a/app/sdk/tmdb_service.py b/app/sdk/tmdb_service.py index c54dccd..af6e8d5 100644 --- a/app/sdk/tmdb_service.py +++ b/app/sdk/tmdb_service.py @@ -16,13 +16,14 @@ import logging logger = logging.getLogger(__name__) class TMDBService: - def __init__(self, api_key: str = None): + def __init__(self, api_key: str = None, poster_language: str = "zh-CN"): self.api_key = api_key # 首选改为 api.tmdb.org,备选为 api.themoviedb.org self.primary_url = "https://api.tmdb.org/3" self.backup_url = "https://api.themoviedb.org/3" self.current_url = self.primary_url self.language = "zh-CN" # 返回中文数据 + self.poster_language = poster_language # 海报语言设置 # 复用会话,开启重试 self.session = requests.Session() retries = Retry(total=3, backoff_factor=0.5, status_forcelist=[429, 500, 502, 503, 504], allowed_methods=["GET"]) # 简单退避 @@ -421,3 +422,66 @@ class TMDBService: except Exception as e: logger.error(f"时间转换失败: {e}") return utc_time_str + + def get_poster_path_with_language(self, tv_id: int) -> str: + """根据海报语言设置获取海报路径""" + try: + if not self.is_configured(): + return '' + + # 获取节目详情 + details = self.get_tv_show_details(tv_id) + if not details: + return '' + + # 如果设置为原始语言,需要获取原始语言代码 + if self.poster_language == "original": + original_language = details.get('original_language', 'en') + # 如果原始语言是zh,使用zh-CN + if original_language == 'zh': + original_language = 'zh-CN' + + # 使用原始语言请求海报 + params = { + 'api_key': self.api_key, + 'language': original_language, + 'include_adult': False + } + + # 尝试获取原始语言海报 + try: + url = f"{self.current_url}/tv/{tv_id}" + response = self.session.get(url, params=params, timeout=10) + response.raise_for_status() + data = response.json() + poster_path = data.get('poster_path', '') + if poster_path: + return poster_path + except Exception as e: + logger.warning(f"获取原始语言海报失败: {e}") + + # 如果设置为中文或原始语言获取失败,尝试中文海报 + if self.poster_language == "zh-CN" or self.poster_language == "original": + params = { + 'api_key': self.api_key, + 'language': 'zh-CN', + 'include_adult': False + } + + try: + url = f"{self.current_url}/tv/{tv_id}" + response = self.session.get(url, params=params, timeout=10) + response.raise_for_status() + data = response.json() + poster_path = data.get('poster_path', '') + if poster_path: + return poster_path + except Exception as e: + logger.warning(f"获取中文海报失败: {e}") + + # 如果都失败了,返回默认海报路径 + return details.get('poster_path', '') + + except Exception as e: + logger.error(f"获取海报路径失败: {e}") + return '' diff --git a/app/templates/index.html b/app/templates/index.html index 34884f9..73e9449 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -827,6 +827,19 @@ + + +