Skip to content

Commit

Permalink
NXP-29815: add an endpoint to recompute video renditions
Browse files Browse the repository at this point in the history
  • Loading branch information
charlesboidot committed Jan 12, 2021
1 parent 8731421 commit 639f045
Show file tree
Hide file tree
Showing 111 changed files with 751 additions and 75 deletions.
Expand Up @@ -48,7 +48,8 @@
</dependency>
<dependency>
<groupId>org.nuxeo.ecm.platform</groupId>
<artifactId>nuxeo-platform-video</artifactId>
<artifactId>nuxeo-platform-video-core</artifactId>
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
Expand Down
@@ -0,0 +1,109 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>org.nuxeo.ecm.platform</groupId>
<artifactId>nuxeo-platform-video-parent</artifactId>
<version>11.5-SNAPSHOT</version>
</parent>

<artifactId>nuxeo-platform-video-core</artifactId>
<name>Nuxeo Platform Video Core</name>
<description>Nuxeo Platform Video provides video management to Nuxeo Web Platform and RCP. </description>

<dependencies>
<dependency>
<groupId>org.nuxeo.ecm.core</groupId>
<artifactId>nuxeo-core-api</artifactId>
</dependency>
<dependency>
<groupId>org.nuxeo.ecm.core</groupId>
<artifactId>nuxeo-core</artifactId>
</dependency>
<dependency>
<groupId>org.nuxeo.ecm.core</groupId>
<artifactId>nuxeo-core-convert-api</artifactId>
</dependency>
<dependency>
<groupId>org.nuxeo.ecm.core</groupId>
<artifactId>nuxeo-core-convert</artifactId>
</dependency>
<dependency>
<groupId>org.nuxeo.ecm.platform</groupId>
<artifactId>nuxeo-platform-convert</artifactId>
</dependency>
<dependency>
<groupId>org.nuxeo.ecm.platform</groupId>
<artifactId>nuxeo-platform-filemanager</artifactId>
</dependency>
<dependency>
<groupId>org.nuxeo.ecm.platform</groupId>
<artifactId>nuxeo-platform-types</artifactId>
</dependency>
<dependency>
<groupId>org.nuxeo.ecm.core</groupId>
<artifactId>nuxeo-core-mimetype</artifactId>
</dependency>
<dependency>
<groupId>org.nuxeo.ecm.platform</groupId>
<artifactId>nuxeo-platform-imaging-core</artifactId>
</dependency>
<dependency>
<groupId>org.nuxeo.ecm.platform</groupId>
<artifactId>nuxeo-platform-rendition-api</artifactId>
</dependency>
<dependency>
<groupId>org.nuxeo.ecm.automation</groupId>
<artifactId>nuxeo-automation-core</artifactId>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</dependency>

<dependency>
<groupId>org.nuxeo.ecm.core</groupId>
<artifactId>nuxeo-core-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.nuxeo.ecm.platform</groupId>
<artifactId>nuxeo-platform-rendition-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.nuxeo.ecm.platform</groupId>
<artifactId>nuxeo-platform-tag</artifactId>
<scope>test</scope>
</dependency>
<!-- binaries data used by the tests -->
<dependency>
<groupId>org.nuxeo.ecm.platform</groupId>
<artifactId>nuxeo-platform-video-convert-test-data</artifactId>
<version>5.3.2-20100324</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<executions>
<execution>
<configuration>
<archive>
<manifestFile>src/test/resources/META-INF/MANIFEST.MF</manifestFile>
</archive>
</configuration>
<goals>
<goal>test-jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

</project>
@@ -0,0 +1,2 @@
[nuxeo]
en_US=src/main/resources/OSGI-INF/l10n/messages_en_US.properties
@@ -0,0 +1,182 @@
/*
* (C) Copyright 2020 Nuxeo (http://nuxeo.com/) and others.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Contributors:
* Charles Boidot
*/
package org.nuxeo.ecm.platform.video.service;

