mediasnacks 0.21.0 → 0.22.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +3 -52
  2. package/install-zsh-completions.js +48 -0
  3. package/package.json +3 -4
  4. package/src/avif.js +5 -5
  5. package/src/cli.js +36 -24
  6. package/src/detectdups.js +9 -7
  7. package/src/dropdups.js +6 -14
  8. package/src/edgespic.js +5 -5
  9. package/src/hev1tohvc1.js +5 -5
  10. package/src/moov2front.js +7 -3
  11. package/src/play.js +64 -0
  12. package/src/prores.js +19 -15
  13. package/src/qdir.js +2 -2
  14. package/src/random.js +45 -0
  15. package/src/resize.js +3 -3
  16. package/src/seqcheck.js +2 -2
  17. package/src/sqcrop.js +3 -3
  18. package/src/ssim.js +3 -3
  19. package/src/utils/parseOptions.js +1 -0
  20. package/src/vsplit.js +5 -5
  21. package/src/vtrim.js +5 -5
  22. package/.zsh/completions/_mediasnacks +0 -47
  23. package/install-zsh-completions.sh +0 -23
  24. package/src/avif.test.js +0 -16
  25. package/src/detectdups.test.js +0 -24
  26. package/src/edgespic.test.js +0 -36
  27. package/src/fixtures/60fps.csv +0 -7
  28. package/src/fixtures/60fps.mp4 +0 -0
  29. package/src/fixtures/big-buck-bunny/bbb_24_to_25fps_dup.mp4 +0 -0
  30. package/src/fixtures/big-buck-bunny/bbb_24_to_30fps_dup.mp4 +0 -0
  31. package/src/fixtures/big-buck-bunny/bbb_24_to_48fps_dup.mp4 +0 -0
  32. package/src/fixtures/big-buck-bunny/bbb_24fps_no_dups.mp4 +0 -0
  33. package/src/fixtures/big-buck-bunny/bbb_25_to_30fps_dup.mp4 +0 -0
  34. package/src/fixtures/big-buck-bunny/bbb_25_to_50fps_dup.mp4 +0 -0
  35. package/src/fixtures/big-buck-bunny/bbb_25_to_60fps_dup.mp4 +0 -0
  36. package/src/fixtures/big-buck-bunny/bbb_25fps_no_dups.mp4 +0 -0
  37. package/src/fixtures/big-buck-bunny/generate.md +0 -71
  38. package/src/fixtures/edgespic/60fps_first.png +0 -0
  39. package/src/fixtures/edgespic/60fps_last.png +0 -0
  40. package/src/fixtures/lenna.avif +0 -0
  41. package/src/fixtures/lenna.png +0 -0
  42. package/src/fixtures/qdir-jobs/job1_good.sh +0 -1
  43. package/src/fixtures/qdir-jobs/job2_bad.sh +0 -1
  44. package/src/fixtures/qdir-jobs/job3_good.sh +0 -2
  45. package/src/fixtures/qdir-jobs/job4_bad.sh +0 -1
  46. package/src/flattendir.test.js +0 -36
  47. package/src/qdir.test.js +0 -24
  48. package/src/seqcheck.test.js +0 -27
  49. package/src/utils/fs-utils.test.js +0 -21
  50. package/src/utils/parseOptions.test.js +0 -59
  51. package/src/vconcat.test.js +0 -23
  52. package/src/vsplit.test.js +0 -32
  53. package/src/vtrim.test.js +0 -40
  54. /package/src/utils/{ffmpeg.js → subprocess.js} +0 -0
package/README.md CHANGED
@@ -13,7 +13,7 @@ npm install -g mediasnacks
13
13
  Optionally, if you have `ignore-scripts=true` in your `.npmprc`,
14
14
  you can install zsh auto-completions with:
15
15
  ```sh
16
- $(npm root -g)/mediasnacks/install-zsh-completions.sh
16
+ $(npm root -g)/mediasnacks/install-zsh-completions.js
17
17
  ```
