Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
NXP-29815: add an endpoint to recompute video renditions
- Loading branch information
1 parent
da52a22
commit b56b270
Showing
115 changed files
with
793 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
<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-rest</artifactId> | ||
<name>Nuxeo Platform Video REST</name> | ||
<description>Management API endpoint used to recompute video renditions</description> | ||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>org.nuxeo.ecm.platform</groupId> | ||
<artifactId>nuxeo-platform-video</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.nuxeo.ecm.platform</groupId> | ||
<artifactId>nuxeo-rest-api-server</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.nuxeo.ecm.platform</groupId> | ||
<artifactId>nuxeo-platform-video</artifactId> | ||
<type>test-jar</type> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.nuxeo.ecm.platform</groupId> | ||
<artifactId>nuxeo-rest-api-test</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
</dependencies> | ||
</project> |
80 changes: 80 additions & 0 deletions
80
...-video-rest/src/main/java/org/nuxeo/ecm/restapi/server/jaxrs/management/VideosObject.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
/* | ||
* (C) Copyright 2021 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.restapi.server.jaxrs.management; | ||
|
||
import static javax.ws.rs.core.MediaType.APPLICATION_JSON; | ||
import static org.nuxeo.ecm.core.api.security.SecurityConstants.SYSTEM_USERNAME; | ||
import static org.nuxeo.ecm.platform.video.computation.RecomputeTranscodedVideosComputation.PARAM_CONVERSION_NAMES; | ||
import static org.nuxeo.ecm.platform.video.computation.RecomputeTranscodedVideosComputation.PARAM_XPATH; | ||
import static org.nuxeo.ecm.platform.video.computation.RecomputeVideoInfoComputation.ONLY_RECOMPUTE_MISSING_VIDEO_INFO; | ||
|
||
import java.io.Serializable; | ||
import java.util.List; | ||
|
||
import javax.ws.rs.FormParam; | ||
import javax.ws.rs.POST; | ||
import javax.ws.rs.Path; | ||
import javax.ws.rs.Produces; | ||
|
||
import org.apache.commons.lang3.BooleanUtils; | ||
import org.apache.commons.lang3.StringUtils; | ||
import org.nuxeo.ecm.core.bulk.BulkService; | ||
import org.nuxeo.ecm.core.bulk.message.BulkCommand; | ||
import org.nuxeo.ecm.core.bulk.message.BulkStatus; | ||
import org.nuxeo.ecm.platform.video.action.RecomputeVideoConversionsAction; | ||
import org.nuxeo.ecm.webengine.model.WebObject; | ||
import org.nuxeo.ecm.webengine.model.impl.AbstractResource; | ||
import org.nuxeo.ecm.webengine.model.impl.ResourceTypeImpl; | ||
import org.nuxeo.runtime.api.Framework; | ||
|
||
/** | ||
* @since 11.5 | ||
*/ | ||
@WebObject(type = ManagementObject.MANAGEMENT_OBJECT_PREFIX + "videos") | ||
@Produces(APPLICATION_JSON) | ||
public class VideosObject extends AbstractResource<ResourceTypeImpl> { | ||
|
||
// By default we only recompute renditions for document without any renditions | ||
public static final String VIDEOS_DEFAULT_QUERY = "SELECT * FROM Document WHERE ecm:mixinType = 'Video' AND ecm:isProxy = 0 AND ecm:isVersion = 0 AND vid:transcodedVideos/0/name IS NULL"; | ||
|
||
/** | ||
* Recomputes video renditions for the documents matching the given query or {@link #VIDEOS_DEFAULT_QUERY} if not | ||
* provided. | ||
* | ||
* @param query a custom query to specify which videos should be processed | ||
* @return the {@link BulkStatus} of the command | ||
*/ | ||
@POST | ||
@Path("recompute") | ||
public BulkStatus doPostVideos(@FormParam("query") String query, | ||
@FormParam("conversionNames") List<String> conversionNames, | ||
@FormParam("recomputeAllVideoInfo") Boolean recomputeAllVideoInfo) { | ||
String finalQuery = StringUtils.defaultIfBlank(query, VIDEOS_DEFAULT_QUERY); | ||
Boolean onlyRecomputeMissingVideoInfo = !BooleanUtils.toBooleanDefaultIfNull(recomputeAllVideoInfo, false); | ||
BulkService bulkService = Framework.getService(BulkService.class); | ||
String commandId = bulkService.submit(new BulkCommand.Builder(RecomputeVideoConversionsAction.ACTION_NAME, | ||
finalQuery, SYSTEM_USERNAME).repository(ctx.getCoreSession().getRepositoryName()) | ||
.param(PARAM_XPATH, "file:content") | ||
.param(ONLY_RECOMPUTE_MISSING_VIDEO_INFO, onlyRecomputeMissingVideoInfo) | ||
.param(PARAM_CONVERSION_NAMES, (Serializable) conversionNames) | ||
.build()); | ||
return bulkService.getStatus(commandId); | ||
} | ||
|
||
} |
7 changes: 7 additions & 0 deletions
7
modules/platform/video/nuxeo-platform-video-rest/src/main/resources/META-INF/MANIFEST.MF
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
Manifest-Version: 1.0 | ||
Bundle-ManifestVersion: 1 | ||
Bundle-Name: Nuxeo Video REST | ||
Bundle-SymbolicName: org.nuxeo.ecm.platform.video.rest;singleton:=true | ||
Fragment-Host: org.nuxeo.ecm.platform.restapi.server | ||
Bundle-Vendor: Nuxeo | ||
Bundle-Version: 1.0.0 |
229 changes: 229 additions & 0 deletions
229
...eo-rest/src/test/java/org/nuxeo/ecm/restapi/server/jaxrs/management/TestVideosObject.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,229 @@ | ||
/* | ||
* (C) Copyright 2021 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.restapi.server.jaxrs.management; | ||
|
||
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST; | ||
import static javax.servlet.http.HttpServletResponse.SC_OK; | ||
import static org.junit.Assert.assertEquals; | ||
import static org.junit.Assert.assertFalse; | ||
import static org.junit.Assert.assertNotNull; | ||
import static org.junit.Assert.assertTrue; | ||
import static org.nuxeo.ecm.core.bulk.io.BulkConstants.STATUS_ERROR_COUNT; | ||
import static org.nuxeo.ecm.core.bulk.io.BulkConstants.STATUS_ERROR_MESSAGE; | ||
import static org.nuxeo.ecm.core.bulk.io.BulkConstants.STATUS_HAS_ERROR; | ||
import static org.nuxeo.ecm.core.bulk.io.BulkConstants.STATUS_PROCESSED; | ||
import static org.nuxeo.ecm.core.bulk.io.BulkConstants.STATUS_TOTAL; | ||
import static org.nuxeo.ecm.platform.video.VideoConstants.TRANSCODED_VIDEOS_PROPERTY; | ||
|
||
import java.io.IOException; | ||
import java.io.InputStream; | ||
import java.io.Serializable; | ||
import java.nio.charset.StandardCharsets; | ||
import java.util.List; | ||
import java.util.Map; | ||
|
||
import javax.inject.Inject; | ||
import javax.ws.rs.core.MultivaluedMap; | ||
|
||
import org.junit.Before; | ||
import org.junit.Test; | ||
import org.nuxeo.common.utils.FileUtils; | ||
import org.nuxeo.ecm.core.api.Blob; | ||
import org.nuxeo.ecm.core.api.Blobs; | ||
import org.nuxeo.ecm.core.api.CoreSession; | ||
import org.nuxeo.ecm.core.api.DocumentModel; | ||
import org.nuxeo.ecm.core.api.DocumentRef; | ||
import org.nuxeo.ecm.platform.video.VideoFeature; | ||
import org.nuxeo.ecm.platform.video.listener.VideoChangedListener; | ||
import org.nuxeo.ecm.platform.video.service.VideoService; | ||
import org.nuxeo.ecm.restapi.test.ManagementBaseTest; | ||
import org.nuxeo.jaxrs.test.CloseableClientResponse; | ||
import org.nuxeo.runtime.api.Framework; | ||
import org.nuxeo.runtime.test.runner.Deploy; | ||
import org.nuxeo.runtime.test.runner.Features; | ||
|
||
import com.fasterxml.jackson.databind.JsonNode; | ||
import com.sun.jersey.core.util.MultivaluedMapImpl; | ||
|
||
/** | ||
* @since 11.5 | ||
*/ | ||
@Features(VideoFeature.class) | ||
@Deploy("org.nuxeo.ecm.platform.video.rest") | ||
public class TestVideosObject extends ManagementBaseTest { | ||
|
||
@Inject | ||
protected CoreSession session; | ||
|
||
@Inject | ||
protected VideoService videoService = Framework.getService(VideoService.class); | ||
|
||
protected DocumentRef docRef; | ||
|
||
@Before | ||
public void createDocument() throws IOException { | ||
DocumentModel doc = session.createDocumentModel("/", "videoDoc", "Video"); | ||
Blob blob = Blobs.createBlob(FileUtils.getResourceFileFromContext("videos/video.mpg"), null, | ||
StandardCharsets.UTF_8.name(), "video.mpg"); | ||
doc.setPropertyValue("file:content", (Serializable) blob); | ||
doc.putContextData(VideoChangedListener.DISABLE_VIDEO_CONVERSIONS_GENERATION_LISTENER, true); | ||
doc = session.createDocument(doc); | ||
docRef = doc.getRef(); | ||
|
||
} | ||
|
||
@Test | ||
public void testRecomputeVideosInvalidQuery() throws IOException { | ||
String query = "SELECT * FROM nowhere"; | ||
doTestRecomputeVideos(query, null, false, false); | ||
} | ||
|
||
@Test | ||
public void testRecomputeVideosNoQueryNoConversions() throws IOException { | ||
doTestRecomputeVideos(null, null, false, true); | ||
} | ||
|
||
@Test | ||
public void testRecomputeVideosValidQueryCustomConversion() throws IOException { | ||
String query = "SELECT * FROM Document WHERE ecm:mixinType = 'Video'"; | ||
doTestRecomputeVideos(query, List.of("WebM 480p"), false, true); | ||
} | ||
|
||
@Test | ||
public void testRecomputeVideosImpossibleConversion() throws IOException { | ||
doTestRecomputeVideos(null, List.of("foo 480p"), true, false); | ||
} | ||
|
||
@Test | ||
public void testRecomputeVideosCustomRenditionsList() throws IOException { | ||
doTestRecomputeVideos(null, List.of("WebM 480p", "MP4 480p"), false, true); | ||
} | ||
|
||
@Test | ||
public void testRecomputeOneAfterRecomputeAll() throws IOException { | ||
// generating all default video renditions | ||
doTestRecomputeVideos(null, null, false, true); | ||
|
||
MultivaluedMap<String, String> formData = new MultivaluedMapImpl(); | ||
String commandId; | ||
|
||
// try recomputing only the Ogg conversion | ||
formData.add("conversionNames", "Ogg 480p"); | ||
try (CloseableClientResponse response = httpClientRule.post("/management/videos/recompute/", formData); | ||
InputStream entityStream = response.getEntityInputStream()) { | ||
assertEquals(SC_OK, response.getStatus()); | ||
JsonNode node = mapper.readTree(entityStream); | ||
assertBulkStatusScheduled(node); | ||
commandId = getBulkCommandId(node); | ||
} | ||
// waiting for the asynchronous video renditions recompute task | ||
txFeature.nextTransaction(); | ||
|
||
try (CloseableClientResponse response = httpClientRule.get("/management/bulk/" + commandId); | ||
InputStream entityStream = response.getEntityInputStream()) { | ||
JsonNode node = mapper.readTree(entityStream); | ||
assertEquals(SC_OK, response.getStatus()); | ||
assertBulkStatusCompleted(node); | ||
DocumentModel doc = session.getDocument(docRef); | ||
@SuppressWarnings("unchecked") | ||
var transcodedVideos = (List<Map<String, Serializable>>) doc.getPropertyValue(TRANSCODED_VIDEOS_PROPERTY); | ||
assertTranscodedVideos(null, transcodedVideos); | ||
} | ||
} | ||
|
||
protected void doTestRecomputeVideos(String query, List<String> expectedRenditions, | ||
boolean expectMissingConversionError, boolean expectSuccess) throws IOException { | ||
// Test there is no already generated renditions | ||
DocumentModel doc = session.getDocument(docRef); | ||
|
||
@SuppressWarnings("unchecked") | ||
var transcodedVideos = (List<Map<String, Serializable>>) doc.getPropertyValue(TRANSCODED_VIDEOS_PROPERTY); | ||
assertTrue(transcodedVideos.isEmpty()); | ||
|
||
// generating new video renditions | ||
MultivaluedMap<String, String> formData = new MultivaluedMapImpl(); | ||
if (query != null) { | ||
formData.add("query", query); | ||
} | ||
if (expectedRenditions != null) { | ||
formData.put("conversionNames", expectedRenditions); | ||
} | ||
|
||
String commandId; | ||
try (CloseableClientResponse response = httpClientRule.post("/management/videos/recompute/", formData); | ||
InputStream entityStream = response.getEntityInputStream()) { | ||
if (expectMissingConversionError) { | ||
assertEquals(SC_BAD_REQUEST, response.getStatus()); | ||
return; | ||
} | ||
assertEquals(SC_OK, response.getStatus()); | ||
JsonNode node = mapper.readTree(entityStream); | ||
assertBulkStatusScheduled(node); | ||
commandId = getBulkCommandId(node); | ||
} | ||
|
||
// waiting for the asynchronous video renditions recompute task | ||
txFeature.nextTransaction(); | ||
assertResponse(commandId, expectedRenditions, expectSuccess); | ||
|
||
} | ||
|
||
protected void assertResponse(String commandId, List<String> expectedRenditions, boolean expectSuccess) | ||
throws IOException { | ||
try (CloseableClientResponse response = httpClientRule.get("/management/bulk/" + commandId); | ||
InputStream entityStream = response.getEntityInputStream()) { | ||
JsonNode node = mapper.readTree(entityStream); | ||
assertEquals(SC_OK, response.getStatus()); | ||
assertBulkStatusCompleted(node); | ||
DocumentModel doc = session.getDocument(docRef); | ||
|
||
@SuppressWarnings("unchecked") | ||
var transcodedVideos = (List<Map<String, Serializable>>) doc.getPropertyValue(TRANSCODED_VIDEOS_PROPERTY); | ||
assertNotNull(transcodedVideos); | ||
if (expectSuccess) { | ||
assertEquals(1, node.get(STATUS_PROCESSED).asInt()); | ||
assertFalse(node.get(STATUS_HAS_ERROR).asBoolean()); | ||
assertEquals(0, node.get(STATUS_ERROR_COUNT).asInt()); | ||
assertEquals(1, node.get(STATUS_TOTAL).asInt()); | ||
assertTranscodedVideos(expectedRenditions, transcodedVideos); | ||
} else { | ||
assertEquals(0, node.get(STATUS_PROCESSED).asInt()); | ||
assertTrue(node.get(STATUS_HAS_ERROR).asBoolean()); | ||
assertEquals(1, node.get(STATUS_ERROR_COUNT).asInt()); | ||
assertEquals(0, node.get(STATUS_TOTAL).asInt()); | ||
assertEquals("Invalid query", node.get(STATUS_ERROR_MESSAGE).asText()); | ||
assertTrue(transcodedVideos.isEmpty()); | ||
} | ||
} | ||
} | ||
|
||
protected void assertTranscodedVideos(List<String> expectedRenditions, | ||
List<Map<String, Serializable>> transcodedVideos) { | ||
if (expectedRenditions == null) { | ||
expectedRenditions = videoService.getAvailableVideoConversionsNames(); | ||
} | ||
int nbExpectedRenditions = expectedRenditions.size(); | ||
assertEquals(nbExpectedRenditions, transcodedVideos.size()); | ||
for (int i = 0; i < nbExpectedRenditions; i++) { | ||
assertEquals(expectedRenditions.get(i), transcodedVideos.get(i).get("name")); | ||
} | ||
} | ||
|
||
} |
1 change: 1 addition & 0 deletions
1
modules/platform/video/nuxeo-platform-video-rest/src/test/resources/META-INF/MANIFEST.MF
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Bundle-SymbolicName: org.nuxeo.ecm.platform.video.rest.tests |
File renamed without changes.
7 changes: 4 additions & 3 deletions
7
...les/platform/nuxeo-platform-video/pom.xml → ...atform/video/nuxeo-platform-video/pom.xml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
[nuxeo] | ||
en_US=src/main/resources/OSGI-INF/l10n/messages_en_US.properties |
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Oops, something went wrong.