Movie.php 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. <?php
  2. /**
  3. * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
  4. * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
  5. * SPDX-License-Identifier: AGPL-3.0-only
  6. */
  7. namespace OC\Preview;
  8. use OCP\Files\File;
  9. use OCP\Files\FileInfo;
  10. use OCP\IImage;
  11. use Psr\Log\LoggerInterface;
  12. class Movie extends ProviderV2 {
  13. /**
  14. * @deprecated 23.0.0 pass option to \OCP\Preview\ProviderV2
  15. * @var string
  16. */
  17. public static $avconvBinary;
  18. /**
  19. * @deprecated 23.0.0 pass option to \OCP\Preview\ProviderV2
  20. * @var string
  21. */
  22. public static $ffmpegBinary;
  23. /** @var string */
  24. private $binary;
  25. /**
  26. * {@inheritDoc}
  27. */
  28. public function getMimeType(): string {
  29. return '/video\/.*/';
  30. }
  31. /**
  32. * {@inheritDoc}
  33. */
  34. public function isAvailable(FileInfo $file): bool {
  35. // TODO: remove when avconv is dropped
  36. if (is_null($this->binary)) {
  37. if (isset($this->options['movieBinary'])) {
  38. $this->binary = $this->options['movieBinary'];
  39. } elseif (is_string(self::$avconvBinary)) {
  40. $this->binary = self::$avconvBinary;
  41. } elseif (is_string(self::$ffmpegBinary)) {
  42. $this->binary = self::$ffmpegBinary;
  43. }
  44. }
  45. return is_string($this->binary);
  46. }
  47. /**
  48. * {@inheritDoc}
  49. */
  50. public function getThumbnail(File $file, int $maxX, int $maxY): ?IImage {
  51. // TODO: use proc_open() and stream the source file ?
  52. if (!$this->isAvailable($file)) {
  53. return null;
  54. }
  55. $result = null;
  56. if ($this->useTempFile($file)) {
  57. // try downloading 5 MB first as it's likely that the first frames are present there
  58. // in some cases this doesn't work for example when the moov atom is at the
  59. // end of the file, so if it fails we fall back to getting the full file
  60. $sizeAttempts = [5242880, null];
  61. } else {
  62. // size is irrelevant, only attempt once
  63. $sizeAttempts = [null];
  64. }
  65. foreach ($sizeAttempts as $size) {
  66. $absPath = $this->getLocalFile($file, $size);
  67. $result = null;
  68. if (is_string($absPath)) {
  69. $result = $this->generateThumbNail($maxX, $maxY, $absPath, 5);
  70. if ($result === null) {
  71. $result = $this->generateThumbNail($maxX, $maxY, $absPath, 1);
  72. if ($result === null) {
  73. $result = $this->generateThumbNail($maxX, $maxY, $absPath, 0);
  74. }
  75. }
  76. }
  77. $this->cleanTmpFiles();
  78. if ($result !== null) {
  79. break;
  80. }
  81. }
  82. return $result;
  83. }
  84. private function generateThumbNail(int $maxX, int $maxY, string $absPath, int $second): ?IImage {
  85. $tmpPath = \OC::$server->getTempManager()->getTemporaryFile();
  86. $binaryType = substr(strrchr($this->binary, '/'), 1);
  87. if ($binaryType === 'avconv') {
  88. $cmd = [$this->binary, '-y', '-ss', (string)$second,
  89. '-i', $absPath,
  90. '-an', '-f', 'mjpeg', '-vframes', '1', '-vsync', '1',
  91. $tmpPath];
  92. } elseif ($binaryType === 'ffmpeg') {
  93. $cmd = [$this->binary, '-y', '-ss', (string)$second,
  94. '-i', $absPath,
  95. '-f', 'mjpeg', '-vframes', '1',
  96. $tmpPath];
  97. } else {
  98. // Not supported
  99. unlink($tmpPath);
  100. return null;
  101. }
  102. $proc = proc_open($cmd, [1 => ['pipe', 'w'], 2 => ['pipe', 'w']], $pipes);
  103. $returnCode = -1;
  104. $output = '';
  105. if (is_resource($proc)) {
  106. $stdout = trim(stream_get_contents($pipes[1]));
  107. $stderr = trim(stream_get_contents($pipes[2]));
  108. $returnCode = proc_close($proc);
  109. $output = $stdout . $stderr;
  110. }
  111. if ($returnCode === 0) {
  112. $image = new \OCP\Image();
  113. $image->loadFromFile($tmpPath);
  114. if ($image->valid()) {
  115. unlink($tmpPath);
  116. $image->scaleDownToFit($maxX, $maxY);
  117. return $image;
  118. }
  119. }
  120. if ($second === 0) {
  121. $logger = \OC::$server->get(LoggerInterface::class);
  122. $logger->info('Movie preview generation failed Output: {output}', ['app' => 'core', 'output' => $output]);
  123. }
  124. unlink($tmpPath);
  125. return null;
  126. }
  127. }