diff options
Diffstat (limited to 'server/lib/thumbnail.ts')
-rw-r--r-- | server/lib/thumbnail.ts | 327 |
1 files changed, 0 insertions, 327 deletions
diff --git a/server/lib/thumbnail.ts b/server/lib/thumbnail.ts deleted file mode 100644 index 0b98da14f..000000000 --- a/server/lib/thumbnail.ts +++ /dev/null | |||
@@ -1,327 +0,0 @@ | |||
1 | import { join } from 'path' | ||
2 | import { ThumbnailType } from '@shared/models' | ||
3 | import { generateImageFilename, generateImageFromVideoFile } from '../helpers/image-utils' | ||
4 | import { CONFIG } from '../initializers/config' | ||
5 | import { ASSETS_PATH, PREVIEWS_SIZE, THUMBNAILS_SIZE } from '../initializers/constants' | ||
6 | import { ThumbnailModel } from '../models/video/thumbnail' | ||
7 | import { MVideoFile, MVideoThumbnail, MVideoUUID, MVideoWithAllFiles } from '../types/models' | ||
8 | import { MThumbnail } from '../types/models/video/thumbnail' | ||
9 | import { MVideoPlaylistThumbnail } from '../types/models/video/video-playlist' | ||
10 | import { VideoPathManager } from './video-path-manager' | ||
11 | import { downloadImageFromWorker, processImageFromWorker } from './worker/parent-process' | ||
12 | |||
13 | type ImageSize = { height?: number, width?: number } | ||
14 | |||
15 | function updateLocalPlaylistMiniatureFromExisting (options: { | ||
16 | inputPath: string | ||
17 | playlist: MVideoPlaylistThumbnail | ||
18 | automaticallyGenerated: boolean | ||
19 | keepOriginal?: boolean // default to false | ||
20 | size?: ImageSize | ||
21 | }) { | ||
22 | const { inputPath, playlist, automaticallyGenerated, keepOriginal = false, size } = options | ||
23 | const { filename, outputPath, height, width, existingThumbnail } = buildMetadataFromPlaylist(playlist, size) | ||
24 | const type = ThumbnailType.MINIATURE | ||
25 | |||
26 | const thumbnailCreator = () => { | ||
27 | return processImageFromWorker({ path: inputPath, destination: outputPath, newSize: { width, height }, keepOriginal }) | ||
28 | } | ||
29 | |||
30 | return updateThumbnailFromFunction({ | ||
31 | thumbnailCreator, | ||
32 | filename, | ||
33 | height, | ||
34 | width, | ||
35 | type, | ||
36 | automaticallyGenerated, | ||
37 | onDisk: true, | ||
38 | existingThumbnail | ||
39 | }) | ||
40 | } | ||
41 | |||
42 | function updateRemotePlaylistMiniatureFromUrl (options: { | ||
43 | downloadUrl: string | ||
44 | playlist: MVideoPlaylistThumbnail | ||
45 | size?: ImageSize | ||
46 | }) { | ||
47 | const { downloadUrl, playlist, size } = options | ||
48 | const { filename, basePath, height, width, existingThumbnail } = buildMetadataFromPlaylist(playlist, size) | ||
49 | const type = ThumbnailType.MINIATURE | ||
50 | |||
51 | // Only save the file URL if it is a remote playlist | ||
52 | const fileUrl = playlist.isOwned() | ||
53 | ? null | ||
54 | : downloadUrl | ||
55 | |||
56 | const thumbnailCreator = () => { | ||
57 | return downloadImageFromWorker({ url: downloadUrl, destDir: basePath, destName: filename, size: { width, height } }) | ||
58 | } | ||
59 | |||
60 | return updateThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail, fileUrl, onDisk: true }) | ||
61 | } | ||
62 | |||
63 | function updateLocalVideoMiniatureFromExisting (options: { | ||
64 | inputPath: string | ||
65 | video: MVideoThumbnail | ||
66 | type: ThumbnailType | ||
67 | automaticallyGenerated: boolean | ||
68 | size?: ImageSize | ||
69 | keepOriginal?: boolean // default to false | ||
70 | }) { | ||
71 | const { inputPath, video, type, automaticallyGenerated, size, keepOriginal = false } = options | ||
72 | |||
73 | const { filename, outputPath, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size) | ||
74 | |||
75 | const thumbnailCreator = () => { | ||
76 | return processImageFromWorker({ path: inputPath, destination: outputPath, newSize: { width, height }, keepOriginal }) | ||
77 | } | ||
78 | |||
79 | return updateThumbnailFromFunction({ | ||
80 | thumbnailCreator, | ||
81 | filename, | ||
82 | height, | ||
83 | width, | ||
84 | type, | ||
85 | automaticallyGenerated, | ||
86 | existingThumbnail, | ||
87 | onDisk: true | ||
88 | }) | ||
89 | } | ||
90 | |||
91 | function generateLocalVideoMiniature (options: { | ||
92 | video: MVideoThumbnail | ||
93 | videoFile: MVideoFile | ||
94 | type: ThumbnailType | ||
95 | }) { | ||
96 | const { video, videoFile, type } = options | ||
97 | |||
98 | return VideoPathManager.Instance.makeAvailableVideoFile(videoFile.withVideoOrPlaylist(video), input => { | ||
99 | const { filename, basePath, height, width, existingThumbnail, outputPath } = buildMetadataFromVideo(video, type) | ||
100 | |||
101 | const thumbnailCreator = videoFile.isAudio() | ||
102 | ? () => processImageFromWorker({ | ||
103 | path: ASSETS_PATH.DEFAULT_AUDIO_BACKGROUND, | ||
104 | destination: outputPath, | ||
105 | newSize: { width, height }, | ||
106 | keepOriginal: true | ||
107 | }) | ||
108 | : () => generateImageFromVideoFile({ | ||
109 | fromPath: input, | ||
110 | folder: basePath, | ||
111 | imageName: filename, | ||
112 | size: { height, width } | ||
113 | }) | ||
114 | |||
115 | return updateThumbnailFromFunction({ | ||
116 | thumbnailCreator, | ||
117 | filename, | ||
118 | height, | ||
119 | width, | ||
120 | type, | ||
121 | automaticallyGenerated: true, | ||
122 | onDisk: true, | ||
123 | existingThumbnail | ||
124 | }) | ||
125 | }) | ||
126 | } | ||
127 | |||
128 | // --------------------------------------------------------------------------- | ||
129 | |||
130 | function updateLocalVideoMiniatureFromUrl (options: { | ||
131 | downloadUrl: string | ||
132 | video: MVideoThumbnail | ||
133 | type: ThumbnailType | ||
134 | size?: ImageSize | ||
135 | }) { | ||
136 | const { downloadUrl, video, type, size } = options | ||
137 | const { filename: updatedFilename, basePath, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size) | ||
138 | |||
139 | // Only save the file URL if it is a remote video | ||
140 | const fileUrl = video.isOwned() | ||
141 | ? null | ||
142 | : downloadUrl | ||
143 | |||
144 | const thumbnailUrlChanged = hasThumbnailUrlChanged(existingThumbnail, downloadUrl, video) | ||
145 | |||
146 | // Do not change the thumbnail filename if the file did not change | ||
147 | const filename = thumbnailUrlChanged | ||
148 | ? updatedFilename | ||
149 | : existingThumbnail.filename | ||
150 | |||
151 | const thumbnailCreator = () => { | ||
152 | if (thumbnailUrlChanged) { | ||
153 | return downloadImageFromWorker({ url: downloadUrl, destDir: basePath, destName: filename, size: { width, height } }) | ||
154 | } | ||
155 | |||
156 | return Promise.resolve() | ||
157 | } | ||
158 | |||
159 | return updateThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail, fileUrl, onDisk: true }) | ||
160 | } | ||
161 | |||
162 | function updateRemoteVideoThumbnail (options: { | ||
163 | fileUrl: string | ||
164 | video: MVideoThumbnail | ||
165 | type: ThumbnailType | ||
166 | size: ImageSize | ||
167 | onDisk: boolean | ||
168 | }) { | ||
169 | const { fileUrl, video, type, size, onDisk } = options | ||
170 | const { filename: generatedFilename, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size) | ||
171 | |||
172 | const thumbnail = existingThumbnail || new ThumbnailModel() | ||
173 | |||
174 | // Do not change the thumbnail filename if the file did not change | ||
175 | if (hasThumbnailUrlChanged(existingThumbnail, fileUrl, video)) { | ||
176 | thumbnail.filename = generatedFilename | ||
177 | } | ||
178 | |||
179 | thumbnail.height = height | ||
180 | thumbnail.width = width | ||
181 | thumbnail.type = type | ||
182 | thumbnail.fileUrl = fileUrl | ||
183 | thumbnail.onDisk = onDisk | ||
184 | |||
185 | return thumbnail | ||
186 | } | ||
187 | |||
188 | // --------------------------------------------------------------------------- | ||
189 | |||
190 | async function regenerateMiniaturesIfNeeded (video: MVideoWithAllFiles) { | ||
191 | if (video.getMiniature().automaticallyGenerated === true) { | ||
192 | const miniature = await generateLocalVideoMiniature({ | ||
193 | video, | ||
194 | videoFile: video.getMaxQualityFile(), | ||
195 | type: ThumbnailType.MINIATURE | ||
196 | }) | ||
197 | await video.addAndSaveThumbnail(miniature) | ||
198 | } | ||
199 | |||
200 | if (video.getPreview().automaticallyGenerated === true) { | ||
201 | const preview = await generateLocalVideoMiniature({ | ||
202 | video, | ||
203 | videoFile: video.getMaxQualityFile(), | ||
204 | type: ThumbnailType.PREVIEW | ||
205 | }) | ||
206 | await video.addAndSaveThumbnail(preview) | ||
207 | } | ||
208 | } | ||
209 | |||
210 | // --------------------------------------------------------------------------- | ||
211 | |||
212 | export { | ||
213 | generateLocalVideoMiniature, | ||
214 | regenerateMiniaturesIfNeeded, | ||
215 | updateLocalVideoMiniatureFromUrl, | ||
216 | updateLocalVideoMiniatureFromExisting, | ||
217 | updateRemoteVideoThumbnail, | ||
218 | updateRemotePlaylistMiniatureFromUrl, | ||
219 | updateLocalPlaylistMiniatureFromExisting | ||
220 | } | ||
221 | |||
222 | // --------------------------------------------------------------------------- | ||
223 | // Private | ||
224 | // --------------------------------------------------------------------------- | ||
225 | |||
226 | function hasThumbnailUrlChanged (existingThumbnail: MThumbnail, downloadUrl: string, video: MVideoUUID) { | ||
227 | const existingUrl = existingThumbnail | ||
228 | ? existingThumbnail.fileUrl | ||
229 | : null | ||
230 | |||
231 | // If the thumbnail URL did not change and has a unique filename (introduced in 3.1), avoid thumbnail processing | ||
232 | return !existingUrl || existingUrl !== downloadUrl || downloadUrl.endsWith(`${video.uuid}.jpg`) | ||
233 | } | ||
234 | |||
235 | function buildMetadataFromPlaylist (playlist: MVideoPlaylistThumbnail, size: ImageSize) { | ||
236 | const filename = playlist.generateThumbnailName() | ||
237 | const basePath = CONFIG.STORAGE.THUMBNAILS_DIR | ||
238 | |||
239 | return { | ||
240 | filename, | ||
241 | basePath, | ||
242 | existingThumbnail: playlist.Thumbnail, | ||
243 | outputPath: join(basePath, filename), | ||
244 | height: size ? size.height : THUMBNAILS_SIZE.height, | ||
245 | width: size ? size.width : THUMBNAILS_SIZE.width | ||
246 | } | ||
247 | } | ||
248 | |||
249 | function buildMetadataFromVideo (video: MVideoThumbnail, type: ThumbnailType, size?: ImageSize) { | ||
250 | const existingThumbnail = Array.isArray(video.Thumbnails) | ||
251 | ? video.Thumbnails.find(t => t.type === type) | ||
252 | : undefined | ||
253 | |||
254 | if (type === ThumbnailType.MINIATURE) { | ||
255 | const filename = generateImageFilename() | ||
256 | const basePath = CONFIG.STORAGE.THUMBNAILS_DIR | ||
257 | |||
258 | return { | ||
259 | filename, | ||
260 | basePath, | ||
261 | existingThumbnail, | ||
262 | outputPath: join(basePath, filename), | ||
263 | height: size ? size.height : THUMBNAILS_SIZE.height, | ||
264 | width: size ? size.width : THUMBNAILS_SIZE.width | ||
265 | } | ||
266 | } | ||
267 | |||
268 | if (type === ThumbnailType.PREVIEW) { | ||
269 | const filename = generateImageFilename() | ||
270 | const basePath = CONFIG.STORAGE.PREVIEWS_DIR | ||
271 | |||
272 | return { | ||
273 | filename, | ||
274 | basePath, | ||
275 | existingThumbnail, | ||
276 | outputPath: join(basePath, filename), | ||
277 | height: size ? size.height : PREVIEWS_SIZE.height, | ||
278 | width: size ? size.width : PREVIEWS_SIZE.width | ||
279 | } | ||
280 | } | ||
281 | |||
282 | return undefined | ||
283 | } | ||
284 | |||
285 | async function updateThumbnailFromFunction (parameters: { | ||
286 | thumbnailCreator: () => Promise<any> | ||
287 | filename: string | ||
288 | height: number | ||
289 | width: number | ||
290 | type: ThumbnailType | ||
291 | onDisk: boolean | ||
292 | automaticallyGenerated?: boolean | ||
293 | fileUrl?: string | ||
294 | existingThumbnail?: MThumbnail | ||
295 | }) { | ||
296 | const { | ||
297 | thumbnailCreator, | ||
298 | filename, | ||
299 | width, | ||
300 | height, | ||
301 | type, | ||
302 | existingThumbnail, | ||
303 | onDisk, | ||
304 | automaticallyGenerated = null, | ||
305 | fileUrl = null | ||
306 | } = parameters | ||
307 | |||
308 | const oldFilename = existingThumbnail && existingThumbnail.filename !== filename | ||
309 | ? existingThumbnail.filename | ||
310 | : undefined | ||
311 | |||
312 | const thumbnail: MThumbnail = existingThumbnail || new ThumbnailModel() | ||
313 | |||
314 | thumbnail.filename = filename | ||
315 | thumbnail.height = height | ||
316 | thumbnail.width = width | ||
317 | thumbnail.type = type | ||
318 | thumbnail.fileUrl = fileUrl | ||
319 | thumbnail.automaticallyGenerated = automaticallyGenerated | ||
320 | thumbnail.onDisk = onDisk | ||
321 | |||
322 | if (oldFilename) thumbnail.previousThumbnailFilename = oldFilename | ||
323 | |||
324 | await thumbnailCreator() | ||
325 | |||
326 | return thumbnail | ||
327 | } | ||