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.reference; 017 018import java.util.ArrayList; 019import java.util.Collection; 020import java.util.EnumSet; 021import java.util.Iterator; 022import java.util.List; 023import java.util.Random; 024import java.util.UUID; 025 026import org.apache.commons.lang3.ArrayUtils; 027import org.apache.commons.lang3.RandomStringUtils; 028import org.apache.commons.lang3.StringUtils; 029import org.apache.log4j.Logger; 030 031import service.tut.pori.contentanalysis.PhotoParameters.AnalysisType; 032import service.tut.pori.contentanalysis.AsyncTask.TaskType; 033import service.tut.pori.contentanalysis.BackendStatusList; 034import service.tut.pori.contentanalysis.CAContentCore.ServiceType; 035import service.tut.pori.contentanalysis.CAContentCore.Visibility; 036import service.tut.pori.contentanalysis.Definitions; 037import service.tut.pori.contentanalysis.ResultInfo; 038import service.tut.pori.contentanalysis.MediaObject; 039import service.tut.pori.contentanalysis.MediaObjectList; 040import service.tut.pori.contentanalysis.reference.CAXMLObjectCreator; 041import service.tut.pori.contentanalysis.video.DeletedVideoList; 042import service.tut.pori.contentanalysis.video.Timecode; 043import service.tut.pori.contentanalysis.video.TimecodeList; 044import service.tut.pori.contentanalysis.video.Video; 045import service.tut.pori.contentanalysis.video.VideoList; 046import service.tut.pori.contentanalysis.video.VideoParameters; 047import service.tut.pori.contentanalysis.video.VideoParameters.SequenceType; 048import service.tut.pori.contentanalysis.video.VideoTaskDetails; 049import service.tut.pori.contentanalysis.video.VideoTaskResponse; 050import core.tut.pori.context.ServiceInitializer; 051import core.tut.pori.http.parameters.DataGroups; 052import core.tut.pori.http.parameters.Limits; 053import core.tut.pori.users.UserIdentity; 054import core.tut.pori.utils.MediaUrlValidator.MediaType; 055 056/** 057 * Class that can be used to created example objects/object lists. 058 */ 059public class VideoXMLObjectCreator { 060 private static final Limits LIMITS_NO_MEDIA_OBJECTS; 061 static{ 062 LIMITS_NO_MEDIA_OBJECTS = new Limits(0, 0); 063 LIMITS_NO_MEDIA_OBJECTS.setTypeLimits(0, -1, Definitions.ELEMENT_MEDIA_OBJECTLIST); 064 } 065 private static final Logger LOGGER = Logger.getLogger(VideoXMLObjectCreator.class); 066 private static final int TEXT_LENGTH = 64; 067 private CAXMLObjectCreator _CACreator = null; 068 069 /** 070 * 071 * @param seed for random generator, or null to use default (system time in nanoseconds) 072 */ 073 public VideoXMLObjectCreator(Long seed){ 074 if(seed == null){ 075 seed = System.nanoTime(); 076 } 077 _CACreator = new CAXMLObjectCreator(seed); 078 } 079 080 /** 081 * 082 * @param analysisTypes 083 * @param dataGroups 084 * @param limits 085 * @param serviceTypes 086 * @param userIdentity 087 * @return a randomly generated video 088 */ 089 public Video createVideo(Collection<AnalysisType> analysisTypes, DataGroups dataGroups, Limits limits, EnumSet<ServiceType> serviceTypes, UserIdentity userIdentity) { 090 Video video = new Video(); 091 String guid = UUID.randomUUID().toString(); 092 video.setGUID(guid); 093 ServiceType serviceType = createVideoServiceType(); 094 video.setServiceType(serviceType); 095 UserIdentity userId = (UserIdentity.isValid(userIdentity) ? userIdentity : _CACreator.createUserIdentity()); 096 video.setOwnerUserId(userId); 097 098 int backendStatusCount = (!DataGroups.hasDataGroup(Definitions.DATA_GROUP_BACKEND_STATUS, dataGroups) || limits == null ? 0 : limits.getMaxItems(Definitions.ELEMENT_BACKEND_STATUS_LIST)); 099 100 BackendStatusList backendStatus = null; 101 if(DataGroups.hasDataGroup(DataGroups.DATA_GROUP_ALL,dataGroups) || DataGroups.hasDataGroup(DataGroups.DATA_GROUP_BASIC, dataGroups)){ 102 video.setCredits(RandomStringUtils.randomAlphabetic(TEXT_LENGTH)); 103 video.setName(RandomStringUtils.randomAlphabetic(TEXT_LENGTH)); 104 video.setDescription(RandomStringUtils.randomAlphabetic(TEXT_LENGTH)); 105 video.setVisibility(_CACreator.createVisibility()); 106 }else if(DataGroups.hasDataGroup(CAXMLObjectCreator.DATA_GROUP_BACKEND_RESPONSE, dataGroups)){ 107 backendStatus = _CACreator.createBackendStatusContainer(backendStatusCount); 108 video.setBackendStatus(backendStatus); 109 }else if(DataGroups.hasDataGroup(Definitions.DATA_GROUP_VISIBILITY, dataGroups)){ 110 video.setVisibility(_CACreator.createVisibility()); 111 } 112 113 MediaObjectList mediaObjectList = createMediaObjectList(analysisTypes, dataGroups, limits, serviceTypes); 114 if(!MediaObjectList.isEmpty(mediaObjectList)){ 115 for(Iterator<MediaObject> vIter = mediaObjectList.getMediaObjects().iterator(); vIter.hasNext();){ // make sure all the new media objects have the same user identity as the created video 116 vIter.next().setOwnerUserId(userId); 117 } 118 video.setMediaObjects(mediaObjectList); 119 } 120 121 if(DataGroups.hasDataGroup(Definitions.DATA_GROUP_STATUS, dataGroups)){ 122 video.setBackendStatus((backendStatus != null ? _CACreator.createBackendStatusContainer(backendStatusCount) : backendStatus)); 123 } 124 125 video.setUrl(generateRedirectUrl(guid, serviceType)); 126 127 return video; 128 } 129 130 /** 131 * Create media object list using {@link service.tut.pori.contentanalysis.MediaObject}} class. 132 * 133 * Note that regardless of the given analysis types, this will only return objects of type {@link core.tut.pori.utils.MediaUrlValidator.MediaType#VIDEO} or {@link core.tut.pori.utils.MediaUrlValidator.MediaType#AUDIO} 134 * 135 * @param analysisTypes 136 * @param dataGroups 137 * @param limits 138 * @param serviceTypes 139 * @return randomly generated media object list 140 */ 141 public MediaObjectList createMediaObjectList(Collection<AnalysisType> analysisTypes, DataGroups dataGroups, Limits limits, EnumSet<ServiceType> serviceTypes) { 142 MediaObjectList objects = _CACreator.createMediaObjectList(analysisTypes, dataGroups, limits, serviceTypes); 143 if(!MediaObjectList.isEmpty(objects) && (DataGroups.hasDataGroup(Definitions.DATA_GROUP_TIMECODES, dataGroups) || DataGroups.hasDataGroup(DataGroups.DATA_GROUP_ALL, dataGroups))){ 144 boolean hasAudio = (analysisTypes != null && analysisTypes.contains(AnalysisType.AUDIO)); 145 boolean hasVideo = (!hasAudio || analysisTypes.contains(AnalysisType.VISUAL)); 146 Random r = _CACreator.getRandom(); 147 for(MediaObject vo : objects.getMediaObjects()){ 148 if(hasAudio){ 149 if(hasVideo && r.nextBoolean()){ 150 vo.setMediaType(MediaType.VIDEO); 151 }else{ 152 vo.setMediaType(MediaType.AUDIO); 153 } 154 }else{ 155 vo.setMediaType(MediaType.VIDEO); 156 } 157 vo.setTimecodes(createTimecodeList(limits, false)); 158 } 159 } 160 return objects; 161 } 162 163 /** 164 * 165 * @param guid 166 * @param type 167 * @return redirection URL for the given GUID and type or null if either one the given values was null 168 */ 169 public String generateRedirectUrl(String guid, ServiceType type){ 170 if(type == null || StringUtils.isBlank(guid)){ 171 LOGGER.error("GUID or service type was null."); 172 return null; 173 } 174 return ServiceInitializer.getPropertyHandler().getRESTBindContext()+service.tut.pori.contentanalysis.video.reference.Definitions.SERVICE_VCA_REFERENCE_CLIENT+"/"+service.tut.pori.contentanalysis.Definitions.METHOD_REDIRECT+"?"+service.tut.pori.contentanalysis.Definitions.PARAMETER_GUID+"="+guid+"&"+service.tut.pori.contentanalysis.Definitions.PARAMETER_SERVICE_ID+"="+type.getServiceId(); 175 } 176 177 /** 178 * 179 * @return serviceType valid for videos (not facebook jazz) 180 */ 181 public ServiceType createVideoServiceType(){ 182 return ServiceType.PICASA_STORAGE_SERVICE; 183 } 184 185 /** 186 * @param analysisTypes 187 * @param dataGroups 188 * @param limits 189 * @param serviceTypes 190 * @return randomly generated media object 191 */ 192 public MediaObject createVideoMediaObject(Collection<AnalysisType> analysisTypes, DataGroups dataGroups, Limits limits, EnumSet<ServiceType> serviceTypes) { 193 MediaObject vvo = _CACreator.createMediaObject(analysisTypes, dataGroups, serviceTypes); 194 vvo.setMediaType(MediaType.VIDEO); 195 vvo.setTimecodes(createTimecodeList(limits, false)); 196 return vvo; 197 } 198 199 /** 200 * 201 * @param previousEnd if not null, the start time will be after this time 202 * @return randomly generated timecode 203 */ 204 public Timecode createTimecode(Double previousEnd) { 205 Timecode timecode = new Timecode(); 206 Random r = _CACreator.getRandom(); 207 double start = r.nextDouble()*r.nextInt(3600); 208 if(previousEnd != null){ 209 start += previousEnd; 210 } 211 timecode.setStart(start); 212 timecode.setEnd(start+r.nextDouble()*r.nextInt(3600)); 213 return timecode; 214 } 215 216 /** 217 * 218 * @param limits 219 * @param sequential if true the timecodes will appear in sequential order, otherwise they are random and may cover duplicate time periods 220 * @return a randomly generated time code list 221 */ 222 public TimecodeList createTimecodeList(Limits limits, boolean sequential) { 223 int count = (limits == null ? 0 : limits.getMaxItems(service.tut.pori.contentanalysis.video.Definitions.ELEMENT_TIMECODELIST)); 224 if(count < 1){ 225 LOGGER.warn("count < 1"); 226 return null; 227 }else if(count >= Limits.DEFAULT_MAX_ITEMS){ 228 LOGGER.debug("Count was "+Limits.DEFAULT_MAX_ITEMS+", using 1."); 229 count = 1; 230 } 231 232 TimecodeList tcl = new TimecodeList(); 233 Double previousEnd = null; 234 for(int i=0;i<count;++i){ 235 Timecode tc = createTimecode(previousEnd); 236 if(sequential){ 237 previousEnd = tc.getEnd(); 238 } 239 tcl.addTimecode(tc); 240 } 241 return tcl; 242 } 243 244 /** 245 * 246 * @param analysisTypes 247 * @param dataGroups 248 * @param limits 249 * @param serviceTypes 250 * @param userIdentity 251 * @return randomly generated video list 252 */ 253 public VideoList createVideoList(Collection<AnalysisType> analysisTypes, DataGroups dataGroups, Limits limits, EnumSet<ServiceType> serviceTypes, UserIdentity userIdentity) { 254 int count = limits.getMaxItems(service.tut.pori.contentanalysis.video.Definitions.ELEMENT_VIDEOLIST); 255 if(count < 1){ 256 LOGGER.warn("count < 1"); 257 return null; 258 }else if(count >= Limits.DEFAULT_MAX_ITEMS){ 259 LOGGER.debug("Count was "+Limits.DEFAULT_MAX_ITEMS+", using 1."); 260 count = 1; 261 } 262 VideoList list = new VideoList(); 263 for(int i=0;i<count;++i){ 264 list.addVideo(createVideo(analysisTypes, dataGroups, limits, serviceTypes, userIdentity)); 265 } 266 if(DataGroups.hasDataGroup(Definitions.DATA_GROUP_RESULT_INFO, dataGroups)){ 267 list.setResultInfo(new ResultInfo(limits.getStartItem(service.tut.pori.contentanalysis.video.Definitions.ELEMENT_VIDEOLIST), limits.getEndItem(service.tut.pori.contentanalysis.video.Definitions.ELEMENT_VIDEOLIST), count)); 268 } 269 return list; 270 } 271 272 /** 273 * 274 * @param limits 275 * @return randomly generated deleted video list 276 */ 277 public DeletedVideoList createDeletedVideoList(Limits limits) { 278 int count = limits.getMaxItems(service.tut.pori.contentanalysis.video.Definitions.ELEMENT_DELETED_VIDEOLIST); 279 if(count < 1){ 280 LOGGER.warn("count < 1"); 281 return null; 282 }else if(count >= Limits.DEFAULT_MAX_ITEMS){ 283 LOGGER.debug("Count was "+Limits.DEFAULT_MAX_ITEMS+", using 1."); 284 count = 1; 285 } 286 DeletedVideoList list = new DeletedVideoList(); 287 for(int i=0;i<count;++i){ 288 list.addVideo(createVideo(null, null, LIMITS_NO_MEDIA_OBJECTS, null, null)); 289 } 290 return list; 291 } 292 293 /** 294 * Create video options for an analysis task. 295 * 296 * @return randomly generated options 297 */ 298 public VideoParameters createVideoOptions() { 299 VideoParameters options = new VideoParameters(); 300 SequenceType t = getSequenceType(); 301 options.setSequenceType(t); 302 Random r = _CACreator.getRandom(); 303 switch(t){ 304 case SECOND: 305 options.setSequenceDuration(Math.abs(r.nextInt())); 306 break; 307 case FRAME: // do not generate duration if frame-based analysis is chosen 308 case FULL: // do not generate duration if entire video is chosen 309 case SHOT: // do not generate duration if variable-length shots are chosen 310 break; 311 default: 312 throw new UnsupportedOperationException("Unhandelled "+SequenceType.class.toString()+" : "+t.name()); 313 } 314 315 if(r.nextBoolean()){ 316 options.setTimecodes(createTimecodeList(new Limits(0,r.nextInt(5)), true)); 317 } 318 options.setAnalysisTypes(EnumSet.allOf(AnalysisType.class)); 319 return options; 320 } 321 322 /** 323 * 324 * @return random sequence type 325 */ 326 public SequenceType getSequenceType() { 327 Random r = _CACreator.getRandom(); 328 SequenceType[] types = SequenceType.values(); 329 return types[r.nextInt(types.length)]; 330 } 331 332 /** 333 * 334 * @param analysisTypes 335 * @param dataGroups 336 * @param limits 337 * @param taskId if null, value is randomly generated 338 * @param taskType if null, value is randomly generated 339 * @return randomly generated task response 340 */ 341 public VideoTaskResponse createTaskResponse(Collection<AnalysisType> analysisTypes, DataGroups dataGroups, Limits limits, Long taskId, TaskType taskType) { 342 VideoTaskResponse response = new VideoTaskResponse(); 343 Random r = _CACreator.getRandom(); 344 response.setTaskId((taskId == null ? Math.abs(r.nextLong()) : taskId)); 345 Integer backendId = Math.abs(r.nextInt()); 346 response.setBackendId(backendId); 347 response.setMessage(RandomStringUtils.randomAlphabetic(TEXT_LENGTH)); 348 response.setTaskType((taskType == null ? _CACreator.createTaskType() : taskType)); 349 response.setStatus(_CACreator.createTaskStatus()); 350 VideoList videoList = createVideoList(analysisTypes, dataGroups, limits, null, _CACreator.createUserIdentity()); 351 if(!VideoList.isEmpty(videoList)){ 352 for(Video v : videoList.getVideos()){ // make sure all media objects have the same back-end id as the task has 353 MediaObjectList mediaObjectList = v.getMediaObjects(); 354 if(!MediaObjectList.isEmpty(mediaObjectList)){ 355 for(MediaObject vo : mediaObjectList.getMediaObjects()){ 356 vo.setBackendId(backendId); 357 } 358 } 359 } 360 response.setVideoList(videoList); 361 } 362 return response; 363 } 364 365 /** 366 * @param backendId if null, the value is randomly generated 367 * @param dataGroups 368 * @param limits 369 * @param taskId if null, the value is randomly generated 370 * @param taskType ANALYSIS or FEEDBACK, if null, type is chosen randomly 371 * @return randomly generated video task details 372 * @throws UnsupportedOperationException on bad type 373 */ 374 public VideoTaskDetails createVideoTaskDetails(Integer backendId, DataGroups dataGroups, Limits limits, Long taskId, TaskType taskType) { 375 if(taskType == null){ 376 taskType = createVideoTaskDetailsType(); 377 } 378 VideoTaskDetails details = new VideoTaskDetails(taskType); 379 UserIdentity userIdentity = _CACreator.createUserIdentity(); 380 Random r = _CACreator.getRandom(); 381 switch(taskType){ 382 case ANALYSIS: 383 details.setTaskParameters(createVideoOptions()); 384 details.setVideoList(createVideoList(null, dataGroups, limits, null, userIdentity)); 385 break; 386 case FEEDBACK: 387 if(r.nextBoolean()){ // create randomly deleted feedback 388 details.setDeletedVideoList(createDeletedVideoList(limits)); 389 }else{ // normal video list 390 details.setVideoList(createVideoList(null, dataGroups, limits, null, userIdentity)); 391 } 392 break; 393 default: 394 throw new UnsupportedOperationException("Unsupported type: "+taskType.name()); 395 } 396 397 details.setBackendId((backendId == null ? Math.abs(r.nextInt()) : backendId)); 398 details.setTaskId((taskId == null ? Math.abs(r.nextLong()) : taskId)); 399 details.setBackends(_CACreator.createBackendStatusContainer(limits.getMaxItems(Definitions.ELEMENT_BACKEND_STATUS_LIST))); 400 details.setUserId(userIdentity); 401 details.setCallbackUri(generateFinishedCallbackUri()); // override the default uri 402 return details; 403 } 404 405 /** 406 * 407 * @return the default task finished callback uri 408 */ 409 public String generateFinishedCallbackUri(){ 410 return ServiceInitializer.getPropertyHandler().getRESTBindContext()+service.tut.pori.contentanalysis.video.reference.Definitions.SERVICE_VCA_REFERENCE_SERVER+"/"+service.tut.pori.contentanalysis.Definitions.METHOD_TASK_FINISHED; 411 } 412 413 /** 414 * 415 * @return randomly generated task type 416 */ 417 public TaskType createVideoTaskDetailsType(){ 418 return (_CACreator.getRandom().nextBoolean() ? TaskType.ANALYSIS : TaskType.FEEDBACK); 419 } 420 421 /** 422 * Create example search results, making sure that one of the given userIds, mediaObjects and serviceTypes is 423 * set for the videos, if userIds, mediaObjects or serviceTypes is null or empty, a random value will be generated. Note: 424 * for videos with visibility PUBLIC, any userId is OK, and the given user id will not necessarily be set. 425 * If GUID is given, the first video in the list (when the given limits permit it) will have the given GUID. 426 * @param guid 427 * @param dataGroups 428 * @param limits 429 * @param serviceTypes 430 * @param userIds 431 * @param mediaObjects 432 * @return randomly generated video list 433 */ 434 public VideoList createSearchResults(String guid, DataGroups dataGroups, Limits limits, EnumSet<ServiceType> serviceTypes, long[] userIds, MediaObjectList mediaObjects) { 435 VideoList list = createVideoList(null, dataGroups, limits, serviceTypes, null); 436 if(list == null){ 437 return null; 438 } 439 440 boolean hasUserIds = !ArrayUtils.isEmpty(userIds); 441 442 List<ServiceType> types = null; 443 int serviceTypeCount = 0; 444 if(serviceTypes != null && !serviceTypes.isEmpty()){ 445 types = new ArrayList<>(serviceTypes); 446 serviceTypeCount = serviceTypes.size(); 447 } 448 449 int mediaObjectCount = 0; 450 if(!MediaObjectList.isEmpty(mediaObjects)){ 451 mediaObjectCount = mediaObjects.getMediaObjects().size(); 452 } 453 454 if(!hasUserIds && types == null && mediaObjectCount < 1){ 455 LOGGER.debug("No userIds, mediaObjects or types."); 456 return list; 457 } 458 459 Random r = _CACreator.getRandom(); 460 for(Video video : list.getVideos()){ 461 if(guid != null){ // if uid has been given, and has not been set already 462 video.setGUID(guid); 463 guid = null; 464 } 465 if(hasUserIds){ 466 if(!Visibility.PUBLIC.equals(video.getVisibility())){ // only change for photos that do not have public visibility 467 video.setOwnerUserId(new UserIdentity(userIds[r.nextInt(userIds.length)])); 468 } 469 }else{ 470 video.setVisibility(Visibility.PUBLIC); // there could also be photos with visibility PRIVATE, if the user was logged in, but setting all to PUBLIC will ensure that the example result is valid 471 } 472 if(types != null){ 473 video.setServiceType(types.get(r.nextInt(serviceTypeCount))); // make sure there are service types only from the given set 474 } 475 if(mediaObjectCount > 0){ 476 List<MediaObject> objects = video.getMediaObjects().getMediaObjects(); 477 if(objects != null){ // don't do anything if there are no other objects (probably filtered out by limits or datagroups) 478 objects.remove(0); // remove one just in case, to keep the limits... 479 video.addMediaObject(mediaObjects.getMediaObjects().get(r.nextInt(mediaObjectCount))); // ...and add new one for the removed one 480 } 481 } 482 } 483 return list; 484 } 485 486 /** 487 * @return the random generator used for this object creator 488 * @see service.tut.pori.contentanalysis.reference.CAXMLObjectCreator#getRandom() 489 */ 490 public Random getRandom() { 491 return _CACreator.getRandom(); 492 } 493}