Skip to content

Commit

Permalink
Merge pull request #1 from zaripych/fix/listing-on-macos
Browse files Browse the repository at this point in the history
fix: listing devices on MacOS now works
  • Loading branch information
zaripych authored Oct 27, 2019
2 parents b27d370 + d590401 commit 8c88511
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 59 deletions.
11 changes: 6 additions & 5 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
.DS_Store
.jest-cache
.pnp
bundled
coverage
coverage-*
ffmpeg
lib
node_modules
.jest-cache
yarn-error.log
package
.pnp
bundled
ffmpeg
yarn-error.log
121 changes: 69 additions & 52 deletions src/helpers/captureVideo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,32 @@ interface IVideoCaptureOpts {
rate: number;
ffmpegPath: string;
ffmpegInputArgs?: string[];
ffmpegOutputArgs?: string[];
verbosity?: 0 | 1 | 2;
}

export function captureVideo(optsRaw: IVideoCaptureOpts) {
return new Observable<Buffer>(subscriber => {
const opts = {
verbosity: 0,
verbosity: 0 as const,
...optsRaw,
};

const inputArgs =
opts.ffmpegInputArgs || determinePlatformSpecificInputArguments(optsRaw);
opts.ffmpegInputArgs ||
determinePlatformSpecificInputArguments({
device: opts.device,
input: opts.input,
size: {
width: opts.width,
height: opts.height,
},
rate: opts.rate,
});

const [pixelFormat, bytesPerPixel] = ['rgba', 4];

const outputArgs = [
const outputArgs = opts.ffmpegOutputArgs || [
'-f',
'image2pipe',
'-s',
Expand All @@ -45,9 +55,40 @@ export function captureVideo(optsRaw: IVideoCaptureOpts) {
'pipe:1',
];

const videoOutput = captureVideoCore({
ffmpegPath: opts.ffmpegPath,
ffmpegInputArgs: inputArgs,
ffmpegOutputArgs: outputArgs,
verbosity: opts.verbosity,
}).pipe(
formFrames({
width: opts.width,
height: opts.height,
bytesPerPixel,
})
);

subscriber.add(videoOutput.subscribe(subscriber));
});
}

interface IVideoCaptureCoreOpts {
ffmpegPath: string;
ffmpegInputArgs: string[];
ffmpegOutputArgs: string[];
verbosity?: 0 | 1 | 2;
}

export function captureVideoCore(optsRaw: IVideoCaptureCoreOpts) {
return new Observable<Buffer>(subscriber => {
const opts = {
verbosity: 0,
...optsRaw,
};

const ffmpeg = spawnWithLogging({
executable: opts.ffmpegPath,
arguments: [...inputArgs, ...outputArgs],
arguments: [...opts.ffmpegInputArgs, ...opts.ffmpegOutputArgs],
allowedExitCodes: [0, 255],

error: subscriber.error.bind(subscriber),
Expand All @@ -59,13 +100,7 @@ export function captureVideo(optsRaw: IVideoCaptureOpts) {
].filter(isTruthy),
});

const videoOutput = readableToObservable(ffmpeg.stdout).pipe(
formFrames({
width: opts.width,
height: opts.height,
bytesPerPixel,
})
);
const videoOutput = readableToObservable(ffmpeg.stdout);

subscriber.add(videoOutput.subscribe(subscriber));
subscriber.add(() => {
Expand All @@ -74,57 +109,39 @@ export function captureVideo(optsRaw: IVideoCaptureOpts) {
});
}

function determinePlatformSpecificInputArguments(opts: IVideoCaptureOpts) {
function determinePlatformSpecificInputArguments(opts: {
device?: string;
input: string;
size?: {
width: number;
height: number;
};
rate?: number;
}) {
function inputCaps(rateOpt = '-framerate', sizeOpt = '-video_size') {
const rateOpts =
(typeof opts.rate === 'number' && [rateOpt, opts.rate.toFixed(0)]) || [];
const sizeOpts =
(opts.size && [sizeOpt, `${opts.size.width}x${opts.size.height}`]) || [];
return [...rateOpts, ...sizeOpts];
}

const formatOpts = inputCaps();

if (opts.device) {
return [
'-f',
opts.device,
'-framerate',
opts.rate.toFixed(0),
'-s',
`${opts.width}x${opts.height}`,
'-i',
opts.input,
];
return ['-f', opts.device, ...formatOpts, '-i', opts.input];
}

if (process.platform === 'win32') {
return [
'-f',
'dshow',
`-framerate`,
opts.rate.toFixed(0),
'-video_size',
`${opts.width}x${opts.height}`,
'-i',
`video=${opts.input}`,
];
return ['-f', 'dshow', ...formatOpts, '-i', `video=${opts.input}`];
}

if (process.platform === 'darwin') {
return [
'-f',
'avfoundation',
'-framerate',
opts.rate.toFixed(0),
'-s',
`${opts.width}x${opts.height}`,
'-i',
`${opts.input}:`,
];
return ['-f', 'avfoundation', ...formatOpts, '-i', `${opts.input}:`];
}

if (process.platform === 'linux') {
return [
'-f',
'v4l2',
'-framerate',
opts.rate.toFixed(0),
'-s',
`${opts.width}x${opts.height}`,
'-i',
opts.input,
];
return ['-f', 'v4l2', ...formatOpts, '-i', opts.input];
}

throw new Error(
Expand Down
5 changes: 3 additions & 2 deletions src/helpers/listDevices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ function extractDirectShowVideoDevices(ffmpegOutput: string) {
function extractAVFoundationVideoDevices(ffmpegOutput: string) {
const delimiterStart = 'AVFoundation video devices:';
const delimiterEnd = 'AVFoundation audio devices:';
const regex = /^\[[^\[\]]+\]\s+\[\d+\]\s+(.+)\s*$/gm;
const regex = /^\[[^\[\]]+\]\s+\[(\d+)\]\s+(.+)\s*$/gm;

return extractDevicesList(ffmpegOutput, regex, delimiterStart, delimiterEnd);
}
Expand All @@ -40,12 +40,13 @@ export async function listAvailableVideoDevices(
if (process.platform === 'win32' || process.platform === 'darwin') {
const result = await spawnOutput(
{
allowedExitCodes: [0, 1],
executable: tools.ffmpegPath,
arguments: [
'-list_devices',
'true',
'-f',
process.platform === 'win32' ? 'dshow' : 'darwin',
process.platform === 'win32' ? 'dshow' : 'avfoundation',
'-i',
'dummy',
],
Expand Down

0 comments on commit 8c88511

Please sign in to comment.