diff --git a/.github/workflows/tauri_app_build.yml b/.github/workflows/tauri_app_build.yml index 63c7560..90fff0b 100644 --- a/.github/workflows/tauri_app_build.yml +++ b/.github/workflows/tauri_app_build.yml @@ -44,6 +44,7 @@ jobs: script-name: app.py output-file: iib_api_server output-dir: out + include-module: imageio.plugins.pyav include-data-dir: | vue/dist=vue/dist @@ -143,7 +144,7 @@ jobs: with: spec: 'app.py' upload_exe_with_name: 'My executable' - options: --onefile + options: --onefile --additional-hooks-dir=pyinstaller_hooks - run: mv dist/app.exe vue/src-tauri/iib_api_server-x86_64-pc-windows-msvc.exe diff --git a/.gitignore b/.gitignore index 328362e..d937dcb 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,7 @@ launch.sh conf.json iib.db-journal .env -standalone.cmd +standalone* .vscode build/**/* dist/**/* diff --git a/pyinstaller_hooks/hook-av.py b/pyinstaller_hooks/hook-av.py new file mode 100644 index 0000000..681443b --- /dev/null +++ b/pyinstaller_hooks/hook-av.py @@ -0,0 +1,4 @@ +from PyInstaller.utils.hooks import collect_all + +# Collect package code, data and hidden imports for PyAV +datas, binaries, hiddenimports = collect_all('av') diff --git a/pyinstaller_hooks/hook-imageio.py b/pyinstaller_hooks/hook-imageio.py new file mode 100644 index 0000000..804c102 --- /dev/null +++ b/pyinstaller_hooks/hook-imageio.py @@ -0,0 +1,4 @@ +from PyInstaller.utils.hooks import collect_all + +# Collect package code, data (including .dist-info) and hidden imports +datas, binaries, hiddenimports = collect_all('imageio') diff --git a/pyinstaller_hooks/hook-imageio_ffmpeg.py b/pyinstaller_hooks/hook-imageio_ffmpeg.py new file mode 100644 index 0000000..5321fbd --- /dev/null +++ b/pyinstaller_hooks/hook-imageio_ffmpeg.py @@ -0,0 +1,4 @@ +from PyInstaller.utils.hooks import collect_all + +# Collect package code, data and hidden imports for imageio-ffmpeg +datas, binaries, hiddenimports = collect_all('imageio_ffmpeg') diff --git a/pyinstaller_hooks/hook-pillow_avif.py b/pyinstaller_hooks/hook-pillow_avif.py new file mode 100644 index 0000000..955e8b7 --- /dev/null +++ b/pyinstaller_hooks/hook-pillow_avif.py @@ -0,0 +1,4 @@ +from PyInstaller.utils.hooks import collect_all + +# Collect package code, data and hidden imports for pillow-avif-plugin +datas, binaries, hiddenimports = collect_all('pillow_avif') diff --git a/requirements.txt b/requirements.txt index d9f46f2..80232e7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,6 +5,6 @@ python-dotenv Pillow pillow-avif-plugin imageio -av +av>=14,<15 lxml filetype \ No newline at end of file diff --git a/scripts/iib/api.py b/scripts/iib/api.py index c4f2b9a..2f1d43e 100644 --- a/scripts/iib/api.py +++ b/scripts/iib/api.py @@ -328,11 +328,45 @@ def infinite_image_browsing_api(app: FastAPI, **kwargs): @app.get(f"{api_base}/version", dependencies=[Depends(verify_secret)]) async def get_version(): - return { + import sys + import platform + + def _get_dist_version(dist_name: str = None, module_name: str = None): + # Try importlib.metadata first (distribution metadata), then fallback to module __version__ + try: + if dist_name: + try: + from importlib.metadata import version + + return version(dist_name) + except Exception: + pass + if module_name: + mod = __import__(module_name) + return getattr(mod, "__version__", None) + if dist_name: + # try importing by normalized name + mod = __import__(dist_name.replace("-", "_")) + return getattr(mod, "__version__", None) + except Exception as e: + logger.debug("Version probe failed for %s/%s: %s", dist_name, module_name, e) + return None + + versions = { + "python_version": sys.version.splitlines()[0], + "platform": platform.platform(), "hash": get_current_commit_hash(), "tag": get_current_tag(), + "av": _get_dist_version("av", "av"), + "imageio": _get_dist_version("imageio", "imageio"), + "pillow": _get_dist_version("Pillow", "PIL"), + "imageio_ffmpeg": _get_dist_version("imageio-ffmpeg", "imageio_ffmpeg"), + "pillow_avif_plugin": _get_dist_version("pillow-avif-plugin", "pillow_avif"), } + logger.info("Version info requested: %s", {k: v for k, v in versions.items() if v}) + return versions + class DeleteFilesReq(BaseModel): file_paths: List[str] @@ -649,16 +683,34 @@ def infinite_image_browsing_api(app: FastAPI, **kwargs): if not is_media_file(path): raise HTTPException(status_code=400, detail=f"{path} is not a video file") # 如果缓存文件不存在,则生成缩略图并保存 - - import imageio.v3 as iio - frame = iio.imread( - path, - index=16, - plugin="pyav", - ) - - os.makedirs(cache_dir, exist_ok=True) - iio.imwrite(cache_path,frame, extension=".webp") + try: + import imageio.v3 as iio + logger.info( + "Generating video cover thumbnail: path=%s, mt=%s, cache_path=%s", + path, + mt, + cache_path, + ) + frame = iio.imread( + path, + index=16, + plugin="pyav", + ) + + os.makedirs(cache_dir, exist_ok=True) + iio.imwrite(cache_path, frame, extension=".webp") + logger.info("Saved video cover thumbnail: %s", cache_path) + except Exception as e: + # record full stack trace and contextual info in English + logger.exception( + "Failed to generate video cover for path=%s mt=%s cache_dir=%s: %s", + path, + mt, + cache_dir, + e, + ) + # return a clear HTTP error (detail contains exception message) + raise HTTPException(status_code=500, detail=f"Failed to generate video cover: {e}") # 返回缓存文件 return FileResponse( diff --git a/vue/src-tauri/tauri.conf.json b/vue/src-tauri/tauri.conf.json index f06c15b..9ed4ff4 100644 --- a/vue/src-tauri/tauri.conf.json +++ b/vue/src-tauri/tauri.conf.json @@ -8,7 +8,7 @@ }, "package": { "productName": "Infinite Image Browsing", - "version": "1.5.0" + "version": "1.5.1" }, "tauri": { "allowlist": {