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/src/seqcheck.js CHANGED
@@ -4,7 +4,7 @@ import { parseArgs } from 'node:util'
4
4
  import { readdirSync } from 'node:fs'
5
5
 
6
6
 
7
- const MAN = `
7
+ const HELP = `
8
8
  SYNOPSIS
9
9
  mediasnacks seqcheck [options] [folder]
10
10
 
@@ -29,7 +29,7 @@ function main() {
29
29
  })
30
30
 
31
31
  if (values.help) {
32
- console.log(MAN)
32
+ console.log(HELP)
33
33
  process.exit(0)
34
34
  }
35
35
 
package/src/sqcrop.js CHANGED
@@ -3,12 +3,12 @@
3
3
  import { join } from 'node:path'
4
4
  import { rename } from 'node:fs/promises'
5
5
 
6
- import { ffmpeg, assertUserHasFFmpeg } from './utils/ffmpeg.js'
6
+ import { ffmpeg, assertUserHasFFmpeg } from './utils/subprocess.js'
7
7
  import { lstat, uniqueFilenameFor } from './utils/fs-utils.js'
8
8
  import { parseOptions } from './utils/parseOptions.js'
9
9
 
10
10
 
11
- const MAN = `
11
+ const HELP = `
12
12
  SYNOPSIS
13
13
  mediasnacks sqcrop [-y | --overwrite] [--output-dir=<dir>] <images>
14
14
 
@@ -27,7 +27,7 @@ async function main() {
27
27
  })
28
28
 
29
29
  if (values.help) {
30
- console.log(MAN)
30
+ console.log(HELP)
31
31
  process.exit(0)
32
32
  }
33
33
 
package/src/ssim.js CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { ffmpeg } from './utils/ffmpeg.js'
3
+ import { ffmpeg } from './utils/subprocess.js'
4
4
 
5
5
 
6
- const MAN = `
6
+ const HELP = `
7
7
  SYNOPSIS
8
8
  mediasnacks ssim <img1> <img2>
9
9
 
@@ -15,7 +15,7 @@ DESCRIPTION
15
15
  async function main() {
16
16
  const [img1, img2] = process.argv.slice(2)
17
17
  if (!img1 || !img2) {
18
- console.log(MAN)
18
+ console.log(HELP)
19
19
  process.exit(1)
20
20
  }
21
21
 
@@ -13,6 +13,7 @@ export async function parseOptions(options = {}, config = {}) {
13
13
  })
14
14
  return {
15
15
  values,
16
+ positionals,
16
17
  files: await resolveGlobs(positionals, tokens)
17
18
  }
18
19
  }
package/src/vsplit.js CHANGED
@@ -4,12 +4,12 @@ import { readFileSync } from 'node:fs'
4
4
  import { resolve, parse, join } from 'node:path'
5
5
 
6
6
  import { parseOptions } from './utils/parseOptions.js'
7
- import { assertUserHasFFmpeg, run } from './utils/ffmpeg.js'
7
+ import { assertUserHasFFmpeg, run } from './utils/subprocess.js'
8
8
 
9
9
 
10
10
  // TODO looks like it's missing a frame (perhaps becaue of -c copy)
11
11
 
12
- const MAN = `
12
+ const HELP = `
13
13
  SYNOPSIS
14
14
  mediasnacks vsplit <csv> <video>
15
15
 
@@ -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
 
@@ -55,7 +55,7 @@ async function main() {
55
55
  throw new Error('CSV file contains no clips')
56
56
 
57
57
  console.log(`Splitting video into ${clips.length} clip${clips.length === 1 ? '' : 's'}…`)
58
- await splitVideo(videoPath, clips)
58
+ await vsplit(videoPath, clips)
59
59
  }
60
60
 
