@joaodotwork/finder-video-thumbnails 1.0.0

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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 joaodotwork
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,76 @@
1
+ # finder-video-thumbnails
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@joaodotwork/finder-video-thumbnails.svg)](https://www.npmjs.com/package/@joaodotwork/finder-video-thumbnails)
4
+ [![npm downloads](https://img.shields.io/npm/dm/@joaodotwork/finder-video-thumbnails.svg)](https://www.npmjs.com/package/@joaodotwork/finder-video-thumbnails)
5
+ [![license](https://img.shields.io/npm/l/@joaodotwork/finder-video-thumbnails.svg)](LICENSE)
6
+ [![platform](https://img.shields.io/badge/platform-macOS-lightgrey.svg)](https://www.apple.com/macos)
7
+
8
+ Generate macOS Finder thumbnails for video files that don't have one, using `ffmpeg` and [`fileicon`](https://github.com/mklement0/fileicon).
9
+
10
+ By default, video files in Finder show a generic icon (or a low-res QuickLook preview that disappears when you scroll away). This tool grabs a frame from each video and sets it as a permanent custom Finder icon — so the thumbnails are always visible, in any view, with the natural aspect ratio preserved.
11
+
12
+ ## Requirements
13
+
14
+ - macOS (uses `fileicon` which sets `com.apple.FinderInfo` xattr — won't work on Linux/Windows)
15
+ - [`ffmpeg`](https://ffmpeg.org/) — `brew install ffmpeg`
16
+ - [`fileicon`](https://github.com/mklement0/fileicon) — `brew install fileicon`
17
+
18
+ ## Install
19
+
20
+ ```sh
21
+ npm install -g @joaodotwork/finder-video-thumbnails
22
+ ```
23
+
24
+ Or run without installing:
25
+
26
+ ```sh
27
+ npx @joaodotwork/finder-video-thumbnails <folder>
28
+ ```
29
+
30
+ ## Usage
31
+
32
+ ```sh
33
+ finder-video-thumbnails [--force] <folder> [seek_seconds]
34
+ ```
35
+
36
+ - `<folder>` — folder to process (recursive)
37
+ - `--force` — re-generate icons even if a custom icon is already set
38
+ - `seek_seconds` — timestamp (in seconds) to grab the frame from. Defaults to `1`
39
+
40
+ ### Examples
41
+
42
+ Add thumbnails to every video in `~/Movies` (recursive):
43
+
44
+ ```sh
45
+ finder-video-thumbnails ~/Movies
46
+ ```
47
+
48
+ Grab the frame at the 5-second mark:
49
+
50
+ ```sh
51
+ finder-video-thumbnails ~/Movies 5
52
+ ```
53
+
54
+ Re-generate all thumbnails, overwriting existing ones:
55
+
56
+ ```sh
57
+ finder-video-thumbnails --force ~/Movies
58
+ ```
59
+
60
+ ## Supported formats
61
+
62
+ `.mov`, `.mp4`, `.m4v`, `.avi`, `.mkv`, `.webm` (case-insensitive)
63
+
64
+ ## How it works
65
+
66
+ For each video without a custom icon:
67
+
68
+ 1. `ffmpeg` extracts a single frame at the seek timestamp.
69
+ 2. The frame is scaled to fit a 512×512 box, preserving aspect ratio, then padded to a square with a transparent background. This ensures Finder displays the thumbnail with the video's natural aspect ratio rather than stretching it.
70
+ 3. `fileicon` writes the resulting PNG as the file's custom Finder icon.
71
+
72
+ Re-runs are idempotent: files that already have a custom icon are skipped unless `--force` is passed.
73
+
74
+ ## License
75
+
76
+ MIT
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/env bash
2
+ # finder-video-thumbnails
3
+ # Generate macOS Finder thumbnails for video files using ffmpeg + fileicon.
4
+ #
5
+ # Usage: finder-video-thumbnails [--force] <folder> [seek_seconds]
6
+ # --force re-generate icons even if one is already set
7
+ # seek_seconds timestamp (in seconds) to grab the frame from. defaults to 1
8
+
9
+ set -euo pipefail
10
+
11
+ force=0
12
+ if [[ "${1:-}" == "--force" ]]; then
13
+ force=1
14
+ shift
15
+ fi
16
+
17
+ if [[ $# -lt 1 ]]; then
18
+ echo "usage: finder-video-thumbnails [--force] <folder> [seek_seconds]" >&2
19
+ exit 1
20
+ fi
21
+
22
+ folder="$1"
23
+ seek="${2:-1}"
24
+
25
+ if [[ ! -d "$folder" ]]; then
26
+ echo "error: not a directory: $folder" >&2
27
+ exit 1
28
+ fi
29
+
30
+ for cmd in ffmpeg fileicon; do
31
+ if ! command -v "$cmd" >/dev/null 2>&1; then
32
+ echo "error: '$cmd' not found on PATH" >&2
33
+ echo " install with: brew install $cmd" >&2
34
+ exit 1
35
+ fi
36
+ done
37
+
38
+ videos=()
39
+ while IFS= read -r -d '' f; do
40
+ videos+=("$f")
41
+ done < <(find "$folder" -type f \( \
42
+ -iname '*.mov' -o \
43
+ -iname '*.mp4' -o \
44
+ -iname '*.m4v' -o \
45
+ -iname '*.avi' -o \
46
+ -iname '*.mkv' -o \
47
+ -iname '*.webm' \) -print0)
48
+
49
+ if [[ ${#videos[@]} -eq 0 ]]; then
50
+ echo "no video files found in: $folder"
51
+ exit 0
52
+ fi
53
+
54
+ tmpdir="$(mktemp -d)"
55
+ trap 'rm -rf "$tmpdir"' EXIT
56
+
57
+ done_count=0
58
+ skip_count=0
59
+ fail_count=0
60
+ i=0
61
+
62
+ for video in "${videos[@]}"; do
63
+ name="${video#$folder/}"
64
+ ((i++))
65
+
66
+ if [[ $force -eq 0 ]] && fileicon test "$video" >/dev/null 2>&1; then
67
+ echo "skip $name (already has icon)"
68
+ ((skip_count++))
69
+ continue
70
+ fi
71
+
72
+ thumb="$tmpdir/thumb_$i.png"
73
+ if ! ffmpeg -y -ss "$seek" -i "$video" -frames:v 1 -update 1 \
74
+ -vf "scale=512:512:force_original_aspect_ratio=decrease,format=rgba,pad=512:512:(ow-iw)/2:(oh-ih)/2:color=black@0" \
75
+ "$thumb" </dev/null >/dev/null 2>&1; then
76
+ echo "FAIL $name (ffmpeg)"
77
+ ((fail_count++))
78
+ continue
79
+ fi
80
+
81
+ if ! fileicon set "$video" "$thumb" >/dev/null 2>&1; then
82
+ echo "FAIL $name (fileicon)"
83
+ ((fail_count++))
84
+ continue
85
+ fi
86
+
87
+ echo "ok $name"
88
+ ((done_count++))
89
+ done
90
+
91
+ echo
92
+ echo "done: $done_count skipped: $skip_count failed: $fail_count"
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@joaodotwork/finder-video-thumbnails",
3
+ "version": "1.0.0",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "description": "Generate macOS Finder thumbnails for video files that don't have one, using ffmpeg + fileicon.",
8
+ "bin": {
9
+ "finder-video-thumbnails": "./bin/finder-video-thumbnails"
10
+ },
11
+ "scripts": {
12
+ "test": "echo \"no tests\" && exit 0"
13
+ },
14
+ "keywords": [
15
+ "macos",
16
+ "finder",
17
+ "thumbnail",
18
+ "video",
19
+ "ffmpeg",
20
+ "fileicon",
21
+ "icon",
22
+ "mov",
23
+ "mp4"
24
+ ],
25
+ "author": "joaodotwork",
26
+ "license": "MIT",
27
+ "os": [
28
+ "darwin"
29
+ ],
30
+ "files": [
31
+ "bin/",
32
+ "README.md",
33
+ "LICENSE"
34
+ ],
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "git+https://github.com/joaodotwork/finder-video-thumbnails.git"
38
+ },
39
+ "bugs": {
40
+ "url": "https://github.com/joaodotwork/finder-video-thumbnails/issues"
41
+ },
42
+ "homepage": "https://github.com/joaodotwork/finder-video-thumbnails#readme"
43
+ }