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