]>
Commit | Line | Data |
---|---|---|
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 } from '../types/models' | |
8 | import { MThumbnail } from '../types/models/video/thumbnail' | |
9 | import { MVideoPlaylistThumbnail } from '../types/models/video/video-playlist' | |
10 | import { downloadImageFromWorker } from './local-actor' | |
11 | import { VideoPathManager } from './video-path-manager' | |
12 | import { processImageFromWorker } from './worker/parent-process' | |
13 | ||
14 | type ImageSize = { height?: number, width?: number } | |
15 | ||
16 | function updatePlaylistMiniatureFromExisting (options: { | |
17 | inputPath: string | |
18 | playlist: MVideoPlaylistThumbnail | |
19 | automaticallyGenerated: boolean | |
20 | keepOriginal?: boolean // default to false | |
21 | size?: ImageSize | |
22 | }) { | |
23 | const { inputPath, playlist, automaticallyGenerated, keepOriginal = false, size } = options | |
24 | const { filename, outputPath, height, width, existingThumbnail } = buildMetadataFromPlaylist(playlist, size) | |
25 | const type = ThumbnailType.MINIATURE | |
26 | ||
27 | const thumbnailCreator = () => { | |
28 | return processImageFromWorker({ path: inputPath, destination: outputPath, newSize: { width, height }, keepOriginal }) | |
29 | } | |
30 | ||
31 | return updateThumbnailFromFunction({ | |
32 | thumbnailCreator, | |
33 | filename, | |
34 | height, | |
35 | width, | |
36 | type, | |
37 | automaticallyGenerated, | |
38 | existingThumbnail | |
39 | }) | |
40 | } | |
41 | ||
42 | function updatePlaylistMiniatureFromUrl (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 }) | |
61 | } | |
62 | ||
63 | function updateVideoMiniatureFromUrl (options: { | |
64 | downloadUrl: string | |
65 | video: MVideoThumbnail | |
66 | type: ThumbnailType | |
67 | size?: ImageSize | |
68 | }) { | |
69 | const { downloadUrl, video, type, size } = options | |
70 | const { filename: updatedFilename, basePath, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size) | |
71 | ||
72 | // Only save the file URL if it is a remote video | |
73 | const fileUrl = video.isOwned() | |
74 | ? null | |
75 | : downloadUrl | |
76 | ||
77 | const thumbnailUrlChanged = hasThumbnailUrlChanged(existingThumbnail, downloadUrl, video) | |
78 | ||
79 | // Do not change the thumbnail filename if the file did not change | |
80 | const filename = thumbnailUrlChanged | |
81 | ? updatedFilename | |
82 | : existingThumbnail.filename | |
83 | ||
84 | const thumbnailCreator = () => { | |
85 | if (thumbnailUrlChanged) { | |
86 | return downloadImageFromWorker({ url: downloadUrl, destDir: basePath, destName: filename, size: { width, height } }) | |
87 | } | |
88 | ||
89 | return Promise.resolve() | |
90 | } | |
91 | ||
92 | return updateThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail, fileUrl }) | |
93 | } | |
94 | ||
95 | function updateVideoMiniatureFromExisting (options: { | |
96 | inputPath: string | |
97 | video: MVideoThumbnail | |
98 | type: ThumbnailType | |
99 | automaticallyGenerated: boolean | |
100 | size?: ImageSize | |
101 | keepOriginal?: boolean // default to false | |
102 | }) { | |
103 | const { inputPath, video, type, automaticallyGenerated, size, keepOriginal = false } = options | |
104 | ||
105 | const { filename, outputPath, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size) | |
106 | ||
107 | const thumbnailCreator = () => { | |
108 | return processImageFromWorker({ path: inputPath, destination: outputPath, newSize: { width, height }, keepOriginal }) | |
109 | } | |
110 | ||
111 | return updateThumbnailFromFunction({ | |
112 | thumbnailCreator, | |
113 | filename, | |
114 | height, | |
115 | width, | |
116 | type, | |
117 | automaticallyGenerated, | |
118 | existingThumbnail | |
119 | }) | |
120 | } | |
121 | ||
122 | function generateVideoMiniature (options: { | |
123 | video: MVideoThumbnail | |
124 | videoFile: MVideoFile | |
125 | type: ThumbnailType | |
126 | }) { | |
127 | const { video, videoFile, type } = options | |
128 | ||
129 | return VideoPathManager.Instance.makeAvailableVideoFile(videoFile.withVideoOrPlaylist(video), input => { | |
130 | const { filename, basePath, height, width, existingThumbnail, outputPath } = buildMetadataFromVideo(video, type) | |
131 | ||
132 | const thumbnailCreator = videoFile.isAudio() | |
133 | ? () => processImageFromWorker({ | |
134 | path: ASSETS_PATH.DEFAULT_AUDIO_BACKGROUND, | |
135 | destination: outputPath, | |
136 | newSize: { width, height }, | |
137 | keepOriginal: true | |
138 | }) | |
139 | : () => generateImageFromVideoFile({ | |
140 | fromPath: input, | |
141 | folder: basePath, | |
142 | imageName: filename, | |
143 | size: { height, width } | |
144 | }) | |
145 | ||
146 | return updateThumbnailFromFunction({ | |
147 | thumbnailCreator, | |
148 | filename, | |
149 | height, | |
150 | width, | |
151 | type, | |
152 | automaticallyGenerated: true, | |
153 | existingThumbnail | |
154 | }) | |
155 | }) | |
156 | } | |
157 | ||
158 | function updatePlaceholderThumbnail (options: { | |
159 | fileUrl: string | |
160 | video: MVideoThumbnail | |
161 | type: ThumbnailType | |
162 | size: ImageSize | |
163 | }) { | |
164 | const { fileUrl, video, type, size } = options | |
165 | const { filename: updatedFilename, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size) | |
166 | ||
167 | const thumbnailUrlChanged = hasThumbnailUrlChanged(existingThumbnail, fileUrl, video) | |
168 | ||
169 | const thumbnail = existingThumbnail || new ThumbnailModel() | |
170 | ||
171 | // Do not change the thumbnail filename if the file did not change | |
172 | const filename = thumbnailUrlChanged | |
173 | ? updatedFilename | |
174 | : existingThumbnail.filename | |
175 | ||
176 | thumbnail.filename = filename | |
177 | thumbnail.height = height | |
178 | thumbnail.width = width | |
179 | thumbnail.type = type | |
180 | thumbnail.fileUrl = fileUrl | |
181 | ||
182 | return thumbnail | |
183 | } | |
184 | ||
185 | // --------------------------------------------------------------------------- | |
186 | ||
187 | export { | |
188 | generateVideoMiniature, | |
189 | updateVideoMiniatureFromUrl, | |
190 | updateVideoMiniatureFromExisting, | |
191 | updatePlaceholderThumbnail, | |
192 | updatePlaylistMiniatureFromUrl, | |
193 | updatePlaylistMiniatureFromExisting | |
194 | } | |
195 | ||
196 | function hasThumbnailUrlChanged (existingThumbnail: MThumbnail, downloadUrl: string, video: MVideoUUID) { | |
197 | const existingUrl = existingThumbnail | |
198 | ? existingThumbnail.fileUrl | |
199 | : null | |
200 | ||
201 | // If the thumbnail URL did not change and has a unique filename (introduced in 3.1), avoid thumbnail processing | |
202 | return !existingUrl || existingUrl !== downloadUrl || downloadUrl.endsWith(`${video.uuid}.jpg`) | |
203 | } | |
204 | ||
205 | function buildMetadataFromPlaylist (playlist: MVideoPlaylistThumbnail, size: ImageSize) { | |
206 | const filename = playlist.generateThumbnailName() | |
207 | const basePath = CONFIG.STORAGE.THUMBNAILS_DIR | |
208 | ||
209 | return { | |
210 | filename, | |
211 | basePath, | |
212 | existingThumbnail: playlist.Thumbnail, | |
213 | outputPath: join(basePath, filename), | |
214 | height: size ? size.height : THUMBNAILS_SIZE.height, | |
215 | width: size ? size.width : THUMBNAILS_SIZE.width | |
216 | } | |
217 | } | |
218 | ||
219 | function buildMetadataFromVideo (video: MVideoThumbnail, type: ThumbnailType, size?: ImageSize) { | |
220 | const existingThumbnail = Array.isArray(video.Thumbnails) | |
221 | ? video.Thumbnails.find(t => t.type === type) | |
222 | : undefined | |
223 | ||
224 | if (type === ThumbnailType.MINIATURE) { | |
225 | const filename = generateImageFilename() | |
226 | const basePath = CONFIG.STORAGE.THUMBNAILS_DIR | |
227 | ||
228 | return { | |
229 | filename, | |
230 | basePath, | |
231 | existingThumbnail, | |
232 | outputPath: join(basePath, filename), | |
233 | height: size ? size.height : THUMBNAILS_SIZE.height, | |
234 | width: size ? size.width : THUMBNAILS_SIZE.width | |
235 | } | |
236 | } | |
237 | ||
238 | if (type === ThumbnailType.PREVIEW) { | |
239 | const filename = generateImageFilename() | |
240 | const basePath = CONFIG.STORAGE.PREVIEWS_DIR | |
241 | ||
242 | return { | |
243 | filename, | |
244 | basePath, | |
245 | existingThumbnail, | |
246 | outputPath: join(basePath, filename), | |
247 | height: size ? size.height : PREVIEWS_SIZE.height, | |
248 | width: size ? size.width : PREVIEWS_SIZE.width | |
249 | } | |
250 | } | |
251 | ||
252 | return undefined | |
253 | } | |
254 | ||
255 | async function updateThumbnailFromFunction (parameters: { | |
256 | thumbnailCreator: () => Promise<any> | |
257 | filename: string | |
258 | height: number | |
259 | width: number | |
260 | type: ThumbnailType | |
261 | automaticallyGenerated?: boolean | |
262 | fileUrl?: string | |
263 | existingThumbnail?: MThumbnail | |
264 | }) { | |
265 | const { | |
266 | thumbnailCreator, | |
267 | filename, | |
268 | width, | |
269 | height, | |
270 | type, | |
271 | existingThumbnail, | |
272 | automaticallyGenerated = null, | |
273 | fileUrl = null | |
274 | } = parameters | |
275 | ||
276 | const oldFilename = existingThumbnail && existingThumbnail.filename !== filename | |
277 | ? existingThumbnail.filename | |
278 | : undefined | |
279 | ||
280 | const thumbnail: MThumbnail = existingThumbnail || new ThumbnailModel() | |
281 | ||
282 | thumbnail.filename = filename | |
283 | thumbnail.height = height | |
284 | thumbnail.width = width | |
285 | thumbnail.type = type | |
286 | thumbnail.fileUrl = fileUrl | |
287 | thumbnail.automaticallyGenerated = automaticallyGenerated | |
288 | ||
289 | if (oldFilename) thumbnail.previousThumbnailFilename = oldFilename | |
290 | ||
291 | await thumbnailCreator() | |
292 | ||
293 | return thumbnail | |
294 | } |