Skip to content

Commit 8d386f6

Browse files
Enable videos for uploading additional files
1 parent f7e1156 commit 8d386f6

File tree

6 files changed

+213
-63
lines changed

6 files changed

+213
-63
lines changed

cds/modules/deposit/api.py

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -860,15 +860,7 @@ def _rename_subtitles(self):
860860
subtitle_obj_key = "{}_{}.vtt".format(
861861
self["report_number"][0], match.group("iso_lang")
862862
)
863-
obj = ObjectVersion.create(
864-
bucket=subtitle_obj.bucket,
865-
key=subtitle_obj_key,
866-
_file_id=subtitle_obj.file_id,
867-
)
868-
# copy tags to the newly created object version
869-
for tag in subtitle_obj.tags:
870-
tag.object_version = obj
871-
subtitle_obj.remove()
863+
subtitle_obj.key = subtitle_obj_key
872864

873865
def _rename_master_file(self, master_file):
874866
"""Rename master file."""

cds/modules/deposit/ext.py

Lines changed: 31 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,13 @@
2525
"""CDSDeposit app for Webhook receivers."""
2626

2727
import re
28+
import mimetypes
2829

2930
from invenio_base.signals import app_loaded
3031
from invenio_db import db
3132
from invenio_files_rest.models import ObjectVersionTag
3233
from invenio_files_rest.signals import file_uploaded
34+
from invenio_files_rest.errors import InvalidKeyError
3335
from invenio_indexer.signals import before_record_index
3436
from invenio_records_files.utils import sorted_files_from_bucket
3537

@@ -45,38 +47,37 @@
4547

4648
def _create_tags(obj):
4749
"""Create additional tags for file."""
48-
# Subtitle file
49-
pattern = re.compile(".*_([a-zA-Z]{2})\.vtt$")
50+
pattern_subtitle = re.compile(r".*_([a-zA-Z]{2})\.vtt$")
51+
pattern_poster = re.compile(r"^poster\.(jpg|png)$")
52+
53+
# Get the media_type and content_type(file ext)
54+
file_name = obj.key
55+
mimetypes.add_type("subtitle/vtt", ".vtt")
56+
guessed_type = mimetypes.guess_type(file_name)[0]
57+
if guessed_type is None:
58+
raise InvalidKeyError(description=f"Unsupported File: {file_name}")
59+
60+
media_type = guessed_type.split("/")[0]
61+
file_ext = guessed_type.split("/")[1]
62+
5063
with db.session.begin_nested():
51-
# language tag
52-
found = pattern.findall(obj.key)
53-
if len(found) == 1:
54-
lang = found[0]
55-
ObjectVersionTag.create_or_update(obj, "language", lang)
56-
else:
57-
# clean to be sure there is no some previous value
58-
ObjectVersionTag.delete(obj, "language")
59-
# other tags
60-
ObjectVersionTag.create_or_update(obj, "content_type", "vtt")
61-
ObjectVersionTag.create_or_update(obj, "context_type", "subtitle")
62-
ObjectVersionTag.create_or_update(obj, "media_type", "subtitle")
63-
# refresh object
64-
db.session.add(obj)
64+
ObjectVersionTag.create_or_update(obj, "content_type", file_ext)
65+
ObjectVersionTag.create_or_update(obj, "media_type", media_type)
66+
if file_ext == "vtt":
67+
# language tag
68+
match = pattern_subtitle.search(file_name)
69+
if match:
70+
ObjectVersionTag.create_or_update(obj, "language", match.group(1))
71+
else:
72+
ObjectVersionTag.delete(obj, "language")
73+
# other tags
74+
ObjectVersionTag.create_or_update(obj, "content_type", "vtt")
75+
ObjectVersionTag.create_or_update(obj, "context_type", "subtitle")
76+
# poster tag
77+
elif pattern_poster.match(file_name):
78+
ObjectVersionTag.create_or_update(obj, "context_type", "poster")
6579

