Skip to content

Commit

Permalink
Merge pull request cgwire#700 from frankrousseau/tile-generation
Browse files Browse the repository at this point in the history
Generate frame tiles after video generation
  • Loading branch information
EvanBldy authored Sep 13, 2023
2 parents 1dbddbe + b77d01a commit f43b23c
Show file tree
Hide file tree
Showing 18 changed files with 409 additions and 19 deletions.
Binary file added tests/fixtures/videos/test_preview_tiles.mp4
Binary file not shown.
Binary file added tests/fixtures/videos/tile01.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file added tests/tiles/__init__.py
Empty file.
63 changes: 63 additions & 0 deletions tests/tiles/test_route_tiles.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import os

from tests.base import ApiDBTestCase

from zou.app.utils import fs

from PIL import Image

TEST_FOLDER = os.path.join("tests", "tmp")


class RouteTileTestCase(ApiDBTestCase):
def setUp(self):
super(RouteTileTestCase, self).setUp()

self.delete_tile_folders()
self.generate_fixture_project_status()
self.generate_fixture_project()
self.generate_fixture_asset_type()
self.generate_fixture_asset()
self.generate_fixture_sequence()
self.generate_fixture_shot()
self.generate_fixture_department()
self.generate_fixture_task_type()
self.generate_fixture_task_status()
self.generate_fixture_task_status_wip()
self.generate_fixture_person()
self.generate_fixture_assigner()
self.generate_fixture_task()
self.generate_fixture_software()
self.generate_fixture_working_file()
self.generate_fixture_preview_file()
self.asset_id = self.asset.id
self.preview_file_id = self.preview_file.id
self.person_id = self.person.id
os.makedirs(TEST_FOLDER)

def tearDown(self):
super(RouteTileTestCase, self).tearDown()

self.delete_tile_folders()

def delete_tile_folders(self):
fs.rm_rf(TEST_FOLDER)

def test_extract_tile(self):
path = "/pictures/preview-files/%s" % self.preview_file_id
file_path_fixture = self.get_fixture_file_path(
"videos/test_preview_tiles.mp4"
)
self.upload_file(path, file_path_fixture)

path = "/actions/preview-files/%s/extract-tile" % self.preview_file_id
try:
self.get(path)
except Exception:
pass
path = "/movies/tiles/preview-files/%s.png" % self.preview_file_id
result_file_path = self.get_file_path("tile01.png")
self.download_file(path, result_file_path)

result_image = Image.open(result_file_path)
self.assertEqual(result_image.size, (1424, 600))
Binary file added tests/utils/preview01_tile.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 22 additions & 0 deletions tests/utils/test_movie.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import shutil
import tempfile
import unittest
from PIL import Image
import math

from pathlib import Path
from urllib import request
Expand Down Expand Up @@ -123,3 +125,23 @@ def concat_testing(self, method, test_name):
width_playlist, height_playlist = movie.get_movie_size(out)
self.assertEqual(width, width_playlist)
self.assertEqual(height, height_playlist)

def test_create_tile(self):
video_path = "./tests/fixtures/videos/test_preview_tiles.mp4"
video_width, video_height = movie.get_movie_size(video_path)
tile_path = movie.generate_tile(video_path, movie_fps=25)
image = Image.open(tile_path)
img_width, img_height = image.size

probe = ffmpeg.probe(video_path)
duration_in_seconds = float(probe['streams'][0]['duration'])
float_movie_fps = eval(probe['streams'][0]['r_frame_rate'])
duration_in_frames = int(duration_in_seconds * float_movie_fps)
rows = math.ceil((duration_in_frames / 8))

aspect_ratio = (video_width / video_height)
target_width = math.ceil(aspect_ratio * 100)

