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}