66-
# Poster frame
67-
pattern = re.compile("^poster\.(jpg|png)$")
68-
try:
69-
poster = pattern.findall(obj.key)
70-
if poster:
71-
ext = pattern.findall(poster.key)[0]
72-
# frame tags
73-
ObjectVersionTag.create_or_update(poster, "content_type", ext)
74-
ObjectVersionTag.create_or_update(poster, "context_type", "poster")
75-
ObjectVersionTag.create_or_update(poster, "media_type", "image")
76-
# refresh object
77-
db.session.add(poster)
78-
except IndexError:
79-
return
80+
db.session.add(obj)
8081

8182

8283
def create_tags_on_file_upload(sender, obj):

cds/modules/deposit/static/templates/cds_deposit/deposits.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ <h5 class="text-muted"><strong>Tips</strong></h5>
3939
<h3>Click here to select videos to upload</h3>
4040
</div>
4141
<p class="cds-deposit-box-upload-description">You can also <strong>Drag & Drop</strong> video files here</p>
42+
<p class="text-muted mt-20">supported files <mark>{{ $ctrl.videoExtensions }}</mark></p>
4243
</div>
4344
</div>
4445
</div>

cds/modules/deposit/static/templates/cds_deposit/types/video/uploader.html

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,7 @@ <h5 class="text-muted"><strong>Tips and suggestions</strong></h5>
283283
</ul>
284284
</div>
285285
<!-- Error alert -->
286+
286287
<div class="cds-deposit-box" ng-if="!$ctrl.cdsDepositCtrl.isPublished()">
287288
<div
288289
ngf-drag-over-class="'cds-deposit-dragover'"
@@ -309,13 +310,36 @@ <h4>Upload complimentary files for this video</h4>
309310
<div class="text-muted">
310311
<h5 class="text-muted"><strong>Tips and suggestions</strong></h5>
311312
<ul>
312-
<li>To replace the video file, just upload another video.</li>
313+
<li>To replace the video file, just upload another video here.</li>
313314
</ul>
315+
<div
316+
ngf-drag-over-class="{accept: 'cds-deposit-dragover', delay:100}"
317+
ngf-drop=""
318+
ngf-change="$ctrl.replaceMasterFile($newFiles, $invalidFiles)"
319+
ngf-select=""
320+
ngf-model-options="{allowInvalid: false}"
321+
ngf-max-size="500GB"
322+
ngf-multiple="true"
323+
ngf-accept="'{{$ctrl.cdsDepositsCtrl.videoExtensions}}'"
324+
ngf-pattern="'{{$ctrl.cdsDepositsCtrl.videoExtensions}}'"
325+
>
326+
<div class="pa-10 cds-deposit-box-upload-wrapper text-center">
327+
<p class="cds-deposit-box-upload-icon mb-20">
328+
<i class="fa fa-2x fa-video-camera" aria-hidden="true"></i>
329+
</p>
330+
<div class="cds-deposit-box-upload-content">
331+
<div class="cds-deposit-box-upload-title">
332+
<h4>To replace the video file, just upload a video here.</h4>
333+
</div>
334+
<p class="cds-deposit-box-upload-description"> Or Drag & Drop files</p>
335+
</div>
336+
</div>
337+
</div>
314338
</div>
315339
</div>
316340
<div ng-if="$ctrl.cdsDepositCtrl.isPublished()" class="cds-deposit-box text-muted">
317341
<h5 class="text-muted"><strong>Tips and suggestions</strong></h5>
318342
<ul>
319343
<li>Click the <strong>Edit</strong> button on the top right corner to add more files.</li>
320344
</ul>
321-
</div>
345+
</div>

cds/modules/theme/assets/bootstrap3/js/cds_deposit/avc/components/cdsUploader.js

