diff --git a/cds/config.py b/cds/config.py index 31e83afab..fa87fde27 100644 --- a/cds/config.py +++ b/cds/config.py @@ -1084,6 +1084,11 @@ def _parse_env_bool(var_name, default=None): "qs": 'q=keywords.name:"VNR" OR keywords.name:"video news release"', }, ] +############################################################################### +# Record Landing page +############################################################################### + +DESCRIPTION_PREVIEW_LINES = 10 ############################################################################### # Security diff --git a/cds/modules/deposit/api.py b/cds/modules/deposit/api.py index b7ae89048..0e3a89384 100644 --- a/cds/modules/deposit/api.py +++ b/cds/modules/deposit/api.py @@ -77,7 +77,7 @@ ) from ..records.minters import cds_doi_generator, is_local_doi, report_number_minter from ..records.resolver import record_resolver -from ..records.utils import is_record, lowercase_value, parse_video_chapters +from ..records.utils import is_record, lowercase_value, parse_video_chapters, get_existing_chapter_frame_timestamps from ..records.validators import PartialDraft4Validator from ..records.permissions import is_public from .errors import DiscardConflict @@ -926,6 +926,10 @@ def _has_chapters_changed(self, old_record=None): if curr["seconds"] != old["seconds"] or curr["title"] != old["title"]: return True + if current_chapters and not get_existing_chapter_frame_timestamps(self): + # Chapters did not change, but chapter frames doesn't exist + return True + return False def _trigger_chapter_frame_extraction(self): diff --git a/cds/modules/flows/tasks.py b/cds/modules/flows/tasks.py index f9df6918a..d85024922 100644 --- a/cds/modules/flows/tasks.py +++ b/cds/modules/flows/tasks.py @@ -63,7 +63,7 @@ from ..opencast.api import OpenCast from ..opencast.error import RequestError from ..opencast.utils import get_qualities -from ..records.utils import to_string, parse_video_chapters +from ..records.utils import to_string, parse_video_chapters, get_existing_chapter_frame_timestamps from ..xrootd.utils import file_opener_xrootd from .deposit import index_deposit_project from .files import dispose_object_version, move_file_into_local @@ -834,7 +834,7 @@ def run(self, *args, **kwargs): raise ValueError("Video duration is 0 - cannot extract frames") # Check which timestamps already have frames - existing_timestamps = self._get_existing_chapter_frame_timestamps(deposit_video) + existing_timestamps = get_existing_chapter_frame_timestamps(deposit_video) def progress_updater(current_chapter): """Progress reporter.""" @@ -883,18 +883,6 @@ def progress_updater(current_chapter): self.log("Finished task {0}".format(kwargs["task_id"])) return "Created {0} chapter frames.".format(total_frames) - def _get_existing_chapter_frame_timestamps(self, deposit): - """Get timestamps of existing chapter frames.""" - master_file = CDSVideosFilesIterator.get_master_video_file(deposit) - frames = CDSVideosFilesIterator.get_video_frames(master_file) - - existing = set() - for f in frames: - tags = f.get("tags", {}) - if tags.get("is_chapter_frame") == "true": - existing.add(float(tags.get("timestamp"))) - return existing - @classmethod def _create_chapter_frames( cls, diff --git a/cds/modules/records/static/templates/cds_records/video/detail.html b/cds/modules/records/static/templates/cds_records/video/detail.html index d079dade0..b536dea08 100644 --- a/cds/modules/records/static/templates/cds_records/video/detail.html +++ b/cds/modules/records/static/templates/cds_records/video/detail.html @@ -235,7 +235,7 @@

{{translation.title.title}}

-

+

