gifski.ts 1.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253
  1. import { spawn } from 'child_process';
  2. import { existsSync, statSync } from 'fs';
  3. import { getLogger } from './loggers';
  4. const logger = getLogger('gifski');
  5. const sizeLimit = 10 * 2 ** 20;
  6. const roundToEven = (n: number) => Math.ceil(n / 2) * 2;
  7. export default async (inputFilePath: string, targetWidth?: number) => {
  8. const outputFilePath = inputFilePath.replace(/(?:\.[^.]*)?$/, '.gif')
  9. try {
  10. const args = [
  11. inputFilePath,
  12. '-o',
  13. outputFilePath,
  14. '--fps',
  15. '12.5',
  16. '--quiet',
  17. '--quality',
  18. '90',
  19. ];
  20. if (typeof(targetWidth) === 'number') {
  21. args.push('--width', roundToEven(targetWidth).toString());
  22. }
  23. logger.info(` gifski ${args.join(' ')}`);
  24. const gifskiSpawn = spawn('gifski', args);
  25. const gifskiResult = new Promise<string>((resolve, reject) => {
  26. const sizeChecker = setInterval(() => {
  27. if (existsSync(outputFilePath) && statSync(outputFilePath).size > sizeLimit) gifskiSpawn.kill();
  28. }, 5000);
  29. gifskiSpawn.on('exit', () => {
  30. clearInterval(sizeChecker);
  31. if (!existsSync(outputFilePath) || statSync(outputFilePath).size === 0) return reject('no file was created on exit');
  32. logger.info(`gif conversion succeeded, file path: ${outputFilePath}`);
  33. resolve(outputFilePath);
  34. });
  35. });
  36. const stderr = [];
  37. gifskiSpawn.stderr.on('data', errdata => stderr.push(errdata));
  38. gifskiSpawn.stderr.on('end', () => {
  39. if (stderr.length !== 0) {
  40. if (!gifskiSpawn.killed) gifskiSpawn.kill();
  41. throw Error(Buffer.concat(stderr).toString());
  42. }
  43. });
  44. return await gifskiResult;
  45. } catch (error) {
  46. logger.error(`error converting video to gif ${error ? `message: ${error}` : ''}`);
  47. throw Error('error converting video to gif');
  48. }
  49. };