Movie.php 3.9 KB

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