diff --git a/index.js b/index.js index 8c0d390..8dfe48f 100644 --- a/index.js +++ b/index.js @@ -7,13 +7,32 @@ const macosVersion = require('macos-version'); const debuglog = util.debuglog('aperture'); +const IS_LINUX = process.platform === 'linux'; +const IS_MACOS = process.platform === 'darwin'; + class Aperture { constructor() { - macosVersion.assertGreaterThanOrEqualTo('10.10'); + if (IS_MACOS) { + macosVersion.assertGreaterThanOrEqualTo('10.10'); + } } getAudioSources() { - return execa.stdout(path.join(__dirname, 'swift/main'), ['list-audio-devices']).then(JSON.parse); + if (IS_MACOS) { + return execa.stdout(path.join(__dirname, 'swift/main'), ['list-audio-devices']).then(JSON.parse); + } else if (IS_LINUX) { + return execa.stdout('arecord', ['-l']).then( + stdout => stdout.split('\n').reduce((result, line) => { + const match = line.match(/card (\d+): ([^,]+),/); + + if (match) { + result.push(`${match[1]}:${match[2]}`); + } + + return result; + }, []) + ); + } } startRecording({ @@ -25,6 +44,8 @@ class Aperture { audioSourceId = 'none' } = {}) { return new Promise((resolve, reject) => { + let cropAreaOpts; + if (this.recorder !== undefined) { reject(new Error('Call `.stopRecording()` first')); return; @@ -45,20 +66,37 @@ class Aperture { return; } - cropArea = `${cropArea.x}:${cropArea.y}:${cropArea.width}:${cropArea.height}`; + cropAreaOpts = `${cropArea.x}:${cropArea.y}:${cropArea.width}:${cropArea.height}`; } - const recorderOpts = [ - this.tmpPath, - fps, - cropArea, - showCursor, - highlightClicks, - displayId, - audioSourceId - ]; + if (IS_MACOS) { + const recorderOpts = [ + this.tmpPath, + fps, + cropAreaOpts || cropArea, + showCursor, + highlightClicks, + displayId, + audioSourceId + ]; + + this.recorder = execa(path.join(__dirname, 'swift', 'main'), recorderOpts); + } else if (IS_LINUX) { + const args = ['-f', 'x11grab']; + + if (typeof cropArea === 'object') { + args.push( + '-video_size', `${cropArea.width}x${cropArea.height}`, + '-i', `:0+${cropArea.x},${cropArea.y}` + ); + } else { + args.push('-i', ':0'); + } + + args.push('-framerate', fps, '-draw_mouse', +(showCursor === true), this.tmpPath); - this.recorder = execa(path.join(__dirname, 'swift', 'main'), recorderOpts); + this.recorder = execa('ffmpeg', args); + } const timeout = setTimeout(() => { // `.stopRecording()` was called already @@ -71,7 +109,7 @@ class Aperture { this.recorder.kill(); delete this.recorder; reject(err); - }, 5000); + }, 7500); this.recorder.catch(err => { clearTimeout(timeout); @@ -80,15 +118,28 @@ class Aperture { }); this.recorder.stdout.setEncoding('utf8'); - this.recorder.stdout.on('data', data => { - debuglog(data); - - if (data.trim() === 'R') { - // `R` is printed by Swift when the recording **actually** starts - clearTimeout(timeout); - resolve(this.tmpPath); - } - }); + if (IS_MACOS) { + this.recorder.stdout.on('data', data => { + debuglog(data); + + if (data.trim() === 'R') { + // `R` is printed by Swift when the recording **actually** starts + clearTimeout(timeout); + resolve(this.tmpPath); + } + }); + } else if (IS_LINUX) { + this.recorder.stderr.on('data', data => { + debuglog(data); + + if (/^frame=\s*\d+\sfps=\s\d+/.test(data.toString('utf8').trim())) { + // fmpeg prints lines like this while it's reocrding + // frame= 203 fps= 30 q=-1.0 Lsize= 54kB time=00:00:06.70 bitrate= 65.8kbits/s dup=21 drop=19 speed=0.996x + clearTimeout(timeout); + resolve(this.tmpPath); + } + }); + } }); } @@ -106,7 +157,12 @@ class Aperture { reject(err.stderr ? new Error(err.stderr) : err); }); - this.recorder.kill(); + if (IS_MACOS) { + this.recorder.kill(); + } else if (IS_LINUX) { + this.recorder.stdin.setEncoding('utf8'); + this.recorder.stdin.write('q'); + } }); } } diff --git a/package.json b/package.json index 8ba1845..ebb0750 100644 --- a/package.json +++ b/package.json @@ -17,8 +17,8 @@ }, "scripts": { "test": "xo", - "build": "cd swift && xcodebuild -derivedDataPath $(mktemp -d) -scheme aperture", - "prepublish": "npm run build" + "build-macos": "cd swift && xcodebuild -derivedDataPath $(mktemp -d) -scheme aperture", + "prepublish": "[ \"$(uname)\" = \"Darwin\" ] && npm run build-macos" }, "files": [ "index.js",