From e407607aa44a19354312877a28ae36bb93593496 Mon Sep 17 00:00:00 2001 From: Breakthrough Date: Thu, 21 Nov 2024 22:00:13 -0500 Subject: [PATCH] [scene_manager] Fix save_images not working with UTF-8 paths #450 --- scenedetect/scene_manager.py | 10 ++++++++-- tests/test_cli.py | 34 +++++++++++++++++++++++++++------- website/pages/changelog.md | 13 +++++++------ 3 files changed, 42 insertions(+), 15 deletions(-) diff --git a/scenedetect/scene_manager.py b/scenedetect/scene_manager.py index 23be9665..ccc8d0ab 100644 --- a/scenedetect/scene_manager.py +++ b/scenedetect/scene_manager.py @@ -86,6 +86,7 @@ def on_new_scene(frame_img: numpy.ndarray, frame_num: int): import sys import threading from enum import Enum +from pathlib import Path from typing import Callable, Dict, Iterable, List, Optional, TextIO, Tuple, Union import cv2 @@ -587,8 +588,13 @@ def save_images( frame_im = cv2.resize( frame_im, (0, 0), fx=scale, fy=scale, interpolation=interpolation.value ) - - cv2.imwrite(get_and_create_path(file_path, output_dir), frame_im, imwrite_param) + path = Path(get_and_create_path(file_path, output_dir)) + (is_ok, encoded) = cv2.imencode(f".{image_extension}", frame_im, imwrite_param) + if is_ok: + encoded.tofile(path) + else: + logger.error(f"Failed to encode image for {file_path}") + # else: completed = False break diff --git a/tests/test_cli.py b/tests/test_cli.py index 9a0f0339..446437cb 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -17,6 +17,7 @@ from pathlib import Path import cv2 +import numpy as np import pytest from scenedetect.video_splitter import is_ffmpeg_available, is_mkvmerge_available @@ -455,17 +456,35 @@ def test_cli_save_images(tmp_path: Path): ) == 0 ) + images = [image for image in tmp_path.glob("*.jpg")] + # Should detect two scenes and generate 3 images per scene with above params. + assert len(images) == 6 # Open one of the created images and make sure it has the correct resolution. - # TODO: Also need to test that the right number of images was generated, and compare with - # expected frames from the actual video. - images = glob.glob(os.path.join(tmp_path, "*.jpg")) - assert images image = cv2.imread(images[0]) assert image.shape == (544, 1280, 3) +def test_cli_save_images_path_handling(tmp_path: Path): + """Test `save-images` ability to handle UTF-8 paths.""" + assert ( + invoke_scenedetect( + "-i {VIDEO} -s {STATS} time {TIME} {DETECTOR} save-images -f %s" + % ("電腦檔案-$SCENE_NUMBER-$IMAGE_NUMBER"), + output_dir=tmp_path, + ) + == 0 + ) + images = [image for image in tmp_path.glob("電腦檔案-*.jpg")] + # Should detect two scenes and generate 3 images per scene with above params. + assert len(images) == 6 + # Check the created images can be read and have the correct size. + # We can't use `cv2.imread` here since it doesn't seem to work correctly with UTF-8 paths. + image = cv2.imdecode(np.fromfile(images[0], dtype=np.uint8), cv2.IMREAD_UNCHANGED) + assert image.shape == (544, 1280, 3) + + # TODO(#134): This works fine with OpenCV currently, but needs to be supported for PyAV and MoviePy. -def test_cli_save_images_rotation(rotated_video_file, tmp_path): +def test_cli_save_images_rotation(rotated_video_file, tmp_path: Path): """Test that `save-images` command rotates images correctly with the default backend.""" assert ( invoke_scenedetect( @@ -475,8 +494,9 @@ def test_cli_save_images_rotation(rotated_video_file, tmp_path): ) == 0 ) - images = glob.glob(os.path.join(tmp_path, "*.jpg")) - assert images + images = [image for image in tmp_path.glob("*.jpg")] + # Should detect two scenes and generate 3 images per scene with above params. + assert len(images) == 6 image = cv2.imread(images[0]) # Note same resolution as in test_cli_save_images but rotated 90 degrees. assert image.shape == (1280, 544, 3) diff --git a/website/pages/changelog.md b/website/pages/changelog.md index 865fccf6..9c24b2e5 100644 --- a/website/pages/changelog.md +++ b/website/pages/changelog.md @@ -583,10 +583,14 @@ Development ## PySceneDetect 0.6.5 (TBD) - - [bugfix] Fix new detectors not working with `default-detector` config option - - [bugfix] Fix crash when using `save-images`/`save_images()` with OpenCV backend [#455](https://github.com/Breakthrough/PySceneDetect/issues/455) + - [bugfix] Fix `SyntaxWarning` due to incorrect escaping [#400](https://github.com/Breakthrough/PySceneDetect/issues/400) + - [bugfix] Fix `ContentDetector` crash when using callbacks [#416](https://github.com/Breakthrough/PySceneDetect/issues/416) [#420](https://github.com/Breakthrough/PySceneDetect/issues/420) + - [feature] Add ability to configure CSV separators for rows/columns in config file [#423](https://github.com/Breakthrough/PySceneDetect/issues/423) + - [feature] Add new `--show` flag to `export-html` command to launch browser after processing [#442](https://github.com/Breakthrough/PySceneDetect/issues/442) - [general] Timecodes of the form `MM:SS[.nnn]` are now processed correctly [#443](https://github.com/Breakthrough/PySceneDetect/issues/443) - - [feature] Add new `--show` flag to `export-html` command to launch browser after processing (#442) + - [bugfix] Fix `save-images`/`save_images()` not working correctly with UTF-8 paths [#450](https://github.com/Breakthrough/PySceneDetect/issues/455) + - [bugfix] Fix crash when using `save-images`/`save_images()` with OpenCV backend [#455](https://github.com/Breakthrough/PySceneDetect/issues/455) + - [bugfix] Fix new detectors not working with `default-detector` config option - [improvement] The `export-html` command now implicitly invokes `save-images` with default parameters - The output of the `export-html` command will always use the result of the `save-images` command that *precedes* it - [general] Updates to Windows distributions: @@ -594,8 +598,5 @@ Development - Bundled Python interpreter is now Python 3.13 - Updated PyAV 10 -> 13.1.0 and OpenCV 4.10.0.82 -> 4.10.0.84 - [improvement] `save_to_csv` now works with paths from `pathlib` - - [bugfix] Fix `SyntaxWarning` due to incorrect escaping [#400](https://github.com/Breakthrough/PySceneDetect/issues/400) - - [bugfix] Fix `ContentDetector` crash when using callbacks [#416](https://github.com/Breakthrough/PySceneDetect/issues/416) [#420](https://github.com/Breakthrough/PySceneDetect/issues/420) - [api] The `save_to_csv` function now works correctly with paths from the `pathlib` module - [api] Add `col_separator` and `row_separator` args to `write_scene_list` function in `scenedetect.scene_manager` - - [feature] Add ability to configure CSV separators for rows/columns in config file [#423](https://github.com/Breakthrough/PySceneDetect/issues/423)