1
- import { resolve , parse } from "path"
1
+ import { parse , resolve } from "path"
2
2
import { performance } from "perf_hooks"
3
+ import { tmpdir } from "os"
3
4
4
5
import { createContentDigest } from "gatsby-core-utils"
5
- import { pathExists , stat , copy , writeFile } from "fs-extra"
6
+ import { pathExists , stat , copy , ensureDir , remove , access } from "fs-extra"
6
7
import ffmpeg from "fluent-ffmpeg"
7
- import fg from "fast-glob"
8
8
import imagemin from "imagemin"
9
9
import imageminGiflossy from "imagemin-giflossy"
10
10
import imageminMozjpeg from "imagemin-mozjpeg"
11
11
import PQueue from "p-queue"
12
12
import sharp from "sharp"
13
+ import { createFileNodeFromBuffer } from "gatsby-source-filesystem"
13
14
14
15
import { cacheContentfulVideo } from "./helpers"
15
16
@@ -128,16 +129,14 @@ export default class FFMPEG {
128
129
}
129
130
130
131
// Queue video for conversion
131
- convertVideo = async ( ...args ) => {
132
- const videoData = await this . queue . add ( ( ) =>
133
- this . queuedConvertVideo ( ...args )
134
- )
132
+ queueConvertVideo = async ( ...args ) => {
133
+ const videoData = await this . queue . add ( ( ) => this . convertVideo ( ...args ) )
135
134
136
135
return videoData
137
136
}
138
137
139
138
// Converts a video based on a given profile, populates cache and public dir
140
- queuedConvertVideo = async ( {
139
+ convertVideo = async ( {
141
140
profile,
142
141
sourcePath,
143
142
cachePath,
@@ -174,94 +173,122 @@ export default class FFMPEG {
174
173
await copy ( cachePath , publicPath , { overwrite : true } )
175
174
}
176
175
177
- // Take screenshots
178
- const screenshots = await this . takeScreenshots ( { fieldArgs, publicPath } )
179
-
180
- return { screenshots, publicPath }
176
+ return { publicPath }
181
177
}
182
178
183
- takeScreenshots = async ( { fieldArgs, publicPath } ) => {
184
- const { screenshots, screenshotWidth } = fieldArgs
179
+ takeScreenshots = async (
180
+ video ,
181
+ fieldArgs ,
182
+ { getCache, createNode, createNodeId }
183
+ ) => {
184
+ const { type } = video . internal
185
185
186
- if ( ! screenshots ) {
187
- return null
186
+ let fileType = null
187
+ if ( type === `File` ) {
188
+ fileType = video . internal . mediaType
188
189
}
189
190
190
- const { dir : publicDir , name } = parse ( publicPath )
191
+ if ( type === `ContentfulAsset` ) {
192
+ fileType = video . file . contentType
193
+ }
191
194
192
- const screenshotPatternCache = resolve (
193
- this . cacheDirConverted ,
194
- `${ name } -screenshot-*.png`
195
- )
196
- const screenshotPatternPublic = resolve (
197
- publicDir ,
198
- `${ name } -screenshot-*.jpg`
199
- )
195
+ if ( fileType . indexOf ( `video/` ) === - 1 ) {
196
+ return null
197
+ }
200
198
201
- const screenshotsCache = await fg ( [ screenshotPatternCache ] )
202
- const screenshotsPublic = await fg ( [ screenshotPatternPublic ] )
199
+ let path , fileName
203
200
204
- if ( ! screenshotsCache . length ) {
205
- const timestamps = screenshots . split ( `,` )
201
+ if ( type === `File` ) {
202
+ path = video . absolutePath
203
+ fileName = video . name
204
+ }
206
205
207
- await new Promise ( ( resolve , reject ) => {
208
- ffmpeg ( publicPath )
209
- . on ( `filenames` , function ( filenames ) {
210
- console . log ( `[FFMPEG] Taking ${ filenames . length } screenshots` )
211
- } )
212
- . on ( `error` , ( err , stdout , stderr ) => {
213
- console . log ( `[FFMPEG] Failed to take screenshots:` )
214
- console . error ( err )
215
- reject ( err )
216
- } )
217
- . on ( `end` , ( ) => {
218
- resolve ( )
219
- } )
220
- . screenshots ( {
221
- timestamps,
222
- filename : `${ name } -screenshot-%ss.png` ,
223
- folder : this . cacheDirConverted ,
224
- size : `${ screenshotWidth } x?` ,
225
- } )
206
+ if ( type === `ContentfulAsset` ) {
207
+ path = await cacheContentfulVideo ( {
208
+ video,
209
+ cacheDir : this . cacheDirOriginal ,
226
210
} )
211
+ fileName = video . file . fileName
227
212
}
228
213
229
- if ( ! screenshotsPublic . length ) {
230
- const screenshotsLatest = await fg ( [ screenshotPatternCache ] )
231
- for ( const rawScreenshotPath of screenshotsLatest ) {
232
- const { name : screenshotName } = parse ( rawScreenshotPath )
233
- const publicScreenshotPath = resolve ( publicDir , `${ screenshotName } .jpg` )
214
+ const { timestamps, width } = fieldArgs
215
+ const { name } = parse ( fileName )
216
+
217
+ const tmpDir = resolve ( tmpdir ( ) , `gatsby-transformer-video` , name )
218
+
219
+ await ensureDir ( tmpDir )
220
+
221
+ let screenshotRawNames
222
+
223
+ await new Promise ( ( resolve , reject ) => {
224
+ ffmpeg ( path )
225
+ . on ( `filenames` , function ( filenames ) {
226
+ screenshotRawNames = filenames
227
+ console . log ( `[FFMPEG] Taking ${ filenames . length } screenshots` )
228
+ } )
229
+ . on ( `error` , ( err , stdout , stderr ) => {
230
+ console . log ( `[FFMPEG] Failed to take screenshots:` )
231
+ console . error ( err )
232
+ reject ( err )
233
+ } )
234
+ . on ( `end` , ( ) => {
235
+ resolve ( )
236
+ } )
237
+ . screenshots ( {
238
+ timestamps,
239
+ filename : `${ name } -%ss.png` ,
240
+ folder : tmpDir ,
241
+ size : `${ width } x?` ,
242
+ } )
243
+ } )
244
+
245
+ const screenshotNodes = [ ]
246
+
247
+ console . log ( { screenshotRawNames, tmpDir } )
248
+
249
+ for ( const screenshotRawName of screenshotRawNames ) {
250
+ try {
251
+ const rawScreenshotPath = resolve ( tmpDir , screenshotRawName )
252
+ const { name } = parse ( rawScreenshotPath )
234
253
235
254
try {
236
- const jpgBuffer = await sharp ( rawScreenshotPath )
237
- . jpeg ( {
238
- quality : 60 ,
239
- progressive : true ,
240
- } )
241
- . toBuffer ( )
242
-
243
- const optimizedBuffer = await imagemin . buffer ( jpgBuffer , {
244
- plugins : [ imageminMozjpeg ( ) ] ,
255
+ await access ( rawScreenshotPath )
256
+ } catch {
257
+ console . warn ( `Screenshot ${ rawScreenshotPath } could not be found!` )
258
+ continue
259
+ }
260
+
261
+ const jpgBuffer = await sharp ( rawScreenshotPath )
262
+ . jpeg ( {
263
+ quality : 80 ,
264
+ progressive : true ,
245
265
} )
266
+ . toBuffer ( )
246
267
247
- await writeFile ( publicScreenshotPath , optimizedBuffer )
248
- } catch ( err ) {
249
- console . log ( `Unable to convert png screenshots to jpegs` )
250
- throw err
251
- }
252
- }
268
+ const optimizedBuffer = await imagemin . buffer ( jpgBuffer , {
269
+ plugins : [ imageminMozjpeg ( ) ] ,
270
+ } )
253
271
254
- console . log ( `[FFMPEG] Finished copying screenshots` )
272
+ const node = await createFileNodeFromBuffer ( {
273
+ ext : `.jpg` ,
274
+ name,
275
+ buffer : optimizedBuffer ,
276
+ getCache,
277
+ createNode,
278
+ createNodeId,
279
+ } )
280
+
281
+ screenshotNodes . push ( node )
282
+ } catch ( err ) {
283
+ console . log ( `Failed to take screenshots:` )
284
+ console . error ( err )
285
+ throw err
286
+ }
255
287
}
256
288
257
- const latestFiles = await fg ( [ screenshotPatternPublic ] )
289
+ await remove ( tmpDir )
258
290
259
- return latestFiles . map ( absolutePath => {
260
- return {
261
- absolutePath,
262
- path : absolutePath . replace ( resolve ( this . rootDir , `public` ) , `` ) ,
263
- }
264
- } )
291
+ return screenshotNodes
265
292
}
266
293
267
294
createFromProfile = async ( { publicDir, path, name, fieldArgs, info } ) => {
@@ -288,7 +315,7 @@ export default class FFMPEG {
288
315
const cachePath = resolve ( this . cacheDirConverted , filename )
289
316
const publicPath = resolve ( publicDir , filename )
290
317
291
- return this . convertVideo ( {
318
+ return this . queueConvertVideo ( {
292
319
profile : profile . converter ,
293
320
sourcePath : path ,
294
321
cachePath,
@@ -303,7 +330,7 @@ export default class FFMPEG {
303
330
const cachePath = resolve ( this . cacheDirConverted , filename )
304
331
const publicPath = resolve ( publicDir , filename )
305
332
306
- return this . convertVideo ( {
333
+ return this . queueConvertVideo ( {
307
334
profile : profileH264 ,
308
335
sourcePath : path ,
309
336
cachePath,
@@ -318,7 +345,7 @@ export default class FFMPEG {
318
345
const cachePath = resolve ( this . cacheDirConverted , filename )
319
346
const publicPath = resolve ( publicDir , filename )
320
347
321
- return this . convertVideo ( {
348
+ return this . queueConvertVideo ( {
322
349
profile : profileH265 ,
323
350
sourcePath : path ,
324
351
cachePath,
@@ -333,7 +360,7 @@ export default class FFMPEG {
333
360
const cachePath = resolve ( this . cacheDirConverted , filename )
334
361
const publicPath = resolve ( publicDir , filename )
335
362
336
- return this . convertVideo ( {
363
+ return this . queueConvertVideo ( {
337
364
profile : profileVP9 ,
338
365
sourcePath : path ,
339
366
cachePath,
@@ -348,7 +375,7 @@ export default class FFMPEG {
348
375
const cachePath = resolve ( this . cacheDirConverted , filename )
349
376
const publicPath = resolve ( publicDir , filename )
350
377
351
- return this . convertVideo ( {
378
+ return this . queueConvertVideo ( {
352
379
profile : profileWebP ,
353
380
sourcePath : path ,
354
381
cachePath,
@@ -363,7 +390,7 @@ export default class FFMPEG {
363
390
const cachePath = resolve ( this . cacheDirConverted , filename )
364
391
const publicPath = resolve ( publicDir , filename )
365
392
366
- const absolutePath = await this . convertVideo ( {
393
+ const absolutePath = await this . queueConvertVideo ( {
367
394
profile : profileGif ,
368
395
sourcePath : path ,
369
396
cachePath,
0 commit comments