@@ -42,6 +42,26 @@ public function isAvailable(FileInfo $file): bool {
4242 return is_string ($ this ->binary );
4343 }
4444
45+ private function connectDirect (File $ file ): string |false {
46+ if (stream_get_meta_data ($ file ->fopen ('r ' ))['seekable ' ] !== true ) {
47+ return false ;
48+ }
49+
50+ // Checks for availability to access the video file directly via HTTP/HTTPS.
51+ // Returns a string containing URL if available. Only implemented and tested
52+ // with Amazon S3 currently. In all other cases, return false. ffmpeg
53+ // supports other protocols so this function may expand in the future.
54+ $ gddValues = $ file ->getStorage ()->getDirectDownload ($ file ->getName ());
55+
56+ if (is_array ($ gddValues )) {
57+ if (array_key_exists ('url ' , $ gddValues ) && array_key_exists ('presigned ' , $ gddValues )) {
58+ $ directUrl = (str_starts_with ($ gddValues ['url ' ], 'http ' ) && ($ gddValues ['presigned ' ] === true )) ? $ gddValues ['url ' ] : false ;
59+ return $ directUrl ;
60+ }
61+ }
62+ return false ;
63+ }
64+
4565 /**
4666 * {@inheritDoc}
4767 */
@@ -54,74 +74,87 @@ public function getThumbnail(File $file, int $maxX, int $maxY): ?IImage {
5474
5575 $ result = null ;
5676
77+ $ connectDirect = $ this ->connectDirect ($ file );
78+
5779 // Timestamps to make attempts to generate a still
5880 $ timeAttempts = [5 , 1 , 0 ];
5981
60- // By default, download $sizeAttempts from the file along with
61- // the 'moov' atom.
62- // Example bitrates in the higher range:
63- // 4K HDR H265 60 FPS = 75 Mbps = 9 MB per second needed for a still
64- // 1080p H265 30 FPS = 10 Mbps = 1.25 MB per second needed for a still
65- // 1080p H264 30 FPS = 16 Mbps = 2 MB per second needed for a still
66- $ sizeAttempts = [1024 * 1024 * 10 ];
67-
68- if ($ this ->useTempFile ($ file )) {
69- if ($ file ->getStorage ()->isLocal ()) {
70- // Temp file required but file is local, so retrieve $sizeAttempt bytes first,
71- // and if it doesn't work, retrieve the entire file.
72- $ sizeAttempts [] = null ;
82+ // If HTTP/HTTPS direct connect is not available or if the file is encrypted,
83+ // process normally
84+ if (($ connectDirect === false ) || $ file ->isEncrypted ()) {
85+ // By default, download $sizeAttempts from the file along with
86+ // the 'moov' atom.
87+ // Example bitrates in the higher range:
88+ // 4K HDR H265 60 FPS = 75 Mbps = 9 MB per second needed for a still
89+ // 1080p H265 30 FPS = 10 Mbps = 1.25 MB per second needed for a still
90+ // 1080p H264 30 FPS = 16 Mbps = 2 MB per second needed for a still
91+ $ sizeAttempts = [1024 * 1024 * 10 ];
92+
93+ if ($ this ->useTempFile ($ file )) {
94+ if ($ file ->getStorage ()->isLocal ()) {
95+ // Temp file required but file is local, so retrieve $sizeAttempt bytes first,
96+ // and if it doesn't work, retrieve the entire file.
97+ $ sizeAttempts [] = null ;
98+ }
99+ } else {
100+ // Temp file is not required and file is local so retrieve entire file.
101+ $ sizeAttempts = [null ];
73102 }
74- } else {
75- // Temp file is not required and file is local so retrieve entire file.
76- $ sizeAttempts = [null ];
77- }
78103
79- foreach ($ sizeAttempts as $ size ) {
80- $ absPath = false ;
81- // File is remote, generate a sparse file
82- if (!$ file ->getStorage ()->isLocal ()) {
83- $ absPath = $ this ->getSparseFile ($ file , $ size );
84- }
85- // Defaults to existing routine if generating sparse file fails
86- if ($ absPath === false ) {
87- $ absPath = $ this ->getLocalFile ($ file , $ size );
88- }
89- if ($ absPath === false ) {
90- Server::get (LoggerInterface::class)->error (
91- 'Failed to get local file to generate thumbnail for: ' . $ file ->getPath (),
92- ['app ' => 'core ' ]
93- );
94- return null ;
95- }
104+ foreach ($ sizeAttempts as $ size ) {
105+ $ absPath = false ;
106+ // File is remote, generate a sparse file
107+ if (!$ file ->getStorage ()->isLocal ()) {
108+ $ absPath = $ this ->getSparseFile ($ file , $ size );
109+ }
110+ // Defaults to existing routine if generating sparse file fails
111+ if ($ absPath === false ) {
112+ $ absPath = $ this ->getLocalFile ($ file , $ size );
113+ }
114+ if ($ absPath === false ) {
115+ Server::get (LoggerInterface::class)->error (
116+ 'Failed to get local file to generate thumbnail for: ' . $ file ->getPath (),
117+ ['app ' => 'core ' ]
118+ );
119+ return null ;
120+ }
121+
122+ // Attempt still image grabs from selected timestamps
123+ foreach ($ timeAttempts as $ timeStamp ) {
124+ $ result = $ this ->generateThumbNail ($ maxX , $ maxY , $ absPath , $ timeStamp );
125+ if ($ result !== null ) {
126+ break ;
127+ }
128+ Server::get (LoggerInterface::class)->debug (
129+ 'Movie preview generation attempt failed '
130+ . ', file= ' . $ file ->getPath ()
131+ . ', time= ' . $ timeStamp
132+ . ', size= ' . ($ size ?? 'entire file ' ),
133+ ['app ' => 'core ' ]
134+ );
135+ }
136+
137+ $ this ->cleanTmpFiles ();
96138
97- // Attempt still image grabs from selected timestamps
98- foreach ($ timeAttempts as $ timeStamp ) {
99- $ result = $ this ->generateThumbNail ($ maxX , $ maxY , $ absPath , $ timeStamp );
100139 if ($ result !== null ) {
140+ Server::get (LoggerInterface::class)->debug (
141+ 'Movie preview generation attempt success '
142+ . ', file= ' . $ file ->getPath ()
143+ . ', time= ' . $ timeStamp
144+ . ', size= ' . ($ size ?? 'entire file ' ),
145+ ['app ' => 'core ' ]
146+ );
101147 break ;
102148 }
103- Server::get (LoggerInterface::class)->debug (
104- 'Movie preview generation attempt failed '
105- . ', file= ' . $ file ->getPath ()
106- . ', time= ' . $ timeStamp
107- . ', size= ' . ($ size ?? 'entire file ' ),
108- ['app ' => 'core ' ]
109- );
110149 }
111-
112- $ this ->cleanTmpFiles ();
113-
114- if ($ result !== null ) {
115- Server::get (LoggerInterface::class)->debug (
116- 'Movie preview generation attempt success '
117- . ', file= ' . $ file ->getPath ()
118- . ', time= ' . $ timeStamp
119- . ', size= ' . ($ size ?? 'entire file ' ),
120- ['app ' => 'core ' ]
121- );
122- break ;
150+ } else {
151+ // HTTP/HTTPS direct connect is available so pass the URL directly to ffmpeg
152+ foreach ($ timeAttempts as $ timeStamp ) {
153+ $ result = $ this ->generateThumbNail ($ maxX , $ maxY , $ connectDirect , $ timeStamp );
154+ if ($ result !== null ) {
155+ break ;
156+ }
123157 }
124-
125158 }
126159 if ($ result === null ) {
127160 Server::get (LoggerInterface::class)->error (
@@ -245,7 +278,8 @@ private function getSparseFile(File $file, int $size): string|false {
245278
246279 private function useHdr (string $ absPath ): bool {
247280 // load ffprobe path from configuration, otherwise generate binary path using ffmpeg binary path
248- $ ffprobe_binary = $ this ->config ->getSystemValue ('preview_ffprobe_path ' , null ) ?? (pathinfo ($ this ->binary , PATHINFO_DIRNAME ) . '/ffprobe ' );
281+ $ ffprobe_binary = $ this ->config ->getSystemValue ('preview_ffprobe_path ' , null )
282+ ?? (pathinfo ($ this ->binary , PATHINFO_DIRNAME ) . '/ffprobe ' );
249283 // run ffprobe on the video file to get value of "color_transfer"
250284 $ test_hdr_cmd = [$ ffprobe_binary ,'-select_streams ' , 'v:0 ' ,
251285 '-show_entries ' , 'stream=color_transfer ' ,
@@ -326,11 +360,11 @@ private function generateThumbNail(int $maxX, int $maxY, string $absPath, int $s
326360 if ($ image ->valid ()) {
327361 unlink ($ tmpPath );
328362 $ image ->scaleDownToFit ($ maxX , $ maxY );
363+
329364 return $ image ;
330365 }
331366 }
332367
333-
334368 unlink ($ tmpPath );
335369 return null ;
336370 }
0 commit comments