kopia lustrzana https://github.com/bellingcat/auto-archiver
159 wiersze
5.4 KiB
Python
159 wiersze
5.4 KiB
Python
import pytest
|
|
from auto_archiver.core import Metadata, Media
|
|
from auto_archiver.modules.thumbnail_enricher import ThumbnailEnricher
|
|
|
|
|
|
@pytest.fixture
|
|
def thumbnail_enricher(setup_module, mock_binary_dependencies) -> ThumbnailEnricher:
|
|
config: dict = {
|
|
"thumbnails_per_minute": 60,
|
|
"max_thumbnails": 4,
|
|
}
|
|
return setup_module("thumbnail_enricher", config)
|
|
|
|
|
|
@pytest.fixture
|
|
def metadata_with_video():
|
|
m = Metadata()
|
|
m.set_url("https://example.com")
|
|
m.add_media(Media(filename="video.mp4").set("id", "video1"))
|
|
return m
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_ffmpeg_environment(mocker):
|
|
# Mocking all the ffmpeg calls in one place
|
|
mock_ffmpeg_input = mocker.patch("ffmpeg.input")
|
|
mock_makedirs = mocker.patch("os.makedirs")
|
|
(mocker.patch.object(Media, "is_video", return_value=True),)
|
|
mock_probe = mocker.patch(
|
|
"ffmpeg.probe",
|
|
return_value={
|
|
"streams": [
|
|
{"codec_type": "video", "duration": "120"}
|
|
] # Default 2-minute duration, but can override in tests
|
|
},
|
|
)
|
|
mock_output = mocker.MagicMock()
|
|
mock_ffmpeg_input.return_value.filter.return_value.output.return_value = mock_output
|
|
|
|
return {
|
|
"mock_ffmpeg_input": mock_ffmpeg_input,
|
|
"mock_makedirs": mock_makedirs,
|
|
"mock_output": mock_output,
|
|
"mock_probe": mock_probe,
|
|
}
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"thumbnails_per_minute, max_thumbnails, expected_count",
|
|
[
|
|
(10, 5, 5), # Capped at max_thumbnails
|
|
(1, 10, 2), # Less than max_thumbnails
|
|
(60, 7, 7), # Matches exactly
|
|
],
|
|
)
|
|
def test_enrich_thumbnail_limits(
|
|
thumbnail_enricher,
|
|
metadata_with_video,
|
|
mock_ffmpeg_environment,
|
|
thumbnails_per_minute,
|
|
max_thumbnails,
|
|
expected_count,
|
|
):
|
|
thumbnail_enricher.thumbnails_per_minute = thumbnails_per_minute
|
|
thumbnail_enricher.max_thumbnails = max_thumbnails
|
|
|
|
thumbnail_enricher.enrich(metadata_with_video)
|
|
|
|
assert mock_ffmpeg_environment["mock_output"].run.call_count == expected_count
|
|
thumbnails = metadata_with_video.media[0].get("thumbnails")
|
|
assert len(thumbnails) == expected_count
|
|
|
|
|
|
def test_enrich_handles_probe_failure(thumbnail_enricher, metadata_with_video, mocker):
|
|
mocker.patch("ffmpeg.probe", side_effect=Exception("Probe error"))
|
|
mocker.patch("os.makedirs")
|
|
mock_logger = mocker.patch("loguru.logger.error")
|
|
mocker.patch.object(Media, "is_video", return_value=True)
|
|
|
|
thumbnail_enricher.enrich(metadata_with_video)
|
|
# Ensure error was logged
|
|
mock_logger.assert_called_with("error getting duration of video video.mp4: Probe error")
|
|
# Ensure no thumbnails were created
|
|
thumbnails = metadata_with_video.media[0].get("thumbnails")
|
|
assert thumbnails is None
|
|
|
|
|
|
def test_enrich_skips_non_video_files(thumbnail_enricher, metadata_with_video, mocker):
|
|
mocker.patch.object(Media, "is_video", return_value=False)
|
|
mock_ffmpeg = mocker.patch("ffmpeg.input")
|
|
thumbnail_enricher.enrich(metadata_with_video)
|
|
mock_ffmpeg.assert_not_called()
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"thumbnails_per_minute,max_thumbnails,expected_count",
|
|
[
|
|
(60, 5, 5), # caught by max
|
|
(60, 20, 10), # caught by t/min
|
|
(0, 20, 1), # test min caught (1)
|
|
(11, 20, 1), # test min caught (1)
|
|
(12, 20, 2), # test caught by t/min
|
|
],
|
|
)
|
|
def test_enrich_handles_short_video(
|
|
thumbnail_enricher,
|
|
metadata_with_video,
|
|
mock_ffmpeg_environment,
|
|
thumbnails_per_minute,
|
|
max_thumbnails,
|
|
expected_count,
|
|
mocker,
|
|
):
|
|
# override mock duration
|
|
fake_duration = 10
|
|
mocker.patch(
|
|
"ffmpeg.probe",
|
|
return_value={"streams": [{"codec_type": "video", "duration": str(fake_duration)}]},
|
|
)
|
|
thumbnail_enricher.thumbnails_per_minute = thumbnails_per_minute
|
|
thumbnail_enricher.max_thumbnails = max_thumbnails
|
|
|
|
thumbnail_enricher.enrich(metadata_with_video)
|
|
assert mock_ffmpeg_environment["mock_output"].run.call_count == expected_count
|
|
thumbnails = metadata_with_video.media[0].get("thumbnails")
|
|
assert len(thumbnails) == expected_count
|
|
|
|
|
|
def test_uses_existing_duration(thumbnail_enricher, metadata_with_video, mock_ffmpeg_environment):
|
|
metadata_with_video.media[0].set("duration", 60)
|
|
thumbnail_enricher.enrich(metadata_with_video)
|
|
mock_ffmpeg_environment["mock_probe"].assert_not_called()
|
|
assert mock_ffmpeg_environment["mock_output"].run.call_count == 4
|
|
|
|
|
|
def test_enrich_metadata_structure(thumbnail_enricher, metadata_with_video, mock_ffmpeg_environment, mocker):
|
|
fake_duration = 120
|
|
mocker.patch("ffmpeg.probe", return_value={"streams": [{"codec_type": "video", "duration": str(fake_duration)}]})
|
|
thumbnail_enricher.thumbnails_per_minute = 2
|
|
thumbnail_enricher.max_thumbnails = 4
|
|
|
|
thumbnail_enricher.enrich(metadata_with_video)
|
|
|
|
media_item = metadata_with_video.media[0]
|
|
thumbnails = media_item.get("thumbnails")
|
|
|
|
# Assert normal metadata
|
|
assert media_item.get("id") == "video1"
|
|
assert media_item.get("duration") == fake_duration
|
|
# Evenly spaced timestamps
|
|
expected_timestamps = ["24.000s", "48.000s", "72.000s", "96.000s"]
|
|
assert thumbnails is not None
|
|
assert len(thumbnails) == 4
|
|
|
|
for index, thumbnail in enumerate(thumbnails):
|
|
assert thumbnail.filename is not None
|
|
assert thumbnail.properties.get("id") == f"thumbnail_{index}"
|
|
assert thumbnail.properties.get("timestamp") == expected_timestamps[index]
|