org.codice.ddf.catalog.content.plugin.video.VideoThumbnailPluginTest.java Source code

Java tutorial

Introduction

Here is the source code for org.codice.ddf.catalog.content.plugin.video.VideoThumbnailPluginTest.java

Source

/**
 * Copyright (c) Codice Foundation
 *
 * <p>This is free software: you can redistribute it and/or modify it under the terms of the GNU
 * Lesser General Public License as published by the Free Software Foundation, either version 3 of
 * the License, or any later version.
 *
 * <p>This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details. A copy of the GNU Lesser General Public
 * License is distributed along with this program and can be found at
 * <http://www.gnu.org/licenses/lgpl.html>.
 */
package org.codice.ddf.catalog.content.plugin.video;

import static org.codice.ddf.catalog.content.plugin.video.VideoThumbnailPlugin.DEFAULT_MAX_FILE_SIZE_MB;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;

import ddf.catalog.Constants;
import ddf.catalog.content.data.ContentItem;
import ddf.catalog.content.operation.CreateStorageRequest;
import ddf.catalog.content.operation.CreateStorageResponse;
import ddf.catalog.content.operation.UpdateStorageRequest;
import ddf.catalog.content.operation.UpdateStorageResponse;
import ddf.catalog.data.impl.MetacardImpl;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import javax.activation.MimeType;
import javax.activation.MimeTypeParseException;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.SystemUtils;
import org.junit.After;
import org.junit.Assume;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;

public class VideoThumbnailPluginTest {

    private static final long BYTES_PER_MEGABYTE = 1024L * 1024L;

    private String binaryPath;

    private VideoThumbnailPlugin videoThumbnailPlugin;

    private HashMap<String, Map> tmpContentPaths;

    @BeforeClass
    public static void setUpClass() {
        Assume.assumeFalse("Skip unit tests on Windows. See DDF-3503.", SystemUtils.IS_OS_WINDOWS);
    }

    @Before
    public void setUp() throws IOException, MimeTypeParseException, URISyntaxException {
        System.setProperty("ddf.home", SystemUtils.USER_DIR);

        binaryPath = FilenameUtils.concat(System.getProperty("ddf.home"), "bin_third_party");

        videoThumbnailPlugin = new VideoThumbnailPlugin(createMockBundleContext());
        tmpContentPaths = new HashMap<>();
    }

    @After
    public void tearDown() {
        videoThumbnailPlugin.destroy();

        final File binaryFolder = new File(binaryPath);
        if (binaryFolder.exists() && !FileUtils.deleteQuietly(binaryFolder)) {
            binaryFolder.deleteOnExit();
        }
    }

    /**
     * Tests processing short.mp4. This file is short enough that FFmpeg will only generate one
     * thumbnail for it even if we request more than one.
     */
    @Test
    public void testProcessShortVideo() throws Exception {
        // given
        final ContentItem mockContentItem = createMockVideoContentItemFromResource("/short.mp4");

        // when
        final CreateStorageResponse processedCreateResponse = videoThumbnailPlugin
                .process(createMockCreateStorageResponse(mockContentItem));

        // then
        final List<ContentItem> processedContentItems = processedCreateResponse.getCreatedContentItems();
        assertThat("There should be exactly 1 returned content item", processedContentItems, hasSize(1));
        verifyThumbnailIsPng(mockContentItem, processedContentItems.get(0));
    }

    /**
     * Tests processing medium.mp4. This file is short enough that the plugin won't try to grab
     * thumbnails from different portions of the video but is long enough that the resulting thumbnail
     * will be a GIF.
     */
    @Test
    public void testProcessMediumVideo() throws Exception {
        // given
        final ContentItem mockContentItem = createMockVideoContentItemFromResource("/medium.mp4");

        // when
        final CreateStorageResponse processedCreateResponse = videoThumbnailPlugin
                .process(createMockCreateStorageResponse(mockContentItem));

        // then
        final List<ContentItem> processedContentItems = processedCreateResponse.getCreatedContentItems();
        assertThat("There should be exactly 1 returned content item", processedContentItems, hasSize(1));
        verifyThumbnailIsGif(mockContentItem, processedContentItems.get(0));
    }

    /**
     * Tests processing long.mp4. This file is long enough that the plugin will try to grab thumbnails
     * from different portions of the video.
     */
    @Test
    public void testProcessLongVideo() throws Exception {
        // given
        final ContentItem mockContentItem = createMockVideoContentItemFromResource("/long.mp4");

        // when
        final CreateStorageResponse processedCreateResponse = videoThumbnailPlugin
                .process(createMockCreateStorageResponse(mockContentItem));

        // then
        final List<ContentItem> processedContentItems = processedCreateResponse.getCreatedContentItems();
        assertThat("There should be exactly 1 returned content item", processedContentItems, hasSize(1));
        verifyThumbnailIsGif(mockContentItem, processedContentItems.get(0));
    }

