Skip to content

Commit 7b1c34c

Browse files
committed
add screenshots and handle --open
1 parent 5eeaf10 commit 7b1c34c

File tree

7 files changed

+149
-37
lines changed

7 files changed

+149
-37
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
*.mp4
2+
*.png
23
node_modules/
34

45
*.log

index.js

Lines changed: 58 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,45 +3,72 @@ const { join } = require("path")
33
const chalk = require("kleur")
44
const { init } = require("./src/adb")
55
const { existsSync } = require("fs")
6-
const { countdown } = require("./src/countdown")
76
const { cli } = require("./src/cli")
87
const { normalize } = require("path")
9-
const { setVisibleTouches, getVisibleTouches } = require("./src/visibleTouches")
108
const { recordVideo } = require("./src/recordVideo")
9+
const { takeScreenshot } = require("./src/takeScreenshot")
10+
const open = require("open")
1111

1212
const version = require(join(__dirname, "./package.json")).version
1313

14-
console.log(chalk.bold("record-android-screen"), version, "\n")
14+
console.log(chalk.bold("android-capture"), version, "\n")
15+
16+
/**
17+
* @type {Record<string, boolean>}
18+
*/
19+
const videoTypes = {
20+
video: true,
21+
movie: true,
22+
mp4: true,
23+
}
24+
25+
/**
26+
* @type {Record<string, boolean>}
27+
*/
28+
const imageTypes = {
29+
image: true,
30+
screenshot: true,
31+
still: true,
32+
picture: true,
33+
png: true,
34+
}
1535

