001/* 002 * (C) Copyright 2006-2012 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 018 * Florent Guillaume 019 * Antoine Taillefer 020 */ 021package org.nuxeo.ecm.platform.video.service; 022 023import static org.nuxeo.ecm.core.api.CoreSession.ALLOW_VERSION_WRITE; 024import static org.nuxeo.ecm.platform.video.VideoConstants.TRANSCODED_VIDEOS_PROPERTY; 025 026import java.io.Serializable; 027import java.util.ArrayList; 028import java.util.List; 029import java.util.Map; 030import java.util.Objects; 031import java.util.stream.Collectors; 032 033import org.apache.commons.lang3.builder.HashCodeBuilder; 034import org.apache.commons.logging.Log; 035import org.apache.commons.logging.LogFactory; 036import org.nuxeo.ecm.core.api.Blob; 037import org.nuxeo.ecm.core.api.DocumentModel; 038import org.nuxeo.ecm.core.api.IdRef; 039import org.nuxeo.ecm.core.event.Event; 040import org.nuxeo.ecm.core.event.EventService; 041import org.nuxeo.ecm.core.event.impl.DocumentEventContext; 042import org.nuxeo.ecm.core.work.AbstractWork; 043import org.nuxeo.ecm.core.work.api.WorkManager; 044import org.nuxeo.ecm.platform.video.TranscodedVideo; 045import org.nuxeo.ecm.platform.video.Video; 046import org.nuxeo.ecm.platform.video.VideoDocument; 047import org.nuxeo.runtime.api.Framework; 048 049/** 050 * Work running a defined video conversion. 051 * 052 * @since 5.6 053 */ 054public class VideoConversionWork extends AbstractWork { 055 056 private static final long serialVersionUID = 1L; 057 058 private static final Log log = LogFactory.getLog(VideoConversionWork.class); 059 060 public static final String CATEGORY_VIDEO_CONVERSION = "videoConversion"; 061 062 public static final String VIDEO_CONVERSIONS_DONE_EVENT = "videoConversionsDone"; 063 064 protected final String conversionName; 065 066 protected static String computeIdPrefix(String repositoryName, String docId) { 067 return repositoryName + ':' + docId + ":videoconv:"; 068 } 069 070 public VideoConversionWork(String repositoryName, String docId, String conversionName) { 071 super(computeIdPrefix(repositoryName, docId) + conversionName); 072 setDocument(repositoryName, docId); 073 this.conversionName = conversionName; 074 } 075 076 @Override 077 public String getCategory() { 078 return CATEGORY_VIDEO_CONVERSION; 079 } 080 081 @Override 082 public String getTitle() { 083 return "Video Conversion: " + conversionName; 084 } 085 086 @Override 087 public void work() { 088 setProgress(Progress.PROGRESS_INDETERMINATE); 089 DocumentModel doc; 090 Video originalVideo; 091 try { 092 openSystemSession(); 093 doc = session.getDocument(new IdRef(docId)); 094 originalVideo = getVideoToConvert(doc); 095 Blob originalBlob; 096 if (originalVideo == null || (originalBlob = originalVideo.getBlob()) == null 097 || originalBlob.getLength() == 0) { 098 resetTranscodedVideos(doc); 099 return; 100 } 101 commitOrRollbackTransaction(); 102 } finally { 103 cleanUp(true, null); 104 } 105 106 // Perform the actual conversion 107 log.debug(String.format("Processing %s conversion of Video document %s.", conversionName, doc)); 108 setStatus("Transcoding"); 109 VideoService service = Framework.getService(VideoService.class); 110 TranscodedVideo transcodedVideo = service.convert(originalVideo, conversionName); 111 112 // Saving it to the document 113 startTransaction(); 114 setStatus("Saving"); 115 openSystemSession(); 116 doc = session.getDocument(new IdRef(docId)); 117 saveNewTranscodedVideo(doc, transcodedVideo); 118 fireVideoConversionsDoneEvent(doc); 119 log.debug(String.format("End processing %s conversion of Video document %s.", conversionName, doc)); 120 setStatus("Done"); 121 } 122 123 @Override 124 public boolean isIdempotent() { 125 // when the video is updated the work id is the same 126 return false; 127 } 128 129 @Override 130 public boolean equals(Object other) { 131 return super.equals(other) && Objects.equals(this, other); 132 } 133 134 @Override 135 public int hashCode() { 136 HashCodeBuilder builder = new HashCodeBuilder(); 137 builder.appendSuper(super.hashCode()); 138 builder.append(conversionName); 139 return builder.toHashCode(); 140 } 141 142 protected Video getVideoToConvert(DocumentModel doc) { 143 VideoDocument videoDocument = doc.getAdapter(VideoDocument.class); 144 return videoDocument.getVideo(); 145 } 146 147 protected void resetTranscodedVideos(DocumentModel doc) { 148 log.warn(String.format("No original video to transcode, resetting transcoded videos of document %s.", doc)); 149 setStatus("No video to process"); 150 doc.setPropertyValue(TRANSCODED_VIDEOS_PROPERTY, null); 151 if (doc.isVersion()) { 152 doc.putContextData(ALLOW_VERSION_WRITE, Boolean.TRUE); 153 } 154 session.saveDocument(doc); 155 } 156 157 protected void saveNewTranscodedVideo(DocumentModel doc, TranscodedVideo transcodedVideo) { 158 @SuppressWarnings("unchecked") 159 List<Map<String, Serializable>> transcodedVideos = (List<Map<String, Serializable>>) doc.getPropertyValue( 160 TRANSCODED_VIDEOS_PROPERTY); 161 if (transcodedVideos == null) { 162 transcodedVideos = new ArrayList<>(); 163 } else { 164 transcodedVideos = transcodedVideos.stream() 165 .filter(map -> !transcodedVideo.getName().equals(map.get("name"))) 166 .collect(Collectors.toList()); 167 } 168 transcodedVideos.add(transcodedVideo.toMap()); 169 doc.setPropertyValue(TRANSCODED_VIDEOS_PROPERTY, (Serializable) transcodedVideos); 170 if (doc.isVersion()) { 171 doc.putContextData(ALLOW_VERSION_WRITE, Boolean.TRUE); 172 } 173 session.saveDocument(doc); 174 } 175 176 /** 177 * Fire a {@code VIDEO_CONVERSIONS_DONE_EVENT} event when no other VideoConversionWork is scheduled for this 178 * document. 179 * 180 * @since 5.8 181 */ 182 protected void fireVideoConversionsDoneEvent(DocumentModel doc) { 183 WorkManager workManager = Framework.getService(WorkManager.class); 184 List<String> workIds = workManager.listWorkIds(CATEGORY_VIDEO_CONVERSION, null); 185 String idPrefix = computeIdPrefix(repositoryName, docId); 186 int worksCount = 0; 187 for (String workId : workIds) { 188 if (workId.startsWith(idPrefix) && ++worksCount > 1) { 189 // another work scheduled 190 return; 191 } 192 } 193 194 DocumentEventContext ctx = new DocumentEventContext(session, session.getPrincipal(), doc); 195 Event event = ctx.newEvent(VIDEO_CONVERSIONS_DONE_EVENT); 196 Framework.getService(EventService.class).fireEvent(event); 197 } 198 199}