From 22f9eb4dff9ee03b5ec655db2204050ffe7a7771 Mon Sep 17 00:00:00 2001 From: Zephyrrus Date: Thu, 2 Jul 2020 23:42:44 +0300 Subject: feat: refactor preview to support random fragment extraction --- src/api/utils/videoPreview/FragmentPreview.js | 87 ++++++++++++++++++++++ src/api/utils/videoPreview/FrameIntervalPreview.js | 72 ++++++++++++++++++ 2 files changed, 159 insertions(+) create mode 100644 src/api/utils/videoPreview/FragmentPreview.js create mode 100644 src/api/utils/videoPreview/FrameIntervalPreview.js (limited to 'src/api/utils/videoPreview') diff --git a/src/api/utils/videoPreview/FragmentPreview.js b/src/api/utils/videoPreview/FragmentPreview.js new file mode 100644 index 0000000..8815392 --- /dev/null +++ b/src/api/utils/videoPreview/FragmentPreview.js @@ -0,0 +1,87 @@ +const ffmpeg = require('fluent-ffmpeg'); +const probe = require('ffmpeg-probe'); + +const noop = () => {}; + +const getRandomInt = (min, max) => { + const minInt = Math.ceil(min); + const maxInt = Math.floor(max); + + // eslint-disable-next-line no-mixed-operators + return Math.floor(Math.random() * (maxInt - minInt + 1) + minInt); +}; + +const getStartTime = (vDuration, fDuration, ignoreBeforePercent, ignoreAfterPercent) => { + // by subtracting the fragment duration we can be sure that the resulting + // start time + fragment duration will be less than the video duration + const safeVDuration = vDuration - fDuration; + + // if the fragment duration is longer than the video duration + if (safeVDuration <= 0) { + return 0; + } + + return getRandomInt(ignoreBeforePercent * safeVDuration, ignoreAfterPercent * safeVDuration); +}; + +module.exports = async opts => { + const { + log = noop, + + // general output options + quality = 2, + width, + height, + input, + output, + + fragmentDurationSecond = 3, + ignoreBeforePercent = 0.25, + ignoreAfterPercent = 0.75 + } = opts; + + const info = await probe(input); + + let { duration } = info.format; + duration = parseInt(duration, 10); + + const startTime = getStartTime(duration, fragmentDurationSecond, ignoreBeforePercent, ignoreAfterPercent); + + const result = { startTime, duration }; + + await new Promise((resolve, reject) => { + let scale = null; + + if (width && height) { + result.width = width | 0; + result.height = height | 0; + scale = `scale=${width}:${height}`; + } else if (width) { + result.width = width | 0; + result.height = ((info.height * width) / info.width) | 0; + scale = `scale=${width}:-1`; + } else if (height) { + result.height = height | 0; + result.width = ((info.width * height) / info.height) | 0; + scale = `scale=-1:${height}`; + } else { + result.width = info.width; + result.height = info.height; + } + + return ffmpeg() + .input(input) + .inputOptions([`-ss ${startTime}`]) + .outputOptions(['-vsync', 'vfr']) + .outputOptions(['-q:v', quality, '-vf', scale]) + .outputOptions([`-t ${fragmentDurationSecond}`]) + .noAudio() + .output(output) + .on('start', cmd => log && log({ cmd })) + .on('end', resolve) + .on('error', reject) + .run(); + }); + + return result; +}; diff --git a/src/api/utils/videoPreview/FrameIntervalPreview.js b/src/api/utils/videoPreview/FrameIntervalPreview.js new file mode 100644 index 0000000..75f6d2b --- /dev/null +++ b/src/api/utils/videoPreview/FrameIntervalPreview.js @@ -0,0 +1,72 @@ +const ffmpeg = require('fluent-ffmpeg'); +const probe = require('ffmpeg-probe'); + +const noop = () => {}; + +module.exports = async opts => { + const { + log = noop, + + // general output options + quality = 2, + width, + height, + input, + output, + + numFrames, + numFramesPercent = 0.05 + } = opts; + + const info = await probe(input); + // const numFramesTotal = parseInt(info.streams[0].nb_frames, 10); + const { avg_frame_rate: avgFrameRate, duration } = info.streams[0]; + const [frames, time] = avgFrameRate.split('/').map(e => parseInt(e, 10)); + + const numFramesTotal = (frames / time) * duration; + + let numFramesToCapture = numFrames || numFramesPercent * numFramesTotal; + numFramesToCapture = Math.max(1, Math.min(numFramesTotal, numFramesToCapture)) | 0; + const nthFrame = (numFramesTotal / numFramesToCapture) | 0; + + const result = { + output, + numFrames: numFramesToCapture + }; + + await new Promise((resolve, reject) => { + let scale = null; + + if (width && height) { + result.width = width | 0; + result.height = height | 0; + scale = `scale=${width}:${height}`; + } else if (width) { + result.width = width | 0; + result.height = ((info.height * width) / info.width) | 0; + scale = `scale=${width}:-1`; + } else if (height) { + result.height = height | 0; + result.width = ((info.width * height) / info.height) | 0; + scale = `scale=-1:${height}`; + } else { + result.width = info.width; + result.height = info.height; + } + + const filter = [`select=not(mod(n\\,${nthFrame}))`, scale].filter(Boolean).join(','); + + ffmpeg(input) + .outputOptions(['-vsync', 'vfr']) + .outputOptions(['-q:v', quality, '-vf', filter]) + .noAudio() + .outputFormat('webm') + .output(output) + .on('start', cmd => log && log({ cmd })) + .on('end', () => resolve()) + .on('error', err => reject(err)) + .run(); + }); + + return result; +}; -- cgit v1.2.3 From ad852de51a0d2dd5d29c08838d5a430c58849e74 Mon Sep 17 00:00:00 2001 From: Zephyrrus Date: Wed, 8 Jul 2020 04:00:12 +0300 Subject: chore: linter the entire project using the new rules --- src/api/utils/videoPreview/FragmentPreview.js | 7 ++++--- src/api/utils/videoPreview/FrameIntervalPreview.js | 13 +++++++------ 2 files changed, 11 insertions(+), 9 deletions(-) (limited to 'src/api/utils/videoPreview') diff --git a/src/api/utils/videoPreview/FragmentPreview.js b/src/api/utils/videoPreview/FragmentPreview.js index 8815392..bf623c1 100644 --- a/src/api/utils/videoPreview/FragmentPreview.js +++ b/src/api/utils/videoPreview/FragmentPreview.js @@ -1,3 +1,4 @@ +/* eslint-disable no-bitwise */ const ffmpeg = require('fluent-ffmpeg'); const probe = require('ffmpeg-probe'); @@ -24,7 +25,7 @@ const getStartTime = (vDuration, fDuration, ignoreBeforePercent, ignoreAfterPerc return getRandomInt(ignoreBeforePercent * safeVDuration, ignoreAfterPercent * safeVDuration); }; -module.exports = async opts => { +module.exports = async (opts) => { const { log = noop, @@ -37,7 +38,7 @@ module.exports = async opts => { fragmentDurationSecond = 3, ignoreBeforePercent = 0.25, - ignoreAfterPercent = 0.75 + ignoreAfterPercent = 0.75, } = opts; const info = await probe(input); @@ -77,7 +78,7 @@ module.exports = async opts => { .outputOptions([`-t ${fragmentDurationSecond}`]) .noAudio() .output(output) - .on('start', cmd => log && log({ cmd })) + .on('start', (cmd) => log && log({ cmd })) .on('end', resolve) .on('error', reject) .run(); diff --git a/src/api/utils/videoPreview/FrameIntervalPreview.js b/src/api/utils/videoPreview/FrameIntervalPreview.js index 75f6d2b..8c5f1c3 100644 --- a/src/api/utils/videoPreview/FrameIntervalPreview.js +++ b/src/api/utils/videoPreview/FrameIntervalPreview.js @@ -1,9 +1,10 @@ +/* eslint-disable no-bitwise */ const ffmpeg = require('fluent-ffmpeg'); const probe = require('ffmpeg-probe'); const noop = () => {}; -module.exports = async opts => { +module.exports = async (opts) => { const { log = noop, @@ -15,13 +16,13 @@ module.exports = async opts => { output, numFrames, - numFramesPercent = 0.05 + numFramesPercent = 0.05, } = opts; const info = await probe(input); // const numFramesTotal = parseInt(info.streams[0].nb_frames, 10); const { avg_frame_rate: avgFrameRate, duration } = info.streams[0]; - const [frames, time] = avgFrameRate.split('/').map(e => parseInt(e, 10)); + const [frames, time] = avgFrameRate.split('/').map((e) => parseInt(e, 10)); const numFramesTotal = (frames / time) * duration; @@ -31,7 +32,7 @@ module.exports = async opts => { const result = { output, - numFrames: numFramesToCapture + numFrames: numFramesToCapture, }; await new Promise((resolve, reject) => { @@ -62,9 +63,9 @@ module.exports = async opts => { .noAudio() .outputFormat('webm') .output(output) - .on('start', cmd => log && log({ cmd })) + .on('start', (cmd) => log && log({ cmd })) .on('end', () => resolve()) - .on('error', err => reject(err)) + .on('error', (err) => reject(err)) .run(); }); -- cgit v1.2.3 From 90001c2df56d58e69fd199a518ae7f3e4ed327fc Mon Sep 17 00:00:00 2001 From: Zephyrrus Date: Thu, 24 Dec 2020 10:40:50 +0200 Subject: chore: remove trailing commas --- src/api/utils/videoPreview/FragmentPreview.js | 2 +- src/api/utils/videoPreview/FrameIntervalPreview.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src/api/utils/videoPreview') diff --git a/src/api/utils/videoPreview/FragmentPreview.js b/src/api/utils/videoPreview/FragmentPreview.js index bf623c1..4f681fa 100644 --- a/src/api/utils/videoPreview/FragmentPreview.js +++ b/src/api/utils/videoPreview/FragmentPreview.js @@ -38,7 +38,7 @@ module.exports = async (opts) => { fragmentDurationSecond = 3, ignoreBeforePercent = 0.25, - ignoreAfterPercent = 0.75, + ignoreAfterPercent = 0.75 } = opts; const info = await probe(input); diff --git a/src/api/utils/videoPreview/FrameIntervalPreview.js b/src/api/utils/videoPreview/FrameIntervalPreview.js index 8c5f1c3..8bb9836 100644 --- a/src/api/utils/videoPreview/FrameIntervalPreview.js +++ b/src/api/utils/videoPreview/FrameIntervalPreview.js @@ -16,7 +16,7 @@ module.exports = async (opts) => { output, numFrames, - numFramesPercent = 0.05, + numFramesPercent = 0.05 } = opts; const info = await probe(input); @@ -32,7 +32,7 @@ module.exports = async (opts) => { const result = { output, - numFrames: numFramesToCapture, + numFrames: numFramesToCapture }; await new Promise((resolve, reject) => { -- cgit v1.2.3 From fb2c27086f570fec60f4d52dcc9ca80e53186293 Mon Sep 17 00:00:00 2001 From: Pitu Date: Thu, 24 Dec 2020 23:45:16 +0900 Subject: Fix ESLint rules once and for all --- src/api/utils/videoPreview/FragmentPreview.js | 4 ++-- src/api/utils/videoPreview/FrameIntervalPreview.js | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) (limited to 'src/api/utils/videoPreview') diff --git a/src/api/utils/videoPreview/FragmentPreview.js b/src/api/utils/videoPreview/FragmentPreview.js index 4f681fa..1d1ee02 100644 --- a/src/api/utils/videoPreview/FragmentPreview.js +++ b/src/api/utils/videoPreview/FragmentPreview.js @@ -25,7 +25,7 @@ const getStartTime = (vDuration, fDuration, ignoreBeforePercent, ignoreAfterPerc return getRandomInt(ignoreBeforePercent * safeVDuration, ignoreAfterPercent * safeVDuration); }; -module.exports = async (opts) => { +module.exports = async opts => { const { log = noop, @@ -78,7 +78,7 @@ module.exports = async (opts) => { .outputOptions([`-t ${fragmentDurationSecond}`]) .noAudio() .output(output) - .on('start', (cmd) => log && log({ cmd })) + .on('start', cmd => log && log({ cmd })) .on('end', resolve) .on('error', reject) .run(); diff --git a/src/api/utils/videoPreview/FrameIntervalPreview.js b/src/api/utils/videoPreview/FrameIntervalPreview.js index 8bb9836..96c6e3a 100644 --- a/src/api/utils/videoPreview/FrameIntervalPreview.js +++ b/src/api/utils/videoPreview/FrameIntervalPreview.js @@ -4,7 +4,7 @@ const probe = require('ffmpeg-probe'); const noop = () => {}; -module.exports = async (opts) => { +module.exports = async opts => { const { log = noop, @@ -22,7 +22,7 @@ module.exports = async (opts) => { const info = await probe(input); // const numFramesTotal = parseInt(info.streams[0].nb_frames, 10); const { avg_frame_rate: avgFrameRate, duration } = info.streams[0]; - const [frames, time] = avgFrameRate.split('/').map((e) => parseInt(e, 10)); + const [frames, time] = avgFrameRate.split('/').map(e => parseInt(e, 10)); const numFramesTotal = (frames / time) * duration; @@ -63,9 +63,9 @@ module.exports = async (opts) => { .noAudio() .outputFormat('webm') .output(output) - .on('start', (cmd) => log && log({ cmd })) + .on('start', cmd => log && log({ cmd })) .on('end', () => resolve()) - .on('error', (err) => reject(err)) + .on('error', err => reject(err)) .run(); }); -- cgit v1.2.3