    @Test
    public void testProcessNotVideoFile() throws Exception {
        // given
        final ContentItem mockContentItem = createMockContentItemOfMimeType("image/jpeg");

        // when
        final CreateStorageResponse processedCreateResponse = videoThumbnailPlugin
                .process(createMockCreateStorageResponse(mockContentItem));

        // then
        final List<ContentItem> processedContentItems = processedCreateResponse.getCreatedContentItems();
        assertThat("There should be exactly 1 returned content item", processedContentItems, hasSize(1));
        verifyThumbnailIsNotSet(mockContentItem, processedContentItems.get(0));
    }

    @Test
    public void testProcessCorruptedVideo() throws Exception {
        // given
        final ContentItem mockContentItem = createMockVideoContentItemFromResource("/corrupted.mp4");

        // when
        final CreateStorageResponse processedCreateResponse = videoThumbnailPlugin
                .process(createMockCreateStorageResponse(mockContentItem));

        // then
        final List<ContentItem> processedContentItems = processedCreateResponse.getCreatedContentItems();
        assertThat("There should be exactly 1 returned content item", processedContentItems, hasSize(1));
        verifyThumbnailIsNotSet(mockContentItem, processedContentItems.get(0));
    }

    @Test
    public void testProcessVideoLargerThanDefaultMaxFileSize() throws Exception {
        // given
        final ContentItem mockContentItem = createMockVideoContentItemWithSizeBytes(
                DEFAULT_MAX_FILE_SIZE_MB * BYTES_PER_MEGABYTE + 1);

        // when
        final CreateStorageResponse processedCreateResponse = videoThumbnailPlugin
                .process(createMockCreateStorageResponse(mockContentItem));

        // then
        final List<ContentItem> processedContentItems = processedCreateResponse.getCreatedContentItems();
        assertThat("There should be exactly 1 returned content item", processedContentItems, hasSize(1));
        verifyThumbnailIsNotSet(mockContentItem, processedContentItems.get(0));
    }

    @Test
    public void testProcessVideoLargerThanConfiguredMaxFileSize() throws Exception {
        // given
        final int maxFileSizeMB = 5;
        videoThumbnailPlugin.setMaxFileSizeMB(maxFileSizeMB);
        final ContentItem mockContentItem = createMockVideoContentItemWithSizeBytes(
                maxFileSizeMB * BYTES_PER_MEGABYTE + 1);

        // when
        final CreateStorageResponse processedCreateResponse = videoThumbnailPlugin
                .process(createMockCreateStorageResponse(mockContentItem));

        // then
        final List<ContentItem> processedContentItems = processedCreateResponse.getCreatedContentItems();
        assertThat("There should be exactly 1 returned content item", processedContentItems, hasSize(1));
        verifyThumbnailIsNotSet(mockContentItem, processedContentItems.get(0));
    }

    @Test
    public void testProcessVideoWhenMaxFileSizeIs0() throws Exception {
        // given
        videoThumbnailPlugin.setMaxFileSizeMB(0);

        final ContentItem mockContentItem = createMockVideoContentItemFromResource("/short.mp4");

        // when
        final CreateStorageResponse processedCreateResponse = videoThumbnailPlugin
                .process(createMockCreateStorageResponse(mockContentItem));

        // then
        final List<ContentItem> processedContentItems = processedCreateResponse.getCreatedContentItems();
        assertThat("There should be exactly 1 returned content item", processedContentItems, hasSize(1));
        verifyThumbnailIsNotSet(mockContentItem, processedContentItems.get(0));
    }

    @Test
    public void testProcessVideoSmallerThanConfiguredMaxFileSize() throws Exception {
        // given
        videoThumbnailPlugin.setMaxFileSizeMB(1);

        final ContentItem mockContentItem = createMockVideoContentItemFromResource("/long.mp4");

        // when
        final CreateStorageResponse processedCreateResponse = videoThumbnailPlugin
                .process(createMockCreateStorageResponse(mockContentItem));

        // then
        final List<ContentItem> processedContentItems = processedCreateResponse.getCreatedContentItems();
        assertThat("There should be exactly 1 returned content item", processedContentItems, hasSize(1));
        verifyThumbnailIsGif(mockContentItem, processedContentItems.get(0));
    }

    @Test
    public void testProcessVideoWhenErrorRetrievingContentItemSize() throws Exception {
        // given
        final ContentItem mockContentItem = createMockVideoContentItem();
        doThrow(new IOException()).when(mockContentItem).getSize();

        // when
        final CreateStorageResponse processedCreateResponse = videoThumbnailPlugin
                .process(createMockCreateStorageResponse(mockContentItem));

        // then
        final List<ContentItem> processedContentItems = processedCreateResponse.getCreatedContentItems();
        assertThat("There should be exactly 1 returned content item", processedContentItems, hasSize(1));
        verifyThumbnailIsNotSet(mockContentItem, processedContentItems.get(0));
    }

