001/* 002 * ModeShape (http://www.modeshape.org) 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.modeshape.sequencer.video; 017 018import static org.modeshape.sequencer.video.VideoMetadataLexicon.BITRATE; 019import static org.modeshape.sequencer.video.VideoMetadataLexicon.CHANNELS; 020import static org.modeshape.sequencer.video.VideoMetadataLexicon.CODEC; 021import static org.modeshape.sequencer.video.VideoMetadataLexicon.COMMENT; 022import static org.modeshape.sequencer.video.VideoMetadataLexicon.DURATION; 023import static org.modeshape.sequencer.video.VideoMetadataLexicon.ENCODER; 024import static org.modeshape.sequencer.video.VideoMetadataLexicon.FRAMERATE; 025import static org.modeshape.sequencer.video.VideoMetadataLexicon.HEIGHT; 026import static org.modeshape.sequencer.video.VideoMetadataLexicon.METADATA_NODE; 027import static org.modeshape.sequencer.video.VideoMetadataLexicon.SAMPLERATE; 028import static org.modeshape.sequencer.video.VideoMetadataLexicon.STREAM_NODE; 029import static org.modeshape.sequencer.video.VideoMetadataLexicon.STREAM_TYPE; 030import static org.modeshape.sequencer.video.VideoMetadataLexicon.TITLE; 031import static org.modeshape.sequencer.video.VideoMetadataLexicon.WIDTH; 032import java.io.ByteArrayInputStream; 033import java.io.IOException; 034import java.io.InputStream; 035import java.util.Calendar; 036import java.util.List; 037import java.util.stream.Collectors; 038import javax.jcr.NamespaceRegistry; 039import javax.jcr.Node; 040import javax.jcr.Property; 041import javax.jcr.RepositoryException; 042import javax.jcr.Value; 043import javax.jcr.ValueFactory; 044import org.modeshape.common.util.CheckArg; 045import org.modeshape.common.util.StringUtil; 046import org.modeshape.jcr.api.Binary; 047import org.modeshape.jcr.api.JcrConstants; 048import org.modeshape.jcr.api.nodetype.NodeTypeManager; 049import org.modeshape.jcr.api.sequencer.Sequencer; 050 051/** 052 * A sequencer that processes the binary content of a video file, extracts the metadata, and then writes that 053 * metadata to the repository. 054 * <p> 055 * This sequencer produces data that corresponds to the following structure: 056 * <ul> 057 * <li><strong>video:metadata</strong> node of type <code>video:metadata</code> 058 * <ul> 059 * <li><strong>jcr:mimeType</strong> - optional string property for the mime type of the video</li> 060 * <li><strong>video:duration</strong> - optional double property specifying the length of the video</li> 061 * <li><strong>video:bitrate</strong> - optional long property specifying the bitrate of the video</li> 062 * <li><strong>video:title</strong> - optional string property for the title of the video</li> 063 * <li><strong>video:comment</strong> - optional string property for the comment of the video</li> 064 * <li><strong>video:encoder</strong> - optional string property specifying the encoder</li> 065 * <li><strong>video:stream</strong> - optional child not which contains metadata of the stream</li> 066 * <ul> 067 * <li><strong>video:streamType</strong> - optional string property which specifies type of stream ('audio', 'video', 'unknown')</li> 068 * <li><strong>video:codec</strong> - optional string property specifying the codec of the stream</li> 069 * <li><strong>video:bitrate</strong> - optional long property specifying the stream's bitrate</li> 070 * <li><strong>video:framerate</strong> - optional double property specifying the stream's framerate</li> 071 * <li><strong>video:samplerate</strong> - optional long property specifying the stream's samplerate</li> 072 * <li><strong>video:channels</strong> - optional long property specifying the number of audio stream's channels</li> 073 * <li><strong>video:width</strong> - optional long property specifying the video stream's width</li> 074 * <li><strong>video:height</strong> - optional long property specifying the video stream's height</li> 075 * </ul> 076 * </ul> 077 * </li> 078 * </ul> 079 * </p> 080 * 081 * @since 5.1 082 */ 083public class VideoMetadataSequencer extends Sequencer { 084 085 @Override 086 public void initialize( NamespaceRegistry registry, 087 NodeTypeManager nodeTypeManager ) throws RepositoryException, IOException { 088 super.registerNodeTypes("video.cnd", nodeTypeManager, true); 089 registerDefaultMimeTypes(VideoMetadata.MIME_TYPE_STRINGS); 090 } 091 092 @Override 093 public boolean execute( Property inputProperty, 094 Node outputNode, 095 Context context ) throws Exception { 096 Binary binaryValue = (Binary) inputProperty.getBinary(); 097 CheckArg.isNotNull(binaryValue, "binary"); 098 String mimeType = binaryValue.getMimeType(); 099 100 Node sequencedNode = getMetadataNode(outputNode); 101 setPropertyIfMetadataPresent(sequencedNode, JcrConstants.JCR_MIME_TYPE, mimeType); 102 return processBasicMetadata(sequencedNode, binaryValue); 103 } 104 105 private boolean processBasicMetadata( Node sequencedNode, 106 Binary binaryValue ) { 107 VideoMetadata metadata = null; 108 try (InputStream stream = binaryValue.getStream()) { 109 metadata = new VideoMetadata(stream); 110 if (metadata.check()) { 111 setPropertyIfMetadataPresent(sequencedNode, DURATION, metadata.getDuration()); 112 setPropertyIfMetadataPresent(sequencedNode, BITRATE, metadata.getBitrate()); 113 setPropertyIfMetadataPresent(sequencedNode, TITLE, metadata.getTitle()); 114 setPropertyIfMetadataPresent(sequencedNode, COMMENT, metadata.getComment()); 115 setPropertyIfMetadataPresent(sequencedNode, ENCODER, metadata.getEncoder()); 116 117 int suffix = 0; 118 for (StreamMetadata streamMetadata : metadata.getStreams()) { 119 Node streamNode = sequencedNode.addNode(STREAM_NODE + String.valueOf(suffix), STREAM_NODE); 120 processStreamMetadata(streamNode, streamMetadata); 121 suffix += 1; 122 } 123 return true; 124 } 125 } catch (Exception e) { 126 getLogger().error(e, "Couldn't process the stream."); 127 } 128 return false; 129 } 130 131 private boolean processStreamMetadata( Node streamNode, 132 StreamMetadata stream) throws RepositoryException { 133 setPropertyIfMetadataPresent(streamNode, STREAM_TYPE, stream.getStreamType()); 134 setPropertyIfMetadataPresent(streamNode, CODEC, stream.getCodec()); 135 setPropertyIfMetadataPresent(streamNode, BITRATE, stream.getBitrate()); 136 setPropertyIfMetadataPresent(streamNode, FRAMERATE, stream.getFramerate()); 137 setPropertyIfMetadataPresent(streamNode, SAMPLERATE, stream.getSamplerate()); 138 setPropertyIfMetadataPresent(streamNode, CHANNELS, stream.getChannels()); 139 setPropertyIfMetadataPresent(streamNode, WIDTH, stream.getWidth()); 140 setPropertyIfMetadataPresent(streamNode, HEIGHT, stream.getHeight()); 141 142 return true; 143 } 144 145 private Node getMetadataNode( Node outputNode ) throws RepositoryException { 146 if (outputNode.isNew()) { 147 outputNode.setPrimaryType(METADATA_NODE); 148 return outputNode; 149 } 150 return outputNode.addNode(METADATA_NODE, METADATA_NODE); 151 } 152 153 private void setPropertyIfMetadataPresent( Node node, 154 String propertyName, 155 Object value ) throws RepositoryException { 156 if (value == null) { 157 return; 158 } 159 if (value instanceof String && !StringUtil.isBlank((String) value)) { 160 node.setProperty(propertyName, (String) value); 161 } else if (value instanceof Boolean) { 162 node.setProperty(propertyName, (Boolean) value); 163 } else if (value instanceof Double) { 164 node.setProperty(propertyName, (Double) value); 165 } else if (value instanceof Number) { 166 node.setProperty(propertyName, ((Number) value).longValue()); 167 } else if (value instanceof Calendar) { 168 node.setProperty(propertyName, (Calendar) value); 169 } else if (value instanceof byte[]) { 170 InputStream is = new ByteArrayInputStream((byte []) value); 171 javax.jcr.Binary binaryProperty = node.getSession().getValueFactory().createBinary(is); 172 node.setProperty(propertyName, binaryProperty); 173 } else if (value instanceof List) { 174 ValueFactory vf = node.getSession().getValueFactory(); 175 List<Value> values = ((List<?>) value).stream() 176 .filter(val -> val instanceof String) 177 .map(val -> vf.createValue((String) val)) 178 .collect(Collectors.toList()); 179 if (!values.isEmpty()) { 180 node.setProperty(propertyName, values.toArray(new Value[values.size()])); 181 } 182 } else { 183 getLogger().warn("The value of the property {0} has unknown type and couldn't be saved.", propertyName); 184 } 185 } 186}