diff --git a/cds/modules/records/templates/cds_records/record_detail.html b/cds/modules/records/templates/cds_records/record_detail.html index ab0efd30e..2239c2c3b 100644 --- a/cds/modules/records/templates/cds_records/record_detail.html +++ b/cds/modules/records/templates/cds_records/record_detail.html @@ -100,6 +100,7 @@ record-views="{{ record_view_url }}" media-download-event-url="{{ user_action_media_download_url }}" related-query-url="{{ related_query_url }}" + preview-lines="{{ config.DESCRIPTION_PREVIEW_LINES }}" >
diff --git a/cds/modules/records/utils.py b/cds/modules/records/utils.py index f269dc5e5..2c6684512 100644 --- a/cds/modules/records/utils.py +++ b/cds/modules/records/utils.py @@ -32,6 +32,7 @@ from urllib import parse import six +from cds.modules.records.api import CDSVideosFilesIterator from flask import current_app, g, request from flask_security import current_user from invenio_db import db @@ -486,6 +487,19 @@ def to_string(value): return json.dumps(value) +def get_existing_chapter_frame_timestamps(deposit): + """Get timestamps of existing chapter frames.""" + master_file = CDSVideosFilesIterator.get_master_video_file(deposit) + frames = CDSVideosFilesIterator.get_video_frames(master_file) + + existing = set() + for f in frames: + tags = f.get("tags", {}) + if tags.get("is_chapter_frame") == "true": + existing.add(float(tags.get("timestamp"))) + return existing + + def parse_video_chapters(description): """Parse YouTube-style chapter timestamps from video description. diff --git a/cds/modules/theme/assets/bootstrap3/js/cds_records/cdsRecord.js b/cds/modules/theme/assets/bootstrap3/js/cds_records/cdsRecord.js index 2760868a3..0327b37de 100644 --- a/cds/modules/theme/assets/bootstrap3/js/cds_records/cdsRecord.js +++ b/cds/modules/theme/assets/bootstrap3/js/cds_records/cdsRecord.js @@ -61,6 +61,8 @@ function cdsRecordController($scope, $sce, $http, $timeout, $filter) { $scope.transcriptSearch = ""; $scope.chapters = []; $scope.activeTab = "chapters"; // Default to chapters tab + $scope.shortDescription = ""; + $scope.fullDescription = ""; const REQUEST_HEADERS = { "Content-Type": "application/json", @@ -188,9 +190,24 @@ function cdsRecordController($scope, $sce, $http, $timeout, $filter) { $scope.$watch("record", function (newVal) { if (newVal) { $scope.initVttLoad(newVal); + $scope.prepareDescriptions(newVal.metadata.description); } }); + $scope.prepareDescriptions = function (description) { + if (!description) { + $scope.shortDescription = "No description"; + $scope.fullDescription = "No description"; + return; + } + + const lines = description.split(/\r?\n/); + const firstTen = lines.slice(0, $scope.DESCRIPTION_PREVIEW_LINES).join("\n"); + + $scope.shortDescription = $scope.processDescriptionWithClickableTimestamps(firstTen); + $scope.fullDescription = $scope.processDescriptionWithClickableTimestamps(description); + }; + $scope.initVttLoad = function (record) { const files = record.metadata._files || []; @@ -644,6 +661,8 @@ function cdsRecordView($http) { scope.relatedQueryUrl = attrs.relatedQueryUrl; + scope.DESCRIPTION_PREVIEW_LINES = parseInt(attrs.previewLines, 10) || 10; + // Get the record object and make it available to the scope $http.get(attrs.record).then( function (response) { diff --git a/tests/unit/test_flows_tasks.py b/tests/unit/test_flows_tasks.py index 71f2b1ffe..ce9ca9eee 100644 --- a/tests/unit/test_flows_tasks.py +++ b/tests/unit/test_flows_tasks.py @@ -725,7 +725,7 @@ def test_extract_chapter_frames_task_cleanup(app, db, bucket, video, users): mock.patch.object(ExtractChapterFramesTask, "_base_payload", {"tags": {"duration": 100}}), \ mock.patch("cds.modules.flows.tasks.ExtractFramesTask._create_object") as mock_create_object, \ mock.patch("cds.modules.flows.tasks.ExtractChapterFramesTask._build_chapter_vtt"), \ - mock.patch("cds.modules.flows.tasks.ExtractChapterFramesTask._get_existing_chapter_frame_timestamps") as mock_existing: + mock.patch("cds.modules.flows.tasks.get_existing_chapter_frame_timestamps") as mock_existing: # Track created & disposed timestamps (floats) created_timestamps = set()