๐Ÿ–ฅ๏ธ๐ŸŽฅ JavaScript๋กœ ์ž๋™ ํ™”๋ฉด ๋…นํ™”

10208 ๋‹จ์–ด opensourcejavascriptmac
macOS defaults recording feature ์„ ๋นŒ๋“œํ•  ๋•Œ ๊ด€๋ฆฌ์ž ์ž‘์—…์„ ์ตœ๋Œ€ํ•œ ์ž๋™ํ™”ํ•˜๊ณ  ์‹ถ์—ˆ์Šต๋‹ˆ๋‹ค. ์ฆ‰, ์Šคํฌ๋ฆฐ์ƒท๊ณผ ๋…น์Œ์„ ์Šคํฌ๋ฆฝํŒ…ํ•˜๊ณ  ์ƒˆ macOS ๋ฒ„์ „์ด ๋‚˜์˜ฌ ๋•Œ ๋‹ค์‹œ ์‹คํ–‰ํ•˜๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค. ๋‚˜์—๊ฒŒ ๋งŽ์€ ๋„์›€์ด ๋œ ๋‘ ๊ฐ€์ง€ ํŒจํ‚ค์ง€๋ฅผ ์ฐพ์•˜์Šต๋‹ˆ๋‹ค.
  • aperture-node์„ ์‚ฌ์šฉํ•˜์—ฌ ์ „์ฒด Mac ํ™”๋ฉด์„ ๊ธฐ๋กํ•˜์‹ญ์‹œ์˜ค.

  • robot.js ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๋ฐฉ์‹์œผ๋กœ ๋งˆ์šฐ์Šค๋ฅผ ์ด๋™ํ•˜๊ณ  ํ‚ค๋ณด๋“œ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค(๋ชจ๋“  OS์—์„œ ์ž‘๋™ํ•ด์•ผ ํ•จ).

  • ์‚ฌ์šฉ ๋ฐฉ๋ฒ•์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

    const aperture = require('aperture')()
    const robot = require('robotjs')
    const delay = require('delay')
    const { compressVideo } = require('../../utils')
    
    async function record() {
      // ...
    
      robot.moveMouse(pos1.x, pos1.y)
    
      // Action!
      await aperture.startRecording({ highlightClicks: true, cropArea })
    
      robot.moveMouseSmooth(pos2.x, pos2.y, 2)
      await delay(1000)
      robot.moveMouseSmooth(pos3.x, pos3.y, 5)
      await delay(100)
      robot.moveMouseSmooth(pos1.x, pos1.y, 10)
      await delay(500)
    
      const tmpRecordingPath = await aperture.stopRecording()
      // End recording
    
      try {
        await compressVideo(tmpRecordingPath, outputPath)
      } catch (compressVideoError) {
        throw new Error(compressVideoError)
      }
    }
    



    ์—ฌ๊ธฐ์„œ ๋ฌด์Šจ ์ผ์ด ์ผ์–ด๋‚˜๋Š”์ง€ ์„ค๋ช…ํ•ฉ์‹œ๋‹ค.

    robot.moveMouse(pos1.x, pos1.y)
    

    robot.jsmoveMouse ๋ฉ”์„œ๋“œ... ๋งˆ์šฐ์Šค๋ฅผ ์›€์ง์—ฌ ๋ณด์„ธ์š”. ์ง€์ฒด ์—†์ด ๋ฐ”๋กœ ํ•ด์ค€๋‹ค.
    x ๊ฐ’์€ ํ™”๋ฉด์˜ ์™ผ์ชฝ ํ…Œ๋‘๋ฆฌ์—์„œ ์„ค์ •๋ฉ๋‹ˆ๋‹ค. y ๊ฐ’์€ ์œ„์ชฝ ํ…Œ๋‘๋ฆฌ์—์„œ ๊ฐ€์ ธ์˜จ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

    robot.moveMouseSmooth(pos2.x, pos2.y, 2)
    
    moveMouseSmooth "์ธ๊ฐ„์ฒ˜๋Ÿผ"ํ•ฉ๋‹ˆ๋‹ค. ์™„๋ฒฝํ•˜์ง€๋Š” ์•Š์ง€๋งŒ ์ถฉ๋ถ„ํ•ฉ๋‹ˆ๋‹ค. ์„ธ ๋ฒˆ์งธ ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” ๋งˆ์šฐ์Šค ์ด๋™ ์†๋„๋ฅผ ์กฐ์ •ํ•ฉ๋‹ˆ๋‹ค.

    ๋งˆ์ง€๋ง‰ ์ž‘์—…์ด ๋‹ค๋ฅธ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๊ธฐ ์ „์— ์ข…๋ฃŒ๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด delay ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ž‘์—… ์‚ฌ์ด์— ์•ฝ๊ฐ„์˜ ์ง€์—ฐ์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

    ๋‚ด๊ฐ€ ์‚ฌ์šฉํ•œ ๋‹ค๋ฅธ robots.js ๋ฐฉ๋ฒ•:

    const { width, height } = robot.getScreenSize()
    
    robot.keyTap('g', ['command', 'shift'])
    
    const pic = robot.screen.capture(x, y, width, height)
    

    ๊ฐ„๋‹จํ•ฉ๋‹ˆ๋‹ค!

    Aperture๋กœ ๋„˜์–ด๊ฐ‘์‹œ๋‹ค.

    Aperture๋Š” ๋›ฐ์–ด๋‚œ ์„ฑ๋Šฅ์œผ๋กœ AVFoundation framework์„ ์‚ฌ์šฉํ•˜๋Š” ์ €์ˆ˜์ค€ Swift ์Šคํฌ๋ฆฝํŠธ์ž…๋‹ˆ๋‹ค. Kap ์ด๋ผ๋Š” ์˜คํ”ˆ ์†Œ์Šค ์Šคํฌ๋ฆฐ ๋ ˆ์ฝ”๋” ์š”๊ตฌ ์‚ฌํ•ญ์„ ์ถฉ์กฑํ•˜๋„๋ก ์ œ์ž‘๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

    Node API๋Š” ๋งค์šฐ ๊ฐ„๋‹จํ•ฉ๋‹ˆ๋‹ค.

    const options = {
      cropArea: {
        x: pos2.x - recordWidth / 2, y: 0,
        width: recordWidth, height: recordHeight
      },
      highlightClicks: true
    }
    
    await aperture.startRecording(options)
    

    CropAreax ๊ฐ’์€ ํ™”๋ฉด์˜ ์™ผ์ชฝ ํ…Œ๋‘๋ฆฌ์—์„œ ์„ค์ •๋ฉ๋‹ˆ๋‹ค. ์•„๋ž˜์ชฝ ํ…Œ๋‘๋ฆฌ์˜ y ๊ฐ’์ž…๋‹ˆ๋‹ค. robots.js์™€ ๋™์ผํ•œ ์ฐธ์กฐ๊ฐ€ ์•„๋‹ˆ๋ฏ€๋กœ ์ฃผ์˜ํ•ด์•ผ ํ–ˆ์Šต๋‹ˆ๋‹ค!

    const tmpRecordingPath = await aperture.stopRecording()
    //=> '/private/var/folders/3x/jf5977fn79jbglr7rk0tq4d00000gn/T/cdf4f7df426c97880f8c10a1600879f7.mp4'
    
    stopRecording ๋ฉ”์„œ๋“œ๋Š” ๋น„๋””์˜ค๊ฐ€ ์ €์žฅ๋˜๋Š” ๊ฒฝ๋กœ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

    ๊ทธ๋Ÿฐ ๋‹ค์Œ ํ™”๋ฉด ๋…นํ™”๋ฅผ ์‚ฌํ›„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ œ ๊ฒฝ์šฐ์—๋Š” ํฌ๊ธฐ๋ฅผ ์กฐ์ •ํ•˜๊ณ  ์••์ถ•ํ•˜๊ณ  ๋‹ค๋ฅธ ํด๋”๋กœ ์ด๋™ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค.

    ๋ถˆํ–‰ํžˆ๋„, ํ•ด๊ฒฐ ์‹œํ–‰์„ ์œ„ํ•œ ๊ฐ•๋ ฅํ•œ ์†”๋ฃจ์…˜์„ ์ฐพ์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ๋‹ค๋ฅธ ์„ค์ •์—์„œ ๊ฒฐ๊ณผ๊ฐ€ 100% ๋™์ผํ•˜๋‹ค๊ณ  ๋ณด์žฅํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

    ๊ทธ๊ฒŒ ๋‹ค์•ผ! ๊ฐ„๋‹จํ•˜์ง€ ์•Š์Šต๋‹ˆ๊นŒ? ์˜๊ฒฌ ์„น์…˜์—์„œ ์–ด๋–ป๊ฒŒ ์ƒ๊ฐํ•˜๋Š”์ง€ ์•Œ๋ ค์ฃผ์‹ญ์‹œ์˜ค ๐Ÿ™‚

    ์ž๋™ ํ™”๋ฉด ๋…นํ™”์— ๋Œ€ํ•ด ๋” ์ž์„ธํžˆ ์•Œ๊ณ  ์‹ถ๋‹ค๋ฉด macOS defaults recorder !

    ์ข‹์€ ์›นํŽ˜์ด์ง€ ์ฆ๊ฒจ์ฐพ๊ธฐ