VideoEditorWorkflowOperationHandler extends ResumableWorkflowOperationHandlerBase { @Override public WorkflowOperationResult resume(WorkflowInstance workflowInstance, JobContext context, Map<String, String> properties) throws WorkflowOperationException { MediaPackage mp = workflowInstance.getMediaPackage(); logger.info("Resume video editor operation for mediapackage {}", mp.getIdentifier().toString()); WorkflowOperationInstance worflowOperationInstance = workflowInstance.getCurrentOperation(); String sourceTrackFlavorsProperty = StringUtils .trimToNull(worflowOperationInstance.getConfiguration(SOURCE_FLAVORS_PROPERTY)); if (sourceTrackFlavorsProperty == null) { throw new WorkflowOperationException( format("Required configuration property %s not set.", SOURCE_FLAVORS_PROPERTY)); } String targetSmilFlavorProperty = StringUtils .trimToNull(worflowOperationInstance.getConfiguration(TARGET_SMIL_FLAVOR_PROPERTY)); if (targetSmilFlavorProperty == null) { throw new WorkflowOperationException( format("Required configuration property %s not set.", TARGET_SMIL_FLAVOR_PROPERTY)); } String targetFlavorSybTypeProperty = StringUtils .trimToNull(worflowOperationInstance.getConfiguration(TARGET_FLAVOR_SUBTYPE_PROPERTY)); final boolean skipProcessing = BooleanUtils .toBoolean(worflowOperationInstance.getConfiguration(SKIP_PROCESSING_PROPERTY)); if (!skipProcessing) { if (targetFlavorSybTypeProperty == null) { throw new WorkflowOperationException( format("Required configuration property %s not set.", TARGET_FLAVOR_SUBTYPE_PROPERTY)); } } boolean skipIfNoTrim = BooleanUtils.toBoolean(worflowOperationInstance.getConfiguration(SKIP_NOT_TRIMMED_PROPERTY)); TrackSelector trackSelector = new TrackSelector(); for (String flavor : asList(sourceTrackFlavorsProperty)) { trackSelector.addFlavor(flavor); } Collection<Track> sourceTracks = trackSelector.select(mp, false); if (sourceTracks.isEmpty()) { throw new WorkflowOperationException(format("No source tracks found in mediapacksge %s with flavors %s.", mp.getIdentifier().toString(), sourceTrackFlavorsProperty)); } MediaPackageElementFlavor smilTargetFlavor = MediaPackageElementFlavor.parseFlavor(targetSmilFlavorProperty); Catalog[] smilCatalogs = mp.getCatalogs(smilTargetFlavor); if (smilCatalogs == null || smilCatalogs.length == 0) { throw new WorkflowOperationException(format("No SMIL catalog found in mediapackage %s with flavor %s.", mp.getIdentifier().toString(), targetSmilFlavorProperty)); } File smilFile = null; Smil smil = null; try { smilFile = workspace.get(smilCatalogs[0].getURI()); smil = smilService.fromXml(smilFile).getSmil(); smil = replaceAllTracksWith(smil, sourceTracks.toArray(new Track[sourceTracks.size()])); InputStream is = null; try { is = IOUtils.toInputStream(smil.toXML(), "UTF-8"); workspace.delete(mp.getIdentifier().toString(), smilCatalogs[0].getIdentifier()); mp.remove(smilCatalogs[0]); URI newSmilUri = workspace.put(mp.getIdentifier().toString(), smil.getId(), SMIL_FILE_NAME, is); Catalog catalog = (Catalog) MediaPackageElementBuilderFactory.newInstance().newElementBuilder() .elementFromURI(newSmilUri, MediaPackageElement.Type.Catalog, smilCatalogs[0].getFlavor()); catalog.setIdentifier(smil.getId()); mp.add(catalog); } catch (Exception ex) { throw new WorkflowOperationException(ex); } finally { IOUtils.closeQuietly(is); } } catch (NotFoundException ex) { throw new WorkflowOperationException(format("Failed to get SMIL catalog %s from mediapackage %s.", smilCatalogs[0].getIdentifier(), mp.getIdentifier().toString()), ex); } catch (IOException ex) { throw new WorkflowOperationException(format("Can't open SMIL catalog %s from mediapackage %s.", smilCatalogs[0].getIdentifier(), mp.getIdentifier().toString()), ex); } catch (SmilException ex) { throw new WorkflowOperationException(ex); } if (skipProcessing) { logger.info("VideoEdit workflow {} finished - smil file is {}", workflowInstance.getId(), smil.getId()); return createResult(mp, Action.CONTINUE); } if (skipIfNoTrim) { try { Smil filteredSmil = smilService.fromXml(smil.toXML()).getSmil(); for (SmilMediaObject element : smil.getBody().getMediaElements()) { if (element.isContainer()) { SmilMediaContainer container = (SmilMediaContainer) element; if (SmilMediaContainer.ContainerType.PAR == container.getContainerType()) { continue; } } filteredSmil = smilService.removeSmilElement(filteredSmil, element.getId()).getSmil(); } switch (filteredSmil.getBody().getMediaElements().size()) { case 0: logger.info("Skipping SMIL job generation for mediapackage '{}', " + "because the SMIL does not define any trimming points", mp.getIdentifier()); return skip(workflowInstance, context); case 1: if (mp.getDuration() < 0) break; SmilMediaContainer parElement = (SmilMediaContainer) filteredSmil.getBody().getMediaElements().get(0); boolean skip = true; for (SmilMediaObject elementChild : parElement.getElements()) { if (!elementChild.isContainer()) { SmilMediaElement media = (SmilMediaElement) elementChild; if ((media.getClipBeginMS() != 0) || (media.getClipEndMS() != mp.getDuration())) { skip = false; break; } } } if (skip) { logger.info("Skipping SMIL job generation for mediapackage '{}', " + "because the trimming points in the SMIL correspond " + "to the beginning and the end of the video", mp.getIdentifier()); return skip(workflowInstance, context); } break; default: break; } } catch (MalformedURLException | SmilException | JAXBException | SAXException e) { logger.warn("Error parsing input SMIL to determine if it has trimpoints. " + "We will assume it does and go on creating jobs."); } } List<Job> jobs = null; try { logger.info("Create processing jobs for SMIL file: {}", smilCatalogs[0].getIdentifier()); jobs = videoEditorService.processSmil(smil); if (!waitForStatus(jobs.toArray(new Job[jobs.size()])).isSuccess()) { throw new WorkflowOperationException( format("Processing SMIL file failed: %s", smilCatalogs[0].getIdentifier())); } logger.info("Finished processing of SMIL file: {}", smilCatalogs[0].getIdentifier()); } catch (ProcessFailedException ex) { throw new WorkflowOperationException( format("Finished processing of SMIL file: %s", smilCatalogs[0].getIdentifier()), ex); } Track editedTrack = null; boolean mpAdded = false; for (Job job : jobs) { try { editedTrack = (Track) MediaPackageElementParser.getFromXml(job.getPayload()); MediaPackageElementFlavor editedTrackFlavor = editedTrack.getFlavor(); editedTrack.setFlavor(new MediaPackageElementFlavor(editedTrackFlavor.getType(), targetFlavorSybTypeProperty)); URI editedTrackNewUri = workspace.moveTo(editedTrack.getURI(), mp.getIdentifier().toString(), editedTrack.getIdentifier(), FilenameUtils.getName(editedTrack.getURI().toString())); editedTrack.setURI(editedTrackNewUri); for (Track track : sourceTracks) { if (track.getFlavor().getType().equals(editedTrackFlavor.getType())) { mp.addDerived(editedTrack, track); mpAdded = true; break; } } if (!mpAdded) { mp.add(editedTrack); } } catch (MediaPackageException ex) { throw new WorkflowOperationException("Failed to get information about the edited track(s)", ex); } catch (NotFoundException | IOException | IllegalArgumentException ex) { throw new WorkflowOperationException("Moving edited track to work location failed.", ex); } catch (Exception ex) { throw new WorkflowOperationException(ex); } } logger.info("VideoEdit workflow {} finished", workflowInstance.getId()); return createResult(mp, Action.CONTINUE); }  @Override void activate(ComponentContext cc); @Override WorkflowOperationResult start(WorkflowInstance workflowInstance, JobContext context); @Override WorkflowOperationResult skip(WorkflowInstance workflowInstance, JobContext context); @Override WorkflowOperationResult resume(WorkflowInstance workflowInstance, JobContext context,
          Map<String, String> properties); void setSmilService(SmilService smilService); void setVideoEditorService(VideoEditorService editor); void setWorkspace(Workspace workspace);  }
@Test public void testEditorResume() throws WorkflowOperationException, URISyntaxException, NotFoundException, IOException, ProcessFailedException, ServiceRegistryException, MediaPackageException { URI episodeSmilURI = VideoEditorWorkflowOperationHandlerTest.class.getResource("/editor_smil_filled.smil").toURI(); File episodeSmilFile = new File(episodeSmilURI); 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: 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)); 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); 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); } }