/**
* Licensed to The Apereo Foundation under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
*
* The Apereo Foundation licenses this file to you under the Educational
* Community 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://opensource.org/licenses/ecl2.txt
*
* 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.
*
*/
package org.opencastproject.workflow.handler.videoeditor;
import org.opencastproject.job.api.Job;
import org.opencastproject.mediapackage.Catalog;
import org.opencastproject.mediapackage.MediaPackage;
import org.opencastproject.mediapackage.MediaPackageBuilder;
import org.opencastproject.mediapackage.MediaPackageBuilderFactory;
import org.opencastproject.mediapackage.MediaPackageElement;
import org.opencastproject.mediapackage.MediaPackageElementFlavor;
import org.opencastproject.mediapackage.MediaPackageElementParser;
import org.opencastproject.mediapackage.MediaPackageException;
import org.opencastproject.mediapackage.Track;
import org.opencastproject.mediapackage.selector.TrackSelector;
import org.opencastproject.serviceregistry.api.ServiceRegistry;
import org.opencastproject.serviceregistry.api.ServiceRegistryException;
import org.opencastproject.smil.api.SmilException;
import org.opencastproject.smil.api.SmilService;
import org.opencastproject.smil.entity.api.Smil;
import org.opencastproject.util.NotFoundException;
import org.opencastproject.videoeditor.api.ProcessFailedException;
import org.opencastproject.videoeditor.api.VideoEditorService;
import org.opencastproject.workflow.api.WorkflowInstance;
import org.opencastproject.workflow.api.WorkflowInstanceImpl;
import org.opencastproject.workflow.api.WorkflowOperationException;
import org.opencastproject.workflow.api.WorkflowOperationInstance;
import org.opencastproject.workflow.api.WorkflowOperationInstanceImpl;
import org.opencastproject.workflow.api.WorkflowOperationResult;
import org.opencastproject.workspace.api.Workspace;
import org.easymock.EasyMock;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.xml.sax.SAXException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.bind.JAXBException;
/**
* Test class for {@link VideoEditorWorkflowOperationHandler}
*/
public class VideoEditorWorkflowOperationHandlerTest {
private VideoEditorWorkflowOperationHandler videoEditorWorkflowOperationHandler = null;
private SmilService smilService = null;
private VideoEditorService videoEditorServiceMock = null;
private Workspace workspaceMock = null;
private URI mpURI = null;
private MediaPackage mp = null;
private URI mpSmilURI = null;
private MediaPackage mpSmil = null;
@Before
public void setUp() throws MediaPackageException, IOException, NotFoundException, URISyntaxException, SmilException,
JAXBException, SAXException {
MediaPackageBuilder mpBuilder = MediaPackageBuilderFactory.newInstance().newMediaPackageBuilder();
mpURI = VideoEditorWorkflowOperationHandlerTest.class.getResource("/editor_mediapackage.xml").toURI();
mp = mpBuilder.loadFromXml(mpURI.toURL().openStream());
mpSmilURI = VideoEditorWorkflowOperationHandlerTest.class.getResource("/editor_smil_mediapackage.xml").toURI();
mpSmil = mpBuilder.loadFromXml(mpSmilURI.toURL().openStream());
videoEditorServiceMock = EasyMock.createNiceMock(VideoEditorService.class);
workspaceMock = EasyMock.createNiceMock(Workspace.class);
smilService = SmilServiceMock.createSmilServiceMock(mpSmilURI);
videoEditorWorkflowOperationHandler = new VideoEditorWorkflowOperationHandler();
videoEditorWorkflowOperationHandler.setJobBarrierPollingInterval(0);
videoEditorWorkflowOperationHandler.setSmilService(smilService);
videoEditorWorkflowOperationHandler.setVideoEditorService(videoEditorServiceMock);
videoEditorWorkflowOperationHandler.setWorkspace(workspaceMock);
}
private static Map<String, String> getDefaultConfiguration(boolean interactive) {
Map<String, String> configuration = new HashMap<String, String>();
configuration.put("source-flavors", "*/work");
configuration.put("preview-flavors", "*/preview");
configuration.put("skipped-flavors", "*/work");
configuration.put("smil-flavors", "*/smil");
configuration.put("target-smil-flavor", "episode/smil");
configuration.put("target-flavor-subtype", "trimmed");
configuration.put("interactive", Boolean.toString(interactive));
return configuration;
}
private WorkflowInstanceImpl getWorkflowInstance(MediaPackage mp, Map<String, String> configurations) {
WorkflowInstanceImpl workflowInstance = new WorkflowInstanceImpl();
workflowInstance.setId(1);
workflowInstance.setState(WorkflowInstance.WorkflowState.RUNNING);
workflowInstance.setMediaPackage(mp);
WorkflowOperationInstanceImpl operation = new WorkflowOperationInstanceImpl("op",
WorkflowOperationInstance.OperationState.RUNNING);
operation.setTemplate("editor");
operation.setState(WorkflowOperationInstance.OperationState.RUNNING);
for (String key : configurations.keySet()) {
operation.setConfiguration(key, configurations.get(key));
}
List<WorkflowOperationInstance> operations = new ArrayList<WorkflowOperationInstance>(1);
operations.add(operation);
workflowInstance.setOperations(operations);
return workflowInstance;
}
@Test
public void testEditorOperationStart() throws WorkflowOperationException, IOException {
// uri for new preview track smil file
EasyMock.expect(
workspaceMock.put((String) EasyMock.anyObject(), (String) EasyMock.anyObject(),
(String) EasyMock.anyObject(), (InputStream) EasyMock.anyObject())).andReturn(
URI.create("http://localhost:8080/foo/presenter.smil"));
// uri for new episode smil file
String episodeSmilUri = "http://localhost:8080/foo/episode.smil";
EasyMock.expect(
workspaceMock.put((String) EasyMock.anyObject(), (String) EasyMock.anyObject(),
(String) EasyMock.anyObject(), (InputStream) EasyMock.anyObject())).andReturn(
URI.create(episodeSmilUri));
EasyMock.replay(workspaceMock);
WorkflowInstanceImpl workflowInstance = getWorkflowInstance(mp, getDefaultConfiguration(true));
WorkflowOperationResult result = videoEditorWorkflowOperationHandler.start(workflowInstance, null);
Assert.assertNotNull(
"VideoEditor workflow operation returns null but should be an instantiated WorkflowOperationResult", result);
EasyMock.verify(workspaceMock);
WorkflowOperationInstance worflowOperationInstance = workflowInstance.getCurrentOperation();
String smillFlavorsProperty = worflowOperationInstance.getConfiguration("smil-flavors");
String previewFlavorsProperty = worflowOperationInstance.getConfiguration("preview-flavors");
MediaPackageElementFlavor smilFlavor = MediaPackageElementFlavor.parseFlavor(smillFlavorsProperty);
MediaPackageElementFlavor previewFlavor = MediaPackageElementFlavor.parseFlavor(previewFlavorsProperty);
// each preview track (e.g. presenter/preview) should have an own smil catalog in media package
Catalog[] previewSmilCatalogs = result.getMediaPackage().getCatalogs(
new MediaPackageElementFlavor("presenter", "smil"));
Assert.assertTrue(previewSmilCatalogs != null && previewSmilCatalogs.length > 0);
for (Track track : result.getMediaPackage().getTracks()) {
if (track.getFlavor().matches(previewFlavor)) {
boolean smilCatalogFound = false;
MediaPackageElementFlavor trackSmilFlavor = new MediaPackageElementFlavor(track.getFlavor().getType(),
smilFlavor.getSubtype());
for (Catalog previewSmilCatalog : previewSmilCatalogs) {
if (previewSmilCatalog.getFlavor().matches(trackSmilFlavor)) {
smilCatalogFound = true;
break;
}
}
Assert.assertTrue("Mediapackage doesn't contain a smil catalog with flavor " + trackSmilFlavor.toString(),
smilCatalogFound);
}
}
// an "target-smil-flavor catalog" schould be in media package
String targetSmilFlavorProperty = worflowOperationInstance.getConfiguration("target-smil-flavor");
Catalog[] episodeSmilCatalogs = result.getMediaPackage().getCatalogs(
MediaPackageElementFlavor.parseFlavor(targetSmilFlavorProperty));
Assert.assertTrue("Mediapackage should contain catalog with flavor " + targetSmilFlavorProperty,
episodeSmilCatalogs != null && episodeSmilCatalogs.length > 0);
Assert.assertTrue("Target smil catalog URI does not match",
episodeSmilCatalogs[0].getURI().compareTo(URI.create(episodeSmilUri)) == 0);
}
@Test
public void testEditorOperationSkip() throws WorkflowOperationException {
WorkflowInstanceImpl workflowInstance = getWorkflowInstance(mp, getDefaultConfiguration(true));
WorkflowOperationResult result = videoEditorWorkflowOperationHandler.skip(workflowInstance, null);
Assert.assertNotNull(
"VideoEditor workflow operation returns null but should be an instantiated WorkflowOperationResult", result);
// mediapackage should contain new derived track with flavor given by "target-flavor-subtype" configuration
WorkflowOperationInstance worflowOperationInstance = workflowInstance.getCurrentOperation();
String targetFlavorSubtypeProperty = worflowOperationInstance.getConfiguration("target-flavor-subtype");
String skippedFlavorsProperty = worflowOperationInstance.getConfiguration("skipped-flavors");
TrackSelector trackSelector = new TrackSelector();
trackSelector.addFlavor(skippedFlavorsProperty);
Collection<Track> skippedTracks = trackSelector.select(result.getMediaPackage(), false);
Assert.assertTrue("Mediapackage does not contain any tracks matching flavor " + skippedFlavorsProperty,
skippedTracks != null && !skippedTracks.isEmpty());
for (Track skippedTrack : skippedTracks) {
MediaPackageElementFlavor derivedTrackFlavor = MediaPackageElementFlavor.flavor(skippedTrack.getFlavor()
.getType(), targetFlavorSubtypeProperty);
MediaPackageElement[] derivedElements = result.getMediaPackage().getDerived(skippedTrack, derivedTrackFlavor);
Assert.assertTrue("Media package should contain track with flavor " + derivedTrackFlavor.toString(),
derivedElements != null && derivedElements.length > 0);
}
}
@Test
public void testEditorOperationInteractiveSkip() throws WorkflowOperationException {
WorkflowInstanceImpl workflowInstance = getWorkflowInstance(mp, getDefaultConfiguration(false));
WorkflowOperationResult result = videoEditorWorkflowOperationHandler.start(workflowInstance, null);
Assert.assertNotNull(
"VideoEditor workflow operation returns null but should be an instantiated WorkflowOperationResult", result);
// mediapackage should contain new derived track with flavor given by "target-flavor-subtype" configuration
WorkflowOperationInstance worflowOperationInstance = workflowInstance.getCurrentOperation();
String targetFlavorSubtypeProperty = worflowOperationInstance.getConfiguration("target-flavor-subtype");
String skippedFlavorsProperty = worflowOperationInstance.getConfiguration("skipped-flavors");
TrackSelector trackSelector = new TrackSelector();
trackSelector.addFlavor(skippedFlavorsProperty);
Collection<Track> skippedTracks = trackSelector.select(result.getMediaPackage(), false);
Assert.assertTrue("Mediapackage does not contain any tracks matching flavor " + skippedFlavorsProperty,
skippedTracks != null && !skippedTracks.isEmpty());
for (Track skippedTrack : skippedTracks) {
MediaPackageElementFlavor derivedTrackFlavor = MediaPackageElementFlavor.flavor(skippedTrack.getFlavor()
.getType(), targetFlavorSubtypeProperty);
MediaPackageElement[] derivedElements = result.getMediaPackage().getDerived(skippedTrack, derivedTrackFlavor);
Assert.assertTrue("Media package should contain track with flavor " + derivedTrackFlavor.toString(),
derivedElements != null && derivedElements.length > 0);
}
}
@Test
@Ignore
public void testEditorOperationSkipWithModifiedSkippedFlavorsAndTargetFlavorProperty()
throws WorkflowOperationException {
Map<String, String> configuration = getDefaultConfiguration(true);
configuration.put("skipped-flavors", "*/preview");
configuration.put("target-flavor-subtype", "edited");
WorkflowInstanceImpl workflowInstance = getWorkflowInstance(mp, configuration);
WorkflowOperationResult result = videoEditorWorkflowOperationHandler.skip(workflowInstance, null);
Assert.assertNotNull(
"VideoEditor workflow operation returns null but should be an instantiated WorkflowOperationResult", result);
// mediapackage should contain new derived track with flavor given by "target-flavor-subtype" configuration
WorkflowOperationInstance worflowOperationInstance = workflowInstance.getCurrentOperation();
String targetFlavorSubtypeProperty = worflowOperationInstance.getConfiguration("target-flavor-subtype");
String skippedFlavorsProperty = worflowOperationInstance.getConfiguration("skipped-flavors");
TrackSelector trackSelector = new TrackSelector();
trackSelector.addFlavor(skippedFlavorsProperty);
Collection<Track> skippedTracks = trackSelector.select(result.getMediaPackage(), false);
Assert.assertTrue("Mediapackage does not contain any tracks matching flavor " + skippedFlavorsProperty,
skippedTracks != null && !skippedTracks.isEmpty());
for (Track skippedTrack : skippedTracks) {
MediaPackageElementFlavor derivedTrackFlavor = MediaPackageElementFlavor.flavor(skippedTrack.getFlavor()
.getType(), targetFlavorSubtypeProperty);
MediaPackageElement[] derivedElements = result.getMediaPackage().getDerived(skippedTrack, derivedTrackFlavor);
Assert.assertTrue("Media package should contain track with flavor " + derivedTrackFlavor.toString(),
derivedElements != null && derivedElements.length > 0);
Assert.assertTrue("Mediapackage schould contain a derived track with flavor subtype "
+ targetFlavorSubtypeProperty,
derivedElements[0].getFlavor().getSubtype().equals(targetFlavorSubtypeProperty));
}
}
@Test
public void testEditorResume() throws WorkflowOperationException, URISyntaxException, NotFoundException, IOException,
ProcessFailedException, ServiceRegistryException, MediaPackageException {
// filled smil file
URI episodeSmilURI = VideoEditorWorkflowOperationHandlerTest.class.getResource("/editor_smil_filled.smil").toURI();
File episodeSmilFile = new File(episodeSmilURI);
// setup mock services
EasyMock.expect(workspaceMock.get((URI) EasyMock.anyObject())).andReturn(episodeSmilFile);
EasyMock.expect(
workspaceMock.put((String) EasyMock.anyObject(), (String) EasyMock.anyObject(),
(String) EasyMock.anyObject(), (InputStream) EasyMock.anyObject())).andReturn(episodeSmilURI);
EasyMock.expect(
workspaceMock.moveTo((URI) EasyMock.anyObject(), (String) EasyMock.anyObject(),
(String) EasyMock.anyObject(), (String) EasyMock.anyObject())).andReturn(
URI.create("http://localhost:8080/foo/trimmed.mp4"));
Job job = EasyMock.createNiceMock(Job.class);
EasyMock.expect(job.getPayload()).andReturn(MediaPackageElementParser.getAsXml(mpSmil.getTracks()[0])).anyTimes();
EasyMock.expect(job.getStatus()).andReturn(Job.Status.FINISHED);
ServiceRegistry serviceRegistry = EasyMock.createNiceMock(ServiceRegistry.class);
videoEditorWorkflowOperationHandler.setServiceRegistry(serviceRegistry);
EasyMock.expect(serviceRegistry.getJob(EasyMock.anyLong())).andReturn(job);
EasyMock.expect(videoEditorServiceMock.processSmil((Smil) EasyMock.anyObject())).andReturn(Arrays.asList(job));
EasyMock.replay(workspaceMock, job, serviceRegistry, videoEditorServiceMock);
WorkflowInstanceImpl workflowInstance = getWorkflowInstance(mpSmil, getDefaultConfiguration(true));
// run test
WorkflowOperationResult result = videoEditorWorkflowOperationHandler.resume(workflowInstance, null, null);
Assert.assertNotNull(
"VideoEditor workflow operation returns null but should be an instantiated WorkflowOperationResult", result);
EasyMock.verify(workspaceMock, job, serviceRegistry, videoEditorServiceMock);
// verify trimmed track derived from source track
WorkflowOperationInstance worflowOperationInstance = workflowInstance.getCurrentOperation();
String targetFlavorSubtypeProperty = worflowOperationInstance.getConfiguration("target-flavor-subtype");
String sourceFlavorsProperty = worflowOperationInstance.getConfiguration("source-flavors");
TrackSelector trackSelector = new TrackSelector();
trackSelector.addFlavor(sourceFlavorsProperty);
Collection<Track> sourceTracks = trackSelector.select(result.getMediaPackage(), false);
Assert.assertTrue("Mediapackage does not contain any tracks matching flavor " + sourceFlavorsProperty,
sourceTracks != null && !sourceTracks.isEmpty());
for (Track sourceTrack : sourceTracks) {
MediaPackageElementFlavor targetFlavor = MediaPackageElementFlavor.flavor(sourceTrack.getFlavor().getType(),
targetFlavorSubtypeProperty);
Track[] targetTracks = result.getMediaPackage().getTracks(targetFlavor);
Assert.assertTrue("Media package doesn't contain track with flavor " + targetFlavor.toString(),
targetTracks != null && targetTracks.length > 0);
}
}
}