From 78dabac6fa35ac10444a7f655b22adff8b629151 Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Wed, 18 Mar 2020 11:46:50 +1100 Subject: [PATCH 01/20] Allows you to not loop. --- package.json | 11 ++++++-- src/crop.js | 25 +++++++++++++++++ src/index.js | 64 +++++++++++++++++-------------------------- test/index.js | 75 ++++++++++++++++++++++++++++++++++++--------------- 4 files changed, 113 insertions(+), 62 deletions(-) create mode 100644 src/crop.js diff --git a/package.json b/package.json index 1e01639..63e986f 100644 --- a/package.json +++ b/package.json @@ -26,15 +26,17 @@ }, "dependencies": { "fluent-ffmpeg": "^2.1.2", - "lodash.last": "^3.0.0" + "lodash.last": "^3.0.0", + "ow": "^0.17.0" }, "devDependencies": { "chai": "^3.5.0", "dirty-chai": "^1.2.2", "ffmpeg-static": "^2.1.0", "jsdoc-to-markdown": "~3.0.0", - "mocha": "~3.3.0", + "mocha": "^7.1.0", "nyc": "^10.1.2", + "p-event": "^4.1.0", "semistandard": "*" }, "peerDependencies": { @@ -46,5 +48,10 @@ "coverage", "test" ] + }, + "semistandard": { + "env": [ + "mocha" + ] } } diff --git a/src/crop.js b/src/crop.js new file mode 100644 index 0000000..19251a4 --- /dev/null +++ b/src/crop.js @@ -0,0 +1,25 @@ +/** + * Apply a crop filter (if required) to the ffmpeg command based on provided opts. + * All crop dimensions are for the original video size (not the output size). + * + * @param {Array} filters The filters array - will be modified in place if applicable + * @param {integer} opts.cropWidth - crop width (width and height are required). + * @param {integer} opts.cropHeight - crop height + * @param {integer} opts.cropX - crop x (x and y are optional. If not set, the + * default is the center position of the video). + * @param {integer} opts.cropY - crop y + */ +function cropFilter (opts) { + const { cropWidth, cropHeight, cropX, cropY } = opts; + if (!cropWidth || isNaN(cropWidth) || !cropHeight || isNaN(cropHeight)) { + return; + } + const crop = [cropWidth, cropHeight]; + if (!isNaN(cropX) && !isNaN(cropY)) crop.push(cropX, cropY); + return { + filter: 'crop', + options: crop.join(':') + }; +} + +module.exports = cropFilter; diff --git a/src/index.js b/src/index.js index 816c57f..707cc71 100644 --- a/src/index.js +++ b/src/index.js @@ -2,9 +2,11 @@ * @module ffmpeg-loop */ +const cropFilter = require('./crop'); const ffmpeg = require('fluent-ffmpeg'); -const assert = require('assert'); const last = require('lodash.last'); +const ow = require('ow'); + ffmpeg.setFfmpegPath(require('ffmpeg-static').path); /** @@ -26,57 +28,41 @@ ffmpeg.setFfmpegPath(require('ffmpeg-static').path); * @returns A fluent ffmpeg process - has pipe() method. */ module.exports = function (filename, opts) { - ['height', 'width', 'fps'].forEach(key => { - assert(!isNaN(opts[key]), `${key} should be number - got ${opts[key]}`); - }); - const { start = 0 } = opts; - const filters = [ + ow(opts, ow.object.partialShape({ + fps: ow.number.integer, + height: ow.number.integer, + width: ow.number.integer + })); + const { fps, height, loop = true, start = 0, width } = opts; + const filters = loop ? [ { filter: 'concat', options: { n: 2, v: 1, a: 0 }, outputs: 'concat' }, { filter: 'setpts', inputs: 'concat', options: 'N/(FRAME_RATE*TB)' } - ]; + ] : []; const command = ffmpeg() // Using -ss and -stream_loop together does not work well, so we have a // single non-looped version first to seek on. .input(filename) - .inputOption('-ss', start) - .input(filename) - .inputOption('-stream_loop', -1) + .inputOption('-ss', start); + if (loop) command.input(filename).inputOption('-stream_loop', -1); + command .noAudio() .outputFormat('rawvideo') .outputOption('-vcodec', 'rawvideo') .outputOption('-pix_fmt', 'rgba') - .outputOption('-s', `${opts.width}x${opts.height}`) - .outputOption('-r', opts.fps); - applyCrop(filters, opts); - command.complexFilter(filters); + .outputOption('-s', `${width}x${height}`) + .outputOption('-r', fps); + const crop = cropFilter(opts); + if (crop) { + if (filters.length) { + last(filters).outputs = 'toCrop'; + crop.inputs = 'toCrop'; + } + filters.push(crop); + } + if (filters.length) command.complexFilter(filters); return command; }; - -/** - * Apply a crop filter (if required) to the ffmpeg command based on provided opts. - * All crop dimensions are for the original video size (not the output size). - * - * @private - * @param {Array} filters The filters array - will be modified in place if applicable - * @param {integer} opts.cropWidth - crop width (width and height are required). - * @param {integer} opts.cropHeight - crop height - * @param {integer} opts.cropX - crop x (x and y are optional. If not set, the - * default is the center position of the video). - * @param {integer} opts.cropY - crop y - */ -function applyCrop (filters, opts) { - const { cropWidth, cropHeight, cropX, cropY } = opts; - if (!cropWidth || isNaN(cropWidth) || !cropHeight || isNaN(cropHeight)) { return; } - const crop = [cropWidth, cropHeight]; - if (!isNaN(cropX) && !isNaN(cropY)) crop.push(cropX, cropY); - last(filters).outputs = 'toCrop'; - return filters.push({ - filter: 'crop', - inputs: 'toCrop', - options: crop.join(':') - }); -} diff --git a/test/index.js b/test/index.js index f69da75..5377b9f 100644 --- a/test/index.js +++ b/test/index.js @@ -1,6 +1,29 @@ const expect = require('chai').expect; const ffmpegLoop = require('..'); const path = require('path'); +const pEvent = require('p-event'); + +async function start (command) { + return new Promise((resolve, reject) => { + let cmd; + command.once('start', _cmd => { + // We use setImmediate() here in case the 'start' event is already waiting + // to process (in which case the callback will run straight away). We need + // command.pipe() to run below so we can resolve the promise including the + // stream. This seems to happen in the second test case as somehow + // fluent-ffmpeg is already hot and doesn't wait for the command.pipe() + // before the 'start' happens. + setImmediate(() => { + cmd = _cmd; + console.log(cmd); + command.removeAllListeners('error'); + resolve({ cmd, stream }); + }); + }); + command.once('error', reject); + const stream = command.pipe(); + }); +} describe('ffmpeg loop', function () { it('should return an ffmpeg proc', function () { @@ -11,8 +34,7 @@ describe('ffmpeg loop', function () { command.kill(); }); - it('should apply a crop filter', function (done) { - this.timeout(5000); // this takes a long time on travis for some reason? + it('should apply a crop filter', async function () { const opts = { height: 28, width: 50, @@ -26,24 +48,35 @@ describe('ffmpeg loop', function () { path.join(__dirname, 'fixtures/user_video-30.mp4'), opts ); - command.once('start', cmd => { - try { - console.log(cmd); - expect(cmd).to.match(/crop=12:24:0:0/); - } catch (err) { - done(err); - } - }); - const stream = command.pipe(); - stream.once('data', function (data) { - try { - expect(data).to.be.ok(); - command.kill(); - done(); - } catch (err) { - done(err); - command.kill(); - } - }); + const { cmd, stream } = await start(command); + expect(cmd).to.match(/crop=12:24:0:0/); + expect(cmd).to.match(/stream_loop/); + + const data = pEvent(stream, 'data'); + expect(data).to.be.ok(); + command.kill(); + await pEvent(command, 'error'); + }); + + it('allows you to not loop if you so desire', async function () { + this.timeout(5000); + const opts = { + fps: 30, + height: 28, + loop: false, + width: 50 + }; + const command = ffmpegLoop( + path.join(__dirname, 'fixtures/user_video-30.mp4'), + opts + ); + + const { cmd, stream } = await start(command); + expect(cmd).to.not.match(/stream_loop/); + + const data = pEvent(stream, 'data'); + expect(data).to.be.ok(); + command.kill(); + await pEvent(command, 'error'); }); }); From 3520f91c0ca7b387418cee9bbeb83443735196b8 Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Wed, 18 Mar 2020 11:50:24 +1100 Subject: [PATCH 02/20] Tidy up. --- src/crop.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/crop.js b/src/crop.js index 19251a4..6b50c20 100644 --- a/src/crop.js +++ b/src/crop.js @@ -1,5 +1,5 @@ /** - * Apply a crop filter (if required) to the ffmpeg command based on provided opts. + * Generate a crop filter (if required) to the ffmpeg command based on provided opts. * All crop dimensions are for the original video size (not the output size). * * @param {Array} filters The filters array - will be modified in place if applicable @@ -8,11 +8,12 @@ * @param {integer} opts.cropX - crop x (x and y are optional. If not set, the * default is the center position of the video). * @param {integer} opts.cropY - crop y + * @return {object|false} The crop filter. */ function cropFilter (opts) { const { cropWidth, cropHeight, cropX, cropY } = opts; if (!cropWidth || isNaN(cropWidth) || !cropHeight || isNaN(cropHeight)) { - return; + return false; } const crop = [cropWidth, cropHeight]; if (!isNaN(cropX) && !isNaN(cropY)) crop.push(cropX, cropY); From bf7ba9dcd67f81334e921583c9e72f6f5e27cb52 Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Wed, 18 Mar 2020 14:22:42 +1100 Subject: [PATCH 03/20] fps can be non-integer. --- src/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.js b/src/index.js index 707cc71..4143833 100644 --- a/src/index.js +++ b/src/index.js @@ -29,7 +29,7 @@ ffmpeg.setFfmpegPath(require('ffmpeg-static').path); */ module.exports = function (filename, opts) { ow(opts, ow.object.partialShape({ - fps: ow.number.integer, + fps: ow.number, height: ow.number.integer, width: ow.number.integer })); From 1a508f366e9cebc43ea88f07adc8248b9e86c7da Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Wed, 18 Mar 2020 15:09:47 +1100 Subject: [PATCH 04/20] update engines, files --- package.json | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 63e986f..f2fad9c 100644 --- a/package.json +++ b/package.json @@ -21,9 +21,12 @@ "url": "https://github.com/noblesamurai/ffmpeg-loop/issues" }, "engines": { - "node": "8.x", - "npm": "5.x" + "node": "^10 || ^12", + "npm": "6.x" }, + "files": [ + "src" + ], "dependencies": { "fluent-ffmpeg": "^2.1.2", "lodash.last": "^3.0.0", From a5ee0760873c22ae5c838218ca7702525a1c01f1 Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Wed, 18 Mar 2020 15:10:54 +1100 Subject: [PATCH 05/20] 2.1.0-loop-option.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f2fad9c..15366aa 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ffmpeg-loop", "description": "Instantiate ffmpeg in looping mode.", - "version": "2.0.0", + "version": "2.1.0-loop-option.0", "author": "Tim Allen ", "license": "BSD", "main": "src/index.js", From d218582898159fed07ed4f1bf49a684b34b2be60 Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Wed, 18 Mar 2020 16:29:22 +1100 Subject: [PATCH 06/20] fix tests --- test/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/index.js b/test/index.js index 5377b9f..087bde2 100644 --- a/test/index.js +++ b/test/index.js @@ -52,7 +52,7 @@ describe('ffmpeg loop', function () { expect(cmd).to.match(/crop=12:24:0:0/); expect(cmd).to.match(/stream_loop/); - const data = pEvent(stream, 'data'); + const data = await pEvent(stream, 'data'); expect(data).to.be.ok(); command.kill(); await pEvent(command, 'error'); @@ -74,7 +74,7 @@ describe('ffmpeg loop', function () { const { cmd, stream } = await start(command); expect(cmd).to.not.match(/stream_loop/); - const data = pEvent(stream, 'data'); + const data = await pEvent(stream, 'data'); expect(data).to.be.ok(); command.kill(); await pEvent(command, 'error'); From 418481241a744f354f047852ba2f3b74e8558175 Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Wed, 18 Mar 2020 16:29:37 +1100 Subject: [PATCH 07/20] show issue when you go past the end. --- test/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/index.js b/test/index.js index 087bde2..90b13a9 100644 --- a/test/index.js +++ b/test/index.js @@ -64,7 +64,8 @@ describe('ffmpeg loop', function () { fps: 30, height: 28, loop: false, - width: 50 + width: 50, + start: 30 }; const command = ffmpegLoop( path.join(__dirname, 'fixtures/user_video-30.mp4'), From 36de5bd82636afdddfa8c698b6210a3a341089ed Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Wed, 18 Mar 2020 16:31:19 +1100 Subject: [PATCH 08/20] Update travis.yml for node 10, 12 --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index b5ea437..782c7ae 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,8 @@ language: node_js before_install: "! grep PLEASE_FILL_IN_HERE README.md" node_js: - - '8' - - '9' + - '10' + - '12' # For the code coverage stuff to work, set your CC_TEST_REPORTER_ID env var. before_script: - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter From a4504065f73ffc74ee574758a1f22cbbf676a5a1 Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Wed, 18 Mar 2020 16:36:11 +1100 Subject: [PATCH 09/20] timeout for travis --- test/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/index.js b/test/index.js index 90b13a9..1ecdf4b 100644 --- a/test/index.js +++ b/test/index.js @@ -35,6 +35,7 @@ describe('ffmpeg loop', function () { }); it('should apply a crop filter', async function () { + this.timeout(5000); const opts = { height: 28, width: 50, From c50c8b9863d16c5c7247eaf45e035761eb44745c Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Thu, 19 Mar 2020 13:51:42 +1100 Subject: [PATCH 10/20] Do things a little differently (use tpad). --- src/index.js | 5 ++++- test/index.js | 7 +++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/index.js b/src/index.js index 4143833..d8c0c6d 100644 --- a/src/index.js +++ b/src/index.js @@ -41,7 +41,10 @@ module.exports = function (filename, opts) { inputs: 'concat', options: 'N/(FRAME_RATE*TB)' } - ] : []; + ] : [ + // repeat last frame forever if not looping + { filter: 'tpad', options: { stop: -1, stop_mode: 'clone' } } + ]; const command = ffmpeg() // Using -ss and -stream_loop together does not work well, so we have a // single non-looped version first to seek on. diff --git a/test/index.js b/test/index.js index 1ecdf4b..cb2c146 100644 --- a/test/index.js +++ b/test/index.js @@ -59,14 +59,14 @@ describe('ffmpeg loop', function () { await pEvent(command, 'error'); }); - it('allows you to not loop if you so desire', async function () { + it('allows you to NOT loop, but you can still go past the end.', async function () { this.timeout(5000); const opts = { fps: 30, height: 28, loop: false, width: 50, - start: 30 + start: 29.9 }; const command = ffmpegLoop( path.join(__dirname, 'fixtures/user_video-30.mp4'), @@ -76,6 +76,9 @@ describe('ffmpeg loop', function () { const { cmd, stream } = await start(command); expect(cmd).to.not.match(/stream_loop/); + for (let i = 0; i < 100; i++) { + await pEvent(stream, 'data'); + } const data = await pEvent(stream, 'data'); expect(data).to.be.ok(); command.kill(); From aac4139a577320c733bc7a3ea8562a2e60a307b4 Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Thu, 19 Mar 2020 13:53:55 +1100 Subject: [PATCH 11/20] 2.1.0-loop-option.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 15366aa..6b3e617 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ffmpeg-loop", "description": "Instantiate ffmpeg in looping mode.", - "version": "2.1.0-loop-option.0", + "version": "2.1.0-loop-option.1", "author": "Tim Allen ", "license": "BSD", "main": "src/index.js", From d5b560133ce97369c54e8b7b8fd64d14a46c4712 Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Thu, 19 Mar 2020 15:27:40 +1100 Subject: [PATCH 12/20] test + debug --- package.json | 1 + src/index.js | 2 ++ test/index.js | 26 ++++++++++++++++++++++++++ 3 files changed, 29 insertions(+) diff --git a/package.json b/package.json index 6b3e617..909ce2d 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "src" ], "dependencies": { + "debug": "^4.1.1", "fluent-ffmpeg": "^2.1.2", "lodash.last": "^3.0.0", "ow": "^0.17.0" diff --git a/src/index.js b/src/index.js index d8c0c6d..9ececb5 100644 --- a/src/index.js +++ b/src/index.js @@ -3,6 +3,7 @@ */ const cropFilter = require('./crop'); +const debug = require('debug'); const ffmpeg = require('fluent-ffmpeg'); const last = require('lodash.last'); const ow = require('ow'); @@ -67,5 +68,6 @@ module.exports = function (filename, opts) { filters.push(crop); } if (filters.length) command.complexFilter(filters); + command.once('start', debug); return command; }; diff --git a/test/index.js b/test/index.js index cb2c146..6a19215 100644 --- a/test/index.js +++ b/test/index.js @@ -59,6 +59,32 @@ describe('ffmpeg loop', function () { await pEvent(command, 'error'); }); + it('should apply a crop filter & not loop ok', async function () { + this.timeout(5000); + const opts = { + height: 28, + width: 50, + fps: 30, + cropWidth: 12, + cropHeight: 24, + cropX: 0, + cropY: 0, + loop: false + }; + const command = ffmpegLoop( + path.join(__dirname, 'fixtures/user_video-30.mp4'), + opts + ); + const { cmd, stream } = await start(command); + expect(cmd).to.match(/crop=12:24:0:0/); + expect(cmd).to.not.match(/stream_loop/); + + const data = await pEvent(stream, 'data'); + expect(data).to.be.ok(); + command.kill(); + await pEvent(command, 'error'); + }); + it('allows you to NOT loop, but you can still go past the end.', async function () { this.timeout(5000); const opts = { From c09017adef88256d39e9847070398f8230ab1ed3 Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Thu, 19 Mar 2020 15:29:10 +1100 Subject: [PATCH 13/20] 2.1.0-loop-option.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 909ce2d..368abc9 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ffmpeg-loop", "description": "Instantiate ffmpeg in looping mode.", - "version": "2.1.0-loop-option.1", + "version": "2.1.0-loop-option.2", "author": "Tim Allen ", "license": "BSD", "main": "src/index.js", From b64c7a84404124ad175ab0693db7bc42aba7be08 Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Thu, 19 Mar 2020 15:43:03 +1100 Subject: [PATCH 14/20] correctly debug() --- src/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.js b/src/index.js index 9ececb5..c00a51e 100644 --- a/src/index.js +++ b/src/index.js @@ -3,7 +3,7 @@ */ const cropFilter = require('./crop'); -const debug = require('debug'); +const debug = require('debug')('ffmpeg-loop'); const ffmpeg = require('fluent-ffmpeg'); const last = require('lodash.last'); const ow = require('ow'); From d0dd7999c088bc5f944d9d294ba86a3fab5b456c Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Thu, 19 Mar 2020 15:44:00 +1100 Subject: [PATCH 15/20] 2.1.0-loop-option.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 368abc9..98f95d1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ffmpeg-loop", "description": "Instantiate ffmpeg in looping mode.", - "version": "2.1.0-loop-option.2", + "version": "2.1.0-loop-option.3", "author": "Tim Allen ", "license": "BSD", "main": "src/index.js", From cf238c3268952ebdec8da395a53ef71c2a6ae6c1 Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Thu, 19 Mar 2020 16:21:49 +1100 Subject: [PATCH 16/20] Use ffmpeg-static@4 --- package.json | 4 ++-- src/index.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 98f95d1..22d8d9c 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "devDependencies": { "chai": "^3.5.0", "dirty-chai": "^1.2.2", - "ffmpeg-static": "^2.1.0", + "ffmpeg-static": "^4.0.1", "jsdoc-to-markdown": "~3.0.0", "mocha": "^7.1.0", "nyc": "^10.1.2", @@ -44,7 +44,7 @@ "semistandard": "*" }, "peerDependencies": { - "ffmpeg-static": "^2.1.0" + "ffmpeg-static": "^4" }, "keywords": [], "nyc": { diff --git a/src/index.js b/src/index.js index c00a51e..9c627dd 100644 --- a/src/index.js +++ b/src/index.js @@ -8,7 +8,7 @@ const ffmpeg = require('fluent-ffmpeg'); const last = require('lodash.last'); const ow = require('ow'); -ffmpeg.setFfmpegPath(require('ffmpeg-static').path); +ffmpeg.setFfmpegPath(require('ffmpeg-static')); /** * Creates an ffmpeg command to loop a video. From ede4c7aba2ea2766f842d0bf53a4c6b9968d5107 Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Thu, 19 Mar 2020 16:24:45 +1100 Subject: [PATCH 17/20] 2.1.0-loop-option.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 22d8d9c..808b349 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ffmpeg-loop", "description": "Instantiate ffmpeg in looping mode.", - "version": "2.1.0-loop-option.3", + "version": "2.1.0-loop-option.4", "author": "Tim Allen ", "license": "BSD", "main": "src/index.js", From aff9e8442318e39e3519807a336eb6f6ec8286c8 Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Fri, 20 Mar 2020 10:03:28 +1100 Subject: [PATCH 18/20] doco --- README.md | 19 +++++++++---------- src/index.js | 9 +++++---- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 37a51c5..e8e7d79 100644 --- a/README.md +++ b/README.md @@ -21,14 +21,12 @@ command.kill(); ## API - - -## ffmpeg-loop ### module.exports(filename) ⇒ ⏏ -Creates an infinitely looping readable stream from a video. -Note: All crop dimensions are for the original video size (not the output size). +Creates an ffmpeg command to loop a video. +Note: All crop dimensions are for the original video size (not the output +size). **Kind**: Exported function **Returns**: A fluent ffmpeg process - has pipe() method. @@ -36,14 +34,15 @@ Note: All crop dimensions are for the original video size (not the output size). | Param | Type | Description | | --- | --- | --- | | filename | string | path to video | -| opts.fps | integer | | -| opts.width | integer | output width | -| opts.height | integer | output height | -| opts.cropWidth | integer | crop width (width and height are required). | | opts.cropHeight | integer | crop height | +| opts.cropWidth | integer | crop width (width and height are required). | | opts.cropX | integer | crop x (x and y are optional. If not set, the default is the center position of the video). | | opts.cropY | integer | crop y | -| opts.start | float | seek to this time before starting. Must be less than video length.| +| opts.fps | integer | | +| opts.height | integer | output height | +| opts.loop | boolean | whether to loop the source clip (defaults to true) | +| opts.start | float | seek to this time before starting. Must be less | +| opts.width | integer | output width than video length. | Note: To regenerate this section from the jsdoc run `npm run docs` and paste the output above. diff --git a/src/index.js b/src/index.js index 9c627dd..29a5312 100644 --- a/src/index.js +++ b/src/index.js @@ -16,15 +16,16 @@ ffmpeg.setFfmpegPath(require('ffmpeg-static')); * size). * * @param {string} filename - path to video - * @param {integer} opts.fps - * @param {integer} opts.width - output width - * @param {integer} opts.height - output height - * @param {integer} opts.cropWidth - crop width (width and height are required). * @param {integer} opts.cropHeight - crop height + * @param {integer} opts.cropWidth - crop width (width and height are required). * @param {integer} opts.cropX - crop x (x and y are optional. If not set, the * default is the center position of the video). * @param {integer} opts.cropY - crop y + * @param {integer} opts.fps + * @param {integer} opts.height - output height + * @param {boolean} opts.loop - whether to loop the source clip (defaults to true) * @param {float} opts.start - seek to this time before starting. Must be less + * @param {integer} opts.width - output width * than video length. * @returns A fluent ffmpeg process - has pipe() method. */ From dca2982fa8d213bd7e4a082d0f65e7e290e3ba8f Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Fri, 20 Mar 2020 12:21:08 +1100 Subject: [PATCH 19/20] Apply CR. --- src/index.js | 41 +++++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/src/index.js b/src/index.js index 29a5312..f1d1ac4 100644 --- a/src/index.js +++ b/src/index.js @@ -10,6 +10,21 @@ const ow = require('ow'); ffmpeg.setFfmpegPath(require('ffmpeg-static')); +/** + * In place update filters to append filter. + * @param {Array} filters + * @param {object} filter + */ +function appendFilter (filters, filter) { + const length = filters.length; + if (length > 0) { + const lastPad = `f${length - 1}`; + last(filters).outputs = lastPad; + filter.inputs = lastPad; + } + filters.push(filter); +} + /** * Creates an ffmpeg command to loop a video. * Note: All crop dimensions are for the original video size (not the output @@ -36,17 +51,17 @@ module.exports = function (filename, opts) { width: ow.number.integer })); const { fps, height, loop = true, start = 0, width } = opts; - const filters = loop ? [ - { filter: 'concat', options: { n: 2, v: 1, a: 0 }, outputs: 'concat' }, - { + const filters = []; + + if (loop) { + appendFilter(filters, { filter: 'concat', options: { n: 2, v: 1, a: 0 } }); + appendFilter(filters, { filter: 'setpts', - inputs: 'concat', options: 'N/(FRAME_RATE*TB)' - } - ] : [ - // repeat last frame forever if not looping - { filter: 'tpad', options: { stop: -1, stop_mode: 'clone' } } - ]; + }); + // repeat last frame forever if not looping + } else appendFilter(filters, { filter: 'tpad', options: { stop: -1, stop_mode: 'clone' } }); + const command = ffmpeg() // Using -ss and -stream_loop together does not work well, so we have a // single non-looped version first to seek on. @@ -61,13 +76,7 @@ module.exports = function (filename, opts) { .outputOption('-s', `${width}x${height}`) .outputOption('-r', fps); const crop = cropFilter(opts); - if (crop) { - if (filters.length) { - last(filters).outputs = 'toCrop'; - crop.inputs = 'toCrop'; - } - filters.push(crop); - } + if (crop) appendFilter(filters, crop); if (filters.length) command.complexFilter(filters); command.once('start', debug); return command; From b4f5c538205a79bfe5bd459d669943c159e7e5cd Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Fri, 20 Mar 2020 12:40:03 +1100 Subject: [PATCH 20/20] name arg for appendFilter() --- src/index.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/index.js b/src/index.js index f1d1ac4..c092805 100644 --- a/src/index.js +++ b/src/index.js @@ -14,11 +14,12 @@ ffmpeg.setFfmpegPath(require('ffmpeg-static')); * In place update filters to append filter. * @param {Array} filters * @param {object} filter + * @param {string} [name] */ -function appendFilter (filters, filter) { +function appendFilter (filters, filter, name) { const length = filters.length; if (length > 0) { - const lastPad = `f${length - 1}`; + const lastPad = `${name || filter.filter}${length - 1}`; last(filters).outputs = lastPad; filter.inputs = lastPad; } @@ -54,13 +55,13 @@ module.exports = function (filename, opts) { const filters = []; if (loop) { - appendFilter(filters, { filter: 'concat', options: { n: 2, v: 1, a: 0 } }); + appendFilter(filters, { filter: 'concat', options: { n: 2, v: 1, a: 0 } }, 'concat-inputs'); appendFilter(filters, { filter: 'setpts', options: 'N/(FRAME_RATE*TB)' - }); + }, 'redo-timecodes'); // repeat last frame forever if not looping - } else appendFilter(filters, { filter: 'tpad', options: { stop: -1, stop_mode: 'clone' } }); + } else appendFilter(filters, { filter: 'tpad', options: { stop: -1, stop_mode: 'clone' } }, 'pad-at-end'); const command = ffmpeg() // Using -ss and -stream_loop together does not work well, so we have a