/** * 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.videosegmenter.ffmpeg; 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 org.opencastproject.job.api.Job; import org.opencastproject.job.api.JobBarrier; import org.opencastproject.mediapackage.Catalog; import org.opencastproject.mediapackage.MediaPackageElementParser; import org.opencastproject.mediapackage.MediaPackageElements; import org.opencastproject.mediapackage.track.TrackImpl; import org.opencastproject.mediapackage.track.VideoStreamImpl; import org.opencastproject.metadata.mpeg7.MediaLocator; import org.opencastproject.metadata.mpeg7.MediaLocatorImpl; import org.opencastproject.metadata.mpeg7.MediaRelTimeImpl; import org.opencastproject.metadata.mpeg7.MediaTime; import org.opencastproject.metadata.mpeg7.Mpeg7Catalog; import org.opencastproject.metadata.mpeg7.Mpeg7CatalogImpl; import org.opencastproject.metadata.mpeg7.Mpeg7CatalogService; import org.opencastproject.metadata.mpeg7.MultimediaContentType; import org.opencastproject.metadata.mpeg7.Segment; import org.opencastproject.metadata.mpeg7.TemporalDecomposition; import org.opencastproject.metadata.mpeg7.Video; import org.opencastproject.security.api.DefaultOrganization; import org.opencastproject.security.api.JaxbRole; import org.opencastproject.security.api.JaxbUser; import org.opencastproject.security.api.Organization; import org.opencastproject.security.api.OrganizationDirectoryService; import org.opencastproject.security.api.SecurityService; import org.opencastproject.security.api.User; import org.opencastproject.security.api.UserDirectoryService; import org.opencastproject.serviceregistry.api.IncidentService; import org.opencastproject.serviceregistry.api.ServiceRegistry; import org.opencastproject.serviceregistry.api.ServiceRegistryInMemoryImpl; import org.opencastproject.util.MimeTypes; import org.opencastproject.workspace.api.Workspace; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.easymock.EasyMock; import org.easymock.IAnswer; import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; import java.net.URI; import java.util.Collections; import java.util.Iterator; import java.util.LinkedList; import java.util.List; /** * Test class for video segmentation. */ public class VideoSegmenterTest { /** Video file to test. Contains a new scene at 00:12 */ protected static final String mediaResource = "/scene-change.mov"; /** Video file to test the optimization */ protected static final String mediaResource1 = "/test-optimization.mp4"; /** Duration of whole movie */ protected static final long mediaDuration = 20000L; protected static final long mediaDuration1 = 30000L; /** Duration of the first segment */ protected static final long firstSegmentDuration = 12000L; /** Duration of the seconds segment */ protected static final long secondSegmentDuration = mediaDuration - firstSegmentDuration; /** The in-memory service registration */ protected ServiceRegistry serviceRegistry = null; protected ServiceRegistry serviceRegistry1 = null; /** The video segmenter */ protected VideoSegmenterServiceImpl vsegmenter = null; protected VideoSegmenterServiceImpl vsegmenter1 = null; protected Mpeg7CatalogService mpeg7Service = null; protected Mpeg7CatalogService mpeg7Service1 = null; /** The media url */ protected static TrackImpl track = null; protected static TrackImpl track1 = null; /** Temp file */ protected File tempFile = null; protected File tempFile1 = null; @Rule public TemporaryFolder testFolder = new TemporaryFolder(); /** * Copies test files to the local file system, since jmf is not able to access movies from the resource section of a * bundle. * * @throws Exception * if setup fails */ @BeforeClass public static void setUpClass() throws Exception { track = TrackImpl.fromURI(VideoSegmenterTest.class.getResource(mediaResource).toURI()); track.setFlavor(MediaPackageElements.PRESENTATION_SOURCE); track.setMimeType(MimeTypes.MJPEG); track.addStream(new VideoStreamImpl()); track.setDuration(new Long(20000)); track1 = TrackImpl.fromURI(VideoSegmenterTest.class.getResource(mediaResource1).toURI()); track1.setFlavor(MediaPackageElements.PRESENTATION_SOURCE); track1.setMimeType(MimeTypes.MJPEG); track1.addStream(new VideoStreamImpl()); track1.setDuration(mediaDuration1); } /** * Setup for the video segmenter service, including creation of a mock workspace. * * @throws Exception * if setup fails */ @Before public void setUp() throws Exception { mpeg7Service = new Mpeg7CatalogService(); Workspace workspace = EasyMock.createNiceMock(Workspace.class); EasyMock.expect(workspace.get((URI) EasyMock.anyObject())).andReturn(new File(track.getURI())); tempFile = testFolder.newFile(getClass().getName() + ".xml"); EasyMock.expect( workspace.putInCollection((String) EasyMock.anyObject(), (String) EasyMock.anyObject(), (InputStream) EasyMock.anyObject())).andAnswer(new IAnswer<URI>() { @Override public URI answer() throws Throwable { InputStream in = (InputStream) EasyMock.getCurrentArguments()[2]; IOUtils.copy(in, new FileOutputStream(tempFile)); return tempFile.toURI(); } }); EasyMock.replay(workspace); mpeg7Service1 = new Mpeg7CatalogService(); Workspace workspace1 = EasyMock.createNiceMock(Workspace.class); EasyMock.expect(workspace1.get((URI) EasyMock.anyObject())).andReturn(new File(track1.getURI())); tempFile1 = testFolder.newFile(getClass().getName() + "-1.xml"); EasyMock.expect( workspace1.putInCollection((String) EasyMock.anyObject(), (String) EasyMock.anyObject(), (InputStream) EasyMock.anyObject())).andAnswer(new IAnswer<URI>() { @Override public URI answer() throws Throwable { InputStream in = (InputStream) EasyMock.getCurrentArguments()[2]; IOUtils.copy(in, new FileOutputStream(tempFile1)); return tempFile1.toURI(); } }); EasyMock.replay(workspace1); User anonymous = new JaxbUser("anonymous", "test", new DefaultOrganization(), new JaxbRole( DefaultOrganization.DEFAULT_ORGANIZATION_ANONYMOUS, new DefaultOrganization())); UserDirectoryService userDirectoryService = EasyMock.createMock(UserDirectoryService.class); EasyMock.expect(userDirectoryService.loadUser((String) EasyMock.anyObject())).andReturn(anonymous).anyTimes(); EasyMock.replay(userDirectoryService); Organization organization = new DefaultOrganization(); OrganizationDirectoryService organizationDirectoryService = EasyMock.createMock(OrganizationDirectoryService.class); EasyMock.expect(organizationDirectoryService.getOrganization((String) EasyMock.anyObject())) .andReturn(organization).anyTimes(); EasyMock.replay(organizationDirectoryService); SecurityService securityService = EasyMock.createNiceMock(SecurityService.class); EasyMock.expect(securityService.getUser()).andReturn(anonymous).anyTimes(); EasyMock.expect(securityService.getOrganization()).andReturn(organization).anyTimes(); EasyMock.replay(securityService); vsegmenter = new VideoSegmenterServiceImpl(); serviceRegistry = new ServiceRegistryInMemoryImpl(vsegmenter, securityService, userDirectoryService, organizationDirectoryService, EasyMock.createNiceMock(IncidentService.class)); vsegmenter.setServiceRegistry(serviceRegistry); vsegmenter.setMpeg7CatalogService(mpeg7Service); vsegmenter.setWorkspace(workspace); vsegmenter.setSecurityService(securityService); vsegmenter.setUserDirectoryService(userDirectoryService); vsegmenter.setOrganizationDirectoryService(organizationDirectoryService); vsegmenter1 = new VideoSegmenterServiceImpl(); serviceRegistry1 = new ServiceRegistryInMemoryImpl(vsegmenter1, securityService, userDirectoryService, organizationDirectoryService, EasyMock.createNiceMock(IncidentService.class)); vsegmenter1.setServiceRegistry(serviceRegistry1); vsegmenter1.setMpeg7CatalogService(mpeg7Service1); vsegmenter1.setWorkspace(workspace1); vsegmenter1.setSecurityService(securityService); vsegmenter1.setUserDirectoryService(userDirectoryService); vsegmenter1.setOrganizationDirectoryService(organizationDirectoryService); // set parameters for segmentation because the default parameters are not suitable for too short videos vsegmenter.prefNumber = 2; vsegmenter.stabilityThreshold = 2; vsegmenter.absoluteMin = 1; vsegmenter1.stabilityThreshold = 2; vsegmenter1.changesThreshold = 0.025f; vsegmenter1.prefNumber = 5; vsegmenter1.maxCycles = 5; vsegmenter1.maxError = 0.2f; vsegmenter1.absoluteMin = 1; } /** * @throws java.io.File.IOException */ @After public void tearDown() throws Exception { FileUtils.deleteQuietly(tempFile); FileUtils.deleteQuietly(tempFile1); ((ServiceRegistryInMemoryImpl) serviceRegistry).dispose(); ((ServiceRegistryInMemoryImpl) serviceRegistry1).dispose(); } @Test public void testAnalyze() throws Exception { Job receipt = vsegmenter.segment(track); JobBarrier jobBarrier = new JobBarrier(null, serviceRegistry, 1000, receipt); jobBarrier.waitForJobs(); Catalog catalog = (Catalog) MediaPackageElementParser.getFromXml(receipt.getPayload()); Mpeg7Catalog mpeg7 = new Mpeg7CatalogImpl(catalog.getURI().toURL().openStream()); // Is there multimedia content in the mpeg7? assertTrue("Audiovisual content was expected", mpeg7.hasVideoContent()); assertNotNull("Audiovisual content expected", mpeg7.multimediaContent().next().elements().hasNext()); MultimediaContentType contentType = mpeg7.multimediaContent().next().elements().next(); // Is there at least one segment? TemporalDecomposition<? extends Segment> segments = contentType.getTemporalDecomposition(); Iterator<? extends Segment> si = segments.segments(); assertTrue(si.hasNext()); Segment firstSegment = si.next(); MediaTime firstSegmentMediaTime = firstSegment.getMediaTime(); long startTime = firstSegmentMediaTime.getMediaTimePoint().getTimeInMilliseconds(); long duration = firstSegmentMediaTime.getMediaDuration().getDurationInMilliseconds(); assertEquals("Unexpected start time of first segment", 0, startTime); assertEquals("Unexpected duration of first segment", firstSegmentDuration, duration); // What about the second one? assertTrue("Video is expected to have more than one segment", si.hasNext()); Segment secondSegment = si.next(); MediaTime secondSegmentMediaTime = secondSegment.getMediaTime(); startTime = secondSegmentMediaTime.getMediaTimePoint().getTimeInMilliseconds(); duration = secondSegmentMediaTime.getMediaDuration().getDurationInMilliseconds(); assertEquals("Unexpected start time of second segment", firstSegmentDuration, startTime); assertEquals("Unexpected duration of second segment", secondSegmentDuration, duration); // There should be no third segment assertFalse("Found an unexpected third video segment", si.hasNext()); } @Test public void testAnalyzeOptimization() throws Exception { Job receipt = vsegmenter1.segment(track1); JobBarrier jobBarrier = new JobBarrier(null, serviceRegistry1, 1000, receipt); jobBarrier.waitForJobs(); Catalog catalog = (Catalog) MediaPackageElementParser.getFromXml(receipt.getPayload()); Mpeg7Catalog mpeg7 = new Mpeg7CatalogImpl(catalog.getURI().toURL().openStream()); // Is there multimedia content in the mpeg7? assertTrue("Audiovisual content was expected", mpeg7.hasVideoContent()); assertNotNull("Audiovisual content expected", mpeg7.multimediaContent().next().elements().hasNext()); MultimediaContentType contentType = mpeg7.multimediaContent().next().elements().next(); // Is there at least one segment? TemporalDecomposition<? extends Segment> segments = contentType.getTemporalDecomposition(); Iterator<? extends Segment> si = segments.segments(); assertTrue(si.hasNext()); // Is the error of optimization small enough? int segmentCounter = 0; for ( ; si.hasNext(); ++segmentCounter) { si.next(); } float error = Math.abs((segmentCounter - vsegmenter1.prefNumber) / (float)vsegmenter1.prefNumber); assertTrue("Error of Optimization is too big", error <= vsegmenter1.maxError); } @Test public void testAnalyzeOptimizedList() throws Exception { Job receipt = vsegmenter.segment(track); JobBarrier jobBarrier = new JobBarrier(null, serviceRegistry, 1000, receipt); jobBarrier.waitForJobs(); Catalog catalog = (Catalog) MediaPackageElementParser.getFromXml(receipt.getPayload()); Mpeg7Catalog mpeg7 = new Mpeg7CatalogImpl(catalog.getURI().toURL().openStream()); List<OptimizationStep> optimizedList = new LinkedList<OptimizationStep>(); OptimizationStep firstStep = new OptimizationStep(10, 0.015f, 46, 41, mpeg7, null); OptimizationStep secondStep = new OptimizationStep(10, 0.167f, 34, 41, mpeg7, null); OptimizationStep thirdStep = new OptimizationStep(10, 0.011f, 44, 41, mpeg7, null); OptimizationStep fourthStep = new OptimizationStep(10, 0.200f, 23, 41, mpeg7, null); float error1 = (46 - 41) / (float)41; // ~ 0.122 float error2 = (34 - 41) / (float)41; // ~ -0.171 float error3 = (44 - 41) / (float)41; // ~ 0.073 float error4 = (23 - 41) / (float)41; // ~ -0.439 optimizedList.add(firstStep); optimizedList.add(secondStep); optimizedList.add(thirdStep); optimizedList.add(fourthStep); Collections.sort(optimizedList); // check if the errors were calculated correctly and whether the elements are in the correct order assertEquals("first element of optimized list incorrect", error3, optimizedList.get(0).getError(), 0.0001f); assertEquals("second element of optimized list incorrect", error1, optimizedList.get(1).getError(), 0.0001f); assertEquals("third element of optimized list incorrect", error4, optimizedList.get(2).getError(), 0.0001f); assertEquals("fourth element of optimized list incorrect", error2, optimizedList.get(3).getError(), 0.0001f); assertTrue("first error in optimized list is not positive", optimizedList.get(0).getError() >= 0); assertTrue("second error in optimized list is not bigger than first", optimizedList.get(1).getError() > optimizedList.get(0).getError()); assertTrue("third error in optimized list is not negative", optimizedList.get(2).getError() < 0); assertTrue("fourth error in optimized list is smaller than third", optimizedList.get(3).getError() > optimizedList.get(2).getError()); } @Test public void testAnalyzeSegmentMerging() { Mpeg7CatalogService mpeg7catalogService = vsegmenter.mpeg7CatalogService; MediaTime contentTime = new MediaRelTimeImpl(0, track.getDuration()); MediaLocator contentLocator = new MediaLocatorImpl(track.getURI()); Mpeg7Catalog mpeg7 = mpeg7catalogService.newInstance(); Video videoContent = mpeg7.addVideoContent("videosegment", contentTime, contentLocator); LinkedList<Segment> segments; LinkedList<Segment> result; int segmentcount = 1; track.setDuration(47000L); // list of segment durations (starttimes can be calculated from those) int[] segmentArray1 = {3000, 2000, 8000, 3000, 1000, 6000, 3000, 2000, 4000, 11000, 2000, 2000}; int[] segmentArray2 = {1000, 2000, 8000, 3000, 1000, 6000, 3000, 2000, 4000, 11000, 2000, 4000}; int[] segmentArray3 = {1000, 2000, 4000, 3000, 1000, 2000, 3000, 2000, 4000, 1000, 2000, 4000}; int[] segmentArray4 = {6000, 7000, 13000, 9000, 8000, 11000, 5000, 16000}; // predicted outcome of filtering the segmentation int[] prediction1 = {5000, 10000, 8000, 9000, 15000}; int[] prediction2 = {13000, 8000, 9000, 11000, 6000}; int[] prediction3 = {29000}; int[] prediction4 = {6000, 7000, 13000, 9000, 8000, 11000, 5000, 16000}; // total duration of respective segment arrays long duration1 = 47000L; long duration2 = 47000L; long duration3 = 29000L; long duration4 = 75000L; int[][] segmentArray = {segmentArray1, segmentArray2, segmentArray3, segmentArray4}; int[][] prediction = {prediction1, prediction2, prediction3, prediction4}; long[] durations = {duration1, duration2, duration3, duration4}; // check for all test segmentations if "filterSegmentation" yields the expected result for (int k = 0; k < segmentArray.length; k++) { segments = new LinkedList<Segment>(); result = new LinkedList<Segment>(); track.setDuration(durations[k]); int previous = 0; for (int i = 0; i < segmentArray[k].length; i++) { Segment s = videoContent.getTemporalDecomposition().createSegment("segment-" + segmentcount++); s.setMediaTime(new MediaRelTimeImpl(previous, segmentArray[k][i])); segments.add(s); previous += segmentArray[k][i]; } vsegmenter.filterSegmentation(segments, track, result, 5000); assertEquals("segment merging yields wrong number of segments", prediction[k].length, result.size()); previous = 0; for (int i = 0; i < prediction[k].length; i++) { String message = "segment " + i + " in set " + k + " has the wrong start time."; String message1 = "segment " + i + " in set " + k + " has the wrong duration."; assertEquals(message, previous, result.get(i).getMediaTime().getMediaTimePoint().getTimeInMilliseconds()); assertEquals(message1, prediction[k][i], result.get(i).getMediaTime().getMediaDuration() .getDurationInMilliseconds()); previous += prediction[k][i]; } } } }