From c81711de5335a50ae291f72e941fb6df279da416 Mon Sep 17 00:00:00 2001 From: Tobias Johansson Date: Wed, 3 Oct 2018 18:30:40 +0200 Subject: [PATCH] Video Duration: The duration of a video is now shown on thumbnails and bellow the video player Asset nodes now have a new field called "properties.duration_seconds". This holds a copy of the duration stored on the referenced video file and stays in sync using eve hooks. To migrate existing duration times from files to nodes you need to run the following: ./manage.py maintenance reconcile_node_video_duration -ag There are 2 more maintenance commands to be used to determine if there are any missing durations in either files or nodes: find_video_files_without_duration find_video_nodes_without_duration FFProbe is now used to detect what duration a video file has. Reviewed by Sybren. --- cloud/routes.py | 12 ++- src/scripts/tagged_assets.js | 4 +- tests/test_routes.py | 166 +++++++++++++++++++++++++++++++++++ 3 files changed, 179 insertions(+), 3 deletions(-) create mode 100644 tests/test_routes.py diff --git a/cloud/routes.py b/cloud/routes.py index 4c9a985..0a5bec8 100644 --- a/cloud/routes.py +++ b/cloud/routes.py @@ -278,6 +278,12 @@ def get_random_featured_nodes() -> typing.List[dict]: 'foreignField': '_id', 'as': 'node'}}, {'$unwind': {'path': '$node'}}, + {'$lookup': {"from": "users", + "localField": "node.user", + "foreignField": "_id", + "as": "node.user"}}, + {'$unwind': {'path': "$node.user"}}, + {'$match': {'node._deleted': {'$ne': True}}}, {'$project': {'url': True, 'name': True, 'summary': True, @@ -287,7 +293,10 @@ def get_random_featured_nodes() -> typing.List[dict]: 'node.permissions': True, 'node.picture': True, 'node.properties.content_type': True, - 'node.properties.url': True}}, + 'node.properties.duration_seconds': True, + 'node.properties.url': True, + 'node._created': True, + 'node.user.full_name': True,}}, ]) featured_node_documents = [] @@ -296,6 +305,7 @@ def get_random_featured_nodes() -> typing.List[dict]: # Turn the project-with-node doc into a node-with-project doc. node_document = node_info.pop('node') node_document['project'] = node_info + node_document['_id'] = str(node_document['_id']) node = Node(node_document) node.picture = get_file(node.picture, api=api) diff --git a/src/scripts/tagged_assets.js b/src/scripts/tagged_assets.js index b0598cc..05842fc 100644 --- a/src/scripts/tagged_assets.js +++ b/src/scripts/tagged_assets.js @@ -91,8 +91,8 @@ } } - if (node.video_duration){ - let card_duration = $('
' + node.video_duration + '
'); + if (node.properties.duration){ + let card_duration = $('
' + node.properties.duration + '
'); thumbnail_container.append(card_duration); } diff --git a/tests/test_routes.py b/tests/test_routes.py new file mode 100644 index 0000000..f12ef1a --- /dev/null +++ b/tests/test_routes.py @@ -0,0 +1,166 @@ +from datetime import timedelta + +from bson import ObjectId + +from abstract_cloud_test import AbstractCloudTest +from cloud.routes import get_random_featured_nodes + + +class RandomFeaturedNodeTest(AbstractCloudTest): + def setUp(self, **kwargs): + super().setUp(**kwargs) + + self.pid, _ = self.ensure_project_exists() + self.file_id, _ = self.ensure_file_exists(file_overrides={ + 'variations': [ + {'format': 'mp4', + 'duration': 75 # 01:15 + }, + ], + }) + + self.uid = self.create_user() + + from pillar.api.utils import utcnow + self.fake_now = utcnow() + + def test_random_feature_node_returns_3_nodes(self): + base_node = { + 'name': 'Just a node name', + 'project': self.pid, + 'description': '', + 'node_type': 'asset', + 'user': self.uid, + } + base_props = { + 'status': 'published', + 'file': self.file_id, + 'content_type': 'video', + 'order': 0 + } + + def create_asset(weeks): + return self.create_node({ + **base_node, + '_created': self.fake_now - timedelta(weeks=weeks), + 'properties': base_props, + }) + + all_asset_ids = [create_asset(i) for i in range(20)] + + with self.app.app_context(): + proj_col = self.app.db('projects') + proj_col.update( + {'_id': self.pid}, + {'$set': { + 'nodes_featured': all_asset_ids, + }}) + + with self.app.test_request_context(): + random_assets = get_random_featured_nodes() + actual_ids = [asset['_id'] for asset in random_assets] + + self.assertIs(len(random_assets), 3) + for aid in actual_ids: + self.assertIn(ObjectId(aid), all_asset_ids) + + def test_random_feature_ignore(self): + def assert_ignored(): + with self.app.test_request_context(): + random_assets = get_random_featured_nodes() + self.assertIs(len(random_assets), 0) + + base_node = { + 'name': 'Just a node name', + 'project': self.pid, + 'description': '', + 'node_type': 'asset', + 'user': self.uid, + } + base_props = { + 'status': 'published', + 'file': self.file_id, + 'content_type': 'video', + 'order': 0 + } + + node_id = self.create_node({ + **base_node, + '_created': self.fake_now - timedelta(days=5), + 'properties': base_props, + }) + + # Not featured, should be ignored + assert_ignored() + + # Featured but project is private, should be ignored + with self.app.app_context(): + proj_col = self.app.db('projects') + proj_col.update( + {'_id': self.pid}, + {'$set': { + 'nodes_featured': [node_id], + 'is_private': True, + }}) + assert_ignored() + + # Featured but node is deleted, should be ignored + with self.app.app_context(): + proj_col = self.app.db('projects') + proj_col.update( + {'_id': self.pid}, + {'$set': { + 'nodes_featured': [node_id], + 'is_private': False, + }}) + + node_col = self.app.db('nodes') + node_col.update( + {'_id': node_id}, + {'$set': { + '_deleted': True, + }}) + assert_ignored() + + def test_random_feature_node_data(self): + base_node = { + 'name': 'Just a node name', + 'project': self.pid, + 'description': '', + 'node_type': 'asset', + 'user': self.uid, + } + base_props = { + 'status': 'published', + 'file': self.file_id, + 'content_type': 'video', + 'duration_seconds': 75, + 'order': 0 + } + + node_id = self.create_node({ + **base_node, + '_created': self.fake_now, + 'properties': base_props, + }) + + with self.app.app_context(): + proj_col = self.app.db('projects') + proj_col.update( + {'_id': self.pid}, + {'$set': { + 'nodes_featured': [node_id], + }}) + + with self.app.test_request_context(): + random_assets = get_random_featured_nodes() + self.assertIs(len(random_assets), 1) + + asset = random_assets[0] + self.assertEquals('Just a node name', asset['name']) + self.assertEquals('Unittest project', asset['project']['name']) + self.assertEquals('video', asset['properties']['content_type']) + self.assertTrue(asset.properties.content_type == 'video') + self.assertEquals(self.fake_now, asset['_created']) + self.assertEquals(str(node_id), asset['_id']) + self.assertEquals(75, asset['properties']['duration_seconds']) \ No newline at end of file