import static org.nuxeo.ecm.core.api.CoreSession.ALLOW_VERSION_WRITE;
import static org.nuxeo.ecm.core.api.versioning.VersioningService.DISABLE_AUTO_CHECKOUT;
import static org.nuxeo.ecm.core.bulk.BulkServiceImpl.STATUS_STREAM;
import static org.nuxeo.ecm.core.bulk.action.SetPropertiesAction.PARAM_DISABLE_AUDIT;
import static org.nuxeo.ecm.platform.video.VideoConstants.TRANSCODED_VIDEOS_PROPERTY;
import static org.nuxeo.lib.stream.computation.AbstractComputation.INPUT_1;
import static org.nuxeo.lib.stream.computation.AbstractComputation.OUTPUT_1;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.nuxeo.ecm.core.api.Blob;
import org.nuxeo.ecm.core.api.CoreSession;
import org.nuxeo.ecm.core.api.DocumentModel;
import org.nuxeo.ecm.core.api.DocumentNotFoundException;
import org.nuxeo.ecm.core.api.IdRef;
import org.nuxeo.ecm.core.api.model.Property;
import org.nuxeo.ecm.core.bulk.action.computation.AbstractBulkComputation;
import org.nuxeo.ecm.core.bulk.message.BulkCommand;
import org.nuxeo.ecm.core.convert.api.ConversionException;
import org.nuxeo.ecm.platform.video.TranscodedVideo;
import org.nuxeo.ecm.platform.video.Video;
import org.nuxeo.ecm.platform.video.VideoConstants;
import org.nuxeo.ecm.platform.video.VideoDocument;
import org.nuxeo.lib.stream.computation.Topology;
import org.nuxeo.runtime.api.Framework;
import org.nuxeo.runtime.stream.StreamProcessorTopology;
import org.nuxeo.runtime.transaction.TransactionHelper;

/**
* BAF Computation that fills video renditions for the blob property described by the given xpath.
*
* @since 11.5
*/
public class RecomputeVideoConversionsAction implements StreamProcessorTopology {

private static final Logger log = LogManager.getLogger(RecomputeVideoConversionsAction.class);

public static final String ACTION_NAME = "recomputeVideoConversion";

// @since 11.4
public static final String ACTION_FULL_NAME = "bulk/" + ACTION_NAME;

public static final String PARAM_XPATH = "xpath";

public static final String PARAM_CONVERSION_NAME = "conversionName";

@Override
public Topology getTopology(Map<String, String> options) {
return Topology.builder()
.addComputation(RecomputeRenditionsComputation::new, //
Arrays.asList(INPUT_1 + ":" + ACTION_FULL_NAME, OUTPUT_1 + ":" + STATUS_STREAM))
.build();
}

public static class RecomputeRenditionsComputation extends AbstractBulkComputation {

public static final String PICTURE_VIEWS_GENERATION_DONE_EVENT = "pictureViewsGenerationDone";

protected String xpath;

protected List<String> conversionNames;

protected List<String> conversionNamesArray = new ArrayList<>();

protected VideoService videoService = Framework.getService(VideoService.class);

public RecomputeRenditionsComputation() {
super(ACTION_FULL_NAME);
}

@Override
public void startBucket(String bucketKey) {
BulkCommand command = getCurrentCommand();
xpath = command.getParam(PARAM_XPATH);
conversionNames = command.getParam(PARAM_CONVERSION_NAME);
conversionNamesArray = new ArrayList<>();
if (conversionNames.isEmpty()) {
// Recomputing all available renditions
for (VideoConversion conversion : videoService.getAvailableVideoConversions()) {
conversionNamesArray.add(conversion.getName());
}
} else {
// Recomputing wanted renditions
conversionNamesArray.addAll(conversionNames);
}
}

@Override
protected void compute(CoreSession session, List<String> ids, Map<String, Serializable> properties) {
log.debug("Compute action: {} for doc ids: {}", ACTION_NAME, ids);
for (String docId : ids) {
if (!session.exists(new IdRef(docId))) {
log.debug("Doc id doesn't exist: {}", docId);
continue;
}

DocumentModel workingDocument = session.getDocument(new IdRef(docId));
Property fileProp = workingDocument.getProperty(xpath);
Blob blob = (Blob) fileProp.getValue();
if (blob == null) {
// do nothing
log.debug("No blob for doc: {}", workingDocument);
continue;
}

var transcodedVideos = new ArrayList<Map<String, Serializable>>();

try {
VideoDocument videoDoc = workingDocument.getAdapter(VideoDocument.class);
Video video = videoDoc.getVideo();

for (String conversion : conversionNamesArray) {
TransactionHelper.commitOrRollbackTransaction();
TranscodedVideo transcodedVideo = null;
try {
transcodedVideo = videoService.convert(video, conversion);
} catch (ConversionException e) {
log.warn("Conversion to {} has failed", conversion);
}
TransactionHelper.startTransaction();
saveRendition(new IdRef(workingDocument.getId()), conversion, transcodedVideo, session);
}
} catch (DocumentNotFoundException e) {
// a parent of the document may have been deleted.
continue;
} finally {
TransactionHelper.startTransaction();
}

session.saveDocument(workingDocument);
}
}
}

protected static void saveRendition(IdRef docId, String conversionName, TranscodedVideo transcodedVideo,
CoreSession session) {
DocumentModel doc = session.getDocument(docId);
var transcodedVideos = (List<Map<String, Serializable>>) doc.getPropertyValue(TRANSCODED_VIDEOS_PROPERTY);
transcodedVideos.removeIf(tv -> conversionName.equals(tv.get("name")));
if (transcodedVideo != null) {
transcodedVideos.add(transcodedVideo.toMap());
}
doc.setPropertyValue(TRANSCODED_VIDEOS_PROPERTY, (Serializable) transcodedVideos);
if (doc.isVersion()) {
doc.putContextData(ALLOW_VERSION_WRITE, Boolean.TRUE);
}
if (doc.isVersion()) {
doc.putContextData(ALLOW_VERSION_WRITE, Boolean.TRUE);
}
doc.putContextData("disableNotificationService", Boolean.TRUE);
doc.putContextData(PARAM_DISABLE_AUDIT, Boolean.TRUE);
doc.putContextData(DISABLE_AUTO_CHECKOUT, Boolean.TRUE);
session.saveDocument(doc);
}
}
@@ -0,0 +1,64 @@
/*
* (C) Copyright 2020 Nuxeo (http://nuxeo.com/) and others.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Contributors:
* Charles Boidot
*/
package org.nuxeo.ecm.platform.video.service;

