Skip to content

Commit fa76668

Browse files
committed
feat(previews): previews for large remote files without full file download
Signed-off-by: invario <67800603+invario@users.noreply.github.com>
1 parent 8210e12 commit fa76668

File tree

1 file changed

+45
-14
lines changed

1 file changed

+45
-14
lines changed

lib/private/Preview/Movie.php

Lines changed: 45 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -54,22 +54,30 @@ public function getThumbnail(File $file, int $maxX, int $maxY): ?IImage {
5454

5555
$result = null;
5656
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-
// Unless the file is not local (e.g. S3) as we do not want to download the whole (e.g. 37Gb) file
57+
// Try downloading 10 MB first, as it's likely that the first needed frames are present
58+
// there along with the 'moov atom" (used in MP4/MOV files). In some cases this doesn't
59+
// work, (e.g. the 'moov atom' is at the end, or the videos is high bitrate)
6160
if ($file->getStorage()->isLocal()) {
62-
$sizeAttempts = [5242880, null];
61+
// File is local, make two attempts: 10 MB, then the entire file
62+
$sizeAttempts = [10485760, null];
6363
} else {
64-
$sizeAttempts = [5242880];
64+
// File is remote, make one attempt: 10 MB will be downloaded from the file along with
65+
// 5 MB from the end with filler (null zeroes) in the middle.
66+
$sizeAttempts = [10485760];
6567
}
6668
} else {
6769
// size is irrelevant, only attempt once
6870
$sizeAttempts = [null];
6971
}
7072

7173
foreach ($sizeAttempts as $size) {
72-
$absPath = $this->getLocalFile($file, $size);
74+
if ($file->getStorage()->isLocal()) {
75+
// File is local
76+
$absPath = $this->getLocalFile($file, $size);
77+
} else {
78+
// File is remote, generate a sparse file
79+
$absPath = $this->getSparseFile($file, $size);
80+
}
7381
if ($absPath === false) {
7482
Server::get(LoggerInterface::class)->error(
7583
'Failed to get local file to generate thumbnail for: ' . $file->getPath(),
@@ -78,13 +86,9 @@ public function getThumbnail(File $file, int $maxX, int $maxY): ?IImage {
7886
return null;
7987
}
8088

81-
$result = $this->generateThumbNail($maxX, $maxY, $absPath, 5);
82-
if ($result === null) {
83-
$result = $this->generateThumbNail($maxX, $maxY, $absPath, 1);
84-
if ($result === null) {
85-
$result = $this->generateThumbNail($maxX, $maxY, $absPath, 0);
86-
}
87-
}
89+
// Attempt still image grab from 1 second and 0 second timestamp
90+
$result = ($this->generateThumbNail($maxX, $maxY, $absPath, 1)) ??
91+
($this->generateThumbNail($maxX, $maxY, $absPath, 0));
8892

8993
$this->cleanTmpFiles();
9094

@@ -95,6 +99,33 @@ public function getThumbnail(File $file, int $maxX, int $maxY): ?IImage {
9599

96100
return $result;
97101
}
102+
103+
private function getSparseFile(File $file, int $size): string|false {
104+
$absPath = Server::get(ITempManager::class)->getTemporaryFile();
105+
if ($absPath === false) {
106+
Server::get(LoggerInterface::class)->error(
107+
'Failed to get sparse file to generate thumbnail for: ' . $file->getPath(),
108+
['app' => 'core']
109+
);
110+
return false;
111+
}
112+
$sparseFile = fopen($absPath,'w');
113+
$content = $file->fopen('r');
114+
$zeroes = fopen('/dev/zero','r');
115+
116+
// If filesize is small (i.e. <= $size + 5 MB) then just download entire file
117+
if (($size+5242880) >= $file->getSize()) {
118+
stream_copy_to_stream($content, $sparseFile);
119+
} else {
120+
stream_copy_to_stream($content, $sparseFile, $size);
121+
stream_copy_to_stream($zeroes, $sparseFile, ($file->getSize()-($size+5242880)));
122+
stream_copy_to_stream($content, $sparseFile, 5242880, ($file->getSize()-5242880));
123+
}
124+
fclose($zeroes);
125+
fclose($sparseFile);
126+
fclose($content);
127+
return $absPath;
128+
}
98129

99130
private function useHdr(string $absPath): bool {
100131
// load ffprobe path from configuration, otherwise generate binary path using ffmpeg binary path

0 commit comments

Comments
 (0)