diff --git a/README.md b/README.md index 8516c74..431f05f 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ pre-commit run --files ```bash sudo apt update -sudo apt install ffmpeg curl +sudo apt install ffmpeg ``` ## License @@ -97,4 +97,4 @@ MIT License © 2025 Ismo Vuorinen [lm-l]: https://opensource.org/licenses/MIT [sample]: https://www.youtube.com/watch?v=SnywvnjHpUk - + \ No newline at end of file diff --git a/aeonview.py b/aeonview.py index ecf43e2..2850be0 100644 --- a/aeonview.py +++ b/aeonview.py @@ -365,11 +365,14 @@ class AeonViewHelpers: logging.error(AeonViewMessages.INVALID_IMAGE_EXTENSION) return None - if url.endswith(".png"): + url_lower = url.lower() + if url_lower.endswith(".jpeg"): + return ".jpeg" + if url_lower.endswith(".png"): return ".png" - if url.endswith(".gif"): + if url_lower.endswith(".gif"): return ".gif" - if url.endswith(".webp"): + if url_lower.endswith(".webp"): return ".webp" return ".jpg" @@ -397,6 +400,7 @@ class AeonViewHelpers: """ return [ "ffmpeg", + "-y", "-framerate", str(fps), "-pattern_type", @@ -531,4 +535,4 @@ if __name__ == "__main__": app = AeonViewApp() app.run() -# vim: set tw=100 fo=cqt wm=0 et: +# vim: set tw=100 fo=cqt wm=0 et: \ No newline at end of file diff --git a/aeonview_test.py b/aeonview_test.py index 8f757bd..55014c4 100644 --- a/aeonview_test.py +++ b/aeonview_test.py @@ -51,8 +51,10 @@ def test_mkdir_p_creates_directory(): @pytest.mark.parametrize("ext", ["jpg", "png", "gif", "webp"]) def test_get_extension_valid(ext): - assert AeonViewHelpers.get_extension( - f"{default_image_domain}.{ext}") == f".{ext}" + assert ( + AeonViewHelpers.get_extension(f"{default_image_domain}.{ext}") + == f".{ext}" + ) def test_get_extension_invalid(): @@ -110,23 +112,20 @@ def test_get_image_paths_invalid_url(): with pytest.raises(SystemExit), mock.patch("aeonview.logging.error"): aeon_view_images = AeonViewImages(default_test_path, "invalid-url") aeon_view_images.get_image_paths( - "invalid-url", - default_test_path, - datetime(2025, 4, 10) + "invalid-url", default_test_path, datetime(2025, 4, 10) ) def test_get_image_paths_invalid_date(): with pytest.raises(SystemExit), mock.patch("aeonview.logging.error"): aeon_view_images = AeonViewImages( - default_test_path, - f"{default_image_domain}.jpg" + default_test_path, f"{default_image_domain}.jpg" ) # noinspection PyTypeChecker aeon_view_images.get_image_paths( f"{default_image_domain}.jpg", default_test_path, - "invalid-date" # pyright: ignore [reportArgumentType] + "invalid-date", # pyright: ignore [reportArgumentType] ) @@ -153,9 +152,7 @@ def test_download_image_success(mock_get): destination = Path(temp_file.name) avi.download_image(destination) mock_get.assert_called_once_with( - f"{default_image_domain}.jpg", - stream=True, - timeout=10 + f"{default_image_domain}.jpg", stream=True, timeout=10 ) assert destination.exists() @@ -179,11 +176,7 @@ def test_generate_daily_video(mock_subprocess_run): with tempfile.TemporaryDirectory() as tmp: project_path = Path(tmp).resolve() args = argparse.Namespace( - simulate=False, - fps=10, - day="01", - month="04", - year="2025" + simulate=False, fps=10, day="01", month="04", year="2025" ) avv = AeonViewVideos(project_path, args) # Create input directory so the existence check passes @@ -193,7 +186,10 @@ def test_generate_daily_video(mock_subprocess_run): avv.generate_daily_video() last_call_args = log.call_args_list[-1][0] assert last_call_args[0] == "%s: %s" - assert last_call_args[1] == AeonViewMessages.VIDEO_GENERATION_SUCCESS + assert ( + last_call_args[1] + == AeonViewMessages.VIDEO_GENERATION_SUCCESS + ) assert last_call_args[2] == ( project_path / "vid" / "2025-04" / "01.mp4" ) @@ -204,11 +200,7 @@ def test_generate_daily_video(mock_subprocess_run): @mock.patch("aeonview.AeonViewHelpers.mkdir_p") def test_generate_daily_video_simulate(mock_mkdir_p): args = argparse.Namespace( - simulate=True, - fps=10, - day="01", - month="04", - year="2025" + simulate=True, fps=10, day="01", month="04", year="2025" ) avv = AeonViewVideos(default_test_path, args) avv.generate_daily_video() @@ -217,11 +209,7 @@ def test_generate_daily_video_simulate(mock_mkdir_p): def test_generate_monthly_video_not_implemented(): args = argparse.Namespace( - simulate=False, - fps=10, - day="01", - month="04", - year="2025" + simulate=False, fps=10, day="01", month="04", year="2025" ) avv = AeonViewVideos(default_test_path, args) with pytest.raises(NotImplementedError): @@ -230,19 +218,17 @@ def test_generate_monthly_video_not_implemented(): def test_generate_yearly_video_not_implemented(): args = argparse.Namespace( - simulate=False, - fps=10, - day="01", - month="04", - year="2025" + simulate=False, fps=10, day="01", month="04", year="2025" ) avv = AeonViewVideos(default_test_path, args) with pytest.raises(NotImplementedError): avv.generate_yearly_video(Path(tempfile.gettempdir())) -@mock.patch("sys.argv", ["aeonview.py", "--mode", "image", "--url", - f"{default_image_domain}.jpg"]) +@mock.patch( + "sys.argv", + ["aeonview.py", "--mode", "image", "--url", f"{default_image_domain}.jpg"], +) def test_parse_arguments_image_mode(): args, _ = AeonViewHelpers.parse_arguments() assert args.mode == "image" @@ -250,8 +236,10 @@ def test_parse_arguments_image_mode(): assert args.dest == default_dest -@mock.patch("sys.argv", ["aeonview.py", "--mode", "video", "--project", - f"{default_project}"]) +@mock.patch( + "sys.argv", + ["aeonview.py", "--mode", "video", "--project", f"{default_project}"], +) def test_parse_arguments_video_mode(): args, _ = AeonViewHelpers.parse_arguments() assert args.mode == "video" @@ -276,8 +264,7 @@ def test_parse_arguments_fps(): @mock.patch( - "sys.argv", - ["aeonview.py", "--mode", "video", "--generate", "2023-10-01"] + "sys.argv", ["aeonview.py", "--mode", "video", "--generate", "2023-10-01"] ) def test_parse_arguments_generate_date(): args, _ = AeonViewHelpers.parse_arguments() @@ -310,11 +297,7 @@ def test_image_simulation(mock_download_image, mock_mkdir_p): args = mock.MagicMock() args.simulate = True args.date = "2025-04-10 12:30:45" - avi = AeonViewImages( - default_test_path, - f"{default_image_domain}.jpg", - args - ) + avi = AeonViewImages(default_test_path, f"{default_image_domain}.jpg", args) with mock.patch("aeonview.logging.info") as log: avi.get_current_image() mock_mkdir_p.assert_not_called() @@ -347,8 +330,7 @@ def test_video_simulation(mock_subprocess_run, mock_mkdir_p): def test_setup_logger_verbose(mock_basic_config): AeonViewHelpers.setup_logger(verbose=True) mock_basic_config.assert_called_once_with( - level=logging.DEBUG, - format="[%(levelname)s] %(message)s" + level=logging.DEBUG, format="[%(levelname)s] %(message)s" ) @@ -356,6 +338,5 @@ def test_setup_logger_verbose(mock_basic_config): def test_setup_logger_non_verbose(mock_basic_config): AeonViewHelpers.setup_logger(verbose=False) mock_basic_config.assert_called_once_with( - level=logging.INFO, - format="[%(levelname)s] %(message)s" - ) + level=logging.INFO, format="[%(levelname)s] %(message)s" + ) \ No newline at end of file