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