os.remove(tile_path)
self.assertEqual(img_width, target_width * 8)
self.assertEqual(img_height, 100 * rows)
Empty file added tests/utils/test_tile.py
Empty file.
1 change: 0 additions & 1 deletion zou/app/blueprints/crud/metadata_descriptor.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ def update_data(self, data, instance_id):
Check if the data descriptor has a valid data_type and valid
departments.
"""

if "data_type" in data:
types = [
type_name for type_name, label in METADATA_DESCRIPTOR_TYPES
Expand Down
12 changes: 11 additions & 1 deletion zou/app/blueprints/previews/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
PreviewFileThumbnailSquareResource,
PreviewFilePreviewResource,
PreviewFileOriginalResource,
PreviewFileTileResource,
CreateOrganisationThumbnailResource,
OrganisationThumbnailResource,
CreateProjectThumbnailResource,
Expand All @@ -24,6 +25,7 @@
UpdateAnnotationsResource,
UpdatePreviewPositionResource,
ExtractFrameFromPreview,
ExtractTileFromPreview,
)

routes = [
Expand Down Expand Up @@ -68,6 +70,10 @@
"/pictures/previews/preview-files/<instance_id>.png",
PreviewFilePreviewResource,
),
(
"/movies/tiles/preview-files/<instance_id>.png",
PreviewFileTileResource,
),
(
"/pictures/thumbnails/organisations/<instance_id>",
CreateOrganisationThumbnailResource,
Expand Down Expand Up @@ -101,7 +107,7 @@
SetMainPreviewResource,
),
(
"/data/preview-files/<preview_file_id>/extract-frame",
"/actions/preview-files/<preview_file_id>/extract-frame",
ExtractFrameFromPreview,
),
(
Expand All @@ -112,6 +118,10 @@
"/actions/preview-files/<preview_file_id>/update-annotations",
UpdateAnnotationsResource,
),
(
"/actions/preview-files/<preview_file_id>/extract-tile",
ExtractTileFromPreview,
),
]
blueprint = Blueprint("thumbnails", "thumbnails")
api = configure_api_from_blueprint(blueprint, routes)
65 changes: 53 additions & 12 deletions zou/app/blueprints/previews/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,12 +218,15 @@ def post(self, instance_id):
original_file_name = ".".join(file_name_parts)

if extension in ALLOWED_PICTURE_EXTENSION:
self.save_picture_preview(instance_id, uploaded_file)
metadada = self.save_picture_preview(instance_id, uploaded_file)
preview_file = preview_files_service.update_preview_file(
instance_id,
{
"extension": "png",
"original_name": original_file_name,
"width": metadada["width"],
"height": metadada["height"],
"file_size": metadada["file_size"],
"status": "ready",
},
)
Expand Down Expand Up @@ -276,12 +279,17 @@ def save_picture_preview(self, instance_id, uploaded_file):
tmp_folder, instance_id, uploaded_file
)
file_size = fs.get_file_size(original_tmp_path)
preview_files_service.update_preview_file(
instance_id, {"file_size": file_size}, silent=True
)
return preview_files_service.save_variants(
width, height = thumbnail_utils.get_dimensions(original_tmp_path)
preview_files_service.save_variants(
instance_id, original_tmp_path
)
return {
"preview_file_id": instance_id,
"file_size": file_size,
"extension": "png",
"width": width,
"height": height,
}

def save_movie_preview(
self, preview_file_id, uploaded_file, normalize=True
Expand Down Expand Up @@ -708,6 +716,11 @@ def __init__(self):
BasePreviewPictureResource.__init__(self, "thumbnails")


class PreviewFileTileResource(BasePreviewPictureResource):
def __init__(self):
BasePreviewPictureResource.__init__(self, "tiles")


class PreviewFilePreviewResource(BasePreviewPictureResource):
"""
Smaller version of uploaded image.
Expand Down Expand Up @@ -1178,17 +1191,45 @@ def get(self, preview_file_id):
preview_file = files_service.get_preview_file(preview_file_id)
task = tasks_service.get_task(preview_file["task_id"])
user_service.check_manager_project_access(task["project_id"])
user_service.check_entity_access(task["entity_id"])
extracted_frame_path = (
preview_files_service.extract_frame_from_preview_file(
preview_file, args["frame_number"]
)
)
try:
return flask_send_file(
extracted_frame_path,
conditional=True,
mimetype="image/png",
as_attachment=False,
download_name=os.path.basename(extracted_frame_path),
)
finally:
os.remove(extracted_frame_path)