Lines changed: 90 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,10 @@ function cdsUploaderCtrl(
115115
if (!upload.key) {
116116
upload.key = upload.name;
117117
}
118-
if (that.cdsDepositsCtrl.isVideoFile(upload.key)) {
118+
if (
119+
!upload.isAdditional &&
120+
that.cdsDepositsCtrl.isVideoFile(upload.key)
121+
) {
119122
_subpromise = Upload.http(_startWorkflow(upload, response));
120123
} else {
121124
var d = $q.defer();
@@ -278,25 +281,44 @@ function cdsUploaderCtrl(
278281
}
279282
// Remove any invalid files
280283
_files = _.difference(_files, invalidFiles || []);
284+
285+
// Filter out files without a valid MIME type or with zero size
286+
_files = _files.filter((file) => {
287+
if (!file.type || file.type.trim() === "") {
288+
toaster.pop(
289+
"warning",
290+
"Invalid File Type",
291+
`The file ${file.name} has no valid type.`
292+
);
293+
return false; // Exclude invalid files
294+
}
295+
296+
if (!file.size || file.size === 0) {
297+
toaster.pop(
298+
"warning",
299+
"Empty File",
300+
`The file ${file.name} is empty and cannot be uploaded.`
301+
);
302+
return false; // Exclude zero-size files
303+
}
304+
305+
return true;
306+
});
307+
281308
// Make sure they have proper metadata
282309
angular.forEach(_files, function (file) {
283310
file.key = file.name;
284311
file.local = !file.receiver;
312+
file.isAdditional = true;
285313
// Add any extra paramemters to the files
286314
if (extraHeaders) {
287315
file.headers = extraHeaders;
288316
}
317+
file.headers = {
318+
"X-Invenio-File-Tags": "context_type=additional_file",
319+
};
289320
});
290321

291-
// Add the files to the list
292-
var masterFile = that.cdsDepositCtrl.findMasterFile() || {};
293-
var videoFiles = _.values(
294-
that.cdsDepositsCtrl.filterOutFiles(_files).videos
295-
);
296-
297-
// Exclude video files
298-
_files = _.difference(_files, videoFiles);
299-
300322
// Find if any of the existing files has been replaced
301323
// (file with same filename), and if yes remove it from the existing
302324
// file list (aka from the interface).
@@ -323,6 +345,44 @@ function cdsUploaderCtrl(
323345
Array.prototype.push.apply(that.files, _files);
324346
// Add the files to the queue
325347
Array.prototype.push.apply(that.queue, _files);
348+
349+
// Start upload automatically if the option is selected
350+
if (that.autoStartUpload) {
351+
that.upload();
352+
}
353+
};
354+
355+
this.replaceMasterFile = function (_files, invalidFiles) {
356+
// Do nothing if files array is empty
357+
if (!_files) {
358+
return;
359+
}
360+
// Remove any invalid files
361+
_files = _.difference(_files, invalidFiles || []);
362+
// Make sure they have proper metadata
363+
angular.forEach(_files, function (file) {
364+
file.key = file.name;
365+
file.local = !file.receiver;
366+
});
367+
368+
// Add the files to the list
369+
var masterFile = that.cdsDepositCtrl.findMasterFile() || {};
370+
var videoFiles = _.values(
371+
that.cdsDepositsCtrl.filterOutFiles(_files).videos
372+
);
373+
374+
if ((invalidFiles || []).length > 0) {
375+
// Push a notification
376+
toaster.pop({
377+
type: "error",
378+
title:
379+
"Invalid file(s) for " +
380+
(that.cdsDepositCtrl.record.title.title || "video."),
381+
body: _.map(invalidFiles, "name").join(", "),
382+
bodyOutputType: "trustedHtml",
383+
});
384+
}
385+
326386
if (!that.cdsDepositCtrl.master) {
327387
// Check for new master file
328388
var newMasterFile = videoFiles[0];
@@ -358,11 +418,6 @@ function cdsUploaderCtrl(
358418
});
359419
}
360420
}
361-
362-
// Start upload automatically if the option is selected
363-
if (that.autoStartUpload) {
364-
that.upload();
365-
}
366421
};
367422

368423
// Prepare file request
@@ -431,13 +486,26 @@ function cdsUploaderCtrl(
431486
function error(response) {
432487
// Inform the parents
433488
$scope.$emit("cds.deposit.error", response);
434-
// Error uploading notification
435-
toaster.pop({
436-
type: "error",
437-
title: "Error uploading the file(s).",
438-
body: (_.map(response, "config.data.key") || []).join(", "),
439-
bodyOutputType: "trustedHtml",
440-
});
489+
// Check if the response contains the error message
490+
if (
491+
response.status === 400 &&
492+
response.data &&
493+
response.data.message
494+
) {
495+
toaster.pop({
496+
type: "error",
497+
title: response.data.message,
498+
bodyOutputType: "trustedHtml",
499+
});
500+
} else {
501+
// Error uploading notification
502+
toaster.pop({
503+
type: "error",
504+
title: "Error uploading the file(s).",
505+
body: (_.map(response, "config.data.key") || []).join(", "),
506+
bodyOutputType: "trustedHtml",
507+
});
508+
}
441509
}
442510
)
443511
.finally(function done() {

tests/unit/test_video_rest.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
import copy
2929
import json
30+
from io import BytesIO
3031
from time import sleep
3132

3233
import mock
@@ -44,6 +45,7 @@
4445
from invenio_db import db
4546
from invenio_indexer.api import RecordIndexer
4647
from invenio_search import current_search_client
48+
from invenio_files_rest.models import ObjectVersion
4749

4850
from cds.modules.deposit.api import deposit_project_resolver, deposit_video_resolver
4951
from cds.modules.deposit.receivers import datacite_register_after_publish
@@ -588,6 +590,68 @@ def test_mint_doi_with_cli(
588590
doi, f"https://videos.cern.ch/record/{recid}"
589591
)
590592

593+
def test_additional_files(
594+
api_app,
595+
users,
596+
location,
597+
json_headers,
598+
json_partial_project_headers,
599+
json_partial_video_headers,
600+
deposit_metadata,
601+
video_deposit_metadata,
602+
project_deposit_metadata,
603+
):
604+
"""Test video publish without DOI, then mint DOI using CLI."""
605+
api_app.config["DEPOSIT_DATACITE_MINTING_ENABLED"] = True
606+
607+
with api_app.test_client() as client:
608+
# Log in as the first user
609+
login_user(User.query.get(users[0]))
610+
611+
# Create a new project
612+
project_dict = _create_new_project(
613+
client, json_partial_project_headers, project_deposit_metadata
614+
)
615+
616+
# Add a new empty video
617+
video_dict = _add_video_info_to_project(
618+
client, json_partial_video_headers, project_dict, video_deposit_metadata
619+
)
620+
621+
video_depid = video_dict["metadata"]["_deposit"]["id"]
622+
video_deposit = deposit_video_resolver(video_depid)
623+
video_deposit_id = video_deposit["_deposit"]["id"]
624+
bucket_id = video_deposit["_buckets"]["deposit"]
625+
626+
# Upload additional file
627+
key = "test.mp4"
628+
headers = {
629+
"X-Invenio-File-Tags": "context_type=additional_file"
630+
}
631+
resp = client.put(
632+
url_for("invenio_files_rest.object_api", bucket_id=bucket_id, key=key),
633+
input_stream=BytesIO(b"updated_content"),
634+
headers=headers,
635+
)
636+
assert resp.status_code == 200
637+
638+
tags = ObjectVersion.get(bucket_id, key).get_tags()
639+
assert tags["context_type"] == "additional_file"
640+
assert tags["content_type"] == "mp4"
641+
assert tags["media_type"] == "video"
642+
643+
# Upload invalid file and return 400
644+
key = "test"
645+
headers = {
646+
"X-Invenio-File-Tags": "context_type=additional_file"
647+
}
648+
resp = client.put(
649+
url_for("invenio_files_rest.object_api", bucket_id=bucket_id, key=key),
650+
input_stream=BytesIO(b"updated_content"),
651+
headers=headers,
652+
)
653+
assert resp.status_code == 400
654+
591655

592656
def _deposit_edit(client, json_headers, id):
593657
"""Post action to edit deposit."""

0 commit comments

Comments
 (0)