001/* 002 * (C) Copyright 2006-2011 Nuxeo SA (http://nuxeo.com/) and others. 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 * 016 * Contributors: 017 * Thomas Roger <troger@nuxeo.com> 018 */ 019 020package org.nuxeo.ecm.platform.video; 021 022import static org.nuxeo.ecm.platform.video.Stream.BIT_RATE_ATTRIBUTE; 023import static org.nuxeo.ecm.platform.video.Stream.CODEC_ATTRIBUTE; 024import static org.nuxeo.ecm.platform.video.Stream.STREAM_INFO_ATTRIBUTE; 025import static org.nuxeo.ecm.platform.video.Stream.TYPE_ATTRIBUTE; 026 027import java.io.Serializable; 028import java.util.ArrayList; 029import java.util.HashMap; 030import java.util.List; 031import java.util.Map; 032import java.util.regex.Matcher; 033import java.util.regex.Pattern; 034 035/** 036 * Object containing info about a video file. 037 * 038 * @author <a href="mailto:troger@nuxeo.com">Thomas Roger</a> 039 * @since 5.5 040 */ 041public final class VideoInfo implements Serializable { 042 043 private static final long serialVersionUID = 1L; 044 045 public static final Pattern FORMAT_PATTERN = Pattern.compile("^\\s*(Input|Output) #0, ([\\w,]+).+$\\s*", 046 Pattern.CASE_INSENSITIVE); 047 048 public static final Pattern DURATION_PATTERN = Pattern.compile("Duration: (\\d\\d):(\\d\\d):(\\d\\d)\\.(\\d+)", 049 Pattern.CASE_INSENSITIVE); 050 051 public static final Pattern STREAM_PATTERN = Pattern.compile( 052 "^\\s*Stream #\\S+: ((?:Audio)|(?:Video)|(?:Data)): (.*)\\s*$", Pattern.CASE_INSENSITIVE); 053 054 public static final Pattern SIZE_PATTERN = Pattern.compile("(\\d+)x(\\d+)", Pattern.CASE_INSENSITIVE); 055 056 public static final Pattern FRAME_RATE_PATTERN = Pattern.compile("([\\d.]+)\\s+(?:fps|tbr)", 057 Pattern.CASE_INSENSITIVE); 058 059 public static final Pattern BIT_RATE_PATTERN = Pattern.compile("(\\d+)\\s+kb/s", Pattern.CASE_INSENSITIVE); 060 061 public static final VideoInfo EMPTY_INFO = new VideoInfo(0, 0, 0, 0, null, null); 062 063 public static final String DURATION = "duration"; 064 065 public static final String WIDTH = "width"; 066 067 public static final String HEIGHT = "height"; 068 069 public static final String FRAME_RATE = "frameRate"; 070 071 public static final String FORMAT = "format"; 072 073 public static final String STREAMS = "streams"; 074 075 private final double duration; 076 077 private final long width; 078 079 private final long height; 080 081 private final String format; 082 083 private final List<Stream> streams; 084 085 private final double frameRate; 086 087 /** 088 * Build a {@code VideoInfo} from a {@code Map} of attributes. 089 * <p> 090 * Used when creating a {@code VideoInfo} from a {@code DocumentModel} property. 091 */ 092 public static VideoInfo fromMap(Map<String, Serializable> map) { 093 Double duration = (Double) map.get(DURATION); 094 if (duration == null) { 095 duration = 0d; 096 } 097 Long width = (Long) map.get(WIDTH); 098 if (width == null) { 099 width = 0l; 100 } 101 Long height = (Long) map.get(HEIGHT); 102 if (height == null) { 103 height = 0l; 104 } 105 String format = (String) map.get(FORMAT); 106 if (format == null) { 107 format = ""; 108 } 109 Double frameRate = (Double) map.get(FRAME_RATE); 110 if (frameRate == null) { 111 frameRate = 0d; 112 } 113 114 List<Stream> streams = new ArrayList<Stream>(); 115 @SuppressWarnings("unchecked") 116 List<Map<String, Serializable>> streamItems = (List<Map<String, Serializable>>) map.get(STREAMS); 117 if (streamItems != null) { 118 for (Map<String, Serializable> m : streamItems) { 119 streams.add(Stream.fromMap(m)); 120 } 121 } 122 123 return new VideoInfo(duration, width, height, frameRate, format, streams); 124 } 125 126 /** 127 * Build a {@code VideoInfo} from a FFmpeg output. 128 */ 129 public static VideoInfo fromFFmpegOutput(List<String> output) { 130 double duration = 0; 131 long width = 0; 132 long height = 0; 133 double frameRate = 0; 134 double bitRate = 0; 135 String format = ""; 136 List<Stream> streams = new ArrayList<Stream>(); 137 138 for (String line : output) { 139 Matcher matcher = FORMAT_PATTERN.matcher(line); 140 if (matcher.find()) { 141 format = matcher.group(2).trim(); 142 if (format.endsWith(",")) { 143 format = format.substring(0, format.length() - 1); 144 } 145 continue; 146 } 147 148 matcher = DURATION_PATTERN.matcher(line); 149 if (matcher.find()) { 150 duration = Double.parseDouble(matcher.group(1)) * 3600 + Double.parseDouble(matcher.group(2)) * 60 151 + Double.parseDouble(matcher.group(3)) + Double.parseDouble(matcher.group(4)) / 100; 152 continue; 153 } 154 155 matcher = STREAM_PATTERN.matcher(line); 156 if (matcher.find()) { 157 String streamType = matcher.group(1).trim(); 158 String specs = matcher.group(2); 159 String[] tokens = specs.split(","); 160 if (Stream.VIDEO_TYPE.equals(streamType)) { 161 for (String token : tokens) { 162 Matcher m = FRAME_RATE_PATTERN.matcher(token); 163 if (m.find()) { 164 frameRate = Double.parseDouble(m.group(1)); 165 continue; 166 } 167 m = SIZE_PATTERN.matcher(token); 168 if (m.find()) { 169 width = Long.parseLong(m.group(1)); 170 height = Long.parseLong(m.group(2)); 171 continue; 172 } 173 m = BIT_RATE_PATTERN.matcher(token); 174 if (m.find()) { 175 bitRate = Double.parseDouble(m.group(1)); 176 } 177 } 178 } else if (Stream.AUDIO_TYPE.equals(streamType)) { 179 for (String token : tokens) { 180 Matcher m = BIT_RATE_PATTERN.matcher(token); 181 if (m.find()) { 182 bitRate = Double.parseDouble(m.group(1)); 183 } 184 } 185 } 186 Map<String, Serializable> map = new HashMap<String, Serializable>(); 187 map.put(TYPE_ATTRIBUTE, streamType); 188 map.put(CODEC_ATTRIBUTE, tokens[0]); 189 map.put(STREAM_INFO_ATTRIBUTE, matcher.group(0).trim()); 190 map.put(BIT_RATE_ATTRIBUTE, bitRate); 191 streams.add(Stream.fromMap(map)); 192 } 193 } 194 return new VideoInfo(duration, width, height, frameRate, format, streams); 195 } 196 197 private VideoInfo(double duration, long width, long height, double frameRate, String format, List<Stream> streams) { 198 this.duration = duration; 199 this.width = width; 200 this.height = height; 201 this.frameRate = frameRate; 202 this.format = format; 203 this.streams = new ArrayList<Stream>(); 204 if (streams != null) { 205 this.streams.addAll(streams); 206 } 207 } 208 209 /** 210 * Returns the duration of the video. 211 */ 212 public double getDuration() { 213 return duration; 214 } 215 216 /** 217 * Returns the width of the video. 218 */ 219 public long getWidth() { 220 return width; 221 } 222 223 /** 224 * Returns the height of the video. 225 */ 226 public long getHeight() { 227 return height; 228 } 229 230 /** 231 * Returns the format of the video. 232 */ 233 public String getFormat() { 234 return format; 235 } 236 237 /** 238 * Returns all the {@link Stream}s of the video. 239 */ 240 public List<Stream> getStreams() { 241 return streams; 242 } 243 244 /** 245 * Returns the frame rate of the video. 246 */ 247 public double getFrameRate() { 248 return frameRate; 249 } 250 251 /** 252 * Returns a {@code Map} of attributes for this {@code VideoInfo}. 253 * <p> 254 * Used when saving this {@code Stream} to a {@code DocumentModel} property. 255 */ 256 public Map<String, Serializable> toMap() { 257 Map<String, Serializable> map = new HashMap<String, Serializable>(); 258 map.put(DURATION, duration); 259 map.put(FRAME_RATE, frameRate); 260 map.put(WIDTH, width); 261 map.put(HEIGHT, height); 262 map.put(FORMAT, format); 263 264 List<Map<String, Serializable>> streamItems = new ArrayList<Map<String, Serializable>>(streams.size()); 265 for (Stream stream : streams) { 266 streamItems.add(stream.toMap()); 267 } 268 map.put(STREAMS, (Serializable) streamItems); 269 270 return map; 271 } 272 273}