1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 package org.opencastproject.editor;
23
24 import static java.util.Collections.emptyList;
25 import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
26 import static org.opencastproject.util.data.Tuple.tuple;
27
28 import org.opencastproject.assetmanager.api.AssetManager;
29 import org.opencastproject.assetmanager.api.AssetManagerException;
30 import org.opencastproject.assetmanager.util.WorkflowPropertiesUtil;
31 import org.opencastproject.assetmanager.util.Workflows;
32 import org.opencastproject.editor.api.EditingData;
33 import org.opencastproject.editor.api.EditorService;
34 import org.opencastproject.editor.api.EditorServiceException;
35 import org.opencastproject.editor.api.ErrorStatus;
36 import org.opencastproject.editor.api.LockData;
37 import org.opencastproject.editor.api.SegmentData;
38 import org.opencastproject.editor.api.TrackData;
39 import org.opencastproject.editor.api.TrackSubData;
40 import org.opencastproject.editor.api.WorkflowData;
41 import org.opencastproject.elasticsearch.api.SearchIndexException;
42 import org.opencastproject.elasticsearch.index.ElasticsearchIndex;
43 import org.opencastproject.elasticsearch.index.objects.event.Event;
44 import org.opencastproject.index.service.api.IndexService;
45 import org.opencastproject.index.service.exception.IndexServiceException;
46 import org.opencastproject.index.service.impl.util.EventUtils;
47 import org.opencastproject.mediapackage.Attachment;
48 import org.opencastproject.mediapackage.Catalog;
49 import org.opencastproject.mediapackage.MediaPackage;
50 import org.opencastproject.mediapackage.MediaPackageElement;
51 import org.opencastproject.mediapackage.MediaPackageElementBuilder;
52 import org.opencastproject.mediapackage.MediaPackageElementBuilderFactory;
53 import org.opencastproject.mediapackage.MediaPackageElementFlavor;
54 import org.opencastproject.mediapackage.Publication;
55 import org.opencastproject.mediapackage.Track;
56 import org.opencastproject.mediapackage.attachment.AttachmentImpl;
57 import org.opencastproject.metadata.dublincore.DublinCoreMetadataCollection;
58 import org.opencastproject.metadata.dublincore.EventCatalogUIAdapter;
59 import org.opencastproject.metadata.dublincore.MetadataJson;
60 import org.opencastproject.metadata.dublincore.MetadataList;
61 import org.opencastproject.security.api.AuthorizationService;
62 import org.opencastproject.security.api.Organization;
63 import org.opencastproject.security.api.SecurityConstants;
64 import org.opencastproject.security.api.SecurityService;
65 import org.opencastproject.security.api.UnauthorizedException;
66 import org.opencastproject.security.api.User;
67 import org.opencastproject.security.urlsigning.exception.UrlSigningException;
68 import org.opencastproject.security.urlsigning.service.UrlSigningService;
69 import org.opencastproject.security.urlsigning.utils.UrlSigningServiceOsgiUtil;
70 import org.opencastproject.smil.api.SmilException;
71 import org.opencastproject.smil.api.SmilResponse;
72 import org.opencastproject.smil.api.SmilService;
73 import org.opencastproject.smil.entity.api.Smil;
74 import org.opencastproject.smil.entity.media.api.SmilMediaObject;
75 import org.opencastproject.smil.entity.media.container.api.SmilMediaContainer;
76 import org.opencastproject.smil.entity.media.element.api.SmilMediaElement;
77 import org.opencastproject.util.MimeType;
78 import org.opencastproject.util.MimeTypes;
79 import org.opencastproject.util.NotFoundException;
80 import org.opencastproject.util.data.Tuple;
81 import org.opencastproject.workflow.api.ConfiguredWorkflow;
82 import org.opencastproject.workflow.api.WorkflowDatabaseException;
83 import org.opencastproject.workflow.api.WorkflowDefinition;
84 import org.opencastproject.workflow.api.WorkflowInstance;
85 import org.opencastproject.workflow.api.WorkflowService;
86 import org.opencastproject.workflow.api.WorkflowUtil;
87 import org.opencastproject.workflow.handler.distribution.InternalPublicationChannel;
88 import org.opencastproject.workspace.api.Workspace;
89
90 import org.apache.commons.io.FileUtils;
91 import org.apache.commons.io.IOUtils;
92 import org.apache.commons.lang3.BooleanUtils;
93 import org.apache.commons.lang3.StringUtils;
94 import org.osgi.service.component.ComponentContext;
95 import org.osgi.service.component.annotations.Activate;
96 import org.osgi.service.component.annotations.Component;
97 import org.osgi.service.component.annotations.Modified;
98 import org.osgi.service.component.annotations.Reference;
99 import org.slf4j.Logger;
100 import org.slf4j.LoggerFactory;
101 import org.xml.sax.SAXException;
102
103 import java.awt.datatransfer.MimeTypeParseException;
104 import java.io.ByteArrayInputStream;
105 import java.io.File;
106 import java.io.IOException;
107 import java.io.InputStream;
108 import java.net.URI;
109 import java.net.URISyntaxException;
110 import java.nio.charset.StandardCharsets;
111 import java.text.MessageFormat;
112 import java.util.ArrayList;
113 import java.util.Arrays;
114 import java.util.Base64;
115 import java.util.Collection;
116 import java.util.Collections;
117 import java.util.Comparator;
118 import java.util.Dictionary;
119 import java.util.HashMap;
120 import java.util.HashSet;
121 import java.util.Iterator;
122 import java.util.List;
123 import java.util.Map;
124 import java.util.Objects;
125 import java.util.Optional;
126 import java.util.Set;
127 import java.util.UUID;
128 import java.util.stream.Collectors;
129
130 import javax.ws.rs.WebApplicationException;
131 import javax.xml.bind.JAXBException;
132
133
134 @Component(
135 property = {
136 "service.description=Editor Service"
137 },
138 immediate = true,
139 service = EditorService.class
140 )
141 public class EditorServiceImpl implements EditorService {
142
143
144 private static final Logger logger = LoggerFactory.getLogger(EditorServiceImpl.class);
145
146
147 private static final String EDITOR_WORKFLOW_TAG = "editor";
148
149 private static EditorLock editorLock;
150
151 private long expireSeconds = UrlSigningServiceOsgiUtil.DEFAULT_URL_SIGNING_EXPIRE_DURATION;
152
153 private Boolean signWithClientIP = UrlSigningServiceOsgiUtil.DEFAULT_SIGN_WITH_CLIENT_IP;
154
155
156 private IndexService index;
157 private AssetManager assetManager;
158 private SecurityService securityService;
159 private SmilService smilService;
160 private UrlSigningService urlSigningService;
161 private WorkflowService workflowService;
162 private Workspace workspace;
163 private AuthorizationService authorizationService;
164
165
166 private MediaPackageElementFlavor smilCatalogFlavor;
167 private String previewVideoSubtype;
168 private String previewTag;
169 private String previewSubtype;
170 private String waveformSubtype;
171 private String thumbnailSubType;
172 private MediaPackageElementFlavor smilSilenceFlavor;
173 private ElasticsearchIndex searchIndex;
174 private MediaPackageElementFlavor captionsFlavor;
175 private MediaPackageElementFlavor chapterFlavor;
176 private String thumbnailWfProperty;
177 private List<MediaPackageElementFlavor> thumbnailSourcePrimary;
178 private String distributionDirectory;
179 private Boolean localPublication = null;
180
181 private static final String DEFAULT_PREVIEW_SUBTYPE = "source";
182 private static final String DEFAULT_PREVIEW_TAG = "editor";
183 private static final String DEFAULT_WAVEFORM_SUBTYPE = "waveform";
184 private static final String DEFAULT_SMIL_CATALOG_FLAVOR = "smil/cutting";
185 private static final String DEFAULT_SMIL_CATALOG_TAGS = "archive";
186 private static final String DEFAULT_SMIL_SILENCE_FLAVOR = "*/silence";
187 private static final String DEFAULT_PREVIEW_VIDEO_SUBTYPE = "video+preview";
188 private static final String DEFAULT_CAPTIONS_FLAVOR = "captions/*";
189 private static final String DEFAULT_CHAPTER_FLAVOR = "chapters/*";
190 private static final String DEFAULT_THUMBNAIL_SUBTYPE = "player+preview";
191 private static final String DEFAULT_THUMBNAIL_WF_PROPERTY = "thumbnail_edited";
192 private static final List<MediaPackageElementFlavor> DEFAULT_THUMBNAIL_PRIORITY_FLAVOR = new ArrayList<>();
193 private static final int DEFAULT_LOCK_TIMEOUT_SECONDS = 300;
194 private static final int DEFAULT_LOCK_REFRESH_SECONDS = 60;
195
196 public static final String OPT_PREVIEW_SUBTYPE = "preview.subtype";
197 public static final String OPT_PREVIEW_TAG = "preview.tag";
198 public static final String OPT_WAVEFORM_SUBTYPE = "waveform.subtype";
199 public static final String OPT_SMIL_CATALOG_FLAVOR = "smil.catalog.flavor";
200 public static final String OPT_SMIL_CATALOG_TAGS = "smil.catalog.tags";
201 public static final String OPT_SMIL_SILENCE_FLAVOR = "smil.silence.flavor";
202 public static final String OPT_PREVIEW_VIDEO_SUBTYPE = "preview.video.subtype";
203 public static final String OPT_CAPTIONS_FLAVOR = "captions.flavor";
204 public static final String OPT_CHAPTER_FLAVOR = "chapter.flavor";
205 public static final String OPT_THUMBNAILSUBTYPE = "thumbnail.subtype";
206 public static final String OPT_THUMBNAIL_WF_PROPERTY = "thumbnail.workflow.property";
207 public static final String OPT_THUMBNAIL_PRIORITY_FLAVOR = "thumbnail.priority.flavor";
208 public static final String OPT_LOCAL_PUBLICATION = "publication.local";
209 public static final String OPT_LOCK_ENABLED = "lock.enable";
210 public static final String OPT_LOCK_TIMEOUT = "lock.release.after.seconds";
211 public static final String OPT_LOCK_REFRESH = "lock.refresh.after.seconds";
212
213 private Boolean lockingActive;
214 private int lockRefresh = DEFAULT_LOCK_REFRESH_SECONDS;
215 private int lockTimeout = DEFAULT_LOCK_TIMEOUT_SECONDS;
216
217 private final Set<String> smilCatalogTagSet = new HashSet<>();
218
219 @Reference
220 void setSecurityService(SecurityService securityService) {
221 this.securityService = securityService;
222 }
223
224 @Reference
225 void setSmilService(SmilService smilService) {
226 this.smilService = smilService;
227 }
228
229 @Reference
230 void setWorkflowService(WorkflowService workflowService) {
231 this.workflowService = workflowService;
232 }
233
234 @Reference
235 void setWorkspace(Workspace workspace) {
236 this.workspace = workspace;
237 }
238
239 @Reference
240 void setUrlSigningService(UrlSigningService urlSigningService) {
241 this.urlSigningService = urlSigningService;
242 }
243
244 @Reference
245 void setAssetManager(AssetManager assetManager) {
246 this.assetManager = assetManager;
247 }
248
249 @Reference
250 public void setElasticsearchIndex(ElasticsearchIndex elasticsearchIndex) {
251 this.searchIndex = elasticsearchIndex;
252 }
253
254 @Reference
255 public void setIndexService(IndexService index) {
256 this.index = index;
257 }
258
259 @Reference
260 public void setAuthorizationService(AuthorizationService authorizationService) {
261 this.authorizationService = authorizationService;
262 }
263
264 public MediaPackageElementFlavor getSmilCatalogFlavor() {
265 return smilCatalogFlavor;
266 }
267
268 public Set<String> getSmilCatalogTags() {
269 return smilCatalogTagSet;
270 }
271
272 public String getPreviewVideoSubtype() {
273 return previewVideoSubtype;
274 }
275
276 public MediaPackageElementFlavor getSmilSilenceFlavor() {
277 return smilSilenceFlavor;
278 }
279
280 private String getPreviewSubtype() {
281 return previewSubtype;
282 }
283
284 public String getPreviewTag() {
285 return previewTag;
286 }
287
288 private String getWaveformSubtype() {
289 return waveformSubtype;
290 }
291
292 private String getThumbnailSubtype() {
293 return thumbnailSubType;
294 }
295
296 @Activate
297 @Modified
298 public void activate(ComponentContext cc) {
299 Dictionary<String, Object> properties = cc.getProperties();
300 if (properties == null) {
301 return;
302 }
303
304 expireSeconds = UrlSigningServiceOsgiUtil.getUpdatedSigningExpiration(properties, this.getClass().getSimpleName());
305 signWithClientIP = UrlSigningServiceOsgiUtil.getUpdatedSignWithClientIP(properties,this.getClass().getSimpleName());
306
307 previewTag = Objects.toString(properties.get(OPT_PREVIEW_TAG), DEFAULT_PREVIEW_TAG);
308 logger.debug("Preview tag configuration set to '{}'", previewTag);
309
310
311 previewSubtype = Objects.toString(properties.get(OPT_PREVIEW_SUBTYPE), DEFAULT_PREVIEW_SUBTYPE);
312 logger.debug("Preview subtype configuration set to '{}'", previewSubtype);
313
314
315 waveformSubtype = Objects.toString(properties.get(OPT_WAVEFORM_SUBTYPE), DEFAULT_WAVEFORM_SUBTYPE);
316 logger.debug("Waveform subtype configuration set to '{}'", waveformSubtype);
317
318
319 smilCatalogFlavor = MediaPackageElementFlavor.parseFlavor(
320 StringUtils.defaultString((String) properties.get(OPT_SMIL_CATALOG_FLAVOR), DEFAULT_SMIL_CATALOG_FLAVOR));
321 logger.debug("Smil catalog flavor configuration set to '{}'", smilCatalogFlavor);
322
323
324 String tags = Objects.toString(properties.get(OPT_SMIL_CATALOG_TAGS), DEFAULT_SMIL_CATALOG_TAGS);
325 String[] smilCatalogTags = StringUtils.split(tags, ",");
326 smilCatalogTagSet.clear();
327 if (smilCatalogTags != null) {
328 smilCatalogTagSet.addAll(Arrays.asList(smilCatalogTags));
329 }
330
331
332 smilSilenceFlavor = MediaPackageElementFlavor.parseFlavor(
333 StringUtils.defaultString((String) properties.get(OPT_SMIL_SILENCE_FLAVOR), DEFAULT_SMIL_SILENCE_FLAVOR));
334 logger.debug("Smil silence flavor configuration set to '{}'", smilSilenceFlavor);
335
336
337 previewVideoSubtype = Objects.toString(properties.get(OPT_PREVIEW_VIDEO_SUBTYPE), DEFAULT_PREVIEW_VIDEO_SUBTYPE);
338
339 logger.debug("Preview video subtype set to '{}'", previewVideoSubtype);
340
341
342 captionsFlavor = MediaPackageElementFlavor.parseFlavor(
343 StringUtils.defaultString((String) properties.get(OPT_CAPTIONS_FLAVOR), DEFAULT_CAPTIONS_FLAVOR));
344 logger.debug("Caption flavor set to '{}'", captionsFlavor);
345
346
347 chapterFlavor = MediaPackageElementFlavor.parseFlavor(
348 StringUtils.defaultString((String) properties.get(OPT_CHAPTER_FLAVOR), DEFAULT_CHAPTER_FLAVOR));
349 logger.debug("Chapter flavor set to '{}'", chapterFlavor);
350
351 thumbnailSubType = Objects.toString(properties.get(OPT_THUMBNAILSUBTYPE), DEFAULT_THUMBNAIL_SUBTYPE);
352 logger.debug("Thumbnail subtype set to '{}'", thumbnailSubType);
353
354 thumbnailWfProperty = Objects.toString(properties.get(OPT_THUMBNAIL_WF_PROPERTY), DEFAULT_THUMBNAIL_WF_PROPERTY);
355 logger.debug("Thumbnail workflow property set to '{}'", thumbnailWfProperty);
356
357 String thumbnailPriorities = Objects.toString(properties.get(OPT_THUMBNAIL_PRIORITY_FLAVOR));
358 if ("null".equals(thumbnailPriorities) || thumbnailPriorities.isEmpty()) {
359 thumbnailSourcePrimary = DEFAULT_THUMBNAIL_PRIORITY_FLAVOR;
360 } else {
361 thumbnailSourcePrimary = Arrays.stream(thumbnailPriorities.split(",", -1))
362 .map(MediaPackageElementFlavor::parseFlavor)
363 .collect(Collectors.toList());
364 }
365
366 String localPublicationConfig = Objects.toString(properties.get(OPT_LOCAL_PUBLICATION), "auto");
367 if (!"auto".equals(localPublicationConfig)) {
368
369 localPublication = BooleanUtils.toBoolean(localPublicationConfig);
370 }
371
372 distributionDirectory = cc.getBundleContext().getProperty("org.opencastproject.download.directory");
373 if (StringUtils.isEmpty(distributionDirectory)) {
374 final String storageDir = cc.getBundleContext().getProperty("org.opencastproject.storage.dir");
375 if (StringUtils.isNotEmpty(storageDir)) {
376 distributionDirectory = new File(storageDir, "downloads").getPath();
377 }
378 }
379 logger.debug("Thumbnail track priority set to '{}'", thumbnailSourcePrimary);
380
381 lockingActive = Boolean.parseBoolean(StringUtils.trimToEmpty((String) properties.get(OPT_LOCK_ENABLED)));
382
383 try {
384 lockTimeout = Integer.parseUnsignedInt(
385 Objects.toString(properties.get(OPT_LOCK_TIMEOUT)));
386 } catch (NumberFormatException e) {
387 logger.info("Configuration {} contains invalid value, defaulting to {}", OPT_LOCK_TIMEOUT, lockTimeout);
388 }
389
390 try {
391 lockRefresh = Integer.parseUnsignedInt(
392 Objects.toString(properties.get(OPT_LOCK_REFRESH)));
393 } catch (NumberFormatException e) {
394 logger.info("Configuration {} contains invalid value, defaulting to {}", OPT_LOCK_REFRESH, lockRefresh);
395 }
396
397 editorLock = new EditorLock(lockTimeout);
398
399 }
400
401
402
403
404
405
406
407
408
409 private boolean isLocal(URI uri) {
410 var path = uri.normalize().getPath();
411 if (!path.startsWith("/static/")) {
412 return false;
413 }
414 var localFile = new File(distributionDirectory, path.substring("/static".length()));
415 return localFile.exists();
416 }
417
418 private Boolean elementHasPreviewTag(MediaPackageElement element) {
419 return element.getTags() != null
420 && Arrays.asList(element.getTags()).contains(getPreviewTag());
421 }
422
423 private Boolean elementHasPreviewFlavor(MediaPackageElement element) {
424 return element.getFlavor() != null
425 && getPreviewSubtype().equals(element.getFlavor().getSubtype());
426 }
427
428 private Boolean elementHasWaveformFlavor(MediaPackageElement element) {
429 return element.getFlavor() != null
430 && getWaveformSubtype().equals(element.getFlavor().getSubtype());
431 }
432
433 private String signIfNecessary(final URI uri) {
434 if (!urlSigningService.accepts(uri.toString())) {
435 return uri.toString();
436 }
437 String clientIP = signWithClientIP ? securityService.getUserIP() : null;
438 try {
439 return new URI(urlSigningService.sign(uri.toString(), expireSeconds, null, clientIP)).toString();
440 } catch (URISyntaxException | UrlSigningException e) {
441 throw new WebApplicationException(e, SC_INTERNAL_SERVER_ERROR);
442 }
443 }
444
445
446
447
448
449
450
451
452
453
454
455
456 Smil createSmilCuttingCatalog(final EditingData editingInfo, final MediaPackage mediaPackage) throws SmilException {
457
458 SmilResponse smilResponse = smilService.createNewSmil(mediaPackage);
459
460
461 ArrayList<Track> tracks = new ArrayList<>();
462
463 for (final TrackData trackdata : editingInfo.getTracks()) {
464 String trackId = trackdata.getId();
465 Track track = mediaPackage.getTrack(trackId);
466 if (track == null) {
467 track = Arrays.stream(getInternalPublication(mediaPackage)
468 .orElseThrow(() -> new IllegalStateException("Event has no internal publication"))
469 .getTracks())
470 .filter(t -> trackId.equals(t.getIdentifier()))
471 .findFirst()
472 .orElseThrow(() -> new IllegalStateException(
473 String.format("The track '%s' doesn't exist in media package '%s'", trackId, mediaPackage)));
474 }
475 tracks.add(track);
476 }
477
478 for (SegmentData segment : editingInfo.getSegments()) {
479 smilResponse = smilService.addParallel(smilResponse.getSmil());
480 final String parentId = smilResponse.getEntity().getId();
481
482 final long duration = segment.getEnd() - segment.getStart();
483 if (!segment.isDeleted()) {
484 smilResponse = smilService.addClips(smilResponse.getSmil(), parentId, tracks.toArray(new Track[0]),
485 segment.getStart(), duration);
486 }
487 }
488
489 return smilResponse.getSmil();
490 }
491
492
493
494
495
496
497
498
499
500
501
502
503 MediaPackage addSmilToArchive(MediaPackage mediaPackage, final Smil smil) throws IOException {
504 MediaPackageElementFlavor mediaPackageElementFlavor = getSmilCatalogFlavor();
505
506 String catalogId = smil.getId();
507 Catalog[] catalogs = mediaPackage.getCatalogs();
508
509
510 for (Catalog p: catalogs) {
511 if (p.getFlavor().matches(mediaPackageElementFlavor)) {
512 logger.debug("Set Identifier for Smil-Catalog to: {}", p.getIdentifier());
513 catalogId = p.getIdentifier();
514 break;
515 }
516 }
517 Catalog catalog = mediaPackage.getCatalog(catalogId);
518
519 URI smilURI;
520 try (InputStream is = IOUtils.toInputStream(smil.toXML(), "UTF-8")) {
521 smilURI = workspace.put(mediaPackage.getIdentifier().toString(), catalogId, EditorService.TARGET_FILE_NAME, is);
522 } catch (SAXException | JAXBException e) {
523 throw new IOException("Error while serializing the SMIL catalog to XML" ,e);
524 }
525
526 if (catalog == null) {
527 MediaPackageElementBuilder mpeBuilder = MediaPackageElementBuilderFactory.newInstance().newElementBuilder();
528 catalog = (Catalog) mpeBuilder.elementFromURI(smilURI, MediaPackageElement.Type.Catalog, getSmilCatalogFlavor());
529 mediaPackage.add(catalog);
530 }
531 catalog.setURI(smilURI);
532 catalog.setIdentifier(catalogId);
533 catalog.setMimeType(MimeTypes.XML);
534 for (String tag : getSmilCatalogTags()) {
535 catalog.addTag(tag);
536 }
537
538 catalog.setChecksum(null);
539 return mediaPackage;
540 }
541
542
543
544
545
546
547
548
549
550
551
552 private MediaPackage processSubtitleTrack(
553 MediaPackage mediaPackage,
554 List<EditingData.Subtitle> subtitles,
555 MediaPackageElementFlavor newTrackFlavor,
556 String newFileName
557 ) throws IOException, IllegalArgumentException {
558 for (EditingData.Subtitle subtitle : subtitles) {
559
560 String subtitleId = UUID.randomUUID().toString();
561 String trackId = null;
562
563
564 for (Track t : mediaPackage.getTracks()) {
565 if (t.getIdentifier().matches(subtitle.getId())) {
566 logger.debug("Set Identifier for {}-Track to: {}", newTrackFlavor.getType(), t.getIdentifier());
567 subtitleId = t.getIdentifier();
568 trackId = t.getIdentifier();
569 break;
570 }
571 }
572
573 Track track = mediaPackage.getTrack(trackId);
574
575 if (subtitle.isDeleted()) {
576
577 if (trackId != null) {
578 mediaPackage.remove(track);
579 }
580 continue;
581 }
582
583
584 URI oldTrackURI = null;
585 if (track != null) {
586 oldTrackURI = track.getURI();
587 }
588
589
590 try (InputStream is = IOUtils.toInputStream(subtitle.getSubtitle(), "UTF-8")) {
591 URI subtitleUri = workspace.put(mediaPackage.getIdentifier().toString(), subtitleId, newFileName + ".vtt", is);
592
593
594 if (track == null) {
595
596 track = (Track) mediaPackage.add(subtitleUri, MediaPackageElement.Type.Track,
597 new MediaPackageElementFlavor(newTrackFlavor.getType(),"source"));
598 logger.info("Creating new {} track {}", newTrackFlavor.getType(), track.getIdentifier());
599 }
600
601 track.setURI(subtitleUri);
602 track.setIdentifier(subtitleId);
603 track.setChecksum(null);
604 for (String tag : subtitle.getTags()) {
605 track.addTag(tag);
606 }
607
608 if (oldTrackURI != null && oldTrackURI != subtitleUri) {
609
610 logger.info("Removing old track file {}", oldTrackURI);
611 try {
612 workspace.delete(oldTrackURI);
613 } catch (NotFoundException | IOException e) {
614 logger.info("Could not remove track from workspace. Could be it was never there.");
615 }
616 }
617 }
618 }
619
620 return mediaPackage;
621 }
622
623
624
625
626
627
628
629
630
631
632
633 private MediaPackage addThumbnailsToArchive(EditingData editingData, MediaPackage mediaPackage)
634 throws MimeTypeParseException, IOException {
635 for (TrackData track : editingData.getTracks()) {
636 String id = track.getId();
637 MediaPackageElementFlavor flavor = new MediaPackageElementFlavor(track.getFlavor().getType(),
638 getThumbnailSubtype());
639 String uri = track.getThumbnailURI();
640
641
642 if (uri == null || uri.isEmpty()) {
643 continue;
644 }
645
646 if (!uri.startsWith("data")) {
647 continue;
648 }
649
650
651 uri = uri.substring(uri.indexOf(",") + 1);
652 byte[] byteArray = Base64.getMimeDecoder().decode(uri);
653 InputStream inputStream = new ByteArrayInputStream(byteArray);
654
655
656 String stringMimeType = detectMimeType(uri);
657 MimeType mimeType = MimeType.mimeType(stringMimeType.split("/")[0], stringMimeType.split("/")[1]);
658
659
660 final String filename = "thumbnail_" + id + "." + mimeType.getSubtype();
661 final String originalThumbnailId = UUID.randomUUID().toString();
662 URI tempThumbnail = null;
663 try {
664 tempThumbnail = workspace
665 .put(mediaPackage.getIdentifier().toString(), originalThumbnailId, filename, inputStream);
666 } catch (IOException e) {
667 throw new IOException("Could not add thumbnail to workspace", e);
668 }
669
670
671 final Attachment attachment = AttachmentImpl.fromURI(tempThumbnail);
672 attachment.setFlavor(flavor);
673 attachment.setMimeType(mimeType);
674 Arrays.stream(mediaPackage.getElementsByFlavor(flavor))
675 .map(MediaPackageElement::getTags)
676 .flatMap(Arrays::stream)
677 .distinct()
678 .forEach(attachment::addTag);
679
680
681 Arrays.stream(mediaPackage.getElementsByFlavor(flavor)).forEach(mediaPackage::remove);
682
683
684 mediaPackage.add(attachment);
685
686
687
688
689 WorkflowPropertiesUtil
690 .storeProperty(assetManager, mediaPackage,
691 flavor.getType() + "/" + thumbnailWfProperty, "true");
692 }
693
694 return mediaPackage;
695 }
696
697
698
699
700
701
702
703
704
705
706 private String detectMimeType(String b64) throws MimeTypeParseException {
707 var signatures = new HashMap<String, String>();
708 signatures.put("R0lGODdh", "image/gif");
709 signatures.put("iVBORw0KGgo", "image/png");
710 signatures.put("/9j/", "image/jpg");
711
712 for (var s : signatures.entrySet()) {
713 if (b64.indexOf(s.getKey()) == 0) {
714 return s.getValue();
715 }
716 }
717 throw new MimeTypeParseException("No image mimetype found");
718 }
719
720 private Optional<Publication> getInternalPublication(MediaPackage mp) {
721 return Arrays.stream(mp.getPublications())
722 .filter(publication -> InternalPublicationChannel.CHANNEL_ID.equals(publication.getChannel()))
723 .findFirst();
724 }
725
726
727
728
729
730
731
732
733 private Event getEvent(final String mediaPackageId) throws EditorServiceException {
734 try {
735 Optional<Event> optEvent = index.getEvent(mediaPackageId, searchIndex);
736 if (optEvent.isEmpty()) {
737 errorExit("Event not found", mediaPackageId,
738 ErrorStatus.MEDIAPACKAGE_NOT_FOUND);
739 } else {
740 return optEvent.get();
741 }
742 } catch (SearchIndexException e) {
743 errorExit("Error while reading event from search index:", mediaPackageId,
744 ErrorStatus.MEDIAPACKAGE_NOT_FOUND, e);
745 }
746 return null;
747 }
748
749
750
751
752
753
754
755 private List<WorkflowDefinition> getEditingWorkflows() {
756 try {
757 return workflowService.listAvailableWorkflowDefinitions().stream()
758 .filter(workflow -> workflow.containsTag(EDITOR_WORKFLOW_TAG))
759 .collect(Collectors.toList());
760 } catch (WorkflowDatabaseException e) {
761 logger.warn("Error while retrieving list of workflow definitions:", e);
762 }
763 return emptyList();
764 }
765
766
767
768
769
770
771
772
773 private List<SegmentData> getSegments(final MediaPackage mediaPackage) {
774 List<SegmentData> segments = new ArrayList<>();
775 for (Catalog smilCatalog : mediaPackage.getCatalogs(getSmilCatalogFlavor())) {
776 try {
777 Smil smil = smilService.fromXml(workspace.get(smilCatalog.getURI())).getSmil();
778 segments = mergeSegments(segments, getSegmentsFromSmil(smil));
779
780 } catch (NotFoundException e) {
781 logger.warn("File '{}' could not be loaded by workspace service:", smilCatalog.getURI(), e);
782 } catch (IOException e) {
783 logger.warn("Reading file '{}' from workspace service failed:", smilCatalog.getURI(), e);
784 } catch (SmilException e) {
785 logger.warn("Error while parsing SMIL catalog '{}':", smilCatalog.getURI(), e);
786 }
787 }
788
789 if (!segments.isEmpty()) {
790 return segments;
791 }
792
793
794 for (Catalog smilCatalog : mediaPackage.getCatalogs(getSmilSilenceFlavor())) {
795 try {
796 Smil smil = smilService.fromXml(workspace.get(smilCatalog.getURI())).getSmil();
797 segments = getSegmentsFromSmil(smil);
798 } catch (NotFoundException e) {
799 logger.warn("File '{}' could not be loaded by workspace service:", smilCatalog.getURI(), e);
800 } catch (IOException e) {
801 logger.warn("Reading file '{}' from workspace service failed:", smilCatalog.getURI(), e);
802 } catch (SmilException e) {
803 logger.warn("Error while parsing SMIL catalog '{}':", smilCatalog.getURI(), e);
804 }
805 }
806
807
808 if (segments.size() == 1) {
809 SegmentData singleSegment = segments.get(0);
810 if (singleSegment.getStart() == 0 && singleSegment.getEnd() >= mediaPackage.getDuration()) {
811 segments.remove(0);
812 }
813 }
814
815 return segments;
816 }
817
818 protected List<SegmentData> getDeletedSegments(MediaPackage mediaPackage, List<SegmentData> segments) {
819
820 long lastTime = 0;
821 List<SegmentData> deletedElements = new ArrayList<>();
822 for (int i = 0; i < segments.size(); i++) {
823 SegmentData segmentData = segments.get(i);
824 if (segmentData.getStart() != lastTime) {
825 SegmentData deleted = new SegmentData(lastTime, segmentData.getStart(), true);
826 deletedElements.add(deleted);
827 }
828 lastTime = segmentData.getEnd();
829
830 if (segments.size() - 1 == i) {
831 if (mediaPackage.getDuration() != null && lastTime < mediaPackage.getDuration()) {
832 deletedElements.add(new SegmentData(lastTime, mediaPackage.getDuration(), true));
833 }
834 }
835 }
836 return deletedElements;
837 }
838
839 protected List<SegmentData> mergeSegments(List<SegmentData> segments, List<SegmentData> segments2) {
840
841 List<SegmentData> mergedSegments = mergeInternal(segments, segments2);
842
843
844 sortSegments(mergedSegments);
845
846 return mergedSegments;
847 }
848
849 private void sortSegments(List<SegmentData> mergedSegments) {
850 mergedSegments.sort(Comparator.comparing(SegmentData::getStart));
851 }
852
853
854
855
856
857
858
859
860
861
862
863 private List<SegmentData> mergeInternal(List<SegmentData> segments, List<SegmentData> segments2) {
864 for (Iterator<SegmentData> it = segments.iterator(); it.hasNext();) {
865 SegmentData seg = it.next();
866 for (Iterator<SegmentData> it2 = segments2.iterator(); it2.hasNext();) {
867 SegmentData seg2 = it2.next();
868 long combinedStart = Math.max(seg.getStart(), seg2.getStart());
869 long combinedEnd = Math.min(seg.getEnd(), seg2.getEnd());
870 if (combinedEnd > combinedStart) {
871 it.remove();
872 it2.remove();
873 List<SegmentData> newSegments = new ArrayList<>(segments);
874 newSegments.add(new SegmentData(combinedStart, combinedEnd));
875 return mergeInternal(newSegments, segments2);
876 }
877 }
878 }
879 segments.addAll(segments2);
880 return segments;
881 }
882
883
884
885
886
887
888
889
890 List<SegmentData> getSegmentsFromSmil(Smil smil) {
891 List<SegmentData> segments = new ArrayList<>();
892 for (SmilMediaObject elem : smil.getBody().getMediaElements()) {
893 if (elem instanceof SmilMediaContainer) {
894 SmilMediaContainer mediaContainer = (SmilMediaContainer) elem;
895
896 SegmentData tuple = null;
897 for (SmilMediaObject video : mediaContainer.getElements()) {
898 if (video instanceof SmilMediaElement) {
899 SmilMediaElement videoElem = (SmilMediaElement) video;
900 try {
901
902 if (tuple == null || (videoElem.getClipEndMS()
903 - videoElem.getClipBeginMS()) > tuple.getEnd() - tuple.getStart()) {
904 tuple = new SegmentData(videoElem.getClipBeginMS(), videoElem.getClipEndMS());
905 }
906 } catch (SmilException e) {
907 logger.warn("Media element '{}' of SMIL catalog '{}' seems to be invalid",
908 videoElem, smil, e);
909 }
910 }
911 }
912 if (tuple != null) {
913 segments.add(tuple);
914 }
915 }
916 }
917 return segments;
918 }
919
920 @Override
921 public void lockMediaPackage(final String mediaPackageId, LockData lockRequest) throws EditorServiceException {
922
923 getEvent(mediaPackageId);
924
925
926 editorLock.lock(mediaPackageId, lockRequest);
927 }
928
929 @Override
930 public void unlockMediaPackage(final String mediaPackageId, LockData lockRequest) throws EditorServiceException {
931
932 getEvent(mediaPackageId);
933
934
935 editorLock.unlock(mediaPackageId, lockRequest);
936 }
937
938 @Override
939 public EditingData getEditData(final String mediaPackageId) throws EditorServiceException, UnauthorizedException {
940
941 Event event = getEvent(mediaPackageId);
942 MediaPackage mp = getMediaPackage(event);
943
944 if (!isAdmin() && !authorizationService.hasPermission(mp, "write")) {
945 throw new UnauthorizedException("User has no write access to this event");
946 }
947
948 boolean workflowActive = WorkflowUtil.isActive(event.getWorkflowState());
949
950 final Optional<Publication> internalPubOpt = getInternalPublication(mp);
951 if (internalPubOpt.isEmpty()) {
952 errorExit("No internal publication", mediaPackageId, ErrorStatus.NO_INTERNAL_PUBLICATION);
953 }
954 Publication internalPub = internalPubOpt.get();
955
956
957 List<SegmentData> segments = getSegments(mp);
958 segments.addAll(getDeletedSegments(mp, segments));
959 sortSegments(segments);
960
961
962
963 List<WorkflowData> workflows = new ArrayList<>();
964 for (WorkflowDefinition workflow : getEditingWorkflows()) {
965 workflows.add(new WorkflowData(workflow.getId(), workflow.getTitle(), workflow.getDisplayOrder(),
966 workflow.getDescription()));
967 }
968
969 final Map<String, String> latestWfProperties = WorkflowPropertiesUtil
970 .getLatestWorkflowProperties(assetManager, mediaPackageId);
971
972
973 final Collection<Tuple<String, String>> hiddens = latestWfProperties.entrySet()
974 .stream()
975 .map(property -> tuple(property.getKey().split("_"), property.getValue()))
976 .filter(property -> property.getA().length == 3)
977 .filter(property -> property.getA()[0].equals("hide"))
978 .filter(property -> property.getB().equals("true"))
979 .map(property -> tuple(property.getA()[1], property.getA()[2]))
980 .collect(Collectors.toSet());
981
982 List<Track> trackList = Arrays.stream(internalPub.getTracks()).filter(this::elementHasPreviewTag)
983 .collect(Collectors.toList());
984 if (trackList.isEmpty()) {
985 trackList = Arrays.stream(internalPub.getTracks()).filter(this::elementHasPreviewFlavor)
986 .collect(Collectors.toList());
987 if (trackList.isEmpty()) {
988 trackList = Arrays.asList(internalPub.getTracks());
989 }
990 }
991
992
993 Track[] subtitleTracks = mp.getTracks(captionsFlavor);
994 List<EditingData.Subtitle> subtitles = new ArrayList<>();
995 for (Track t: subtitleTracks) {
996 try {
997 File subtitleFile = workspace.get(t.getURI());
998 String subtitleString = FileUtils.readFileToString(subtitleFile, StandardCharsets.UTF_8);
999 subtitles.add(new EditingData.Subtitle(t.getIdentifier(), subtitleString, t.getTags()));
1000 } catch (NotFoundException | IOException e) {
1001 errorExit("Could not read subtitle from file", mediaPackageId, ErrorStatus.UNKNOWN);
1002 }
1003 }
1004
1005
1006 Track[] chapterTracks = mp.getTracks(chapterFlavor);
1007 List<EditingData.Subtitle> chapters = new ArrayList<>();
1008 for (Track t: chapterTracks) {
1009 try {
1010 File chapterFile = workspace.get(t.getURI());
1011 String chapterString = FileUtils.readFileToString(chapterFile, StandardCharsets.UTF_8);
1012 chapters.add(new EditingData.Subtitle(t.getIdentifier(), chapterString, t.getTags()));
1013 } catch (NotFoundException | IOException e) {
1014 errorExit("Could not read chapter from file", mediaPackageId, ErrorStatus.UNKNOWN);
1015 }
1016 }
1017
1018
1019
1020 final List<TrackData> tracks = trackList.stream().map(track -> {
1021 final String uri = signIfNecessary(track.getURI());
1022 final boolean audioEnabled = !hiddens.contains(tuple(track.getFlavor().getType(), "audio"));
1023 final TrackSubData audio = new TrackSubData(track.hasAudio(), null,
1024 audioEnabled);
1025 final boolean videoEnable = !hiddens.contains(tuple(track.getFlavor().getType(), "video"));
1026 final String videoPreview = Arrays.stream(internalPub.getAttachments())
1027 .filter(attachment -> attachment.getFlavor().getType().equals(track.getFlavor().getType()))
1028 .filter(attachment -> attachment.getFlavor().getSubtype().equals(getPreviewVideoSubtype()))
1029 .map(MediaPackageElement::getURI).map(this::signIfNecessary)
1030 .findAny()
1031 .orElse(null);
1032 final TrackSubData video = new TrackSubData(track.hasVideo(), videoPreview,
1033 videoEnable);
1034
1035
1036
1037
1038 String thumbnailURI = Arrays.stream(mp.getAttachments())
1039 .filter(attachment -> attachment.getFlavor().getType().equals(track.getFlavor().getType()))
1040 .filter(attachment -> attachment.getFlavor().getSubtype().equals(getThumbnailSubtype()))
1041 .map(MediaPackageElement::getURI).map(this::signIfNecessary)
1042 .findAny()
1043 .orElse(null);
1044
1045
1046
1047 if (thumbnailURI == null) {
1048 thumbnailURI = Arrays.stream(internalPub.getAttachments())
1049 .filter(attachment -> attachment.getFlavor().getType().equals(track.getFlavor().getType()))
1050 .filter(attachment -> attachment.getFlavor().getSubtype().equals(getThumbnailSubtype()))
1051 .map(MediaPackageElement::getURI).map(this::signIfNecessary)
1052 .findAny()
1053 .orElse(null);
1054 }
1055
1056 final int priority = thumbnailSourcePrimary.indexOf(track.getFlavor());
1057
1058 if (localPublication == null) {
1059 localPublication = isLocal(track.getURI());
1060 }
1061
1062 return new TrackData(track.getFlavor().getType(), track.getFlavor().getSubtype(), audio, video, uri,
1063 track.getIdentifier(), thumbnailURI, priority);
1064 }).collect(Collectors.toList());
1065
1066 List<String> waveformList = Arrays.stream(internalPub.getAttachments())
1067 .filter(this::elementHasWaveformFlavor)
1068 .map(Attachment::getURI).map(this::signIfNecessary)
1069 .collect(Collectors.toList());
1070
1071 User user = securityService.getUser();
1072
1073 return new EditingData(segments, tracks, workflows, mp.getDuration(), mp.getTitle(), event.getRecordingStartDate(),
1074 event.getSeriesId(), event.getSeriesName(), workflowActive, waveformList, subtitles, chapters,
1075 localPublication, lockingActive, lockRefresh, user, "");
1076 }
1077
1078
1079 private boolean isAdmin() {
1080 final User currentUser = securityService.getUser();
1081
1082
1083 if (currentUser.hasRole(SecurityConstants.GLOBAL_ADMIN_ROLE)) {
1084 return true;
1085 }
1086
1087
1088 final Organization currentOrg = securityService.getOrganization();
1089 return currentUser.getOrganization().getId().equals(currentOrg.getId())
1090 && currentUser.hasRole(currentOrg.getAdminRole());
1091 }
1092
1093 private MediaPackage getMediaPackage(Event event) throws EditorServiceException {
1094 if (event == null) {
1095 errorExit("No Event provided", "", ErrorStatus.UNKNOWN);
1096 return null;
1097 }
1098 try {
1099 return index.getEventMediapackage(event);
1100 } catch (IndexServiceException e) {
1101 errorExit("Not Found", event.getIdentifier(), ErrorStatus.MEDIAPACKAGE_NOT_FOUND);
1102 return null;
1103 }
1104 }
1105
1106 private void errorExit(final String message, final String mediaPackageId, ErrorStatus status)
1107 throws EditorServiceException {
1108 errorExit(message, mediaPackageId, status, null);
1109 }
1110
1111 private void errorExit(final String message, final String mediaPackageId, ErrorStatus status, Exception e)
1112 throws EditorServiceException {
1113 String errorMessage = MessageFormat.format("{0}. Event ID: {1}", message, mediaPackageId);
1114 throw new EditorServiceException(errorMessage, status, e);
1115 }
1116
1117 @Override
1118 public void setEditData(String mediaPackageId, EditingData editingData) throws EditorServiceException,
1119 IOException {
1120 final Event event = getEvent(mediaPackageId);
1121
1122 if (WorkflowUtil.isActive(event.getWorkflowState())) {
1123 errorExit("Workflow is running", mediaPackageId, ErrorStatus.WORKFLOW_ACTIVE);
1124 }
1125
1126 MediaPackage mediaPackage = getMediaPackage(event);
1127 Smil smil = null;
1128 try {
1129 smil = createSmilCuttingCatalog(editingData, mediaPackage);
1130 } catch (Exception e) {
1131 errorExit("Unable to create SMIL cutting catalog", mediaPackageId, ErrorStatus.UNABLE_TO_CREATE_CATALOG, e);
1132 }
1133
1134 final Map<String, String> workflowProperties = new HashMap<String, String>();
1135 for (TrackData track : editingData.getTracks()) {
1136 MediaPackageElementFlavor flavor = track.getFlavor();
1137 String type = null;
1138 if (flavor != null) {
1139 type = flavor.getType();
1140 } else {
1141 Track mpTrack = mediaPackage.getTrack(track.getId());
1142 if (mpTrack != null) {
1143 type = mpTrack.getFlavor().getType();
1144 } else {
1145 errorExit("Unable to determine track type", mediaPackageId, ErrorStatus.UNKNOWN);
1146 }
1147 }
1148 workflowProperties.put("hide_" + type + "_audio", Boolean.toString(!track.getAudio().isEnabled()));
1149 workflowProperties.put("hide_" + type + "_video", Boolean.toString(!track.getVideo().isEnabled()));
1150 }
1151 WorkflowPropertiesUtil.storeProperties(assetManager, mediaPackage, workflowProperties);
1152
1153 try {
1154 mediaPackage = addSmilToArchive(mediaPackage, smil);
1155 } catch (IOException e) {
1156 errorExit("Unable to add SMIL cutting catalog to archive", mediaPackageId, ErrorStatus.UNKNOWN, e);
1157 }
1158
1159 try {
1160 if (editingData.getSubtitles() != null) {
1161 mediaPackage = processSubtitleTrack(mediaPackage, editingData.getSubtitles(), captionsFlavor, "subtitle");
1162 }
1163 } catch (IOException e) {
1164 errorExit("Unable to add subtitle track to archive", mediaPackageId, ErrorStatus.UNKNOWN, e);
1165 } catch (IllegalArgumentException e) {
1166 errorExit("Illegal subtitle given", mediaPackageId, ErrorStatus.UNKNOWN, e);
1167 }
1168
1169 try {
1170 if (editingData.getChapters() != null) {
1171 mediaPackage = processSubtitleTrack(mediaPackage, editingData.getChapters(), chapterFlavor, "chapters");
1172 }
1173 } catch (IOException e) {
1174 errorExit("Unable to add subtitle track to archive", mediaPackageId, ErrorStatus.UNKNOWN, e);
1175 } catch (IllegalArgumentException e) {
1176 errorExit("Illegal subtitle given", mediaPackageId, ErrorStatus.UNKNOWN, e);
1177 }
1178
1179 try {
1180 mediaPackage = addThumbnailsToArchive(editingData, mediaPackage);
1181 } catch (MimeTypeParseException e) {
1182 errorExit("Thumbnail had an illegal MimeType", mediaPackageId, ErrorStatus.UNKNOWN, e);
1183 } catch (IOException e) {
1184 errorExit("Unable to add thumbnail to archive", mediaPackageId, ErrorStatus.UNKNOWN, e);
1185 }
1186
1187 try {
1188 assetManager.takeSnapshot(mediaPackage);
1189 } catch (AssetManagerException e) {
1190 logger.error("Error while adding the updated media package ({}) to the archive",
1191 mediaPackage.getIdentifier(), e);
1192 throw new IOException(e);
1193 }
1194
1195
1196 try {
1197 index.updateAllEventMetadata(mediaPackageId, editingData.getMetadataJSON(), searchIndex);
1198 } catch (SearchIndexException | IndexServiceException | IllegalArgumentException e) {
1199 errorExit("Event metadata can't be updated.", mediaPackageId, ErrorStatus.METADATA_UPDATE_FAIL, e);
1200 } catch (NotFoundException e) {
1201 errorExit("Event not found.", mediaPackageId, ErrorStatus.MEDIAPACKAGE_NOT_FOUND, e);
1202 } catch (UnauthorizedException e) {
1203 errorExit("Not authorized to update event metadata .", mediaPackageId, ErrorStatus.NOT_AUTHORIZED, e);
1204 }
1205
1206 if (editingData.getPostProcessingWorkflow() != null) {
1207 final String workflowId = editingData.getPostProcessingWorkflow();
1208 try {
1209 final Map<String, String> workflowParameters = WorkflowPropertiesUtil
1210 .getLatestWorkflowProperties(assetManager, mediaPackage.getIdentifier().toString());
1211 final Workflows workflows = new Workflows(assetManager, workflowService);
1212 workflows.applyWorkflowToLatestVersion(Collections.singletonList(mediaPackage.getIdentifier().toString()),
1213 ConfiguredWorkflow.workflow(workflowService.getWorkflowDefinitionById(workflowId), workflowParameters));
1214 } catch (AssetManagerException e) {
1215 errorExit("Unable to start workflow" + workflowId, mediaPackageId, ErrorStatus.WORKFLOW_ERROR, e);
1216 } catch (WorkflowDatabaseException e) {
1217 errorExit("Unable to load workflow" + workflowId, mediaPackageId, ErrorStatus.WORKFLOW_ERROR, e);
1218 } catch (NotFoundException e) {
1219 errorExit("Unable to load workflow" + workflowId, mediaPackageId, ErrorStatus.WORKFLOW_NOT_FOUND, e);
1220 }
1221 }
1222 }
1223
1224 @Override
1225 public String getMetadata(String mediaPackageId) throws EditorServiceException {
1226 final Event event = getEvent(mediaPackageId);
1227 MediaPackage mediaPackage = getMediaPackage(event);
1228 MetadataList metadataList = new MetadataList();
1229 List<EventCatalogUIAdapter> catalogUIAdapters = index.getEventCatalogUIAdapters();
1230 catalogUIAdapters.remove(index.getCommonEventCatalogUIAdapter());
1231 for (EventCatalogUIAdapter catalogUIAdapter : catalogUIAdapters) {
1232 metadataList.add(catalogUIAdapter, catalogUIAdapter.getFields(mediaPackage));
1233 }
1234
1235 DublinCoreMetadataCollection metadataCollection = null;
1236 try {
1237 metadataCollection = EventUtils.getEventMetadata(event,
1238 index.getCommonEventCatalogUIAdapter());
1239 } catch (Exception e) {
1240 errorExit("Unable to retrieve event metadata", mediaPackageId, ErrorStatus.UNKNOWN);
1241 }
1242 metadataList.add(index.getCommonEventCatalogUIAdapter(), metadataCollection);
1243
1244 final String wfState = event.getWorkflowState();
1245 if (wfState != null && WorkflowUtil.isActive(WorkflowInstance.WorkflowState.valueOf(wfState))) {
1246 metadataList.setLocked(MetadataList.Locked.WORKFLOW_RUNNING);
1247 }
1248
1249 return MetadataJson.listToJson(metadataList, true).toString();
1250 }
1251 }