return flask_send_file(
extracted_frame_path,
conditional=True,
mimetype="image/png",
as_attachment=False,
download_name=os.path.basename(extracted_frame_path),

class ExtractTileFromPreview(Resource):
"""
Extract a tile from a preview_file
"""

@jwt_required()
def get(self, preview_file_id):
preview_file = files_service.get_preview_file(preview_file_id)
task = tasks_service.get_task(preview_file["task_id"])
user_service.check_manager_project_access(task["project_id"])
user_service.check_entity_access(task["entity_id"])
extracted_tile_path = (
preview_files_service.extract_tile_from_preview_file(preview_file)
)
file_store.add_picture("tiles", preview_file_id, extracted_tile_path)
try:
return flask_send_file(
extracted_tile_path,
conditional=True,
mimetype="image/png",
as_attachment=False,
download_name=os.path.basename(extracted_tile_path),
)
finally:
os.remove(extracted_tile_path)
2 changes: 2 additions & 0 deletions zou/app/models/preview_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ class PreviewFile(db.Model, BaseMixin, SerializerMixin):
ChoiceType(VALIDATION_STATUSES), default="neutral"
)
annotations = db.Column(JSONB)
width = db.Column(db.Integer(), default=0)
height = db.Column(db.Integer(), default=0)

task_id = db.Column(
UUIDType(binary=False), db.ForeignKey("task.id"), index=True
Expand Down
19 changes: 19 additions & 0 deletions zou/app/services/playlists_service.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import base64

import json
import os
import zlib
Expand Down Expand Up @@ -173,6 +174,9 @@ def get_playlist_with_preview_file_revisions(playlist_id):
if preview_file is not None:
shot["preview_file_id"] = preview_file["id"]
shot["preview_file_extension"] = preview_file["extension"]
shot["preview_file_revision"] = preview_file["revision"]
shot["preview_file_width"] = preview_file["width"]
shot["preview_file_height"] = preview_file["height"]
shot["preview_file_status"] = preview_file["status"]
shot["preview_file_annotations"] = preview_file["annotations"]
shot["preview_file_task_id"] = preview_file["task_id"]
Expand Down Expand Up @@ -242,6 +246,8 @@ def set_preview_files_for_entities(playlist_dict):
"id": preview_file_id,
"revision": preview_file.revision,
"extension": preview_file.extension,
"width": preview_file.width,
"height": preview_file.height,
"status": str(preview_file.status),
"annotations": preview_file.annotations,
"created_at": fields.serialize_value(preview_file.created_at),
Expand Down Expand Up @@ -279,6 +285,8 @@ def get_preview_files_for_entity(entity_id):
PreviewFile.position,
PreviewFile.original_name,
PreviewFile.extension,
PreviewFile.width,
PreviewFile.height,
PreviewFile.status,
PreviewFile.annotations,
PreviewFile.created_at,
Expand All @@ -300,6 +308,8 @@ def get_preview_files_for_entity(entity_id):
preview_file_position,
preview_file_original_name,
preview_file_extension,
preview_file_width,
preview_file_height,
preview_file_status,
preview_file_annotations,
preview_file_created_at,
Expand All @@ -316,6 +326,8 @@ def get_preview_files_for_entity(entity_id):
"position": preview_file_position,
"original_name": preview_file_original_name,
"extension": preview_file_extension,
"width": preview_file_width,
"height": preview_file_height,
"status": preview_file_status,
"annotations": preview_file_annotations,
"created_at": preview_file_created_at,
Expand All @@ -337,6 +349,8 @@ def get_preview_files_for_entity(entity_id):
"revision": preview_file["revision"],
"original_name": preview_file["original_name"],
"extension": preview_file["extension"],
"width": preview_file["width"],
"height": preview_file["height"],
"status": preview_file["status"],
"annotations": preview_file["annotations"],
"previews": preview_file["previews"],
Expand Down Expand Up @@ -846,6 +860,9 @@ def generate_playlisted_entity_from_task(task_id):
{
"preview_file_id": preview_file["id"],
"preview_file_extension": preview_file["extension"],
"preview_file_width": preview_file["width"],
"preview_file_height": preview_file["height"],
"preview_file_revision": preview_file["revision"],
"preview_file_status": preview_file["status"],
"preview_file_annotations": preview_file["annotations"],
"preview_file_previews": preview_file["previews"],
Expand Down Expand Up @@ -946,6 +963,8 @@ def _get_playlist_preview_file_list(preview_files):
"id": str(preview_file.id),
"revision": preview_file.revision,
"extension": preview_file.extension,
"width": preview_file.width,
"height": preview_file.height,
"status": str(preview_file.status),
"annotations": preview_file.annotations,
"created_at": fields.serialize_value(preview_file.created_at),
Expand Down
Loading

0 comments on commit f43b23c

Please sign in to comment.