1636
async function run() {
1737
try {
18-
const [filename, ...others] = cli.input
38+
const [type = "", filename, ...others] = cli.input
39+
40+
const mode =
41+
type in imageTypes ? "image" : type in videoTypes ? "video" : null
42+
43+
if (!mode) {
44+
cli.showHelp(1)
45+
}
1946

2047
if (others.length) {
2148
console.error("Unexpected arguements: ", others.join(" "))
2249
cli.showHelp(1)
2350
}
2451

25-
const outPath = normalize(sanitizeFilename(filename) ?? generateFilename())
26-
27-
await init()
28-
await countdown()
52+
const extension = mode === "image" ? "png" : "mp4"
53+
const defaultFilename =
54+
mode === "image" ? "android-screenshot" : "android-video"
2955

30-
const hasVisibleTouchesByDefault = getVisibleTouches()
56+
const outPath = normalize(
57+
sanitizeFilename(filename, extension) ??
58+
generateFilename(defaultFilename, extension),
59+
)
3160

32-
setVisibleTouches(true)
61+
await init()
3362

34-
try {
35-
const result = await recordVideo(outPath)
36-
if (!result.escaped) {
37-
console.log("That's a wrap!", chalk.bold(outPath))
38-
}
39-
console.log()
40-
} catch (e) {
41-
console.error("failed in main loop", e)
63+
if (mode === "video") {
64+
await recordVideo(outPath)
65+
} else {
66+
await takeScreenshot(outPath)
4267
}
4368

44-
setVisibleTouches(hasVisibleTouchesByDefault)
69+
if (cli.flags.open) {
70+
open(outPath)
71+
}
4572

4673
process.exit(0)
4774
} catch (e) {
@@ -50,18 +77,22 @@ async function run() {
5077
}
5178
}
5279

53-
function generateFilename() {
80+
/**
81+
* @param {string} name
82+
* @param {string} extension
83+
*/
84+
function generateFilename(name, extension) {
5485
const date = new Date()
5586
const year = date.getFullYear()
5687
const month = date.getMonth() + 1
5788
const day = date.getDate()
5889

59-
let filename = `./recording.${year}-${month}-${day}.00.mp4`
90+
let filename = `./${name}.${year}-${month}-${day}.00.${extension}`
6091
let i = 1
6192
while (existsSync(filename)) {
62-
filename = `./recording.${year}-${month}-${day}.${i
93+
filename = `./${name}.${year}-${month}-${day}.${i
6394
.toString()
64-
.padStart(2, "0")}.mp4`
95+
.padStart(2, "0")}.${extension}`
6596
i++
6697
}
6798
return filename
@@ -70,13 +101,14 @@ function generateFilename() {
70101
/**
71102
*
72103
* @param {string | null | undefined} filename
104+
* @param {string} extension
73105
*/
74-
function sanitizeFilename(filename) {
106+
function sanitizeFilename(filename, extension) {
75107
if (!filename) {
76108
return null
77109
}
78-
if (!filename.endsWith(".mp4")) {
79-
return filename + ".mp4"
110+
if (!filename.endsWith("." + extension)) {
111+
return filename + "." + extension
80112
} else {
81113
return filename
82114
}

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"name": "record-android-screen",
2+
"name": "android-capture",
33
"version": "1.0.0",
44
"main": "index.js",
55
"author": "David Sheldrick",
@@ -12,6 +12,7 @@
1212
"dependencies": {
1313
"kleur": "^4.1.4",
1414
"log-update": "^4.0.0",
15-
"meow": "^9.0.0"
15+
"meow": "^9.0.0",
16+
"open": "^8.0.4"
1617
}
1718
}

src/cli.js

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,35 @@
1+
const { bold } = require("kleur")
12
const meow = require("meow")
23

34
module.exports.cli = meow(
45
`
56
Usage
6-
$ npx record-android-screen [<out-file>] [...options]
7+
8+
$ npx android-capture video [<out-file>] [...options]
9+
$ npx android-capture image [<out-file>] [...options]
710
811
Options
9-
--full-res Record in full resolution (may not work in emulators)
10-
--open Open the video after saving
11-
--no-countdown Prevent the 3..2..1.. countdown
12-
--help, -h Show this help text
12+
13+
${bold("--full-res")}
14+
Record video in full resolution (may not work in emulators).
15+
Images are always full-resolution.
16+
17+
${bold("--open")}
18+
Open the file after saving
19+
20+
${bold("--no-countdown")}
21+
Prevent the 3..2..1.. countdown when recording video.
22+
23+
${bold("--help, -h")}
24+
Show this help text
1325
1426
Examples
15-
$ npx record-android-screen
1627
17-
$ npx record-android-screen ./my-recording.mp4
28+
$ npx android-capture video
29+
30+
$ npx android-capture video ./my-recording.mp4 --ful-res
1831
19-
$ npx record-android-screen --open
32+
$ npx android-capture image --open
2033
`,
2134
{
2235
flags: {

src/recordVideo.js

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ const { red, green, bold, gray } = require("kleur")
22
const logUpdate = require("log-update")
33
const { adb, adbAsync } = require("./adb")
44
const { cli } = require("./cli")
5+
const { countdown } = require("./countdown")
56
const { pushKeyboardContext, popKeyboardContext } = require("./keyboardInput")
7+
const { getVisibleTouches, setVisibleTouches } = require("./visibleTouches")
68

79
function getScreenSize() {
810
const output = adb("shell", "wm", "size").stdout.toString()
@@ -26,11 +28,34 @@ const ENTER_KEYS = {
2628
" ": true,
2729
}
2830

31+
/**
32+
* @param {string} outFile
33+
*/
34+
module.exports.recordVideo = async (outFile) => {
35+
await countdown()
36+
37+
const hasVisibleTouchesByDefault = getVisibleTouches()
38+
39+
setVisibleTouches(true)
40+
41+
try {
42+
const result = await createRecording(outFile)
43+
if (!result.escaped) {
44+
console.log("That's a wrap!", bold(outFile))
45+
}
46+
console.log()
47+
} catch (e) {
48+
console.error("failed in main loop", e)
49+
}
50+
51+
setVisibleTouches(hasVisibleTouchesByDefault)
52+
}
53+
2954
/**
3055
* @param {string} outFile
3156
* @returns {Promise<{escaped: boolean}>}
3257
*/
33-
module.exports.recordVideo = async function recordVideo(outFile) {
58+
async function createRecording(outFile) {
3459
const internalFilePath = `/sdcard/android-screen-recording.mp4`
3560
const screenSize = getScreenSize()
3661

src/takeScreenshot.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
const { bold } = require("kleur")
2+
const { adb } = require("./adb")
3+
4+
/**
5+
* @param {string} outFile
6+
*/
7+
module.exports.takeScreenshot = async (outFile) => {
8+
const internalFilePath = "/sdcard/android-capture-image.png"
9+
console.log("Taking screenshot...\n")
10+
adb("shell", "screencap", "-p", internalFilePath)
11+
adb("pull", internalFilePath, outFile)
12+
adb("shell", "rm", internalFilePath)
13+
console.log("Image saved to", bold(outFile))
14+
}

yarn.lock

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,11 @@ decamelize@^1.1.0, decamelize@^1.2.0:
141141
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
142142
integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
143143

144+
define-lazy-prop@^2.0.0:
145+
version "2.0.0"
146+
resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f"
147+
integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==
148+
144149
emoji-regex@^8.0.0:
145150
version "8.0.0"
146151
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
@@ -217,6 +222,11 @@ is-core-module@^2.2.0:
217222
dependencies:
218223
has "^1.0.3"
219224

225+
is-docker@^2.0.0, is-docker@^2.1.1:
226+
version "2.1.1"
227+
resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.1.1.tgz#4125a88e44e450d384e09047ede71adc2d144156"
228+
integrity sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw==
229+
220230
is-fullwidth-code-point@^3.0.0:
221231
version "3.0.0"
222232
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
@@ -227,6 +237,13 @@ is-plain-obj@^1.1.0:
227237
resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e"
228238
integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4=
229239

240+
is-wsl@^2.2.0:
241+
version "2.2.0"
242+
resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271"
243+
integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==
244+
dependencies:
245+
is-docker "^2.0.0"
246+
230247
js-tokens@^4.0.0:
231248
version "4.0.0"
232249
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
@@ -350,6 +367,15 @@ onetime@^5.1.0:
350367
dependencies:
351368
mimic-fn "^2.1.0"
352369

370+
open@^8.0.4:
371+
version "8.0.4"
372+
resolved "https://registry.yarnpkg.com/open/-/open-8.0.4.tgz#2fb90debffcf20f4d7be537502ed3e3ee9e5dcbc"
373+
integrity sha512-Txc9FOcvjrr5Kv+Zb3w89uKMKiP7wH8mLdYj1xJa+YnhhntEYhbB6cQHjS4O6P+jFwMEzEQVVcpfnu9WkKNuLQ==
374+
dependencies:
375+
define-lazy-prop "^2.0.0"
376+
is-docker "^2.1.1"
377+
is-wsl "^2.2.0"
378+
353379
p-limit@^2.2.0:
354380
version "2.3.0"
355381
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"

0 commit comments

Comments
 (0)