mediasnacks 0.17.0 → 0.18.1

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.
@@ -45,9 +45,3 @@ case "$cmd" in
45
45
  _files -/
46
46
  ;;
47
47
  esac
48
-
49
-
50
- # INSTALL in: ~/.zshrc
51
- #fpath=(~/.zsh/completions $fpath)
52
- #zmodload zsh/complist
53
- #autoload -U compinit && compinit
package/README.md CHANGED
@@ -1,15 +1,22 @@
1
1
  # mediasnacks
2
2
 
3
- Utilities optimizing and preparing video and images for the web.
3
+ Utilities video and images.
4
4
 
5
5
 
6
- ## Overview
6
+ ### Install
7
7
  **FFmpeg and Node.js must be installed.**
8
8
 
9
9
  ```shell
10
- npx mediasnacks <command> <args>
10
+ npm install -g mediasnacks --ignore-scripts=true
11
11
  ```
12
12
 
13
+
14
+ ## Overview
15
+ ```shell
16
+ mediasnacks <command> <args>
17
+ ```
18
+
19
+
13
20
  ### Commands
14
21
  - `avif` Converts images to AVIF
15
22
  - `sqcrop` Square crops images
@@ -0,0 +1,26 @@
1
+ #!/bin/sh
2
+ set -eu
3
+
4
+ # Exit on systems without ZSH
5
+ zsh=$(command -v zsh) || exit 0
6
+
7
+ # Exit on non-global (npm -g) installations
8
+ [ "${npm_config_global:-}" = "true" ] || exit 0
9
+
10
+ src="$(cd "$(dirname "$0")" && pwd)/.zsh/completions/_mediasnacks"
11
+ [ -f "$src" ] || exit 0
12
+
13
+ exec "$zsh" -s "$src" << 'ZSH_EOF'
14
+ src="$1"
15
+ for dir in "${fpath[@]}"; do
16
+ if [ -w "$dir" ]; then
17
+ dst="$dir/_mediasnacks"
18
+ ln -sf "$src" "$dst"
19
+ echo "linked zsh completions: $dst -> $src"
20
+ exit 0
21
+ fi
22
+ done
23
+ echo "zsh completions: no writable fpath directory found, skipping." >&2
24
+ exit 0
25
+ ZSH_EOF
26
+
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mediasnacks",
3
- "version": "0.17.0",
4
- "description": "Utilities for optimizing and preparing videos and images for the web",
3
+ "version": "0.18.1",
4
+ "description": "Utilities for optimizing and preparing videos and images",
5
5
  "license": "MIT",
6
6
  "author": "Eric Fortis",
7
7
  "type": "module",
@@ -9,6 +9,12 @@
9
9
  "mediasnacks": "src/cli.js"
10
10
  },
11
11
  "scripts": {
12
- "test": "docker run --rm $(docker build -q .)"
13
- }
12
+ "test": "docker run --rm $(docker build -q .)",
13
+ "postinstall": "sh install-zsh-completions.sh"
14
+ },
15
+ "files": [
16
+ "src",
17
+ ".zsh",
18
+ "install-zsh-completions.sh"
19
+ ]
14
20
  }
