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}