@@ -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,58 +74,72 @@ 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+ }
129+
130+ $ this ->cleanTmpFiles ();
96131
97- // Attempt still image grabs from selected timestamps
98- foreach ($ timeAttempts as $ timeStamp ) {
99- $ result = $ this ->generateThumbNail ($ maxX , $ maxY , $ absPath , $ timeStamp );
100132 if ($ result !== null ) {
101133 break ;
102134 }
103135 }
104-
105- $ this ->cleanTmpFiles ();
106-
107- if ($ result !== null ) {
108- break ;
136+ } else {
137+ // HTTP/HTTPS direct connect is available so pass the URL directly to ffmpeg
138+ foreach ($ timeAttempts as $ timeStamp ) {
139+ $ result = $ this ->generateThumbNail ($ maxX , $ maxY , $ connectDirect , $ timeStamp );
140+ if ($ result !== null ) {
141+ break ;
142+ }
109143 }
110144 }
111145 return $ result ;
0 commit comments