18
18
 
19
19
 
@@ -50,6 +50,8 @@ mediasnacks <command> <args>
50
50
  - `flattendir`: Moves unique files to the top dir and deletes empty dirs
51
51
  - `qdir` Sequentially runs all *.sh files in a folder
52
52
  - `seqcheck` Finds missing sequence number
53
+ - `random` Opens a random file
54
+ - `play` Plays filtered playlist with mpv
53
55
 
54
56
 
55
57
  - `dlaudio`: yt-dlp best audio
@@ -74,57 +76,6 @@ mediasnacks avif -- file[234].png
74
76
  ```
75
77
 
76
78
 
77
- ---
78
-
79
- ## Commands
80
-
81
- ### Converting Images to AVIF
82
- ```shell
83
- mediasnacks avif [-y | --overwrite] [--output-dir=<dir>] <images>
84
- ```
85
-
86
- <br/>
87
-
88
- ### Resizing Images or Videos
89
- Resizes videos and images. The aspect ratio is preserved when only one dimension is specified.
90
-
91
- `--width` and `--height` are `-2` by default:
92
- - `-1` auto-compute while preserving the aspect ratio (may result in an odd number)
93
- - `-2` same as `-1` but rounds to the nearest even number
94
-
95
- ```shell
96
- mediasnacks resize [--width=<num>] [--height=<num>] [-y | --overwrite] [--output-dir=<dir>] <files>
97
- ```
98
-
99
- Example: Overwrites the input file (-y)
100
- ```shell
101
- mediasnacks resize -y --width 480 'dir-a/**/*.png' 'dir-b/**/*.mp4'
102
- ```
103
-
104
- Example: Output directory (-o)
105
- ```shell
106
- mediasnacks resize --height 240 --output-dir /tmp/out video.mov
107
- ```
108
-
109
- <br/>
110
-
111
- ### Fast-Start Streaming Video
112
- Rearranges .mov and .mp4 metadata to the start of the file for fast-start streaming.
113
-
114
- **Files are overwritten**
115
-
116
- ```shell
117
- mediasnacks moov2front <videos>
118
- ```
119
- What is Fast Start?
120
- - https://wiki.avblocks.com/avblocks-for-cpp/muxer-parameters/mp4
121
- - https://trac.ffmpeg.org/wiki/HowToCheckIfFaststartIsEnabledForPlayback
122
-
123
-
124
- <br/>
125
-
126
- ---
127
-
128
79
  ## Adding a macOS Quick Action
129
80
 
130
81
 
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { join } from 'node:path'
4
+ import { execSync } from 'node:child_process'
5
+ import { writeFileSync } from 'node:fs'
6
+ import { commandsSummary } from './src/cli.js'
7
+
8
+ let zshFuncDefsDirs
9
+ try {
10
+ zshFuncDefsDirs = execSync('zsh -c "print -l \\$fpath"', { encoding: 'utf-8' })
11
+ }
12
+ catch {
13
+ process.exit(0) // Exit on systems without ZSH
14
+ }
15
+
16
+ for (const dir of zshFuncDefsDirs.split('\n'))
17
+ try {
18
+ writeFileSync(join(dir, '_mediasnacks'), makeScript(), { mode: 0o755 })
19
+ break
20
+ }
21
+ catch {}
22
+
23
+
24
+ function makeScript() {
25
+ return `#compdef mediasnacks
26
+
27
+ _mediasnacks_commands=(
28
+ ${commandsSummary().map(([cmd, desc]) => `'${cmd}:${desc}'`).join('\n')}
29
+ )
30
+
31
+ if (( CURRENT == 2 )); then
32
+ _describe -t commands 'mediasnacks commands' _mediasnacks_commands
33
+ return
34
+ fi
35
+
36
+ local cmd="$words[2]"
37
+ case "$cmd" in
38
+ qdir)
39
+ _files -/
40
+ ;;
41
+ *)
42
+ _files
43
+ ;;
44
+ esac
45
+ `
46
+ }
47
+
48
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mediasnacks",
3
- "version": "0.21.0",
3
+ "version": "0.22.2",
4
4
  "description": "Utilities for optimizing and preparing videos and images",
5
5
  "license": "MIT",
6
6
  "author": "Eric Fortis",
@@ -10,12 +10,11 @@
10
10
  },
11
11
  "scripts": {
12
12
  "test": "docker run --rm $(docker build -q .)",
13
- "postinstall": "sh install-zsh-completions.sh",
13
+ "postinstall": "node install-zsh-completions.js",
14
14
  "dev-install": "npm i -g . --ignore-scripts=false"
15
15
  },
16
16
  "files": [
17
17
  "src",
18
- ".zsh",
19
- "install-zsh-completions.sh"
18
+ "install-zsh-completions.js"
20
19
  ]
21
20
  }
package/src/avif.js CHANGED
@@ -4,10 +4,10 @@ import { join, basename, dirname } from 'node:path'
4
4
 
5
5
  import { parseOptions } from './utils/parseOptions.js'
6
6
  import { replaceExt, lstat } from './utils/fs-utils.js'
7
- import { ffmpeg, assertUserHasFFmpeg } from './utils/ffmpeg.js'
7
+ import { ffmpeg, assertUserHasFFmpeg } from './utils/subprocess.js'
8
8
 
9
9
 
10
- const MAN = `
10
+ const HELP = `
11
11
  SYNOPSIS
12
12
  mediasnacks avif [-y | --overwrite] [--output-dir=<dir>] <images>
13
13
 
@@ -26,7 +26,7 @@ async function main() {
26
26
  })
27
27
 
28
28
  if (values.help) {
29
- console.log(MAN)
29
+ console.log(HELP)
30
30
  process.exit(0)
31
31
  }
32
32
 
@@ -35,14 +35,14 @@ async function main() {
35
35
 
36
36
  console.log('AVIF…')
37
37
  for (const file of files)
38
- await toAvif({
38
+ await avif({
39
39
  file,
40
40
  outFile: join(values['output-dir'] || dirname(file), replaceExt(basename(file), 'avif')),
41
41
  overwrite: values.overwrite
42
42
  })
43
43
  }
44
44
 
45
- async function toAvif({ file, outFile, overwrite }) {
45
+ async function avif({ file, outFile, overwrite }) {
46
46
  const stAvif = lstat(outFile)
47
47
 
48
48
  if (!overwrite && stAvif?.isFile()) {
package/src/cli.js CHANGED
@@ -18,7 +18,7 @@ const COMMANDS = {
18
18
  detectdups: ['detectdups.js', 'Detects duplicate frames in a video'],
19
19
  dropdups: ['dropdups.js', 'Removes duplicate frames in a video'],
20
20
  framediff: ['framediff.sh', 'Plays a video of adjacent frames diff'],
21
- hev1tohvc1: ['hev1tohvc1.js', 'Fixes video thumbnails not rendering in macOS Finder '],
21
+ hev1tohvc1: ['hev1tohvc1.js', 'Fixes video thumbnails not rendering on macOS Finder'],
22
22
  moov2front: ['moov2front.js', 'Rearranges .mov and .mp4 metadata for fast-start streaming'],
23
23
  vconcat: ['vconcat.sh', 'Concatenates videos'],
24
24
  vdiff: ['vdiff.sh', 'Plays a video with the difference of two videos'],
@@ -28,7 +28,9 @@ const COMMANDS = {
28
28
 
29
29
  flattendir: ['flattendir.sh', 'Moves all files to top dir and deletes dirs'],
30
30
  qdir: ['qdir.js', 'Sequentially runs all *.sh files in a folder'],
31
- seqcheck: ['seqcheck.js', 'Finds missing sequence number\n'],
31
+ seqcheck: ['seqcheck.js', 'Finds missing sequence number'],
32
+ random: ['random.js', 'Opens a random file (macOS only)'],
33
+ play: ['play.js', 'Plays filtered playlist with mpv\n'],
32
34
 
33
35
  dlaudio: ['dlaudio.sh', 'yt-dlp best audio'],
34
36
  dlvideo: ['dlvideo.sh', 'yt-dlp best video\n'],
@@ -37,36 +39,46 @@ const COMMANDS = {
37
39
  rmcover: ['rmcover.sh', 'Removes cover art'],
38
40
  }
39
41
 
40
- const MAN = `
42
+ export function commandsSummary() {
43
+ return Object.entries(COMMANDS)
44
+ .map(([cmd, [, desc]]) => [cmd, desc])
45
+ }
46
+
47
+ const HELP = `
41
48
  SYNOPSIS
42
49
  mediasnacks <command> <args>
43
50
 
44
51
  COMMANDS
45
- ${Object.entries(COMMANDS).map(([cmd, [, title]]) =>
46
- ` ${styleText('bold', cmd.padEnd(12, ' '))}\t${title}`).join('\n')}
52
+ ${commandsSummary().map(([cmd, desc]) =>
53
+ ` ${styleText('bold', cmd.padEnd(12, ' '))}\t${desc}`).join('\n')}
47
54
  `.trim()
