Skip to content

Commit be54fec

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

File tree

1 file changed

+49
-14
lines changed

1 file changed

+49
-14
lines changed

lib/private/Preview/Movie.php

Lines changed: 49 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,8 @@ 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) ?? $this->generateThumbNail($maxX, $maxY, $absPath, 0);
8891

8992
$this->cleanTmpFiles();
9093

@@ -95,6 +98,38 @@ public function getThumbnail(File $file, int $maxX, int $maxY): ?IImage {
9598

9699
return $result;
97100
}
101+
102+
private function getSparseFile(File $file, int $size): string|false {
103+
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+
115+
// If filesize is small (i.e. <= $size + 5 MB) then just download entire file
116+
if (($size + 5242880) >= $file->getSize()) {
117+
stream_copy_to_stream($content, $sparseFile);
118+
} else {
119+
// Create a sparse file of equal size to original video
120+
ftruncate($sparseFile, $file->getSize());
121+
// Copy $size bytes to front end
122+
fseek($sparseFile, 0);
123+
stream_copy_to_stream($content, $sparseFile, $size, 0);
124+
// Copy 5 MB to tail end of file
125+
fseek($sparseFile, ($file->getSize() - 5242880));
126+
stream_copy_to_stream($content, $sparseFile, 5242880, ($file->getSize() - 5242880));
127+
}
128+
129+
fclose($sparseFile);
130+
fclose($content);
131+
return $absPath;
132+
}
98133

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

0 commit comments

Comments
 (0)