package io.lumify.gpw.video; import com.google.inject.Inject; import io.lumify.core.ingest.graphProperty.GraphPropertyWorkData; import io.lumify.core.ingest.graphProperty.GraphPropertyWorker; import io.lumify.core.ingest.graphProperty.GraphPropertyWorkerPrepareData; import io.lumify.core.model.properties.LumifyProperties; import io.lumify.core.model.properties.MediaLumifyProperties; import io.lumify.core.model.properties.types.DoubleLumifyProperty; import io.lumify.core.model.properties.types.IntegerLumifyProperty; import io.lumify.core.util.LumifyLogger; import io.lumify.core.util.LumifyLoggerFactory; import io.lumify.core.util.ProcessRunner; import io.lumify.gpw.util.FFprobeRotationUtil; import org.securegraph.Element; import org.securegraph.Metadata; import org.securegraph.Property; import org.securegraph.Vertex; import org.securegraph.mutation.ExistingElementMutation; import org.securegraph.property.StreamingPropertyValue; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.util.ArrayList; public class VideoPosterFrameWorker extends GraphPropertyWorker { private static final LumifyLogger LOGGER = LumifyLoggerFactory.getLogger(VideoPosterFrameWorker.class); private static final String PROPERTY_KEY = VideoPosterFrameWorker.class.getName(); private ProcessRunner processRunner; private DoubleLumifyProperty durationProperty; private IntegerLumifyProperty videoRotationProperty; @Override public void prepare(GraphPropertyWorkerPrepareData workerPrepareData) throws Exception { super.prepare(workerPrepareData); durationProperty = new DoubleLumifyProperty(getOntologyRepository().getRequiredPropertyIRIByIntent("media.duration")); videoRotationProperty = new IntegerLumifyProperty(getOntologyRepository().getRequiredPropertyIRIByIntent("media.clockwiseRotation")); } @Override public void execute(InputStream in, GraphPropertyWorkData data) throws Exception { File videoPosterFrameFile = File.createTempFile("video_poster_frame", ".png"); String[] ffmpegOptionsArray = prepareFFMPEGOptions(data, videoPosterFrameFile); try { processRunner.execute( "ffmpeg", ffmpegOptionsArray, null, data.getLocalFile().getAbsolutePath() + ": " ); if (videoPosterFrameFile.length() == 0) { throw new RuntimeException("Poster frame not created. Zero length file detected. (from: " + data.getLocalFile().getAbsolutePath() + ")"); } try (InputStream videoPosterFrameFileIn = new FileInputStream(videoPosterFrameFile)) { ExistingElementMutation<Vertex> m = data.getElement().prepareMutation(); StreamingPropertyValue spv = new StreamingPropertyValue(videoPosterFrameFileIn, byte[].class); spv.searchIndex(false); Metadata metadata = new Metadata(); metadata.add(LumifyProperties.MIME_TYPE.getPropertyName(), "image/png", getVisibilityTranslator().getDefaultVisibility()); MediaLumifyProperties.RAW_POSTER_FRAME.addPropertyValue(m, PROPERTY_KEY, spv, metadata, data.getProperty().getVisibility()); m.save(getAuthorizations()); getGraph().flush(); } } finally { if (!videoPosterFrameFile.delete()) { LOGGER.warn("Could not delete %s", videoPosterFrameFile.getAbsolutePath()); } } } private String[] prepareFFMPEGOptions(GraphPropertyWorkData data, File videoPosterFrameFile) { ArrayList<String> ffmpegOptionsList = new ArrayList<>(); Double duration = durationProperty.getPropertyValue(data.getElement(), 0); if (duration != null) { ffmpegOptionsList.add("-itsoffset"); ffmpegOptionsList.add("-" + (duration / 3.0)); } ffmpegOptionsList.add("-i"); ffmpegOptionsList.add(data.getLocalFile().getAbsolutePath()); ffmpegOptionsList.add("-vcodec"); ffmpegOptionsList.add("png"); ffmpegOptionsList.add("-vframes"); ffmpegOptionsList.add("1"); ffmpegOptionsList.add("-an"); ffmpegOptionsList.add("-f"); ffmpegOptionsList.add("rawvideo"); Integer videoRotation = videoRotationProperty.getPropertyValue(data.getElement()); if (videoRotation != null) { //Scale. //Will not force conversion to 720:480 aspect ratio, but will resize video with original aspect ratio. if (videoRotation == 0 || videoRotation == 180) { ffmpegOptionsList.add("-s"); ffmpegOptionsList.add("720x480"); } else if (videoRotation == 90 || videoRotation == 270) { ffmpegOptionsList.add("-s"); ffmpegOptionsList.add("480x720"); } String[] ffmpegRotationOptions = FFprobeRotationUtil.createFFMPEGRotationOptions(videoRotation); //Rotate if (ffmpegRotationOptions != null) { ffmpegOptionsList.add(ffmpegRotationOptions[0]); ffmpegOptionsList.add(ffmpegRotationOptions[1]); } } ffmpegOptionsList.add("-y"); ffmpegOptionsList.add(videoPosterFrameFile.getAbsolutePath()); return ffmpegOptionsList.toArray(new String[ffmpegOptionsList.size()]); } @Override public boolean isHandled(Element element, Property property) { if (property == null) { return false; } if (!property.getName().equals(LumifyProperties.RAW.getPropertyName())) { return false; } String mimeType = LumifyProperties.MIME_TYPE.getMetadataValue(property.getMetadata(), null); if (mimeType == null || !mimeType.startsWith("video")) { return false; } return true; } @Override public boolean isLocalFileRequired() { return true; } @Inject public void setProcessRunner(ProcessRunner ffmpeg) { this.processRunner = ffmpeg; } }