import static org.nuxeo.ecm.platform.video.service.RecomputeVideoConversionsAction.PARAM_CONVERSION_NAME;
import static org.nuxeo.ecm.platform.video.service.RecomputeVideoConversionsAction.PARAM_XPATH;

import java.util.ArrayList;
import java.util.List;

import org.nuxeo.ecm.core.bulk.AbstractBulkActionValidation;
import org.nuxeo.ecm.core.bulk.message.BulkCommand;
import org.nuxeo.runtime.api.Framework;

/**
* @since 11.5
*/
public class RecomputeVideoConversionsActionValidation extends AbstractBulkActionValidation {
@Override
protected List<String> getParametersToValidate() {
return List.of(PARAM_XPATH, PARAM_CONVERSION_NAME);
}

@Override
protected void validateCommand(BulkCommand command) throws IllegalArgumentException {

// Check XPath
String xpath = command.getParam(PARAM_XPATH);
validateXpath(PARAM_XPATH, xpath, command);

// Check conversions list
validateList(PARAM_CONVERSION_NAME, command);
List<String> conversionNames = new ArrayList<>(command.getParam(PARAM_CONVERSION_NAME));
// recompute all renditions
if (conversionNames.isEmpty()) {
return;
}
VideoService videoService = Framework.getService(VideoService.class);
List<String> conversionNamesArray = new ArrayList<>();
for (VideoConversion conversion : videoService.getAvailableVideoConversions()) {
conversionNamesArray.add(conversion.getName());
}
conversionNames.removeAll(conversionNamesArray);
if(!conversionNames.isEmpty()) {
throw new IllegalArgumentException(String.format("The conversions: %s are not supported.", conversionNames));
}
}
}
Expand Up @@ -5,6 +5,7 @@ Bundle-SymbolicName: org.nuxeo.ecm.platform.video
Bundle-Vendor: Nuxeo
Nuxeo-Component: OSGI-INF/adapters-contrib.xml,
OSGI-INF/commandline-contrib.xml,
OSGI-INF/video-bulk-contrib.xml,
OSGI-INF/convert-service-contrib.xml,
OSGI-INF/core-types-contrib.xml,
OSGI-INF/filemanager-importer-contrib.xml,
Expand Down
@@ -0,0 +1,19 @@
<?xml version="1.0"?>
<component name="org.nuxeo.ecm.platform.video.bulk" version="1.0.0">

<require>org.nuxeo.ecm.core.bulk</require>

<extension target="org.nuxeo.ecm.core.bulk" point="actions">
<!-- Here the batch size is equal to the bucket size because in this case the computation handles the transaction -->
<action name="recomputeVideoConversion" inputStream="bulk/recomputeVideoConversion" bucketSize="10" batchSize="10" httpEnabled="true"
validationClass="org.nuxeo.ecm.platform.video.service.RecomputeVideoConversionsActionValidation" />
</extension>

<extension target="org.nuxeo.runtime.stream.service" point="streamProcessor">
<streamProcessor name="recomputeVideoConversion" class="org.nuxeo.ecm.platform.video.service.RecomputeVideoConversionsAction"
defaultConcurrency="2" defaultPartitions="6">
<policy name="default" maxRetries="3" delay="1s" maxDelay="10s" continueOnFailure="true" />
</streamProcessor>
</extension>

</component>

0 comments on commit 639f045

Please sign in to comment.