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 io.humble.video.Decoder; 019import io.humble.video.Demuxer; 020import io.humble.video.DemuxerStream; 021import io.humble.video.Global; 022import io.humble.video.KeyValueBag; 023import io.humble.video.MediaDescriptor.Type; 024 025import java.io.BufferedOutputStream; 026import java.io.File; 027import java.io.FileOutputStream; 028import java.io.InputStream; 029import java.util.ArrayList; 030import java.util.List; 031import java.util.Locale; 032 033import org.modeshape.common.util.IoUtil; 034 035/** 036 * Utility for extracting Metadata from video formats. 037 * 038 * @since 5.1 039 */ 040public class VideoMetadata { 041 042 static final String[] MIME_TYPE_STRINGS = { "video/x-matroska", // mkv 043 "video/quicktime", // mov 044 "video/x-msvideo", // avi 045 "video/x-ms-wmv", // wmv 046 "video/x-flv", // flv 047 "video/3gpp", // 3gpp 048 "video/webm", // webp 049 "video/mp4", // mp4 050 "video/ogg" //ogg 051 }; 052 053 private Double duration; 054 private Integer bitrate; 055 private String title; 056 private String comment; 057 private String encoder; 058 private List<StreamMetadata> streams = new ArrayList<>(); 059 060 private InputStream in; 061 062 public VideoMetadata( InputStream inputStream ) { 063 this.in = inputStream; 064 } 065 066 /* 067 * Check that given file is supported by this sequencer. 068 */ 069 public boolean check() throws Exception { 070 // create a temporary copy from input 071 File fileCopy = File.createTempFile("modeshape-sequencer-video", ".tmp"); 072 IoUtil.write(in, new BufferedOutputStream(new FileOutputStream(fileCopy))); 073 074 final Demuxer container = Demuxer.make(); 075 container.open(fileCopy.getAbsolutePath(), null, false, true, null, null); 076 077 // try to delete the file immediately or on JVM exit 078 boolean deleted = false; 079 try { 080 deleted = fileCopy.delete(); 081 } catch (SecurityException e) { 082 // ignore 083 } 084 if (!deleted) { 085 fileCopy.deleteOnExit(); 086 } 087 088 KeyValueBag metadata = container.getMetaData(); 089 for (String key : metadata.getKeys()) { 090 if (key.toLowerCase(Locale.ROOT).equals("title")) { 091 title = metadata.getValue(key); 092 } 093 if (key.toLowerCase(Locale.ROOT).equals("comment")) { 094 comment = metadata.getValue(key); 095 } 096 if (key.toLowerCase(Locale.ROOT).equals("encoder")) { 097 encoder = metadata.getValue(key); 098 } 099 } 100 101 if (container.getDuration() != Global.NO_PTS) { 102 // convert to seconds 103 duration = (float) container.getDuration() / 1000.0 / 1000.0; 104 } 105 if (container.getBitRate() > 0) { 106 bitrate = container.getBitRate(); 107 } 108 109 for (int i = 0; i < container.getNumStreams(); i++) { 110 StreamMetadata streamMetadata = new StreamMetadata(); 111 DemuxerStream stream = container.getStream(i); 112 113 Decoder coder = stream.getDecoder(); 114 115 if (coder.getCodecType() == Type.MEDIA_AUDIO) { 116 streamMetadata.setStreamType("audio"); 117 } else if (coder.getCodecType() == Type.MEDIA_VIDEO) { 118 streamMetadata.setStreamType("video"); 119 } else { 120 streamMetadata.setStreamType("unknown"); 121 } 122 123 if (coder.getCodec() != null) { 124 streamMetadata.setCodec(coder.getCodec().getName()); 125 } 126 streamMetadata.setSamplerate(nullValueIfInvalid(coder.getSampleRate())); 127 streamMetadata.setChannels(nullValueIfInvalid(coder.getChannels())); 128 streamMetadata.setWidth(nullValueIfInvalid(coder.getWidth())); 129 streamMetadata.setHeight(nullValueIfInvalid(coder.getHeight())); 130 if (coder.getTimeBase() != null) { 131 streamMetadata.setFramerate(nullValueIfInvalid(1.0 / coder.getTimeBase().getDouble())); 132 } 133 streams.add(streamMetadata); 134 } 135 container.close(); 136 return true; 137 } 138 139 private Integer nullValueIfInvalid( int value ) { 140 if (value > 0) { 141 return value; 142 } else { 143 return null; 144 } 145 } 146 147 private Double nullValueIfInvalid( double value ) { 148 if (value > 0) { 149 return value; 150 } else { 151 return null; 152 } 153 } 154 155 public Double getDuration() { 156 return duration; 157 } 158 159 public Integer getBitrate() { 160 return bitrate; 161 } 162 163 public String getTitle() { 164 return title; 165 } 166 167 public String getComment() { 168 return comment; 169 } 170 171 public String getEncoder() { 172 return encoder; 173 } 174 175 public List<StreamMetadata> getStreams() { 176 return streams; 177 } 178 179}