@@ -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