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 /** @since 11.1 */ 062 public static final Pattern METADATA_ROTATE_PATTERN = Pattern.compile("\\s*rotate\\s*:\\s*(\\d+)\\s*", 063 Pattern.CASE_INSENSITIVE); 064 065 public static final VideoInfo EMPTY_INFO = new VideoInfo(0, 0, 0, 0, null, null); 066 067 public static final String DURATION = "duration"; 068 069 public static final String WIDTH = "width"; 070 071 public static final String HEIGHT = "height"; 072 073 public static final String FRAME_RATE = "frameRate"; 074 075 public static final String FORMAT = "format"; 076 077 public static final String STREAMS = "streams"; 078 079 private final double duration; 080 081 private final long width; 082 083 private final long height; 084 085 private final String format; 086 087 private final List<Stream> streams; 088 089 private final double frameRate; 090 091 /** 092 * Build a {@code VideoInfo} from a {@code Map} of attributes. 093 * <p> 094 * Used when creating a {@code VideoInfo} from a {@code DocumentModel} property. 095 */ 096 public static VideoInfo fromMap(Map<String, Serializable> map) { 097 Double duration = (Double) map.get(DURATION); 098 if (duration == null) { 099 duration = 0d; 100 } 101 Long width = (Long) map.get(WIDTH); 102 if (width == null) { 103 width = 0L; 104 } 105 Long height = (Long) map.get(HEIGHT); 106 if (height == null) { 107 height = 0L; 108 } 109 String format = (String) map.get(FORMAT); 110 if (format == null) { 111 format = ""; 112 } 113 Double frameRate = (Double) map.get(FRAME_RATE); 114 if (frameRate == null) { 115 frameRate = 0d; 116 } 117 118 List<Stream> streams = new ArrayList<>(); 119 @SuppressWarnings("unchecked") 120 List<Map<String, Serializable>> streamItems = (List<Map<String, Serializable>>) map.get(STREAMS); 121 if (streamItems != null) { 122 for (Map<String, Serializable> m : streamItems) { 123 streams.add(Stream.fromMap(m)); 124 } 125 } 126 127 return new VideoInfo(duration, width, height, frameRate, format, streams); 128 } 129 130 /** 131 * Build a {@code VideoInfo} from a FFmpeg output. 132 */ 133 public static VideoInfo fromFFmpegOutput(List<String> output) { 134 double duration = 0; 135 long width = 0; 136 long height = 0; 137 double frameRate = 0; 138 double bitRate = 0; 139 String format = ""; 140 List<Stream> streams = new ArrayList<>(); 141 142 for (String line : output) { 143 Matcher matcher = FORMAT_PATTERN.matcher(line); 144 if (matcher.find()) { 145 format = matcher.group(2).trim(); 146 if (format.endsWith(",")) { 147 format = format.substring(0, format.length() - 1); 148 } 149 continue; 150 } 151 152 matcher = DURATION_PATTERN.matcher(line); 153 if (matcher.find()) { 154 duration = Double.parseDouble(matcher.group(1)) * 3600 + Double.parseDouble(matcher.group(2)) * 60 155 + Double.parseDouble(matcher.group(3)) + Double.parseDouble(matcher.group(4)) / 100; 156 continue; 157 } 158 159 matcher = STREAM_PATTERN.matcher(line); 160 if (matcher.find()) { 161 String streamType = matcher.group(1).trim(); 162 String specs = matcher.group(2); 163 String[] tokens = specs.split(","); 164 if (Stream.VIDEO_TYPE.equals(streamType)) { 165 for (String token : tokens) { 166 Matcher m = FRAME_RATE_PATTERN.matcher(token); 167 if (m.find()) { 168 frameRate = Double.parseDouble(m.group(1)); 169 continue; 170 } 171 m = SIZE_PATTERN.matcher(token); 172 if (m.find()) { 173 width = Long.parseLong(m.group(1)); 174 height = Long.parseLong(m.group(2)); 175 continue; 176 } 177 m = BIT_RATE_PATTERN.matcher(token); 178 if (m.find()) { 179 bitRate = Double.parseDouble(m.group(1)); 180 } 181 } 182 } else if (Stream.AUDIO_TYPE.equals(streamType)) { 183 for (String token : tokens) { 184 Matcher m = BIT_RATE_PATTERN.matcher(token); 185 if (m.find()) { 186 bitRate = Double.parseDouble(m.group(1)); 187 } 188 } 189 } 190 Map<String, Serializable> map = new HashMap<>(); 191 map.put(TYPE_ATTRIBUTE, streamType); 192 map.put(CODEC_ATTRIBUTE, tokens[0]); 193 map.put(STREAM_INFO_ATTRIBUTE, matcher.group(0).trim()); 194 map.put(BIT_RATE_ATTRIBUTE, bitRate); 195 streams.add(Stream.fromMap(map)); 196 } 197 198 matcher = METADATA_ROTATE_PATTERN.matcher(line); 199 if (matcher.find()) { 200 long rotate = Long.parseLong(matcher.group(1)); 201 if (rotate == 90 || rotate == 270) { 202 // invert width and height 203 long temp = width; 204 width = height; 205 height = temp; 206 } 207 } 208 } 209 return new VideoInfo(duration, width, height, frameRate, format, streams); 210 } 211 212 private VideoInfo(double duration, long width, long height, double frameRate, String format, List<Stream> streams) { 213 this.duration = duration; 214 this.width = width; 215 this.height = height; 216 this.frameRate = frameRate; 217 this.format = format; 218 this.streams = new ArrayList<>(); 219 if (streams != null) { 220 this.streams.addAll(streams); 221 } 222 } 223 224 /** 225 * Returns the duration of the video. 226 */ 227 public double getDuration() { 228 return duration; 229 } 230 231 /** 232 * Returns the width of the video. 233 */ 234 public long getWidth() { 235 return width; 236 } 237 238 /** 239 * Returns the height of the video. 240 */ 241 public long getHeight() { 242 return height; 243 } 244 245 /** 246 * Returns the format of the video. 247 */ 248 public String getFormat() { 249 return format; 250 } 251 252 /** 253 * Returns all the {@link Stream}s of the video. 254 */ 255 public List<Stream> getStreams() { 256 return streams; 257 } 258 259 /** 260 * Returns the frame rate of the video. 261 */ 262 public double getFrameRate() { 263 return frameRate; 264 } 265 266 /** 267 * Returns a {@code Map} of attributes for this {@code VideoInfo}. 268 * <p> 269 * Used when saving this {@code Stream} to a {@code DocumentModel} property. 270 */ 271 public Map<String, Serializable> toMap() { 272 Map<String, Serializable> map = new HashMap<>(); 273 map.put(DURATION, duration); 274 map.put(FRAME_RATE, frameRate); 275 map.put(WIDTH, width); 276 map.put(HEIGHT, height); 277 map.put(FORMAT, format); 278 279 List<Map<String, Serializable>> streamItems = new ArrayList<>(streams.size()); 280 for (Stream stream : streams) { 281 streamItems.add(stream.toMap()); 282 } 283 map.put(STREAMS, (Serializable) streamItems); 284 285 return map; 286 } 287 288}