001/** 002 * Copyright 2015 Tampere University of Technology, Pori Department 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 service.tut.pori.contentanalysis.video; 017 018import java.util.EnumSet; 019import java.util.HashMap; 020import java.util.Iterator; 021import java.util.List; 022import java.util.Map; 023import java.util.Set; 024 025import javax.xml.bind.annotation.XmlAccessType; 026import javax.xml.bind.annotation.XmlAccessorType; 027import javax.xml.bind.annotation.XmlElement; 028import javax.xml.bind.annotation.XmlElementWrapper; 029import javax.xml.bind.annotation.XmlEnum; 030import javax.xml.bind.annotation.XmlEnumValue; 031import javax.xml.bind.annotation.XmlRootElement; 032 033import org.apache.commons.lang3.ArrayUtils; 034import org.apache.commons.lang3.StringUtils; 035 036import service.tut.pori.contentanalysis.AbstractTaskDetails.TaskParameters; 037import service.tut.pori.contentanalysis.PhotoParameters.AnalysisType; 038 039/** 040 * Options for video analysis. 041 * 042 * <h2>Conditional Elements</h2> 043 * <ul> 044 * <li>{@value service.tut.pori.contentanalysis.video.Definitions#ELEMENT_SEQUENCE_DURATION}</li> 045 * </ul> 046 * 047 * If sequence type {@link service.tut.pori.contentanalysis.video.VideoParameters.SequenceType#SECOND} is used, the element {@value service.tut.pori.contentanalysis.video.Definitions#ELEMENT_SEQUENCE_DURATION} must be present. 048 * 049 * <h2>Optional Elements</h2> 050 * <ul> 051 * <li>{@value service.tut.pori.contentanalysis.Definitions#ELEMENT_ANALYSIS_TYPELIST}. If not given, the defaults {@link service.tut.pori.contentanalysis.PhotoParameters.AnalysisType#KEYWORD_EXTRACTION} and {@link service.tut.pori.contentanalysis.PhotoParameters.AnalysisType#VISUAL} should be used.</li> 052 * <li>{@value service.tut.pori.contentanalysis.video.Definitions#ELEMENT_TIMECODELIST}. Can be used to specify which parts of the video should be analysed.</li> 053 * </ul> 054 * 055 * <h3>XML Example</h3> 056 * 057 * {@doc.restlet service="[service.tut.pori.contentanalysis.video.reference.Definitions#SERVICE_VCA_REFERENCE_EXAMPLE]" method="[service.tut.pori.contentanalysis.Definitions#ELEMENT_TASK_PARAMETERS]" type="GET" query="" body_uri=""} 058 * 059 * @see service.tut.pori.contentanalysis.video.TimecodeList 060 */ 061@XmlRootElement(name=service.tut.pori.contentanalysis.Definitions.ELEMENT_TASK_PARAMETERS) 062@XmlAccessorType(value=XmlAccessType.NONE) 063public final class VideoParameters extends TaskParameters { 064 private static final String SEQUENCE_TYPE_FRAME = "FRAME"; 065 private static final String SEQUENCE_TYPE_FULL = "FULL"; 066 private static final String SEQUENCE_TYPE_SECOND = "SECOND"; 067 private static final String SEQUENCE_TYPE_SHOT = "SHOT"; 068 @XmlElement(name = service.tut.pori.contentanalysis.Definitions.ELEMENT_ANALYSIS_TYPE) 069 @XmlElementWrapper(name = service.tut.pori.contentanalysis.Definitions.ELEMENT_ANALYSIS_TYPELIST) 070 private Set<AnalysisType> _analysisTypes = null; 071 @XmlElement(name = Definitions.ELEMENT_SEQUENCE_DURATION) 072 private Integer _sequenceDuration = null; 073 @XmlElement(name = Definitions.ELEMENT_SEQUENCE_TYPE) 074 private SequenceType _sequenceType = null; 075 @XmlElement(name = Definitions.ELEMENT_TIMECODELIST) 076 private TimecodeList _timecodes = null; 077 078 /** 079 * Type of the requested analysis sequence 080 * 081 */ 082 @XmlEnum 083 public enum SequenceType { 084 /** Analysis is based on each individual frame */ 085 @XmlEnumValue(value=SEQUENCE_TYPE_FRAME) 086 FRAME(0), 087 /** 088 * Analysis is based on timed intervals, reported in seconds. 089 * 090 * This can be used to define the accuracy of the analysis. 091 * 092 * For example, sequence duration of 5 would mean that the analysis should be performed in 5 second blocks, i.e. there would be one keyword per 5 seconds of video (if any are found). 093 * 094 * @see service.tut.pori.contentanalysis.video.VideoParameters#getSequenceDuration() 095 */ 096 @XmlEnumValue(value=SEQUENCE_TYPE_SECOND) 097 SECOND(1), 098 /** Analysis is based on back-end detected shots. I.e. variable length sequences. The generated keywords may or may not have timecodes, depending on the back-ends decision. */ 099 @XmlEnumValue(value=SEQUENCE_TYPE_SHOT) 100 SHOT(2), 101 /** The back-end should analyze the entire video, and only report the detected tags without any timecode information. */ 102 @XmlEnumValue(value=SEQUENCE_TYPE_FULL) 103 FULL(3); 104 105 private int _value; 106 107 /** 108 * 109 * @param value 110 */ 111 private SequenceType(int value){ 112 _value = value; 113 } 114 115 /** 116 * 117 * @return the sequence as integer value 118 */ 119 public int toInt(){ 120 return _value; 121 } 122 123 /** 124 * 125 * @param value 126 * @return the value converted to SequenceType 127 * @throws IllegalArgumentException on bad value 128 */ 129 public static SequenceType fromInt(int value) throws IllegalArgumentException { 130 for(SequenceType t : SequenceType.values()){ 131 if(t._value == value){ 132 return t; 133 } 134 } 135 throw new IllegalArgumentException("Bad "+SequenceType.class.toString()+" : "+value); 136 } 137 138 /** 139 * 140 * @return this type as sequence type string 141 */ 142 public String toSequenceTypeString(){ 143 switch(this){ 144 case FRAME: 145 return SEQUENCE_TYPE_FRAME; 146 case FULL: 147 return SEQUENCE_TYPE_FULL; 148 case SECOND: 149 return SEQUENCE_TYPE_SECOND; 150 case SHOT: 151 return SEQUENCE_TYPE_SHOT; 152 default: 153 throw new UnsupportedOperationException("Unhandeled "+SequenceType.class.toString()+" : "+name()); 154 } 155 } 156 157 /** 158 * 159 * @param value 160 * @return the value converted to sequence type 161 * @throws IllegalArgumentException on bad value 162 */ 163 public static SequenceType fromSequenceTypeString(String value) throws IllegalArgumentException { 164 if(!StringUtils.isBlank(value)){ 165 for(SequenceType t : SequenceType.values()){ 166 if(t.toSequenceTypeString().equalsIgnoreCase(value)){ 167 return t; 168 } 169 } 170 } 171 throw new IllegalArgumentException("Invalid sequence type : "+value); 172 } 173 } // enum SequenceType 174 175 /** 176 * @return the sequence duration in seconds or null if none set 177 * @see #setSequenceDuration(Integer) 178 */ 179 public Integer getSequenceDuration() { 180 return _sequenceDuration; 181 } 182 183 /** 184 * @param sequenceDuration the sequence duration in seconds 185 * @see #getSequenceDuration() 186 */ 187 public void setSequenceDuration(Integer sequenceDuration) { 188 _sequenceDuration = sequenceDuration; 189 } 190 191 /** 192 * @return the sequenceType 193 * @see #setSequenceType(service.tut.pori.contentanalysis.video.VideoParameters.SequenceType) 194 */ 195 public SequenceType getSequenceType() { 196 return _sequenceType; 197 } 198 199 /** 200 * @param sequenceType the sequenceType to set 201 * @see #getSequenceType() 202 */ 203 public void setSequenceType(SequenceType sequenceType) { 204 _sequenceType = sequenceType; 205 } 206 207 /** 208 * @return the timecodes 209 * @see #setTimecodes(TimecodeList) 210 */ 211 public TimecodeList getTimecodes() { 212 return _timecodes; 213 } 214 215 /** 216 * @param timecodes the timecodes to set 217 * @see #getTimecodes() 218 */ 219 public void setTimecodes(TimecodeList timecodes) { 220 _timecodes = timecodes; 221 } 222 223 @Override 224 public void initialize(Map<String, String> metadata) throws IllegalArgumentException { 225 _analysisTypes = null; 226 _timecodes = null; 227 _sequenceDuration = null; 228 _sequenceType = null; 229 if(metadata != null && !metadata.isEmpty()){ 230 String[] timecodesStart = StringUtils.split(metadata.get(Definitions.ELEMENT_TIMECODE_START), core.tut.pori.http.Definitions.SEPARATOR_URI_QUERY_PARAM_VALUES); 231 String[] timecodesEnd = StringUtils.split(metadata.get(Definitions.ELEMENT_TIMECODE_END), core.tut.pori.http.Definitions.SEPARATOR_URI_QUERY_PARAM_VALUES); 232 int startLength = ArrayUtils.getLength(timecodesStart); 233 if(startLength != ArrayUtils.getLength(timecodesEnd)){ 234 throw new IllegalArgumentException(Definitions.ELEMENT_TIMECODE_START+" does not match "+Definitions.ELEMENT_TIMECODE_END+"."); 235 } 236 if(startLength > 0){ 237 _timecodes = new TimecodeList(); 238 for(int i=0;i<startLength;++i){ 239 _timecodes.addTimecode(new Timecode(Double.valueOf(timecodesStart[i]), Double.valueOf(timecodesEnd[i]))); 240 } 241 } 242 243 String temp = metadata.get(Definitions.ELEMENT_SEQUENCE_TYPE); 244 if(!StringUtils.isBlank(temp)){ 245 _sequenceType = SequenceType.fromSequenceTypeString(temp); 246 } 247 248 temp = metadata.get(Definitions.ELEMENT_SEQUENCE_DURATION); 249 if(!StringUtils.isBlank(temp)){ 250 _sequenceDuration = Integer.valueOf(temp); 251 } 252 253 String[] analysisTypes = StringUtils.split(metadata.get(service.tut.pori.contentanalysis.Definitions.ELEMENT_ANALYSIS_TYPELIST), core.tut.pori.http.Definitions.SEPARATOR_URI_QUERY_PARAM_VALUES); 254 if(!ArrayUtils.isEmpty(analysisTypes)){ 255 _analysisTypes = EnumSet.of(AnalysisType.fromAnalysisTypeString(analysisTypes[0])); 256 for(int i=1;i<analysisTypes.length;++i){ 257 _analysisTypes.add(AnalysisType.fromAnalysisTypeString(analysisTypes[i])); 258 } 259 } 260 } 261 } 262 263 @Override 264 public HashMap<String, String> toMetadata() { 265 HashMap<String, String> map = new HashMap<>(4); 266 267 if(_sequenceDuration != null){ 268 map.put(Definitions.ELEMENT_SEQUENCE_DURATION, _sequenceDuration.toString()); 269 } 270 271 if(_sequenceType != null){ 272 map.put(Definitions.ELEMENT_SEQUENCE_TYPE, _sequenceType.toSequenceTypeString()); 273 } 274 275 if(!TimecodeList.isEmpty(_timecodes)){ 276 List<Timecode> timecodes = _timecodes.getTimecodes(); 277 Iterator<Timecode> tcIter = timecodes.iterator(); 278 Timecode tc = tcIter.next(); 279 if(timecodes.size() == 1){ 280 map.put(Definitions.ELEMENT_TIMECODE_START, tc.getStart().toString()); // let it throw null pointer on bad value 281 map.put(Definitions.ELEMENT_TIMECODE_END, tc.getEnd().toString()); // let it throw null pointer on bad value 282 }else{ 283 StringBuilder start = new StringBuilder(tc.getStart().toString()); // let it throw null pointer on bad value 284 StringBuilder end = new StringBuilder(tc.getEnd().toString()); // let it throw null pointer on bad value 285 while(tcIter.hasNext()){ 286 start.append(core.tut.pori.http.Definitions.SEPARATOR_URI_QUERY_PARAM_VALUES); 287 end.append(core.tut.pori.http.Definitions.SEPARATOR_URI_QUERY_PARAM_VALUES); 288 tc = tcIter.next(); 289 start.append(tc.getStart().toString()); // let it throw null pointer on bad value 290 end.append(tc.getEnd().toString()); // let it throw null pointer on bad value 291 } 292 map.put(Definitions.ELEMENT_TIMECODE_START, start.toString()); 293 map.put(Definitions.ELEMENT_TIMECODE_END, end.toString()); 294 } 295 } 296 297 if(_analysisTypes != null && !_analysisTypes.isEmpty()){ 298 Iterator<AnalysisType> typeIter = _analysisTypes.iterator(); 299 if(_analysisTypes.size() == 1){ 300 map.put(service.tut.pori.contentanalysis.Definitions.ELEMENT_ANALYSIS_TYPELIST, typeIter.next().toAnalysisTypeString()); 301 }else{ 302 StringBuilder tb = new StringBuilder(typeIter.next().toAnalysisTypeString()); 303 while(typeIter.hasNext()){ 304 tb.append(core.tut.pori.http.Definitions.SEPARATOR_URI_QUERY_PARAM_VALUES); 305 tb.append(typeIter.next().toAnalysisTypeString()); 306 } 307 map.put(service.tut.pori.contentanalysis.Definitions.ELEMENT_ANALYSIS_TYPELIST, tb.toString()); 308 } 309 } 310 311 return (map.isEmpty() ? null : map); 312 } 313 314 /** 315 * @return the analysisTypes 316 */ 317 public Set<AnalysisType> getAnalysisTypes() { 318 return _analysisTypes; 319 } 320 321 /** 322 * @param analysisTypes the analysisTypes to set 323 */ 324 public void setAnalysisTypes(Set<AnalysisType> analysisTypes) { 325 _analysisTypes = analysisTypes; 326 } 327}