48
55
 
49
56
 
50
- const [, , opt, ...args] = process.argv
57
+ function main() {
58
+ const [, , opt, ...args] = process.argv
51
59
 
52
- if (opt === '-v' || opt === '--version') {
53
- console.log(pkgJSON.version)
54
- process.exit(0)
55
- }
56
- if (opt === '-h' || opt === '--help') {
57
- console.log(MAN)
58
- process.exit(0)
59
- }
60
+ if (opt === '-v' || opt === '--version') {
61
+ console.log(pkgJSON.version)
62
+ process.exit(0)
63
+ }
64
+ if (opt === '-h' || opt === '--help') {
65
+ console.log(HELP)
66
+ process.exit(0)
67
+ }
60
68
 
61
- if (!opt) {
62
- console.log(MAN)
63
- process.exit(1)
64
- }
65
- if (!Object.hasOwn(COMMANDS, opt)) {
66
- console.error(`'${opt}' is not a command. See mediasnacks --help\n`)
67
- process.exit(1)
69
+ if (!opt) {
70
+ console.log(HELP)
71
+ process.exit(1)
72
+ }
73
+ if (!Object.hasOwn(COMMANDS, opt)) {
74
+ console.error(`'${opt}' is not a command. See mediasnacks --help\n`)
75
+ process.exit(1)
76
+ }
77
+
78
+ const cmd = join(import.meta.dirname, COMMANDS[opt][0])
79
+ spawn(cmd, args, { stdio: 'inherit' })
80
+ .on('exit', process.exit)
68
81
  }
69
82
 
70
- const cmd = join(import.meta.dirname, COMMANDS[opt][0])
71
- spawn(cmd, args, { stdio: 'inherit' })
72
- .on('exit', process.exit)
83
+ if (import.meta.main)
84
+ main()
package/src/detectdups.js CHANGED
@@ -1,14 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { parseOptions } from './utils/parseOptions.js'
4
- import { ffmpeg, assertUserHasFFmpeg, videoAttrs } from './utils/ffmpeg.js'
4
+ import { ffmpeg, assertUserHasFFmpeg, videoAttrs } from './utils/subprocess.js'
5
5
 
6
6
  const STDEV_THRESHOLD = 0.2
7
7
 
8
- const MAN = `
9
- Usage: mediasnacks detectdups [options] <video>
8
+ const HELP = `
9
+ SYNOPSIS
10
+ mediasnacks detectdups [options] <video>
10
11
 
11
- Detects sequentially duplicate frames in a video and prints a histogram of their distance.
12
+ DESCRIPTION
13
+ Detects sequentially duplicate frames in a video and prints a histogram of their distance.
12
14
 
13
15
  EXAMPLES
14
16
  Peak at N=2, means that every other frame is repeated, such as in a
@@ -38,7 +40,7 @@ async function main() {
38
40
  })