    /**
     * Tests processing a mix of {@link ContentItem}s where some get thumbnails. Also tests that
     * processing an {@link UpdateStorageResponse} works for different edge cases.
     */
    @Test
    public void testProcessMixedContentItems() throws Exception {
        // given
        final ContentItem mediumVideoMockContentItem = createMockVideoContentItemFromResource("/medium.mp4");
        final ContentItem throwsIoExceptionVideoMockContentItem = createMockVideoContentItem();
        doThrow(new IOException()).when(throwsIoExceptionVideoMockContentItem).getSize();
        final ContentItem longVideoMockContentItem = createMockVideoContentItemFromResource("/long.mp4");
        final ContentItem corruptedVideoMockContentItem = createMockVideoContentItemFromResource("/corrupted.mp4");
        final ContentItem videoLargerThanDefaultMaxFileSizeMockContentItem = createMockVideoContentItemWithSizeBytes(
                DEFAULT_MAX_FILE_SIZE_MB * BYTES_PER_MEGABYTE + 1);
        final ContentItem shortVideoMockContentItem = createMockVideoContentItemFromResource("/short.mp4");
        final ContentItem notVideoMockContentItem = createMockContentItemOfMimeType("image/jpeg");

        final UpdateStorageResponse updateStorageResponse = createMockUpdateStorageResponse(
                mediumVideoMockContentItem, throwsIoExceptionVideoMockContentItem, longVideoMockContentItem,
                corruptedVideoMockContentItem, videoLargerThanDefaultMaxFileSizeMockContentItem,
                shortVideoMockContentItem, notVideoMockContentItem);

        // when
        final UpdateStorageResponse processedUpdateResponse = videoThumbnailPlugin.process(updateStorageResponse);

        // then
        final List<ContentItem> processedContentItems = processedUpdateResponse.getUpdatedContentItems();
        assertThat("There should be exactly 7 returned content items", processedContentItems, hasSize(7));

        verifyThumbnailIsGif(mediumVideoMockContentItem, processedContentItems.get(0));
        verifyThumbnailIsNotSet(throwsIoExceptionVideoMockContentItem, processedContentItems.get(1));
        verifyThumbnailIsGif(longVideoMockContentItem, processedContentItems.get(2));
        verifyThumbnailIsNotSet(corruptedVideoMockContentItem, processedContentItems.get(3));
        verifyThumbnailIsNotSet(videoLargerThanDefaultMaxFileSizeMockContentItem, processedContentItems.get(4));
        verifyThumbnailIsPng(shortVideoMockContentItem, processedContentItems.get(5));
        verifyThumbnailIsNotSet(notVideoMockContentItem, processedContentItems.get(6));
    }

    /** create mock methods */
    private BundleContext createMockBundleContext() {
        final BundleContext mockBundleContext = mock(BundleContext.class);

        final Bundle mockBundle = mock(Bundle.class);
        doReturn(mockBundle).when(mockBundleContext).getBundle();

        String ffmpegResourcePath;
        URL ffmpegBinaryUrl;

        if (SystemUtils.IS_OS_LINUX) {
            ffmpegResourcePath = "linux/ffmpeg-4.0";
        } else if (SystemUtils.IS_OS_MAC) {
            ffmpegResourcePath = "osx/ffmpeg-4.0";
            //      Skip unit tests on Windows. See DDF-3503.
            //    } else if (SystemUtils.IS_OS_WINDOWS) {
            //      ffmpegResourcePath = "windows/ffmpeg.exe";
        } else {
            fail("Platform is not Linux, Mac, or Windows. No FFmpeg binaries are provided for this platform.");
            return null;
        }

        ffmpegBinaryUrl = getClass().getClassLoader().getResource(ffmpegResourcePath);

        doReturn(ffmpegBinaryUrl).when(mockBundle).getEntry(ffmpegResourcePath);

        return mockBundleContext;
    }

    private ContentItem createMockContentItemOfMimeType(String mimeType) throws MimeTypeParseException {
        final ContentItem mockContentItem = mock(ContentItem.class);
        doReturn(new MimeType(mimeType)).when(mockContentItem).getMimeType();
        doReturn(new MetacardImpl()).when(mockContentItem).getMetacard();
        doReturn(UUID.randomUUID().toString()).when(mockContentItem).getId();
        return mockContentItem;
    }

    private ContentItem createMockVideoContentItem() throws Exception {
        return createMockContentItemOfMimeType("video/mp4");
    }

    private ContentItem createMockVideoContentItemWithSizeBytes(long sizeBytes) throws Exception {
        final ContentItem mockContentItem = createMockVideoContentItem();
        doReturn(sizeBytes).when(mockContentItem).getSize();
        return mockContentItem;
    }

