001
014
015 package com.liferay.portlet.documentlibrary.util;
016
017 import com.liferay.document.library.kernel.exception.NoSuchFileEntryException;
018 import com.liferay.document.library.kernel.model.DLProcessorConstants;
019 import com.liferay.document.library.kernel.util.AudioProcessor;
020 import com.liferay.document.library.kernel.util.DLPreviewableProcessor;
021 import com.liferay.document.library.kernel.util.DLUtil;
022 import com.liferay.exportimport.kernel.lar.PortletDataContext;
023 import com.liferay.portal.fabric.InputResource;
024 import com.liferay.portal.fabric.OutputResource;
025 import com.liferay.portal.kernel.log.Log;
026 import com.liferay.portal.kernel.log.LogFactoryUtil;
027 import com.liferay.portal.kernel.messaging.DestinationNames;
028 import com.liferay.portal.kernel.process.ClassPathUtil;
029 import com.liferay.portal.kernel.process.ProcessCallable;
030 import com.liferay.portal.kernel.process.ProcessChannel;
031 import com.liferay.portal.kernel.process.ProcessException;
032 import com.liferay.portal.kernel.process.ProcessExecutorUtil;
033 import com.liferay.portal.kernel.repository.model.FileEntry;
034 import com.liferay.portal.kernel.repository.model.FileVersion;
035 import com.liferay.portal.kernel.util.FileUtil;
036 import com.liferay.portal.kernel.util.PropsKeys;
037 import com.liferay.portal.kernel.util.ServerDetector;
038 import com.liferay.portal.kernel.util.SetUtil;
039 import com.liferay.portal.kernel.util.StreamUtil;
040 import com.liferay.portal.kernel.util.StringBundler;
041 import com.liferay.portal.kernel.util.StringPool;
042 import com.liferay.portal.kernel.util.SystemEnv;
043 import com.liferay.portal.kernel.xml.Element;
044 import com.liferay.portal.kernel.xuggler.XugglerUtil;
045 import com.liferay.portal.log.Log4jLogFactoryImpl;
046 import com.liferay.portal.repository.liferayrepository.model.LiferayFileVersion;
047 import com.liferay.portal.util.PropsUtil;
048 import com.liferay.portal.util.PropsValues;
049 import com.liferay.util.log4j.Log4JUtil;
050
051 import java.io.File;
052 import java.io.InputStream;
053
054 import java.util.List;
055 import java.util.Map;
056 import java.util.Properties;
057 import java.util.Set;
058 import java.util.Vector;
059 import java.util.concurrent.CancellationException;
060 import java.util.concurrent.Future;
061
062 import org.apache.commons.lang.time.StopWatch;
063
064
070 public class AudioProcessorImpl
071 extends DLPreviewableProcessor implements AudioProcessor {
072
073 @Override
074 public void afterPropertiesSet() {
075 boolean valid = true;
076
077 if ((_PREVIEW_TYPES.length == 0) || (_PREVIEW_TYPES.length > 2)) {
078 valid = false;
079 }
080 else {
081 for (String previewType : _PREVIEW_TYPES) {
082 if (!previewType.equals("mp3") && !previewType.equals("ogg")) {
083 valid = false;
084
085 break;
086 }
087 }
088 }
089
090 if (!valid && _log.isWarnEnabled()) {
091 StringBundler sb = new StringBundler(5);
092
093 sb.append("Liferay is incorrectly configured to generate video ");
094 sb.append("previews using video containers other than MP3 or ");
095 sb.append("OGG. Please change the property ");
096 sb.append(PropsKeys.DL_FILE_ENTRY_PREVIEW_AUDIO_CONTAINERS);
097 sb.append(" in portal-ext.properties.");
098
099 _log.warn(sb.toString());
100 }
101
102 FileUtil.mkdirs(PREVIEW_TMP_PATH);
103 }
104
105 @Override
106 public void generateAudio(
107 FileVersion sourceFileVersion, FileVersion destinationFileVersion)
108 throws Exception {
109
110 _generateAudio(sourceFileVersion, destinationFileVersion);
111 }
112
113 @Override
114 public Set<String> getAudioMimeTypes() {
115 return _audioMimeTypes;
116 }
117
118 @Override
119 public InputStream getPreviewAsStream(FileVersion fileVersion, String type)
120 throws Exception {
121
122 return doGetPreviewAsStream(fileVersion, type);
123 }
124
125 @Override
126 public long getPreviewFileSize(FileVersion fileVersion, String type)
127 throws Exception {
128
129 return doGetPreviewFileSize(fileVersion, type);
130 }
131
132 @Override
133 public String getType() {
134 return DLProcessorConstants.AUDIO_PROCESSOR;
135 }
136
137 @Override
138 public boolean hasAudio(FileVersion fileVersion) {
139 boolean hasAudio = false;
140
141 try {
142 hasAudio = _hasAudio(fileVersion);
143
144 if (!hasAudio && isSupported(fileVersion)) {
145 _queueGeneration(null, fileVersion);
146 }
147 }
148 catch (Exception e) {
149 _log.error(e, e);
150 }
151
152 return hasAudio;
153 }
154
155 @Override
156 public boolean isAudioSupported(FileVersion fileVersion) {
157 return isSupported(fileVersion);
158 }
159
160 @Override
161 public boolean isAudioSupported(String mimeType) {
162 return isSupported(mimeType);
163 }
164
165 @Override
166 public boolean isSupported(String mimeType) {
167 if (_audioMimeTypes.contains(mimeType) && XugglerUtil.isEnabled()) {
168 return true;
169 }
170
171 return false;
172 }
173
174 @Override
175 public void trigger(
176 FileVersion sourceFileVersion, FileVersion destinationFileVersion) {
177
178 super.trigger(sourceFileVersion, destinationFileVersion);
179
180 _queueGeneration(sourceFileVersion, destinationFileVersion);
181 }
182
183 @Override
184 protected void doExportGeneratedFiles(
185 PortletDataContext portletDataContext, FileEntry fileEntry,
186 Element fileEntryElement)
187 throws Exception {
188
189 exportPreviews(portletDataContext, fileEntry, fileEntryElement);
190 }
191
192 @Override
193 protected void doImportGeneratedFiles(
194 PortletDataContext portletDataContext, FileEntry fileEntry,
195 FileEntry importedFileEntry, Element fileEntryElement)
196 throws Exception {
197
198 importPreviews(
199 portletDataContext, fileEntry, importedFileEntry, fileEntryElement);
200 }
201
202 protected void exportPreviews(
203 PortletDataContext portletDataContext, FileEntry fileEntry,
204 Element fileEntryElement)
205 throws Exception {
206
207 FileVersion fileVersion = fileEntry.getFileVersion();
208
209 if (!isSupported(fileVersion) || !hasPreviews(fileVersion)) {
210 return;
211 }
212
213 if (!portletDataContext.isPerformDirectBinaryImport()) {
214 if ((_PREVIEW_TYPES.length == 0) || (_PREVIEW_TYPES.length > 2)) {
215 return;
216 }
217
218 for (String previewType : _PREVIEW_TYPES) {
219 if (previewType.equals("mp3") || previewType.equals("ogg")) {
220 exportPreview(
221 portletDataContext, fileEntry, fileEntryElement,
222 "audio", previewType);
223 }
224 }
225 }
226 }
227
228 @Override
229 protected List<Long> getFileVersionIds() {
230 return _fileVersionIds;
231 }
232
233 @Override
234 protected String getPreviewType(FileVersion fileVersion) {
235 return _PREVIEW_TYPES[0];
236 }
237
238 @Override
239 protected String[] getPreviewTypes() {
240 return _PREVIEW_TYPES;
241 }
242
243 @Override
244 protected String getThumbnailType(FileVersion fileVersion) {
245 return null;
246 }
247
248 protected void importPreviews(
249 PortletDataContext portletDataContext, FileEntry fileEntry,
250 FileEntry importedFileEntry, Element fileEntryElement)
251 throws Exception {
252
253 if ((_PREVIEW_TYPES.length == 0) || (_PREVIEW_TYPES.length > 2)) {
254 return;
255 }
256
257 for (String previewType : _PREVIEW_TYPES) {
258 if (previewType.equals("mp3") || previewType.equals("ogg")) {
259 importPreview(
260 portletDataContext, fileEntry, importedFileEntry,
261 fileEntryElement, "audio", previewType);
262 }
263 }
264 }
265
266 private void _generateAudio(
267 FileVersion sourceFileVersion, FileVersion destinationFileVersion)
268 throws Exception {
269
270 String tempFileId = DLUtil.getTempFileId(
271 destinationFileVersion.getFileEntryId(),
272 destinationFileVersion.getVersion());
273
274 File[] previewTempFiles = new File[_PREVIEW_TYPES.length];
275
276 for (int i = 0; i < _PREVIEW_TYPES.length; i++) {
277 previewTempFiles[i] = getPreviewTempFile(
278 tempFileId, _PREVIEW_TYPES[i]);
279 }
280
281 File audioTempFile = null;
282
283 InputStream inputStream = null;
284
285 try {
286 if (sourceFileVersion != null) {
287 copy(sourceFileVersion, destinationFileVersion);
288
289 return;
290 }
291
292 if (!XugglerUtil.isEnabled() || _hasAudio(destinationFileVersion)) {
293 return;
294 }
295
296 audioTempFile = FileUtil.createTempFile(
297 destinationFileVersion.getExtension());
298
299 if (!hasPreviews(destinationFileVersion)) {
300 File file = null;
301
302 if (destinationFileVersion instanceof LiferayFileVersion) {
303 try {
304 LiferayFileVersion liferayFileVersion =
305 (LiferayFileVersion)destinationFileVersion;
306
307 file = liferayFileVersion.getFile(false);
308 }
309 catch (UnsupportedOperationException uoe) {
310 }
311 }
312
313 if (file == null) {
314 inputStream = destinationFileVersion.getContentStream(
315 false);
316
317 FileUtil.write(audioTempFile, inputStream);
318
319 file = audioTempFile;
320 }
321
322 try {
323 _generateAudioXuggler(
324 destinationFileVersion, file, previewTempFiles);
325 }
326 catch (Exception e) {
327 _log.error(e, e);
328 }
329 }
330 }
331 catch (NoSuchFileEntryException nsfee) {
332 if (_log.isDebugEnabled()) {
333 _log.debug(nsfee, nsfee);
334 }
335 }
336 finally {
337 StreamUtil.cleanUp(inputStream);
338
339 _fileVersionIds.remove(destinationFileVersion.getFileVersionId());
340
341 for (int i = 0; i < previewTempFiles.length; i++) {
342 FileUtil.delete(previewTempFiles[i]);
343 }
344
345 FileUtil.delete(audioTempFile);
346 }
347 }
348
349 private void _generateAudioXuggler(
350 FileVersion fileVersion, File srcFile, File destFile,
351 String containerType)
352 throws Exception {
353
354 if (hasPreview(fileVersion, containerType)) {
355 return;
356 }
357
358 StopWatch stopWatch = new StopWatch();
359
360 stopWatch.start();
361
362 try {
363 if (PropsValues.DL_FILE_ENTRY_PREVIEW_FORK_PROCESS_ENABLED) {
364 ProcessCallable<String> processCallable =
365 new LiferayAudioProcessCallable(
366 ServerDetector.getServerId(),
367 PropsUtil.get(PropsKeys.LIFERAY_HOME),
368 Log4JUtil.getCustomLogSettings(), srcFile, destFile,
369 containerType,
370 PropsUtil.getProperties(
371 PropsKeys.DL_FILE_ENTRY_PREVIEW_AUDIO, false));
372
373 ProcessChannel<String> processChannel =
374 ProcessExecutorUtil.execute(
375 ClassPathUtil.getPortalProcessConfig(),
376 processCallable);
377
378 Future<String> future =
379 processChannel.getProcessNoticeableFuture();
380
381 String processIdentity = String.valueOf(
382 fileVersion.getFileVersionId());
383
384 futures.put(processIdentity, future);
385
386 future.get();
387 }
388 else {
389 LiferayConverter liferayConverter = new LiferayAudioConverter(
390 srcFile.getCanonicalPath(), destFile.getCanonicalPath(),
391 containerType,
392 PropsUtil.getProperties(
393 PropsKeys.DL_FILE_ENTRY_PREVIEW_AUDIO, false));
394
395 liferayConverter.convert();
396 }
397 }
398 catch (CancellationException ce) {
399 if (_log.isInfoEnabled()) {
400 _log.info(
401 "Cancellation received for " +
402 fileVersion.getFileVersionId() + " " +
403 fileVersion.getTitle());
404 }
405 }
406 catch (Exception e) {
407 _log.error(e, e);
408 }
409
410 addFileToStore(
411 fileVersion.getCompanyId(), PREVIEW_PATH,
412 getPreviewFilePath(fileVersion, containerType), destFile);
413
414 if (_log.isInfoEnabled()) {
415 _log.info(
416 "Xuggler generated a " + containerType + " preview audio for " +
417 fileVersion.getFileVersionId() + " in " +
418 stopWatch.getTime() + "ms");
419 }
420 }
421
422 private void _generateAudioXuggler(
423 FileVersion fileVersion, File srcFile, File[] destFiles) {
424
425 try {
426 for (int i = 0; i < destFiles.length; i++) {
427 _generateAudioXuggler(
428 fileVersion, srcFile, destFiles[i], _PREVIEW_TYPES[i]);
429 }
430 }
431 catch (Exception e) {
432 _log.error(e, e);
433 }
434 }
435
436 private boolean _hasAudio(FileVersion fileVersion) throws Exception {
437 if (!isSupported(fileVersion)) {
438 return false;
439 }
440
441 return hasPreviews(fileVersion);
442 }
443
444 private void _queueGeneration(
445 FileVersion sourceFileVersion, FileVersion destinationFileVersion) {
446
447 if (_fileVersionIds.contains(
448 destinationFileVersion.getFileVersionId()) ||
449 !isSupported(destinationFileVersion)) {
450
451 return;
452 }
453
454 _fileVersionIds.add(destinationFileVersion.getFileVersionId());
455
456 sendGenerationMessage(
457 DestinationNames.DOCUMENT_LIBRARY_AUDIO_PROCESSOR,
458 sourceFileVersion, destinationFileVersion);
459 }
460
461 private static final String[] _PREVIEW_TYPES =
462 PropsValues.DL_FILE_ENTRY_PREVIEW_AUDIO_CONTAINERS;
463
464 private static final Log _log = LogFactoryUtil.getLog(
465 AudioProcessorImpl.class);
466
467 private final Set<String> _audioMimeTypes = SetUtil.fromArray(
468 PropsValues.DL_FILE_ENTRY_PREVIEW_AUDIO_MIME_TYPES);
469 private final List<Long> _fileVersionIds = new Vector<>();
470
471 private static class LiferayAudioProcessCallable
472 implements ProcessCallable<String> {
473
474 public LiferayAudioProcessCallable(
475 String serverId, String liferayHome,
476 Map<String, String> customLogSettings, File inputFile,
477 File outputFile, String audioContainer,
478 Properties audioProperties) {
479
480 _serverId = serverId;
481 _liferayHome = liferayHome;
482 _customLogSettings = customLogSettings;
483 _inputFile = inputFile;
484 _outputFile = outputFile;
485 _audioContainer = audioContainer;
486 _audioProperties = audioProperties;
487 }
488
489 @Override
490 public String call() throws ProcessException {
491 XugglerAutoInstallHelper.installNativeLibraries();
492
493 Properties systemProperties = System.getProperties();
494
495 SystemEnv.setProperties(systemProperties);
496
497 Class<?> clazz = getClass();
498
499 ClassLoader classLoader = clazz.getClassLoader();
500
501 Log4JUtil.initLog4J(
502 _serverId, _liferayHome, classLoader, new Log4jLogFactoryImpl(),
503 _customLogSettings);
504
505 try {
506 LiferayConverter liferayConverter = new LiferayAudioConverter(
507 _inputFile.getCanonicalPath(),
508 _outputFile.getCanonicalPath(), _audioContainer,
509 _audioProperties);
510
511 liferayConverter.convert();
512 }
513 catch (Exception e) {
514 throw new ProcessException(e);
515 }
516
517 return StringPool.BLANK;
518 }
519
520 private static final long serialVersionUID = 1L;
521
522 private final String _audioContainer;
523 private final Properties _audioProperties;
524 private final Map<String, String> _customLogSettings;
525
526 @InputResource
527 private final File _inputFile;
528
529 private final String _liferayHome;
530
531 @OutputResource
532 private final File _outputFile;
533
534 private final String _serverId;
535
536 }
537
538 }