]>
Commit | Line | Data |
---|---|---|
a35a2279 | 1 | import { join } from 'path' |
c729caf6 | 2 | import { ThumbnailType } from '@shared/models' |
3a54605d | 3 | import { generateImageFilename, generateImageFromVideoFile } from '../helpers/image-utils' |
e8bafea3 | 4 | import { CONFIG } from '../initializers/config' |
453e83ea | 5 | import { ASSETS_PATH, PREVIEWS_SIZE, THUMBNAILS_SIZE } from '../initializers/constants' |
e8bafea3 | 6 | import { ThumbnailModel } from '../models/video/thumbnail' |
1ef447bd | 7 | import { MVideoFile, MVideoThumbnail, MVideoUUID } from '../types/models' |
26d6bf65 | 8 | import { MThumbnail } from '../types/models/video/thumbnail' |
a35a2279 | 9 | import { MVideoPlaylistThumbnail } from '../types/models/video/video-playlist' |
c53853ca | 10 | import { downloadImageFromWorker } from './local-actor' |
0305db28 | 11 | import { VideoPathManager } from './video-path-manager' |
3a54605d | 12 | import { processImageFromWorker } from './worker/parent-process' |
e8bafea3 | 13 | |
84531547 | 14 | type ImageSize = { height?: number, width?: number } |
e8bafea3 | 15 | |
91f8f8db | 16 | function updatePlaylistMiniatureFromExisting (options: { |
a35a2279 C |
17 | inputPath: string |
18 | playlist: MVideoPlaylistThumbnail | |
19 | automaticallyGenerated: boolean | |
20 | keepOriginal?: boolean // default to false | |
65af03a2 | 21 | size?: ImageSize |
a35a2279 C |
22 | }) { |
23 | const { inputPath, playlist, automaticallyGenerated, keepOriginal = false, size } = options | |
e8bafea3 | 24 | const { filename, outputPath, height, width, existingThumbnail } = buildMetadataFromPlaylist(playlist, size) |
3acc5084 | 25 | const type = ThumbnailType.MINIATURE |
e8bafea3 | 26 | |
3a54605d C |
27 | const thumbnailCreator = () => { |
28 | return processImageFromWorker({ path: inputPath, destination: outputPath, newSize: { width, height }, keepOriginal }) | |
29 | } | |
30 | ||
91f8f8db | 31 | return updateThumbnailFromFunction({ |
a35a2279 C |
32 | thumbnailCreator, |
33 | filename, | |
34 | height, | |
35 | width, | |
36 | type, | |
37 | automaticallyGenerated, | |
38 | existingThumbnail | |
39 | }) | |
e8bafea3 C |
40 | } |
41 | ||
91f8f8db | 42 | function updatePlaylistMiniatureFromUrl (options: { |
a35a2279 C |
43 | downloadUrl: string |
44 | playlist: MVideoPlaylistThumbnail | |
45 | size?: ImageSize | |
46 | }) { | |
47 | const { downloadUrl, playlist, size } = options | |
e8bafea3 | 48 | const { filename, basePath, height, width, existingThumbnail } = buildMetadataFromPlaylist(playlist, size) |
3acc5084 | 49 | const type = ThumbnailType.MINIATURE |
e8bafea3 | 50 | |
a8b1b404 C |
51 | // Only save the file URL if it is a remote playlist |
52 | const fileUrl = playlist.isOwned() | |
53 | ? null | |
54 | : downloadUrl | |
55 | ||
c53853ca C |
56 | const thumbnailCreator = () => { |
57 | return downloadImageFromWorker({ url: downloadUrl, destDir: basePath, destName: filename, size: { width, height } }) | |
58 | } | |
59 | ||
91f8f8db | 60 | return updateThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail, fileUrl }) |
e8bafea3 C |
61 | } |
62 | ||
91f8f8db | 63 | function updateVideoMiniatureFromUrl (options: { |
a35a2279 C |
64 | downloadUrl: string |
65 | video: MVideoThumbnail | |
66 | type: ThumbnailType | |
67 | size?: ImageSize | |
68 | }) { | |
69 | const { downloadUrl, video, type, size } = options | |
d9a2a031 | 70 | const { filename: updatedFilename, basePath, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size) |
e8bafea3 | 71 | |
a8b1b404 C |
72 | // Only save the file URL if it is a remote video |
73 | const fileUrl = video.isOwned() | |
74 | ? null | |
75 | : downloadUrl | |
76 | ||
1ef447bd | 77 | const thumbnailUrlChanged = hasThumbnailUrlChanged(existingThumbnail, downloadUrl, video) |
d9a2a031 C |
78 | |
79 | // Do not change the thumbnail filename if the file did not change | |
80 | const filename = thumbnailUrlChanged | |
81 | ? updatedFilename | |
82 | : existingThumbnail.filename | |
83 | ||
374b725d | 84 | const thumbnailCreator = () => { |
c53853ca C |
85 | if (thumbnailUrlChanged) { |
86 | return downloadImageFromWorker({ url: downloadUrl, destDir: basePath, destName: filename, size: { width, height } }) | |
87 | } | |
374b725d | 88 | |
d9a2a031 | 89 | return Promise.resolve() |
374b725d C |
90 | } |
91 | ||
91f8f8db | 92 | return updateThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail, fileUrl }) |
e8bafea3 C |
93 | } |
94 | ||
91f8f8db | 95 | function updateVideoMiniatureFromExisting (options: { |
1ef65f4c C |
96 | inputPath: string |
97 | video: MVideoThumbnail | |
98 | type: ThumbnailType | |
99 | automaticallyGenerated: boolean | |
65af03a2 | 100 | size?: ImageSize |
a35a2279 | 101 | keepOriginal?: boolean // default to false |
1ef65f4c | 102 | }) { |
a35a2279 | 103 | const { inputPath, video, type, automaticallyGenerated, size, keepOriginal = false } = options |
1ef65f4c | 104 | |
e8bafea3 | 105 | const { filename, outputPath, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size) |
3a54605d C |
106 | |
107 | const thumbnailCreator = () => { | |
108 | return processImageFromWorker({ path: inputPath, destination: outputPath, newSize: { width, height }, keepOriginal }) | |
109 | } | |
e8bafea3 | 110 | |
91f8f8db | 111 | return updateThumbnailFromFunction({ |
a35a2279 C |
112 | thumbnailCreator, |
113 | filename, | |
114 | height, | |
115 | width, | |
116 | type, | |
117 | automaticallyGenerated, | |
118 | existingThumbnail | |
119 | }) | |
e8bafea3 C |
120 | } |
121 | ||
a35a2279 C |
122 | function generateVideoMiniature (options: { |
123 | video: MVideoThumbnail | |
124 | videoFile: MVideoFile | |
125 | type: ThumbnailType | |
126 | }) { | |
127 | const { video, videoFile, type } = options | |
128 | ||
ad5db104 | 129 | return VideoPathManager.Instance.makeAvailableVideoFile(videoFile.withVideoOrPlaylist(video), input => { |
0305db28 JB |
130 | const { filename, basePath, height, width, existingThumbnail, outputPath } = buildMetadataFromVideo(video, type) |
131 | ||
132 | const thumbnailCreator = videoFile.isAudio() | |
3a54605d C |
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 | }) | |
0305db28 JB |
145 | |
146 | return updateThumbnailFromFunction({ | |
147 | thumbnailCreator, | |
148 | filename, | |
149 | height, | |
150 | width, | |
151 | type, | |
152 | automaticallyGenerated: true, | |
153 | existingThumbnail | |
154 | }) | |
a35a2279 | 155 | }) |
e8bafea3 C |
156 | } |
157 | ||
91f8f8db | 158 | function updatePlaceholderThumbnail (options: { |
a35a2279 C |
159 | fileUrl: string |
160 | video: MVideoThumbnail | |
161 | type: ThumbnailType | |
162 | size: ImageSize | |
163 | }) { | |
164 | const { fileUrl, video, type, size } = options | |
1ef447bd C |
165 | const { filename: updatedFilename, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size) |
166 | ||
167 | const thumbnailUrlChanged = hasThumbnailUrlChanged(existingThumbnail, fileUrl, video) | |
e8bafea3 | 168 | |
a1587156 | 169 | const thumbnail = existingThumbnail || new ThumbnailModel() |
e8bafea3 | 170 | |
1ef447bd C |
171 | // Do not change the thumbnail filename if the file did not change |
172 | const filename = thumbnailUrlChanged | |
173 | ? updatedFilename | |
174 | : existingThumbnail.filename | |
175 | ||
e8bafea3 C |
176 | thumbnail.filename = filename |
177 | thumbnail.height = height | |
178 | thumbnail.width = width | |
179 | thumbnail.type = type | |
9cc8d43e | 180 | thumbnail.fileUrl = fileUrl |
e8bafea3 C |
181 | |
182 | return thumbnail | |
183 | } | |
184 | ||
185 | // --------------------------------------------------------------------------- | |
186 | ||
187 | export { | |
3acc5084 | 188 | generateVideoMiniature, |
91f8f8db C |
189 | updateVideoMiniatureFromUrl, |
190 | updateVideoMiniatureFromExisting, | |
191 | updatePlaceholderThumbnail, | |
192 | updatePlaylistMiniatureFromUrl, | |
193 | updatePlaylistMiniatureFromExisting | |
e8bafea3 C |
194 | } |
195 | ||
1ef447bd C |
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 | ||
453e83ea | 205 | function buildMetadataFromPlaylist (playlist: MVideoPlaylistThumbnail, size: ImageSize) { |
e8bafea3 C |
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 | ||
453e83ea | 219 | function buildMetadataFromVideo (video: MVideoThumbnail, type: ThumbnailType, size?: ImageSize) { |
e8bafea3 C |
220 | const existingThumbnail = Array.isArray(video.Thumbnails) |
221 | ? video.Thumbnails.find(t => t.type === type) | |
222 | : undefined | |
223 | ||
3acc5084 | 224 | if (type === ThumbnailType.MINIATURE) { |
84531547 | 225 | const filename = generateImageFilename() |
e8bafea3 C |
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) { | |
84531547 | 239 | const filename = generateImageFilename() |
e8bafea3 C |
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 | ||
91f8f8db | 255 | async function updateThumbnailFromFunction (parameters: { |
a1587156 C |
256 | thumbnailCreator: () => Promise<any> |
257 | filename: string | |
258 | height: number | |
259 | width: number | |
260 | type: ThumbnailType | |
261 | automaticallyGenerated?: boolean | |
262 | fileUrl?: string | |
453e83ea | 263 | existingThumbnail?: MThumbnail |
e8bafea3 | 264 | }) { |
a35a2279 C |
265 | const { |
266 | thumbnailCreator, | |
267 | filename, | |
268 | width, | |
269 | height, | |
270 | type, | |
271 | existingThumbnail, | |
272 | automaticallyGenerated = null, | |
273 | fileUrl = null | |
274 | } = parameters | |
275 | ||
d9a2a031 | 276 | const oldFilename = existingThumbnail && existingThumbnail.filename !== filename |
a35a2279 C |
277 | ? existingThumbnail.filename |
278 | : undefined | |
6302d599 | 279 | |
a35a2279 | 280 | const thumbnail: MThumbnail = existingThumbnail || new ThumbnailModel() |
e8bafea3 C |
281 | |
282 | thumbnail.filename = filename | |
283 | thumbnail.height = height | |
284 | thumbnail.width = width | |
285 | thumbnail.type = type | |
9cc8d43e | 286 | thumbnail.fileUrl = fileUrl |
65af03a2 | 287 | thumbnail.automaticallyGenerated = automaticallyGenerated |
d9a2a031 C |
288 | |
289 | if (oldFilename) thumbnail.previousThumbnailFilename = oldFilename | |
e8bafea3 C |
290 | |
291 | await thumbnailCreator() | |
292 | ||
293 | return thumbnail | |
294 | } |