    private ContentItem createMockVideoContentItemFromResource(final String resource) throws Exception {
        final Path tmpPath = Paths.get(getClass().getResource(resource).toURI());

        final ContentItem mockContentItem = createMockVideoContentItemWithSizeBytes(Files.size(tmpPath));

        HashMap<String, Path> contentItemPaths = new HashMap<>();
        contentItemPaths.put(null, tmpPath);
        tmpContentPaths.put(mockContentItem.getId(), contentItemPaths);

        return mockContentItem;
    }

    private CreateStorageResponse createMockCreateStorageResponse(ContentItem contentItem) {
        final CreateStorageResponse mockCreateResponse = mock(CreateStorageResponse.class);
        doReturn(Collections.singletonList(contentItem)).when(mockCreateResponse).getCreatedContentItems();
        final CreateStorageRequest mockCreateRequest = mock(CreateStorageRequest.class);
        doReturn(mockCreateRequest).when(mockCreateResponse).getRequest();

        final Map<String, Serializable> properties = new HashMap<>();
        properties.put(Constants.CONTENT_PATHS, tmpContentPaths);
        doReturn(properties).when(mockCreateResponse).getProperties();
        return mockCreateResponse;
    }

    private UpdateStorageResponse createMockUpdateStorageResponse(ContentItem... contentItems) {
        final UpdateStorageResponse mockUpdateResponse = mock(UpdateStorageResponse.class);
        doReturn(Arrays.asList(contentItems)).when(mockUpdateResponse).getUpdatedContentItems();
        final UpdateStorageRequest mockUpdateRequest = mock(UpdateStorageRequest.class);
        doReturn(mockUpdateRequest).when(mockUpdateResponse).getRequest();

        final Map<String, Serializable> properties = new HashMap<>();
        properties.put(Constants.CONTENT_PATHS, tmpContentPaths);
        doReturn(properties).when(mockUpdateResponse).getProperties();
        return mockUpdateResponse;
    }

    /** verify methods */
    private byte[] getThumbnail(ContentItem unprocessedContentItem, ContentItem processedContentItem) {
        assertThat("The returned content item should be the same as the original content item",
                unprocessedContentItem, is(processedContentItem));
        final byte[] thumbnail = processedContentItem.getMetacard().getThumbnail();
        return thumbnail;
    }

    private void verifyThumbnailIsGif(ContentItem unprocessedContentItem, ContentItem processedContentItem) {
        final byte[] thumbnail = getThumbnail(unprocessedContentItem, processedContentItem);
        assertThat("The thumbnail should not be null", thumbnail, notNullValue());

        assertThat("The thumbnail should have the right GIF header bytes", thumbnail[0], is((byte) 0x47));
        assertThat("The thumbnail should have the right GIF header bytes", thumbnail[1], is((byte) 0x49));
        assertThat("The thumbnail should have the right GIF header bytes", thumbnail[2], is((byte) 0x46));
        assertThat("The thumbnail should have the right GIF header bytes", thumbnail[3], is((byte) 0x38));
        assertThat("The thumbnail should have the right GIF header bytes", thumbnail[4], is((byte) 0x39));
        assertThat("The thumbnail should have the right GIF header bytes", thumbnail[5], is((byte) 0x61));
    }

    private void verifyThumbnailIsPng(ContentItem unprocessedContentItem, ContentItem processedContentItem) {
        final byte[] thumbnail = getThumbnail(unprocessedContentItem, processedContentItem);
        assertThat("The thumbnail should not be null", thumbnail, notNullValue());

        assertThat("The thumbnail should have the right PNG header bytes", thumbnail[0], is((byte) 0x89));
        assertThat("The thumbnail should have the right PNG header bytes", thumbnail[1], is((byte) 0x50));
        assertThat("The thumbnail should have the right PNG header bytes", thumbnail[2], is((byte) 0x4E));
        assertThat("The thumbnail should have the right PNG header bytes", thumbnail[3], is((byte) 0x47));
        assertThat("The thumbnail should have the right PNG header bytes", thumbnail[4], is((byte) 0x0D));
        assertThat("The thumbnail should have the right PNG header bytes", thumbnail[5], is((byte) 0x0A));
        assertThat("The thumbnail should have the right PNG header bytes", thumbnail[6], is((byte) 0x1A));
        assertThat("The thumbnail should have the right PNG header bytes", thumbnail[7], is((byte) 0x0A));
    }

    private void verifyThumbnailIsNotSet(ContentItem unprocessedContentItem, ContentItem processedContentItem) {
        final byte[] thumbnail = getThumbnail(unprocessedContentItem, processedContentItem);
        assertThat("The thumbnail should be null", thumbnail, nullValue());
    }
}