39
41
 
40
42
  if (values.help) {
41
- console.log(MAN)
43
+ console.log(HELP)
42
44
  process.exit(0)
43
45
  }
44
46
 
@@ -70,7 +72,7 @@ async function main() {
70
72
  throw new Error(`Invalid analysis range. Exceeds video duration: ${vDur}`)
71
73
 
72
74
 
73
- const dups = await detectDuplicateFramesNums(files[0], seek, duration)
75
+ const dups = await detectdups(files[0], seek, duration)
74
76
  const h = deltaHistogram(dups)
75
77
  const report = {
76
78
  n: maxFreqKey(h),
@@ -83,7 +85,7 @@ async function main() {
83
85
  console.log(JSON.stringify(report, null, 2))
84
86
  }
85
87
 
86
- export async function detectDuplicateFramesNums(video, seek, duration) {
88
+ export async function detectdups(video, seek, duration) {
87
89
  const { stderr } = await ffmpeg([
88
90
  '-v', 'info',
89
91
  '-stats',
package/src/dropdups.js CHANGED
@@ -3,21 +3,13 @@
3
3
  import { resolve, parse, format } from 'node:path'
4
4
 
5
5
  import { parseOptions } from './utils/parseOptions.js'
6
- import { ffmpeg, assertUserHasFFmpeg, run } from './utils/ffmpeg.js'
6
+ import { ffmpeg, assertUserHasFFmpeg, run } from './utils/subprocess.js'
7
+ import { PRORES_PROFILES } from './prores.js'
7
8
 
8
9
 
9
- const PRORES_PROFILES = {
10
- 'proxy': 0,
11
- 'lt': 1,
12
- 'standard': 2,
13
- 'hq': 3,
14
- '4444': 4,
15
- '4444xq': 5,
16
- }
17
10
  const PROFILE = PRORES_PROFILES.hq
18
11
 
19
-
20
- const MAN = `
12
+ const HELP = `
21
13
  SYNOPSIS
22
14
  mediasnacks dropdups [-n <bad-frame-number>] <video>
23
15
 
@@ -42,7 +34,7 @@ async function main() {
42
34
  })
43
35
 
44
36
  if (values.help) {
45
- console.log(MAN)
37
+ console.log(HELP)
46
38
  process.exit(0)
47
39
  }
48
40
 
@@ -55,10 +47,10 @@ async function main() {
55
47
 
56
48
  console.log('Dropping Duplicate Frames…')
57
49
  for (const file of files)
58
- await drop(resolve(file), nBadFrame)
50
+ await dropdups(resolve(file), nBadFrame)
59
51
  }
60
52
 
61
- async function drop(video, nBadFrame) {
53
+ async function dropdups(video, nBadFrame) {
62
54
  await run('ffmpeg', [
63
55
  '-v', 'error',
64
56
  '-stats',
package/src/edgespic.js CHANGED
@@ -4,10 +4,10 @@ import { basename, extname, join, parse } from 'node:path'
4
4
 
5
5
  import { mkDir } from './utils/fs-utils.js'
6
6
  import { parseOptions } from './utils/parseOptions.js'
7
- import { ffmpeg, videoAttrs, assertUserHasFFmpeg } from './utils/ffmpeg.js'
7
+ import { ffmpeg, videoAttrs, assertUserHasFFmpeg } from './utils/subprocess.js'
8
8
 
9
9
 
10
- const MAN = `
10
+ const HELP = `
11
11
  SYNOPSIS
12
12
  mediasnacks edgespic [--width=<num>] <files>
13
13
 
@@ -30,7 +30,7 @@ async function main() {
30
30
  })
31
31
 
32
32
  if (values.help) {
33
- console.log(MAN)
33
+ console.log(HELP)
34
34
  process.exit(0)
35
35
  }
36
36
 
@@ -46,11 +46,11 @@ async function main() {
46
46
 
47
47
  console.log('Extracting edge frames…')
48
48
  for (const file of files)
49
- await extractEdgeFrames(file, width, outDir)
49
+ await edgespic(file, width, outDir)
50
50
  }
51
51
 
52
52
 
53
- async function extractEdgeFrames(video, width, outDir) {
53
+ async function edgespic(video, width, outDir) {
54
54
  const { r_frame_rate } = await videoAttrs(video)
55
55
  const name = basename(video, extname(video))
56
56
 
package/src/hev1tohvc1.js CHANGED
@@ -2,10 +2,10 @@
2
2
 
3
3
  import { parseOptions } from './utils/parseOptions.js'
4
4
  import { uniqueFilenameFor, overwrite } from './utils/fs-utils.js'
5
- import { videoAttrs, ffmpeg, assertUserHasFFmpeg } from './utils/ffmpeg.js'
5
+ import { videoAttrs, ffmpeg, assertUserHasFFmpeg } from './utils/subprocess.js'
6
6
 
7
7
 
8
- const MAN = `
8
+ const HELP = `
9
9
  SYNOPSIS
10
10
  mediasnacks hev1tohvc1 <videos>
11
11
 
@@ -22,14 +22,14 @@ async function main() {
22
22
  const { files } = await parseOptions()
23
23
 
24
24
  if (!files.length)
25
- throw new Error(MAN)
25
+ throw new Error(HELP)
26
26
 
27
27
  console.log('HEV1 to HVC1…')
28
28
  for (const file of files)
29
- await toHvc1(file)
29
+ await hev1tohvc1(file)
30
30
  }
31
31
 
32
- async function toHvc1(file) {
32
+ async function hev1tohvc1(file) {
33
33
  const v = await videoAttrs(file)
34
34
  if (v.codec_tag_string !== 'hev1') {
35
35
  console.log('(skipped: non hev1)', file)
package/src/moov2front.js CHANGED
@@ -1,16 +1,20 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { ffmpeg, assertUserHasFFmpeg } from './utils/ffmpeg.js'
3
+ import { ffmpeg, assertUserHasFFmpeg } from './utils/subprocess.js'
4
4
  import { uniqueFilenameFor, overwrite } from './utils/fs-utils.js'
5
5
  import { parseOptions } from './utils/parseOptions.js'
6
6
 
7
7
 
8
- const MAN = `
8
+ const HELP = `
9
9
  SYNOPSIS
10
10
  mediasnacks moov2front <videos>
11
11
 
12
12
  DESCRIPTION
13
13
  Rearranges .mov and .mp4 metadata to the start of the file for fast-start streaming.
14
+
15
+ What is Fast Start?
16
+ - https://wiki.avblocks.com/avblocks-for-cpp/muxer-parameters/mp4
17
+ - https://trac.ffmpeg.org/wiki/HowToCheckIfFaststartIsEnabledForPlayback
14
18
 
15
19
  NOTES
16
20
  Files are overwritten.
@@ -22,7 +26,7 @@ async function main() {
22
26
  const { files } = await parseOptions()
23
27
 
24
28
  if (!files.length)
25
- throw new Error(MAN)
29
+ throw new Error(HELP)
26
30
 
27
31
  console.log('Optimizing video for progressive download…')
28
32
  for (const file of files)
package/src/play.js ADDED
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env node
2
+ import { join } from 'node:path'
3
+ import { spawn } from 'node:child_process'
4
+ import { readdirSync } from 'node:fs'
5
+ import { parseOptions } from './utils/parseOptions.js'
6
+
7
+
8
+ const HELP = `
9
+ SYNOPSIS
10
+ mediasnacks play [--no-recursive] [-h | --help] [query ...]
11
+
12
+ DESCRIPTION
13
+ Plays a filtered playlist with mpv.
14
+
15
+ EXAMPLE
16
+ cd Music
17
+ mediasnacks play artistX artistY
18
+ `.trim()
19
+
20
+
21
+ async function main() {
22
+ const { values, positionals } = await parseOptions({
23
+ recursive: { short: 'r', type: 'boolean', default: true },
24
+ help: { short: 'h', type: 'boolean' }
25
+ }, { allowNegative: true })
26
+
27
+ if (values.help) {
28
+ console.log(HELP)
29
+ process.exit(0)
30
+ }
31
+
32
+ const pattern = positionals.length
33
+ ? positionals.join('|')
34
+ : ''
35
+ const files = findFiles('.', new RegExp(pattern, 'i'), values.recursive)
36
+
37
+ if (!files.length) {
38
+ console.error('No matching files found.')
39
+ process.exit(0)
40
+ }
41
+
42
+ const child = spawn('mpv', ['--playlist=-'], {
43
+ detached: true,
44
+ stdio: ['pipe', 'ignore', 'ignore']
45
+ })
46
+ child.stdin.end(files.join('\n'))
47
+ child.unref()
48
+ }
49
+
50
+ function findFiles(dir, regex, recursive = true) {
51
+ const IGNORED_DIRS = ['.fcpbundle/']
52
+ return readdirSync(dir, { withFileTypes: true, recursive })
53
+ .filter(entry =>
54
+ entry.isFile()
55
+ && !entry.name.startsWith('.')
56
+ && !IGNORED_DIRS.some(d => entry.parentPath.includes(d))
57
+ && regex.test(entry.name))
58
+ .map(entry => join(entry.parentPath, entry.name))
59
+ }
60
+
61
+ main().catch(err => {
62
+ console.error(err.message)
63
+ process.exit(1)
64
+ })
package/src/prores.js CHANGED
@@ -3,9 +3,9 @@
3
3
  import { resolve, parse, join } from 'node:path'
4
4
 
5
5
  import { parseOptions } from './utils/parseOptions.js'
6
- import { assertUserHasFFmpeg, run } from './utils/ffmpeg.js'
6
+ import { assertUserHasFFmpeg, run } from './utils/subprocess.js'
7
7
 
8
- const PRORES_PROFILES = {
8
+ export const PRORES_PROFILES = {
9
9
  'proxy': 0,
10
10
  'lt': 1,
11
11
  'standard': 2,
@@ -14,7 +14,7 @@ const PRORES_PROFILES = {
14
14
  '4444xq': 5,
15
15
  }
16
16
 
17
- const MAN = `
17
+ const HELP = `
18
18
  SYNOPSIS
19
19
  mediasnacks prores [options] <video>
20
20
 
@@ -42,31 +42,35 @@ async function main() {
42
42
  })
43
43
 
44
44
  if (values.help) {
45
- console.log(MAN)
45
+ console.log(HELP)
46
46
  process.exit(0)
47
47
  }
48
48
 
49
49
  if (files.length !== 1)
50
50
  throw new Error('Expected 1 argument: video file. See mediasnacks prores --help')
51
51
 
52
- const videoPath = resolve(files[0])
53
-
54
- const { name, dir } = parse(videoPath)
55
- const outputPath = join(dir, `${name}.prores.mov`)
52
+ const video = resolve(files[0])
53
+ const { name, dir } = parse(video)
54
+ const output = join(dir, `${name}.prores.mov`)
56
55
 
57
56
  console.log(`Converting to ProRes…`)
57
+ await prores(video, values.profile, output)
58
+ }
59
+
60
+ async function prores(video, profile, output) {
58
61
  await run('ffmpeg', [
59
62
  '-v', 'error',
60
63
  '-stats',
61
- '-i', videoPath,
64
+ '-i', video,
62
65
  '-c:v', 'prores_ks',
63
- '-profile:v', values.profile,
66
+ '-profile:v', profile,
64
67
  '-pix_fmt', 'yuv422p10le',
65
- outputPath
68
+ output
66
69
  ])
67
70
  }
68
71
 
69
- main().catch(err => {
70
- console.error(err.message || err)
71
- process.exit(1)
72
- })
72
+ if (import.meta.main)
73
+ main().catch(err => {
74
+ console.error(err.message || err)
75
+ process.exit(1)
76
+ })
package/src/qdir.js CHANGED
@@ -8,7 +8,7 @@ import { readdir, writeFile, unlink, rename } from 'node:fs/promises'
8
8
  import { isFile } from './utils/fs-utils.js'
9
9
 
10
10
 
11
- const MAN = `
11
+ const HELP = `
12
12
  SYNOPSIS
13
13
  mediasnacks qdir [folder]
14
14
 
@@ -26,7 +26,7 @@ async function main() {
26
26
  })
27
27
 
28
28
  if (values.help) {
29
- console.log(MAN)
29
+ console.log(HELP)
30
30
  process.exit(0)
31
31
  }
32
32
 
package/src/random.js ADDED
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env node
2
+ import { join } from 'node:path'
3
+ import { spawn } from 'node:child_process'
4
+ import { readdirSync } from 'node:fs'
5
+ import { parseOptions } from './utils/parseOptions.js'
6
+
7
+
8
+ const HELP = `
9
+ SYNOPSIS
10
+ mediasnacks random [-r | --recursive] [-h | --help]
11
+
12
+ DESCRIPTION
13
+ Opens a random file in the current working directory
14
+ `.trim()
15
+
16
+ async function main() {
17
+ if (process.platform !== 'darwin') {
18
+ console.error('Error: This command is only supported on macOS.')
19
+ process.exit(1)
20
+ }
21
+
22
+ const { values } = await parseOptions({
23
+ recursive: { short: 'r', type: 'boolean' },
24
+ help: { short: 'h', type: 'boolean' }
25
+ })
26
+
27
+ if (values.help) {
28
+ console.log(HELP)
29
+ process.exit(0)
30
+ }
31
+
32
+ spawn('open', [pickRandomFile('.', values.recursive)])
33
+ }
34
+
35
+ function pickRandomFile(dir, recursive) {
36
+ const files = readdirSync(dir, { withFileTypes: true, recursive })
37
+ .filter(entry => !entry.name.startsWith('.') && entry.isFile())
38
+ .map(entry => join(entry.parentPath, entry.name))
39
+ return files[Math.floor(Math.random() * files.length)]
40
+ }
41
+
42
+ main().catch(err => {
43
+ console.error(err.message)
44
+ process.exit(1)
45
+ })
package/src/resize.js CHANGED
@@ -5,10 +5,10 @@ import { rename } from 'node:fs/promises'
5
5
 
6
6
  import { parseOptions } from './utils/parseOptions.js'
7
7
  import { isFile, uniqueFilenameFor } from './utils/fs-utils.js'
8
- import { ffmpeg, videoAttrs, assertUserHasFFmpeg } from './utils/ffmpeg.js'
8
+ import { ffmpeg, videoAttrs, assertUserHasFFmpeg } from './utils/subprocess.js'
9
9
 
10
10
 
11
- const MAN = `
11
+ const HELP = `
12
12
  SYNOPSIS
13
13
  mediasnacks resize [--width=<num>] [--height=<num>] [-y | --overwrite] [--output-dir=<dir>] <files>
14
14
 
@@ -41,7 +41,7 @@ async function main() {
41
41
  })
42
42
 
43
43
  if (values.help) {
44
- console.log(MAN)
44
+ console.log(HELP)
45
45
  process.exit(0)
46
46
  }
47
47