61
61
  function parseCSV(csvPath) {
@@ -77,7 +77,7 @@ function parseCSV(csvPath) {
77
77
  return clips
78
78
  }
79
79
 
80
- async function splitVideo(videoPath, clips) {
80
+ async function vsplit(videoPath, clips) {
81
81
  const { dir, name, ext } = parse(videoPath)
82
82
  const seqLen = Math.log10(clips.length) + 1 | 0
83
83
 
package/src/vtrim.js CHANGED
@@ -2,10 +2,10 @@
2
2
 
3
3
  import { resolve, parse } from 'node:path'
4
4
  import { parseOptions } from './utils/parseOptions.js'
5
- import { ffmpeg, assertUserHasFFmpeg } from './utils/ffmpeg.js'
5
+ import { ffmpeg, assertUserHasFFmpeg } from './utils/subprocess.js'
6
6
 
7
7
 
8
- const MAN = `
8
+ const HELP = `
9
9
  SYNOPSIS
10
10
  mediasnacks vtrim [--start <time>] [--end <time>] <video>
11
11
 
@@ -29,7 +29,7 @@ async function main() {
29
29
  })
30
30
 
31
31
  if (values.help) {
32
- console.log(MAN)
32
+ console.log(HELP)
33
33
  process.exit(0)
34
34
  }
35
35
 
@@ -37,10 +37,10 @@ async function main() {
37
37
  throw new Error('No video specified. See mediasnacks vtrim --help')
38
38
 
39
39
  for (const file of files)
40
- await trim(resolve(file), values.start, values.end)
40
+ await vtrim(resolve(file), values.start, values.end)
41
41
  }
42
42
 
43
- async function trim(video, start, end) {
43
+ async function vtrim(video, start, end) {
44
44
  const { dir, name, ext } = parse(video)
45
45
  await ffmpeg([
46
46
  '-v', 'error',
@@ -1,47 +0,0 @@
1
- #compdef mediasnacks
2
-
3
- _mediasnacks_commands=(
4
- 'avif:Converts images to AVIF'
5
- 'sqcrop:Square crops images'
6
-
7
- 'resize:Resizes videos or images'
8
- 'edgespic:Extracts first and last frames'
9
- 'ssim:Computes similarity of two images'
10
- 'gif:Video to GIF'
11
-
12
- 'detectdups:Detects sequentially duplicate frames in a video'
13
- 'dropdups:Removes sequentially duplicate frames in a video'
14
- 'framediff:ffplay with a filter for diffing adjacent frames'
15
- 'hev1tohvc1:Fixes video thumbnails not rendering in macOS Finder'
16
- 'moov2front:Rearranges metadata for fast-start streaming'
17
- 'vconcat:Concatenates videos'
18
- 'vdiff:Plays a video with the difference of two videos'
19
- 'vsplit:Splits a video into multiple clips from CSV timestamps'
20
- 'vtrim:Trims video from start to end time'
21
- 'prores:Converts video to Apple ProRes'
22
-
23
- 'flattendir:Moves unique files to the top dir and deletes empty dirs'
24
- 'seqcheck:Finds missing sequence number'
25
- 'qdir:Sequentially runs all *.sh files in a folder'
26
-
27
- 'dlaudio: yt-dlp best audio'
28
- 'dlvideo: yt-dlp best video'
29
-
30
- 'unemoji:Removes emojis from filenames'
31
- 'rmcover:Removes cover art'
32
- )
33
-
34
- if (( CURRENT == 2 )); then
35
- _describe -t commands 'mediasnacks commands' _mediasnacks_commands
36
- return
37
- fi
38
-
39
- local cmd="$words[2]"
40
- case "$cmd" in
41
- avif|resize|sqcrop|moov2front|detectdups|dropdups|edgespic|seqcheck|hev1tohvc1|framediff|vdiff|vconcat|vsplit|vtrim|dlaudio|dlvideo|unemoji|rmcover|curltime|gif|flattendir|prores|ssim)
42
- _files
43
- ;;
44
- qdir)
45
- _files -/
46
- ;;
47
- esac
@@ -1,23 +0,0 @@
1
- #!/bin/sh
2
- set -eu
3
-
4
- # Exit on systems without ZSH
5
- zsh=$(command -v zsh) || exit 0
6
-
7
- src="$(cd "$(dirname "$0")" && pwd)/.zsh/completions/_mediasnacks"
8
- [ -f "$src" ] || exit 0
9
-
10
- exec "$zsh" -s "$src" << 'ZSH_EOF'
11
- src="$1"
12
- for dir in "${fpath[@]}"; do
13
- if [ -w "$dir" ]; then
14
- dst="$dir/_mediasnacks"
15
- ln -sf "$src" "$dst"
16
- echo "linked zsh completions: $dst -> $src"
17
- exit 0
18
- fi
19
- done
20
- echo "zsh completions: no writable fpath directory found, skipping." >&2
21
- exit 0
22
- ZSH_EOF
23
-
package/src/avif.test.js DELETED
@@ -1,16 +0,0 @@
1
- import { join } from 'node:path'
2
- import { test } from 'node:test'
3
- import { ok } from 'node:assert/strict'
4
-
5
- import { ssim } from './ssim.js'
6
- import { mkTempDir, cli } from './utils/test-utils.js'
7
-
8
- const rel = f => join(import.meta.dirname, f)
9
-
10
- test('PNG to AVIF', async () => {
11
- const tmp = mkTempDir('avif')
12
- cli('avif', '--output-dir', tmp, rel('fixtures/lenna.png'))
13
-
14
- const similarityScore = await ssim(join(tmp, 'lenna.avif'), rel('fixtures/lenna.avif'))
15
- ok(similarityScore > 0.99, `Similarity too low: ${similarityScore}`)
16
- })
@@ -1,24 +0,0 @@
1
- import { test } from 'node:test'
2
- import { join } from 'node:path'
3
- import { equal } from 'node:assert/strict'
4
- import { cli } from './utils/test-utils.js'
5
-
6
- const rel = f => join(import.meta.dirname, f)
7
-
8
- function detect(video) {
9
- const { stdout } = cli('detectdups', rel(video))
10
- return JSON.parse(stdout).n
11
- }
12
-
13
- test('no dups', () => equal(detect('fixtures/big-buck-bunny/bbb_24fps_no_dups.mp4'), null))
14
-
15
- // These fixtures are badly retimed (non-interpolated, just duplicating a frame)
16
-
17
- test('24 to 48 (has dup at n=2)', () => equal(detect('fixtures/big-buck-bunny/bbb_24_to_48fps_dup.mp4'), 2))
18
- test('25 to 50 (has dup at n=2)', () => equal(detect('fixtures/big-buck-bunny/bbb_25_to_50fps_dup.mp4'), 2))
19
-
20
- test('24 to 30 (has dup at n=5)', () => equal(detect('fixtures/big-buck-bunny/bbb_24_to_30fps_dup.mp4'), 5))
21
- test('25 to 30 (has dup at n=6)', () => equal(detect('fixtures/big-buck-bunny/bbb_25_to_30fps_dup.mp4'), 6))
22
-
23
- test('24 to 25 (has dup at n=25)', () => equal(detect('fixtures/big-buck-bunny/bbb_24_to_25fps_dup.mp4'), 25))
24
-
@@ -1,36 +0,0 @@
1
- import { ok } from 'node:assert/strict'
2
- import { join } from 'node:path'
3
- import { describe, test } from 'node:test'
4
- import { cpSync, readdirSync, } from 'node:fs'
5
-
6
- import { ssim } from './ssim.js'
7
- import { cli, mkTempDir } from './utils/test-utils.js'
8
-
9
- const rel = f => join(import.meta.dirname, f)
10
-
11
- describe('edgespic', () => {
12
- const tmp = mkTempDir('edgespic')
13
- const inputFile = join(tmp, '60fps.mp4')
14
- cpSync(rel('fixtures/60fps.mp4'), inputFile)
15
- cli('edgespic', inputFile)
16
-
17
- test('creates output directory', () => {
18
- const files = readdirSync(join(tmp, 'edgespic'))
19
- ok(files.length === 2, `Expected 2 PNG files, got ${files.length}`)
20
- })
21
-
22
- test('extracts first frame', async () => {
23
- const out = join(tmp, 'edgespic', '60fps_first.png')
24
- const fixture = rel('fixtures/edgespic/60fps_first.png')
25
- const similarityScore = await ssim(out, fixture)
26
- ok(similarityScore > 0.99, `Similarity too low: ${similarityScore}`)
27
- })
28
-
29
- test('extracts last frame', async () => {
30
- const out = join(tmp, 'edgespic', '60fps_last.png')
31
- const fixture = rel('fixtures/edgespic/60fps_last.png')
32
- const similarityScore = await ssim(out, fixture)
33
- ok(similarityScore > 0.99, `Similarity too low: ${similarityScore}`)
34
- })
35
- })
36
-
@@ -1,7 +0,0 @@
1
- start,end
2
- 0,5
3
- 5,10
4
- 10,15
5
- 15,20
6
- 20,25
7
- 25,30
Binary file
@@ -1,71 +0,0 @@
1
- # Generating Fixtures
2
-
3
-
4
- ## 1. Download a video (Big Buck Bunny)
5
-
6
- ```sh
7
- curl https://download.blender.org/peach/bigbuckbunny_movies/big_buck_bunny_1080p_h264.mov -o bbb_full_24fps.mov
8
- ```
9
-
10
- ## 2. Extract a scene
11
- Using the third scene because it's complex enough to cover many edge cases.
12
- It has a bird flapping its wings with motion blur. Also, animated text titles,
13
- and fairly static frames after the title ends.
14
-
15
- The scene is 1080p, 24fps, 7.28sec, h.264.
16
- ```sh
17
- brew tap ericfortis/fcpscene
18
- brew install fcpscene
19
- fcpscene -m files bbb_full_24fps.mov
20
-
21
- cp bbb/bbb_full_24fps_003.mov ./bbb_24fps_no_dups.mov
22
- rm -rf bbb
23
- rm bbb_full_24fps.mov
24
- ```
25
-
26
- ## 3. Re-encode the scene 24fps
27
- This way all videos will share the same encoding, and no audio.
28
-
29
- ```sh
30
- ffmpeg -i bbb_24fps_no_dups.mov \
31
- -c:v libx264 -crf 18 -preset slow \
32
- -an \
33
- bbb_24fps_no_dups.mp4
34
- ```
35
-
36
- ## 4. Retime (no dups) speed stretch to 25fps
37
- For a good (no dups) 25fps, retiming by speeding it up.
38
- ```sh
39
- ffmpeg -i bbb_24fps_no_dups.mp4 \
40
- -vf "setpts=24/25*PTS" \
41
- -r 25 \
42
- bbb_25fps_no_dups.mp4
43
- ```
44
-
45
-
46
- ## 5. Retime by inserting duplicates (no interpolation)
47
- ```sh
48
- for TARGET_FPS in 48 30 25; do
49
- ffmpeg -i bbb_24fps_no_dups.mp4 \
50
- -vf fps=$TARGET_FPS \
51
- -c:v libx264 -crf 18 -preset slow \
52
- -an \
53
- "bbb_24_to_${TARGET_FPS}fps_dup.mp4"
54
- done
55
-
56
- for TARGET_FPS in 60 50 30; do
57
- ffmpeg -i bbb_25fps_no_dups.mp4 \
58
- -vf fps=$TARGET_FPS \
59
- -c:v libx264 -crf 18 -preset slow \
60
- -an \
61
- "bbb_25_to_${TARGET_FPS}fps_dup.mp4"
62
- done
63
- ```
64
- Counting the cycle from 1 (not from 0):
65
-
66
- - 24 to 48 (inserts dup at n=2) 0101
67
- - 25 to 50 (inserts dup at n=2) 0101
68
- - 24 to 30 (inserts dup at n=5) 0000100001
69
- - 25 to 30 (inserts dup at n=6) 000001000001
70
- - 24 to 25 (inserts dup at n=25) (0*24)1
71
- - 25 to 60 (inserts dup at n=2 and n=3) 01011
Binary file
Binary file
@@ -1 +0,0 @@
1
- exit 0
@@ -1 +0,0 @@
1
- exit 1
@@ -1,2 +0,0 @@
1
- sleep 0.5
2
- exit 0
@@ -1 +0,0 @@
1
- exit 1
@@ -1,36 +0,0 @@
1
- import { test } from 'node:test'
2
- import { deepEqual } from 'node:assert/strict'
3
- import { readdirSync } from 'node:fs'
4
-
5
- import { mkTempDir, cli, dir, touch } from './utils/test-utils.js'
6
-
7
- test('flattendir moves files to top level and deletes empty dirs', () => {
8
- const tmp = mkTempDir('flattendir')
9
- dir(tmp, 'dir1', 'dir1-1')
10
- dir(tmp, 'dir2')
11
- touch(tmp, 'file1.txt')
12
- touch(tmp, 'dir1', 'file2.txt')
13
- touch(tmp, 'dir1', 'dir1-1', 'file3.txt')
14
- touch(tmp, 'dir1', '.DS_Store')
15
-
16
- cli('flattendir', tmp)
17
- deepEqual(readdirSync(tmp).sort(), [
18
- 'file1.txt',
19
- 'file2.txt',
20
- 'file3.txt'
21
- ])
22
- })
23
-
24
- test('flattendir does not move files if filename collision occurs', () => {
25
- const tmp = mkTempDir('flattendir-collision')
26
- dir(tmp, 'dir1')
27
- touch(tmp, 'file1.txt')
28
- touch(tmp, 'dir1', 'file1.txt')
29
-
30
- cli('flattendir', tmp)
31
- deepEqual(readdirSync(tmp, { recursive: true }).sort(), [
32
- 'dir1',
33
- 'dir1/file1.txt',
34
- 'file1.txt'
35
- ])
36
- })
package/src/qdir.test.js DELETED
@@ -1,24 +0,0 @@
1
- import { test } from 'node:test'
2
- import { join } from 'node:path'
3
- import { equal, deepEqual } from 'node:assert/strict'
4
- import { cpSync, readdirSync } from 'node:fs'
5
-
6
- import { qdir } from './qdir.js'
7
- import { mkTempDir } from './utils/test-utils.js'
8
-
9
- const rel = f => join(import.meta.dirname, f)
10
-
11
- test('qdir-jobs get renamed and failed have their exit status code', async () => {
12
- const tmp = mkTempDir('qdir')
13
- cpSync(rel('fixtures/qdir-jobs'), tmp, { recursive: true })
14
-
15
- const err = await qdir(tmp, 0.2)
16
- equal(err, null)
17
-
18
- deepEqual(readdirSync(tmp).sort(), [
19
- 'job1_good.sh.done',
20
- 'job2_bad.sh.failed.1',
21
- 'job3_good.sh.done',
22
- 'job4_bad.sh.failed.1'
23
- ])
24
- })
@@ -1,27 +0,0 @@
1
- import { test } from 'node:test'
2
- import { deepEqual } from 'node:assert/strict'
3
- import { extractSeqNums, findMissingNumbers } from './seqcheck.js'
4
-
5
-
6
- test('extractSeqNums extracts sequence numbers from filenames', () => {
7
- const filenames = [
8
- 'video-111_001.mov',
9
- 'video-111_002.mov',
10
- 'video-111_004.mov',
11
- 'bad.mov',
12
- 'bad_too_a39.mov',
13
- ]
14
- deepEqual(extractSeqNums(filenames, '_', '.'), [1, 2, 4])
15
- })
16
-
17
-
18
- test('findMissingNumbers ', () => {
19
- test('finds gaps in a sequence', () =>
20
- deepEqual(findMissingNumbers([1, 2, 4, 5, 8]), [3, 6, 7]))
21
-
22
- test('returns empty array for empty input', () =>
23
- deepEqual(findMissingNumbers([]), []))
24
-
25
- test('returns empty array when there are no gaps', () =>
26
- deepEqual(findMissingNumbers([10, 11, 12]), []))
27
- })
@@ -1,21 +0,0 @@
1
- import { equal } from 'node:assert/strict'
2
- import test, { describe } from 'node:test'
3
- import { replaceExt } from './fs-utils.js'
4
-
5
-
6
- describe('replaceExt', () => {
7
- test('replaces a simple extension', () =>
8
- equal(replaceExt('file.txt', 'md'), 'file.md'))
9
-
10
- test('replaces a multi-part extension', () =>
11
- equal(replaceExt('archive.tar.gz', 'zip'), 'archive.tar.zip'))
12
-
13
- test('adds extension when none exists', () =>
14
- equal(replaceExt('README', 'md'), 'README.md'))
15
-
16
- test('handles empty filename', () =>
17
- equal(replaceExt('', 'ext'), '.ext'))
18
-
19
- test('handles dot-files', () =>
20
- equal(replaceExt('.env', 'txt'), '.env.txt'))
21
- })
@@ -1,59 +0,0 @@
1
- import { join } from 'node:path'
2
- import { tmpdir } from 'node:os'
3
- import { equal, deepEqual } from 'node:assert/strict'
4
- import { mkdtemp, writeFile, rm } from 'node:fs/promises'
5
- import { test, describe, before, after } from 'node:test'
6
-
7
- import { parseOptions } from './parseOptions.js'
8
-
9
-
10
- describe('parseOptions', () => {
11
- let testDir
12
- let inTmpDir = f => join(testDir, f)
13
- const testFiles = ['file1.png', 'file2.png', 'file3.png']
14
-
15
- before(async () => {
16
- testDir = await mkdtemp(join(tmpdir(), 'parse-args-'))
17
- for (const file of testFiles)
18
- await writeFile(inTmpDir(file), '')
19
- })
20
-
21
- after(() => rm(testDir, { recursive: true }))
22
-
23
- test('parses args and globs files', async () => {
24
- const { values, files } = await parseOptions({
25
- 'output-dir': { type: 'string' }
26
- }, {
27
- args: ['--output-dir', '/tmp', inTmpDir('file[12].png')],
28
- })
29
- equal(values['output-dir'], '/tmp')
30
- deepEqual(files, [
31
- inTmpDir('file1.png'),
32
- inTmpDir('file2.png')
33
- ])
34
- })
35
-
36
- test('respects verbatim tokens', async () => {
37
- const literal0 = 'literal-file[98].png'
38
- const literal1 = 'literal-file[99].png'
39
- const { files } = await parseOptions({}, {
40
- args: [inTmpDir('file[12].png'), '--', literal0, literal1]
41
- })
42
- deepEqual(files, [
43
- inTmpDir('file1.png'),
44
- inTmpDir('file2.png'),
45
- literal0,
46
- literal1,
47
- ])
48
- })
49
-
50
- test('empty files array when no positionals', async () => {
51
- const { files, values } = await parseOptions({
52
- foo: { type: 'boolean' }
53
- }, {
54
- args: ['--foo'],
55
- })
56
- equal(values.foo, true)
57
- deepEqual(files, [])
58
- })
59
- })
@@ -1,23 +0,0 @@
1
- import { ok } from 'node:assert/strict'
2
- import { join } from 'node:path'
3
- import { test } from 'node:test'
4
- import { cpSync } from 'node:fs'
5
-
6
- import { videoAttrs } from './utils/ffmpeg.js'
7
- import { mkTempDir, cli } from './utils/test-utils.js'
8
-
9
- const rel = f => join(import.meta.dirname, f)
10
-
11
- test('vconcat concatenates videos with single quotes in filenames', async () => {
12
- const tmp = mkTempDir('vconcat')
13
-
14
- const file1 = join(tmp, `video'1.mp4`)
15
- const file2 = join(tmp, `video'2.mp4`)
16
- cpSync(rel('fixtures/60fps.mp4'), file1)
17
- cpSync(rel('fixtures/60fps.mp4'), file2)
18
-
19
- cli('vconcat', file1, file2)
20
-
21
- const { duration } = await videoAttrs(join(tmp, `video'1.concat.mp4`))
22
- ok(parseFloat(duration) === 60, `Duration should be 60s, got ${duration}s`)
23
- })
@@ -1,32 +0,0 @@
1
- import { ok } from 'node:assert/strict'
2
- import { join } from 'node:path'
3
- import { describe, test } from 'node:test'
4
- import { cpSync, readdirSync } from 'node:fs'
5
-
6
- import { videoAttrs } from './utils/ffmpeg.js'
7
- import { mkTempDir, cli } from './utils/test-utils.js'
8
-
9
-
10
- const rel = f => join(import.meta.dirname, f)
11
-
12
- describe('vsplit splits video into multiple clips from CSV', () => {
13
- const tmp = mkTempDir('vsplit')
14
-
15
- const csvFile = join(tmp, '60fps.csv')
16
- const inputFile = join(tmp, '60fps.mp4')
17
-
18
- cpSync(rel('fixtures/60fps.csv'), csvFile)
19
- cpSync(rel('fixtures/60fps.mp4'), inputFile)
20
- cli('vsplit', csvFile, inputFile)
21
-
22
- test('all 6 clips were created', () => {
23
- const files = readdirSync(tmp).filter(f => f.startsWith('60fps_'))
24
- ok(files.length === 6, `Expected 6 clips, got ${files.length}`)
25
- })
26
-
27
- test('first clip has correct duration (5 seconds)', async () => {
28
- const { duration } = await videoAttrs(join(tmp, '60fps_1.mp4'))
29
- const EPSILON = 0.05
30
- ok(Math.abs(parseFloat(duration) - 5) < EPSILON, `Duration should be 5s, got ${duration}s`)
31
- })
32
- })