package/src/cli.js CHANGED
@@ -21,7 +21,7 @@ const COMMANDS = {
21
21
  vconcat: ['vconcat.sh', 'Concatenates videos'],
22
22
  vdiff: ['vdiff.sh', 'Plays a video with the difference of two videos'],
23
23
  vsplit: ['vsplit.js', 'Splits a video into multiple clips from CSV timestamps'],
24
- vtrim: ['vtrim.sh', 'Trims video from start to end time'],
24
+ vtrim: ['vtrim.js', 'Trims video from start to end time'],
25
25
  prores: ['prores.js', 'Converts video to ProRes\n'],
26
26
 
27
27
  flattendir: ['flattendir.sh', 'Moves all files to top dir and deletes dirs'],
package/src/prores.js CHANGED
@@ -23,8 +23,8 @@ Arguments:
23
23
  <video> Video file to convert
24
24
 
25
25
  Options:
26
- --profile <n> ProRes profile (default: 3 (HQ))
27
- -h, --help Show this help message
26
+ -p, --profile <n> ProRes profile (default: 3 (422 HQ))
27
+ -h, --help Show this help message
28
28
 
29
29
  Example:
30
30
  npx mediasnacks prores video.mov
@@ -51,6 +51,7 @@ async function main() {
51
51
  throw new Error('Expected 1 argument: video file. See npx mediasnacks prores --help')
52
52
 
53
53
  const videoPath = resolve(files[0])
54
+
54
55
  const { name, dir } = parse(videoPath)
55
56
  const outputPath = join(dir, `${name}.prores.mov`)
56
57
 
package/src/vtrim.js ADDED
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { resolve, parse } from 'node:path'
4
+ import { parseOptions } from './utils/parseOptions.js'
5
+ import { ffmpeg, assertUserHasFFmpeg } from './utils/ffmpeg.js'
6
+
7
+
8
+ const USAGE = `
9
+ Usage: mediasnacks vtrim [--start <time>] [--end <time>] <video>
10
+
11
+ Trims a video without re-encoding (fast, but approximate cuts).
12
+
13
+ Options:
14
+ -s, --start <time> Start time (e.g. 10, 00:00:10, 1:23.5). Default: beginning.
15
+ -e, --end <time> End time (e.g. 30, 00:00:30, 2:45.0). Default: end of video.
16
+ -h, --help
17
+ `.trim()
18
+
19
+
20
+ async function main() {
21
+ await assertUserHasFFmpeg()
22
+
23
+ const { values, files } = await parseOptions({
24
+ start: { short: 's', type: 'string', default: '' },
25
+ end: { short: 'e', type: 'string', default: '' },
26
+ help: { short: 'h', type: 'boolean' }
27
+ })
28
+
29
+ if (values.help) {
30
+ console.log(USAGE)
31
+ process.exit(0)
32
+ }
33
+
34
+ if (!files.length)
35
+ throw new Error('No video specified. See npx mediasnacks vtrim --help')
36
+
37
+ for (const file of files)
38
+ await trim(resolve(file), values.start, values.end)
39
+ }
40
+
41
+ async function trim(video, start, end) {
42
+ const { dir, name, ext } = parse(video)
43
+ await ffmpeg([
44
+ '-v', 'error',
45
+ '-y',
46
+ start ? ['-ss', start] : [],
47
+ end ? ['-to', end] : [],
48
+ '-i', video,
49
+ '-c', 'copy',
50
+ resolve(dir, `${name}.trim${ext}`)
51
+ ].flat())
52
+ }
53
+
54
+ main().catch(err => {
55
+ console.error(err.message || err)
56
+ process.exit(1)
57
+ })
package/src/vtrim.test.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { ok } from 'node:assert/strict'
2
2
  import { join } from 'node:path'
3
- import { test } from 'node:test'
4
3
  import { cpSync } from 'node:fs'
4
+ import { describe, test } from 'node:test'
5
5
 
6
6
  import { videoAttrs } from './utils/ffmpeg.js'
7
7
  import { mkTempDir, cli } from './utils/test-utils.js'
@@ -9,14 +9,32 @@ import { mkTempDir, cli } from './utils/test-utils.js'
9
9
  const rel = f => join(import.meta.dirname, f)
10
10
 
11
11
 
12
- test('vtrim trims video from start to end time', async () => {
13
- const tmp = mkTempDir('vtrim')
12
+ describe('vtrim', () => {
13
+ function almostEqual(actual, expected) {
14
+ const EPSILON = 0.05
15
+ ok(Math.abs(parseFloat(actual) - expected) < EPSILON,
16
+ `Duration should be around ${expected}s, got ${actual}s`)
17
+ }
14
18
 
19
+ const tmp = mkTempDir('vtrim')
15
20
  const inputFile = join(tmp, '60fps.mp4')
16
21
  cpSync(rel('fixtures/60fps.mp4'), inputFile)
17
- cli('vtrim', 5, 10, inputFile)
18
22
 
19
- const { duration } = await videoAttrs(join(tmp, '60fps.trim.mp4'))
20
- const EPSILON = 0.05
21
- ok(Math.abs(parseFloat(duration) - 5) < EPSILON, `Duration should be 5s, got ${duration}s`)
23
+ test('from start to end time', async () => {
24
+ cli('vtrim', '--start', 5, '--end', 11, inputFile)
25
+ const { duration } = await videoAttrs(join(tmp, '60fps.trim.mp4'))
26
+ almostEqual(duration, 6)
27
+ })
28
+
29
+ test('start time only', async () => {
30
+ cli('vtrim', '--start', 5, inputFile)
31
+ const { duration } = await videoAttrs(join(tmp, '60fps.trim.mp4'))
32
+ almostEqual(duration, 25)
33
+ })
34
+
35
+ test('end time only', async () => {
36
+ cli('vtrim', '--end', 11, inputFile)
37
+ const { duration } = await videoAttrs(join(tmp, '60fps.trim.mp4'))
38
+ almostEqual(duration, 11)
39
+ })
22
40
  })
package/.editorconfig DELETED
@@ -1,11 +0,0 @@
1
- root = true
2
-
3
- [*]
4
- charset = utf-8
5
- end_of_line = lf
6
- indent_size = 2
7
- indent_style = tab
8
- insert_final_newline = true
9
-
10
- [*.md]
11
- indent_style = space
@@ -1,25 +0,0 @@
1
- name: Test
2
-
3
- on: [ push, pull_request, workflow_dispatch ]
4
-
5
- permissions:
6
- contents: read
7
- pull-requests: write
8
-
9
- jobs:
10
- test:
11
- runs-on: ubuntu-latest
12
-
13
- steps:
14
- - uses: actions/checkout@v6
15
- - uses: docker/setup-buildx-action@v4
16
- - uses: docker/build-push-action@v7
17
- with:
18
- context: .
19
- load: true
20
- tags: app-test
21
- cache-from: type=gha
22
- cache-to: type=gha,mode=max
23
-
24
- - name: Tests
25
- run: docker run --rm app-test
package/Dockerfile DELETED
@@ -1,13 +0,0 @@
1
- FROM mwader/static-ffmpeg:8.0.1 AS ffmpeg
2
- FROM node:24-bookworm-slim
3
-
4
- COPY --from=ffmpeg /ffmpeg /usr/local/bin/ffmpeg
5
- COPY --from=ffmpeg /ffprobe /usr/local/bin/ffprobe
6
-
7
- ENV FORCE_COLOR=1
8
- WORKDIR /workspace
9
-
10
- COPY src/ src/
11
- COPY package.json .
12
-
13
- CMD ["node", "--test"]
package/TODO.md DELETED
@@ -1,7 +0,0 @@
1
- # TODO
2
-
3
- - Test on Windows
4
- - Test on Linux
5
-
6
-
7
- Handle when shell expands, but files have special chars
Binary file
@@ -1,11 +0,0 @@
1
- #!/bin/sh
2
-
3
- docker run --rm \
4
- -v "$(pwd)/src/fixtures:/workspace/src/fixtures" \
5
- $(docker build -q .) \
6
- /bin/bash -c "
7
- node --test
8
- cp /tmp/avif*/lenna.avif /workspace/src/fixtures/
9
- cp /tmp/edgespic*/edgespic/*.png /workspace/src/fixtures/edgespic/
10
- "
11
-
package/src/vtrim.sh DELETED
@@ -1,36 +0,0 @@
1
- #!/bin/sh
2
-
3
- if [ "$#" -ne 3 ]; then
4
- cat << EOF
5
- Usage:
6
- $(basename $0) <start-time> <end-time> <video-file>
7
-
8
- Examples:
9
- $(basename $0) 10 30 input.mp4
10
- $(basename $0) 00:00:10 00:00:30 input.mkv
11
- $(basename $0) 1:23.5 2:45.0 video.mov
12
- EOF
13
- exit 1
14
- fi
15
-
16
- START="$1"
17
- END="$2"
18
- VIDEO="$3"
19
-
20
- if [ ! -f "$VIDEO" ]; then
21
- echo "Error: file not found: $VIDEO"
22
- exit 1
23
- fi
24
-
25
- BASENAME=$(basename "$VIDEO")
26
- DIRNAME=$(dirname "$VIDEO")
27
- EXT="${BASENAME##*.}"
28
- NAME="${BASENAME%.*}"
29
-
30
- # For speed, we copy without re-encoding (with -ss before -i), but
31
- # that means that the output isn’t going to be exact
32
- ffmpeg -v error -y \
33
- -ss "$START" \
34
- -to "$END" \
35
- -i "$VIDEO" \
36
- -c copy "$DIRNAME/${NAME}.trim.$EXT"