Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions tests/services/test_preview_files_service.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import os
import tempfile
from unittest.mock import patch

from tests.base import ApiDBTestCase


Expand Down Expand Up @@ -267,3 +271,108 @@ def test_get_last_preview_file_for_task(self):
self.task_id
)
self.assertEqual(preview_file["revision"], 3)

@patch("zou.app.services.preview_files_service.movie.generate_tile")
@patch("zou.app.services.preview_files_service.save_variants")
@patch(
"zou.app.services.preview_files_service.thumbnail_utils"
".turn_into_thumbnail"
)
@patch("zou.app.services.preview_files_service.movie.generate_thumbnail")
@patch("zou.app.services.preview_files_service.movie.normalize_movie")
@patch("zou.app.services.preview_files_service.file_store.add_movie")
def test_prepare_and_store_movie_saves_original_metadata(
self,
mock_add_movie,
mock_normalize,
mock_gen_thumbnail,
mock_turn_thumbnail,
mock_save_variants,
mock_gen_tile,
):
preview_file = self.generate_fixture_preview_file(
status="processing"
)
preview_file_id = str(preview_file.id)

# Create a small temp file to act as the uploaded movie
tmp = tempfile.NamedTemporaryFile(suffix=".mp4", delete=False)
tmp.write(b"\x00" * 1024)
tmp.close()
uploaded_path = tmp.name

# Create temp files for normalized outputs
norm_path = uploaded_path + "_norm.mp4"
norm_low_path = uploaded_path + "_norm_low.mp4"
for p in (norm_path, norm_low_path):
with open(p, "wb") as f:
f.write(b"\x00" * 512)

mock_normalize.return_value = (norm_path, norm_low_path, None)
mock_gen_thumbnail.return_value = norm_path
mock_gen_tile.return_value = norm_path

original_width = 720
original_height = 1280
original_duration = 42.5
normalized_width = 1920
normalized_height = 1080

with patch(
"zou.app.services.preview_files_service.movie.get_movie_size"
) as mock_size, patch(
"zou.app.services.preview_files_service.movie.get_movie_duration"
) as mock_duration:

call_count = {"size": 0}

def size_side_effect(path, **kwargs):
call_count["size"] += 1
if call_count["size"] == 1:
# First call: reading original file metadata
return (original_width, original_height)
else:
# Second call: reading normalized file metadata
return (normalized_width, normalized_height)

mock_size.side_effect = size_side_effect

duration_call_count = {"n": 0}

def duration_side_effect(path=None, **kwargs):
duration_call_count["n"] += 1
if duration_call_count["n"] == 1:
return original_duration
else:
return 40.0

mock_duration.side_effect = duration_side_effect

preview_files_service.prepare_and_store_movie(
preview_file_id,
uploaded_path,
normalize=True,
add_source_to_file_store=False,
)

persisted = files_service.get_preview_file(preview_file_id)

# The width/height fields reflect the normalized output
self.assertEqual(persisted["width"], normalized_width)
self.assertEqual(persisted["height"], normalized_height)

# The data field preserves the original metadata
self.assertIsNotNone(persisted["data"])
self.assertEqual(persisted["data"]["original_width"], original_width)
self.assertEqual(
persisted["data"]["original_height"], original_height
)
self.assertEqual(
persisted["data"]["original_duration"], original_duration
)
self.assertEqual(persisted["data"]["original_file_size"], 1024)

# Clean up
for p in (uploaded_path, norm_path, norm_low_path):
if os.path.exists(p):
os.remove(p)
15 changes: 15 additions & 0 deletions zou/app/services/playlists_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,7 @@ def get_preview_files_for_entity(entity_id):
PreviewFile.annotations,
PreviewFile.created_at,
PreviewFile.task_id,
PreviewFile.data,
)
.join(PreviewFile, Task.id == PreviewFile.task_id)
.join(TaskType)
Expand All @@ -351,10 +352,12 @@ def get_preview_files_for_entity(entity_id):
preview_file_annotations,
preview_file_created_at,
preview_file_task_id,
preview_file_data,
) in query.all():
task_id = str(task.id)
if task_id not in task_previews:
task_previews[task_id] = []
data = preview_file_data or {}
task_previews[task_id].append(
fields.serialize_dict(
{
Expand All @@ -371,6 +374,10 @@ def get_preview_files_for_entity(entity_id):
"created_at": preview_file_created_at,
"task_id": task_id,
"task_type_id": str(task.task_type_id),
"original_width": data.get("original_width"),
"original_height": data.get("original_height"),
"original_duration": data.get("original_duration"),
"original_file_size": data.get("original_file_size"),
}
)
)
Expand All @@ -395,6 +402,14 @@ def get_preview_files_for_entity(entity_id):
"previews": preview_file["previews"],
"created_at": preview_file["created_at"],
"task_id": preview_file["task_id"],
"original_width": preview_file.get("original_width"),
"original_height": preview_file.get("original_height"),
"original_duration": preview_file.get(
"original_duration"
),
"original_file_size": preview_file.get(
"original_file_size"
),
}
for preview_file in preview_files
] # Do not add too much field to avoid building too big responses
Expand Down
38 changes: 38 additions & 0 deletions zou/app/services/preview_files_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,44 @@ def prepare_and_store_movie(
preview_file_id, "source movie upload", exc
)
preview_file_raw = files_service.get_preview_file_raw(preview_file_id)

# Capture original metadata before normalization. This is a
# nice-to-have, so any failure here must not block the main pipeline.
# silent=True avoids emitting a second preview-file:update event
# alongside the final "ready" update at the end of this function.
try:
original_width, original_height = movie.get_movie_size(
uploaded_movie_path
)
original_duration = movie.get_movie_duration(uploaded_movie_path)
original_file_size = os.path.getsize(uploaded_movie_path)
update_preview_file_raw(
preview_file_raw,
{
"data": {
**(preview_file_raw.data or {}),
"original_width": original_width,
"original_height": original_height,
"original_duration": original_duration,
"original_file_size": original_file_size,
}
},
silent=True,
)
except PreviewFileNotFoundException:
current_app.logger.warning(
"Preview file %s was deleted while capturing original "
"video metadata; skipping metadata capture",
preview_file_id,
)
except Exception:
current_app.logger.warning(
"Failed to capture original video metadata for %s; "
"continuing without it",
uploaded_movie_path,
exc_info=1,
)

normalized_movie_low_path = None
try:
project = get_project_from_preview_file(preview_file_id)
Expand Down
Loading