diff --git a/info.yaml b/info.yaml index 08a14c7..3a63776 100644 --- a/info.yaml +++ b/info.yaml @@ -1,5 +1,5 @@ title: "애니 다운로더" -version: "0.3.4" +version: "0.3.5" package_name: "anime_downloader" developer: "projectdx" description: "anime downloader" diff --git a/lib/cdndania_downloader.py b/lib/cdndania_downloader.py index 15e07ae..c8f39fa 100644 --- a/lib/cdndania_downloader.py +++ b/lib/cdndania_downloader.py @@ -475,13 +475,18 @@ async def _download_worker_async( completed_segments += 1 total_bytes += len(content) - # Log Progress + # Progress Update & Log + pct = int((completed_segments / total_segments) * 100) + elapsed = time.time() - start_time + speed = total_bytes / elapsed if elapsed > 0 else 0 + + # Update progress file frequently (for UI callback) if completed_segments == 1 or completed_segments % 10 == 0 or completed_segments == total_segments: - pct = int((completed_segments / total_segments) * 100) - elapsed = time.time() - start_time - speed = total_bytes / elapsed if elapsed > 0 else 0 - log.info(f"Progress: {pct}% ({completed_segments}/{total_segments}) Speed: {format_speed(speed)}") update_progress(pct, completed_segments, total_segments, format_speed(speed), format_time(elapsed)) + + # Log only at 25% intervals (reduce log spam) + if completed_segments == 1 or pct in [25, 50, 75, 100] and (completed_segments == total_segments or completed_segments * 4 % total_segments < 4): + log.info(f"Progress: {pct}% ({completed_segments}/{total_segments}) Speed: {format_speed(speed)}") return except asyncio.TimeoutError: if retry == 2: diff --git a/mod_anilife.py b/mod_anilife.py index 7f97b62..a53062d 100644 --- a/mod_anilife.py +++ b/mod_anilife.py @@ -683,6 +683,32 @@ class LogicAniLife(PluginModuleBase): logger.error(f"Exception: {e}") logger.error(traceback.format_exc()) return jsonify({"ret": False, "log": str(e)}) + elif sub == "browse_dir": + try: + path = request.form.get("path", "") + if not path or not os.path.exists(path): + path = P.ModelSetting.get("anilife_download_path") or os.path.expanduser("~") + path = os.path.abspath(path) + if not os.path.isdir(path): + path = os.path.dirname(path) + directories = [] + try: + for item in sorted(os.listdir(path)): + item_path = os.path.join(path, item) + if os.path.isdir(item_path) and not item.startswith('.'): + directories.append({"name": item, "path": item_path}) + except PermissionError: + pass + parent = os.path.dirname(path) if path != "/" else None + return jsonify({ + "ret": "success", + "current_path": path, + "parent_path": parent, + "directories": directories + }) + except Exception as e: + logger.error(f"browse_dir error: {e}") + return jsonify({"ret": "error", "error": str(e)}), 500 except Exception as e: P.logger.error("Exception:%s", e) P.logger.error(traceback.format_exc()) diff --git a/mod_linkkf.py b/mod_linkkf.py index 8227de7..2181dc3 100644 --- a/mod_linkkf.py +++ b/mod_linkkf.py @@ -395,6 +395,33 @@ class LogicLinkkf(PluginModuleBase): return jsonify({"error": str(e)}), 500 # 매치되는 sub가 없는 경우 기본 응답 + if sub == "browse_dir": + try: + path = request.form.get("path", "") + if not path or not os.path.exists(path): + path = P.ModelSetting.get("linkkf_download_path") or os.path.expanduser("~") + path = os.path.abspath(path) + if not os.path.isdir(path): + path = os.path.dirname(path) + directories = [] + try: + for item in sorted(os.listdir(path)): + item_path = os.path.join(path, item) + if os.path.isdir(item_path) and not item.startswith('.'): + directories.append({"name": item, "path": item_path}) + except PermissionError: + pass + parent = os.path.dirname(path) if path != "/" else None + return jsonify({ + "ret": "success", + "current_path": path, + "parent_path": parent, + "directories": directories + }) + except Exception as e: + logger.error(f"browse_dir error: {e}") + return jsonify({"ret": "error", "error": str(e)}), 500 + return jsonify({"ret": "error", "log": f"Unknown sub: {sub}"}) except Exception as e: diff --git a/mod_ohli24.py b/mod_ohli24.py index 39f083f..5f44606 100644 --- a/mod_ohli24.py +++ b/mod_ohli24.py @@ -18,6 +18,7 @@ import sys import threading import traceback import urllib +import unicodedata from datetime import datetime, date from typing import Any, Dict, List, Optional, Tuple, Union, Callable, TYPE_CHECKING from urllib import parse @@ -396,14 +397,19 @@ class LogicOhli24(PluginModuleBase): # 보안 체크 download_path = P.ModelSetting.get("ohli24_download_path") - if not file_path.startswith(download_path): + + # Normalize both paths to NFC and absolute paths for comparison + norm_file_path = unicodedata.normalize('NFC', os.path.abspath(file_path)) + norm_dl_path = unicodedata.normalize('NFC', os.path.abspath(download_path)) + + if not norm_file_path.startswith(norm_dl_path): return jsonify({"error": "Access denied", "playlist": [], "current_index": 0}), 403 folder = os.path.dirname(file_path) current_file = os.path.basename(file_path) - # 파일명에서 SxxExx 패턴 추출 - ep_match = re.search(r'\.S(\d+)E(\d+)\.', current_file, re.IGNORECASE) + # 파일명에서 SxxExx 패턴 추출 (구분자 유연화) + ep_match = re.search(r'[ .\-_]S(\d+)E(\d+)[ .\-_]', current_file, re.IGNORECASE) if not ep_match: # 패턴 없으면 현재 파일만 반환 return jsonify({ @@ -417,8 +423,10 @@ class LogicOhli24(PluginModuleBase): # 같은 폴더의 모든 mp4 파일 가져오기 all_files = [] for f in os.listdir(folder): - if f.endswith('.mp4'): - match = re.search(r'\.S(\d+)E(\d+)\.', f, re.IGNORECASE) + # Normalize to NFC for consistent matching + f_nfc = unicodedata.normalize('NFC', f) + if f_nfc.endswith('.mp4'): + match = re.search(r'[ .\-_]S(\d+)E(\d+)[ .\-_]', f_nfc, re.IGNORECASE) if match: s = int(match.group(1)) e = int(match.group(2)) @@ -432,11 +440,16 @@ class LogicOhli24(PluginModuleBase): # 시즌/에피소드 순으로 정렬 all_files.sort(key=lambda x: (x["season"], x["episode"])) - # 현재 에피소드 이상인 것만 필터링 (현재 + 다음 에피소드들) + logger.debug(f"[PLAYLIST_DEBUG] Folder: {folder}") + logger.debug(f"[PLAYLIST_DEBUG] All files in folder: {os.listdir(folder)[:10]}...") # First 10 + logger.debug(f"[PLAYLIST_DEBUG] Matched SxxExx files: {len(all_files)}") + logger.debug(f"[PLAYLIST_DEBUG] Current: S{current_season:02d}E{current_episode:02d}") + + # 현재 시즌의 모든 에피소드 포함 (전체 시즌 재생) playlist = [] current_index = 0 for i, f in enumerate(all_files): - if f["season"] == current_season and f["episode"] >= current_episode: + if f["season"] == current_season: entry = {"path": f["path"], "name": f["name"]} if f["episode"] == current_episode: current_index = len(playlist) @@ -458,6 +471,47 @@ class LogicOhli24(PluginModuleBase): P.logger.error(traceback.format_exc()) return jsonify({"error": str(e)}), 500 + # 폴더 탐색 엔드포인트 + if sub == "browse_dir": + try: + path = request.form.get("path", "") + + # 기본 경로: 홈 디렉토리 또는 현재 다운로드 경로 + if not path or not os.path.exists(path): + path = P.ModelSetting.get("ohli24_download_path") or os.path.expanduser("~") + + # 경로 정규화 + path = os.path.abspath(path) + + if not os.path.isdir(path): + path = os.path.dirname(path) + + # 디렉토리 목록 가져오기 + directories = [] + try: + for item in sorted(os.listdir(path)): + item_path = os.path.join(path, item) + if os.path.isdir(item_path) and not item.startswith('.'): + directories.append({ + "name": item, + "path": item_path + }) + except PermissionError: + pass + + # 상위 폴더 + parent = os.path.dirname(path) if path != "/" else None + + return jsonify({ + "ret": "success", + "current_path": path, + "parent_path": parent, + "directories": directories + }) + except Exception as e: + logger.error(f"browse_dir error: {e}") + return jsonify({"ret": "error", "error": str(e)}), 500 + # 매칭되지 않는 sub 요청에 대한 기본 응답 return jsonify({"error": f"Unknown sub: {sub}"}), 404 @@ -1577,11 +1631,16 @@ class Ohli24QueueEntity(FfmpegQueueEntity): def download_completed(self) -> None: logger.debug("download completed.......!!") + logger.debug(f"[DB_COMPLETE] Looking up entity by ohli24_id: {self.info.get('_id')}") db_entity = ModelOhli24Item.get_by_ohli24_id(self.info["_id"]) + logger.debug(f"[DB_COMPLETE] Found db_entity: {db_entity}") if db_entity is not None: db_entity.status = "completed" db_entity.completed_time = datetime.now() - db_entity.save() + result = db_entity.save() + logger.debug(f"[DB_COMPLETE] Save result: {result}") + else: + logger.warning(f"[DB_COMPLETE] No db_entity found for _id: {self.info.get('_id')}") def download_failed(self, reason: str) -> None: logger.debug(f"download failed.......!! reason: {reason}") @@ -1721,6 +1780,17 @@ class Ohli24QueueEntity(FfmpegQueueEntity): ) self.filename = Util.change_text_for_use_filename(ret) self.filepath = os.path.join(self.savepath, self.filename) + + # [NFD CHECK] Mac/Docker Compatibility + # If NFC (Python standard) file doesn't exist, check NFD (Mac filesystem standard) + if not os.path.exists(self.filepath): + nfd_filename = unicodedata.normalize('NFD', self.filename) + nfd_filepath = os.path.join(self.savepath, nfd_filename) + if os.path.exists(nfd_filepath): + logger.info(f"[NFD Match] Found existing file with NFD normalization: {nfd_filename}") + self.filename = nfd_filename + self.filepath = nfd_filepath + logger.info(f"self.filename::> {self.filename}") if not video_url: @@ -2137,25 +2207,31 @@ class ModelOhli24Item(ModelBase): @classmethod def append(cls, q): - item = ModelOhli24Item() - item.content_code = q["content_code"] - item.season = q["season"] - item.episode_no = q["epi_queue"] - item.title = q["content_title"] - item.episode_title = q["title"] - item.ohli24_va = q["va"] - item.ohli24_vi = q["_vi"] - item.ohli24_id = q["_id"] - item.quality = q["quality"] - item.filepath = q["filepath"] - item.filename = q["filename"] - item.savepath = q["savepath"] - item.video_url = q["url"] - item.vtt_url = q["vtt"] - item.thumbnail = q["thumbnail"] - item.status = "wait" - item.ohli24_info = q["ohli24_info"] - item.save() + try: + logger.debug(f"[DB_APPEND] Starting append for _id: {q.get('_id')}") + item = ModelOhli24Item() + item.content_code = q["content_code"] + item.season = q["season"] + item.episode_no = q["epi_queue"] + item.title = q["content_title"] + item.episode_title = q["title"] + item.ohli24_va = q["va"] + item.ohli24_vi = q["_vi"] + item.ohli24_id = q["_id"] + item.quality = q["quality"] + item.filepath = q["filepath"] + item.filename = q["filename"] + item.savepath = q["savepath"] + item.video_url = q["url"] + item.vtt_url = q["vtt"] + item.thumbnail = q["thumbnail"] + item.status = "wait" + item.ohli24_info = q["ohli24_info"] + result = item.save() + logger.debug(f"[DB_APPEND] Save result for _id {q.get('_id')}: {result}") + except Exception as e: + logger.error(f"[DB_APPEND] Exception during append: {e}") + logger.error(traceback.format_exc()) class ModelOhli24Program(ModelBase): diff --git a/templates/anime_downloader_anilife_setting.html b/templates/anime_downloader_anilife_setting.html index 35729e0..81da086 100644 --- a/templates/anime_downloader_anilife_setting.html +++ b/templates/anime_downloader_anilife_setting.html @@ -23,7 +23,27 @@
+ + + + + {% endblock %} \ No newline at end of file