gifvid 0.1.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 +21 -0
- package/README.md +210 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +376 -0
- package/dist/cli.js.map +1 -0
- package/dist/core/manifest.d.ts +2 -0
- package/dist/core/manifest.js +5 -0
- package/dist/core/manifest.js.map +1 -0
- package/dist/core/media.d.ts +38 -0
- package/dist/core/media.js +197 -0
- package/dist/core/media.js.map +1 -0
- package/dist/core/presets.d.ts +11 -0
- package/dist/core/presets.js +101 -0
- package/dist/core/presets.js.map +1 -0
- package/dist/core/publish.d.ts +10 -0
- package/dist/core/publish.js +47 -0
- package/dist/core/publish.js.map +1 -0
- package/dist/core/source.d.ts +13 -0
- package/dist/core/source.js +135 -0
- package/dist/core/source.js.map +1 -0
- package/dist/core/tools.d.ts +15 -0
- package/dist/core/tools.js +60 -0
- package/dist/core/tools.js.map +1 -0
- package/dist/types.d.ts +89 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/files.d.ts +5 -0
- package/dist/utils/files.js +41 -0
- package/dist/utils/files.js.map +1 -0
- package/dist/utils/format.d.ts +3 -0
- package/dist/utils/format.js +36 -0
- package/dist/utils/format.js.map +1 -0
- package/dist/utils/time.d.ts +4 -0
- package/dist/utils/time.js +52 -0
- package/dist/utils/time.js.map +1 -0
- package/package.json +64 -0
- package/skills/gifvid-cli/SKILL.md +98 -0
- package/skills/gifvid-cli/agents/openai.yaml +4 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tools.js","sourceRoot":"","sources":["../../src/core/tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,OAAO,CAAA;AAEzC,MAAM,aAAa,GAAG;IACpB,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,QAAQ;IAC/C,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,QAAQ;IACjD,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,SAAS;CAC5C,CAAA;AAEV,MAAM,iBAAiB,GAA+B;IACpD,KAAK,EAAE,CAAC,WAAW,CAAC;IACpB,MAAM,EAAE,CAAC,UAAU,CAAC;IACpB,OAAO,EAAE,CAAC,UAAU,CAAC;CACtB,CAAA;AAID,MAAM,UAAU,aAAa,CAAC,QAAkB;IAC9C,OAAO,aAAa,CAAC,QAAQ,CAAC,CAAA;AAChC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,SAAqB;IAC7D,MAAM,WAAW,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,SAAS,CAAC,CAAC,CAAA;IAElD,KAAK,MAAM,QAAQ,IAAI,WAAW,EAAE,CAAC;QACnC,MAAM,MAAM,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAA;QACtC,IAAI,CAAC;YACH,MAAM,KAAK,CAAC,MAAM,EAAE,iBAAiB,CAAC,QAAQ,CAAC,EAAE;gBAC/C,MAAM,EAAE,QAAQ;gBAChB,MAAM,EAAE,QAAQ;aACjB,CAAC,CAAA;QACJ,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,KAAK,CACb,kBAAkB,MAAM,2BAA2B,cAAc,CAAC,QAAQ,CAAC,yBAAyB,CACrG,CAAA;QACH,CAAC;IACH,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,MAAc,EACd,IAAc,EACd,UAA4B,EAAE;IAE9B,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,MAAM,EAAE,IAAI,EAAE;YACvC,MAAM,EAAE,MAAM;YACd,MAAM,EAAE,MAAM;YACd,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC7C,CAAC,CAAA;QAEF,OAAO;YACL,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,MAAM,EAAE,MAAM,CAAC,MAAM;SACtB,CAAA;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,UAAU,EAAE,CAAC;YAChC,MAAM,OAAO,GAAG,CAAC,KAAK,CAAC,YAAY,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YAC7E,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,CAAA;QAC1B,CAAC;QAED,MAAM,KAAK,CAAA;IACb,CAAC;AACH,CAAC;AAED,SAAS,cAAc,CAAC,QAAkB;IACxC,QAAQ,QAAQ,EAAE,CAAC;QACjB,KAAK,OAAO;YACV,OAAO,kBAAkB,CAAA;QAC3B,KAAK,QAAQ;YACX,OAAO,mBAAmB,CAAA;QAC5B,KAAK,SAAS;YACZ,OAAO,oBAAoB,CAAA;IAC/B,CAAC;AACH,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
export type SourceKind = 'url' | 'file';
|
|
2
|
+
export type BrowserProfile = 'brave' | 'chrome' | 'chromium' | 'edge' | 'firefox' | 'safari';
|
|
3
|
+
export type PresetId = 'slack' | 'web' | 'tiny' | 'hq';
|
|
4
|
+
export type OutputFormat = 'gif' | 'mp4' | 'webm' | 'all';
|
|
5
|
+
export type AssetFormat = Exclude<OutputFormat, 'all'> | 'manifest';
|
|
6
|
+
export interface ClipWindow {
|
|
7
|
+
startSeconds: number;
|
|
8
|
+
endSeconds: number;
|
|
9
|
+
durationSeconds: number;
|
|
10
|
+
}
|
|
11
|
+
export interface SourceMetadata {
|
|
12
|
+
kind: SourceKind;
|
|
13
|
+
input: string;
|
|
14
|
+
title: string;
|
|
15
|
+
durationSeconds: number;
|
|
16
|
+
width?: number;
|
|
17
|
+
height?: number;
|
|
18
|
+
uploader?: string;
|
|
19
|
+
sourceUrl?: string;
|
|
20
|
+
thumbnailUrl?: string;
|
|
21
|
+
description?: string;
|
|
22
|
+
format?: string;
|
|
23
|
+
}
|
|
24
|
+
export interface RenderProfile {
|
|
25
|
+
width: number;
|
|
26
|
+
fps: number;
|
|
27
|
+
colors: number;
|
|
28
|
+
targetBytes?: number;
|
|
29
|
+
}
|
|
30
|
+
export interface RenderPreset {
|
|
31
|
+
id: PresetId;
|
|
32
|
+
label: string;
|
|
33
|
+
defaultDurationSeconds: number;
|
|
34
|
+
maxDurationSeconds: number;
|
|
35
|
+
defaultProfile: RenderProfile;
|
|
36
|
+
}
|
|
37
|
+
export interface RenderIteration extends RenderProfile {
|
|
38
|
+
bytes: number;
|
|
39
|
+
}
|
|
40
|
+
export interface RenderedAsset {
|
|
41
|
+
format: AssetFormat;
|
|
42
|
+
path: string;
|
|
43
|
+
bytes: number;
|
|
44
|
+
width?: number;
|
|
45
|
+
height?: number;
|
|
46
|
+
iterations?: RenderIteration[];
|
|
47
|
+
targetBytes?: number;
|
|
48
|
+
publishedUrl?: string;
|
|
49
|
+
}
|
|
50
|
+
export interface RenderResult {
|
|
51
|
+
createdAt: string;
|
|
52
|
+
source: SourceMetadata;
|
|
53
|
+
clipWindow: ClipWindow;
|
|
54
|
+
preset: PresetId;
|
|
55
|
+
requestedFormat: OutputFormat;
|
|
56
|
+
renderProfile: RenderProfile;
|
|
57
|
+
assets: RenderedAsset[];
|
|
58
|
+
manifestPath: string;
|
|
59
|
+
manifestPublishedUrl?: string;
|
|
60
|
+
}
|
|
61
|
+
export interface InspectResult {
|
|
62
|
+
source: SourceMetadata;
|
|
63
|
+
recommendedWindowSeconds: number;
|
|
64
|
+
presetEstimates: Array<{
|
|
65
|
+
preset: PresetId;
|
|
66
|
+
profile: RenderProfile;
|
|
67
|
+
estimatedBytes: number;
|
|
68
|
+
}>;
|
|
69
|
+
}
|
|
70
|
+
export interface MakeOptions {
|
|
71
|
+
browser?: BrowserProfile;
|
|
72
|
+
outputDir: string;
|
|
73
|
+
outputName?: string;
|
|
74
|
+
preset: PresetId;
|
|
75
|
+
format: OutputFormat;
|
|
76
|
+
startSeconds: number;
|
|
77
|
+
endSeconds?: number;
|
|
78
|
+
durationSeconds?: number;
|
|
79
|
+
width?: number;
|
|
80
|
+
fps?: number;
|
|
81
|
+
colors?: number;
|
|
82
|
+
maxBytes?: number;
|
|
83
|
+
publish: boolean;
|
|
84
|
+
json: boolean;
|
|
85
|
+
}
|
|
86
|
+
export interface PublishOptions {
|
|
87
|
+
key?: string;
|
|
88
|
+
json: boolean;
|
|
89
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare function ensureDir(dirPath: string): Promise<void>;
|
|
2
|
+
export declare function pathExists(targetPath: string): Promise<boolean>;
|
|
3
|
+
export declare function slugify(value: string): string;
|
|
4
|
+
export declare function inferMimeType(filePath: string): string;
|
|
5
|
+
export declare function writeJson(filePath: string, payload: unknown): Promise<void>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { access, mkdir, writeFile } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
export async function ensureDir(dirPath) {
|
|
4
|
+
await mkdir(dirPath, { recursive: true });
|
|
5
|
+
}
|
|
6
|
+
export async function pathExists(targetPath) {
|
|
7
|
+
try {
|
|
8
|
+
await access(targetPath);
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export function slugify(value) {
|
|
16
|
+
const normalized = value
|
|
17
|
+
.toLowerCase()
|
|
18
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
19
|
+
.replace(/^-+|-+$/g, '');
|
|
20
|
+
return normalized || 'gifvid';
|
|
21
|
+
}
|
|
22
|
+
export function inferMimeType(filePath) {
|
|
23
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
24
|
+
switch (ext) {
|
|
25
|
+
case '.gif':
|
|
26
|
+
return 'image/gif';
|
|
27
|
+
case '.mp4':
|
|
28
|
+
return 'video/mp4';
|
|
29
|
+
case '.webm':
|
|
30
|
+
return 'video/webm';
|
|
31
|
+
case '.json':
|
|
32
|
+
return 'application/json';
|
|
33
|
+
default:
|
|
34
|
+
return 'application/octet-stream';
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export async function writeJson(filePath, payload) {
|
|
38
|
+
await ensureDir(path.dirname(filePath));
|
|
39
|
+
await writeFile(filePath, `${JSON.stringify(payload, null, 2)}\n`, 'utf8');
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=files.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"files.js","sourceRoot":"","sources":["../../src/utils/files.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAC3D,OAAO,IAAI,MAAM,WAAW,CAAA;AAE5B,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,OAAe;IAC7C,MAAM,KAAK,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;AAC3C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,UAAkB;IACjD,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,UAAU,CAAC,CAAA;QACxB,OAAO,IAAI,CAAA;IACb,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAA;IACd,CAAC;AACH,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,KAAa;IACnC,MAAM,UAAU,GAAG,KAAK;SACrB,WAAW,EAAE;SACb,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC;SAC3B,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAA;IAE1B,OAAO,UAAU,IAAI,QAAQ,CAAA;AAC/B,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,QAAgB;IAC5C,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAA;IAChD,QAAQ,GAAG,EAAE,CAAC;QACZ,KAAK,MAAM;YACT,OAAO,WAAW,CAAA;QACpB,KAAK,MAAM;YACT,OAAO,WAAW,CAAA;QACpB,KAAK,OAAO;YACV,OAAO,YAAY,CAAA;QACrB,KAAK,OAAO;YACV,OAAO,kBAAkB,CAAA;QAC3B;YACE,OAAO,0BAA0B,CAAA;IACrC,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,QAAgB,EAAE,OAAgB;IAChE,MAAM,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAA;IACvC,MAAM,SAAS,CAAC,QAAQ,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;AAC5E,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export function formatBytes(bytes) {
|
|
2
|
+
if (bytes < 1024) {
|
|
3
|
+
return `${bytes} B`;
|
|
4
|
+
}
|
|
5
|
+
const units = ['KB', 'MB', 'GB'];
|
|
6
|
+
let value = bytes / 1024;
|
|
7
|
+
let unitIndex = 0;
|
|
8
|
+
while (value >= 1024 && unitIndex < units.length - 1) {
|
|
9
|
+
value /= 1024;
|
|
10
|
+
unitIndex += 1;
|
|
11
|
+
}
|
|
12
|
+
return `${value.toFixed(value >= 10 ? 1 : 2)} ${units[unitIndex]}`;
|
|
13
|
+
}
|
|
14
|
+
export function parseByteInput(value) {
|
|
15
|
+
const trimmed = value.trim().toLowerCase();
|
|
16
|
+
const match = trimmed.match(/^(\d+(?:\.\d+)?)(b|kb|mb|gb)?$/);
|
|
17
|
+
if (!match) {
|
|
18
|
+
throw new Error(`Unsupported byte format: ${value}`);
|
|
19
|
+
}
|
|
20
|
+
const numeric = Number(match[1]);
|
|
21
|
+
const unit = match[2] ?? 'b';
|
|
22
|
+
const multiplier = {
|
|
23
|
+
b: 1,
|
|
24
|
+
kb: 1024,
|
|
25
|
+
mb: 1024 ** 2,
|
|
26
|
+
gb: 1024 ** 3,
|
|
27
|
+
}[unit] ?? 1;
|
|
28
|
+
return Math.round(numeric * multiplier);
|
|
29
|
+
}
|
|
30
|
+
export function formatRatio(width, height) {
|
|
31
|
+
if (!width || !height) {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
return `${width}x${height}`;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.js","sourceRoot":"","sources":["../../src/utils/format.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,WAAW,CAAC,KAAa;IACvC,IAAI,KAAK,GAAG,IAAI,EAAE,CAAC;QACjB,OAAO,GAAG,KAAK,IAAI,CAAA;IACrB,CAAC;IAED,MAAM,KAAK,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAA;IAChC,IAAI,KAAK,GAAG,KAAK,GAAG,IAAI,CAAA;IACxB,IAAI,SAAS,GAAG,CAAC,CAAA;IAEjB,OAAO,KAAK,IAAI,IAAI,IAAI,SAAS,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACrD,KAAK,IAAI,IAAI,CAAA;QACb,SAAS,IAAI,CAAC,CAAA;IAChB,CAAC;IAED,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,SAAS,CAAC,EAAE,CAAA;AACpE,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAAa;IAC1C,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;IAC1C,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,gCAAgC,CAAC,CAAA;IAE7D,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,4BAA4B,KAAK,EAAE,CAAC,CAAA;IACtD,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;IAChC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,GAAG,CAAA;IAC5B,MAAM,UAAU,GAAG;QACjB,CAAC,EAAE,CAAC;QACJ,EAAE,EAAE,IAAI;QACR,EAAE,EAAE,IAAI,IAAI,CAAC;QACb,EAAE,EAAE,IAAI,IAAI,CAAC;KACd,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAEZ,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,UAAU,CAAC,CAAA;AACzC,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,KAAc,EAAE,MAAe;IACzD,IAAI,CAAC,KAAK,IAAI,CAAC,MAAM,EAAE,CAAC;QACtB,OAAO,SAAS,CAAA;IAClB,CAAC;IAED,OAAO,GAAG,KAAK,IAAI,MAAM,EAAE,CAAA;AAC7B,CAAC"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
const DURATION_RE = /^(?:(\d+):)?([0-5]?\d):([0-5]?\d(?:\.\d+)?)$/;
|
|
2
|
+
const UNIT_RE = /^(?:(\d+(?:\.\d+)?)h)?(?:(\d+(?:\.\d+)?)m)?(?:(\d+(?:\.\d+)?)s)?$/i;
|
|
3
|
+
export function parseTimeInput(value) {
|
|
4
|
+
const trimmed = value.trim();
|
|
5
|
+
if (!trimmed) {
|
|
6
|
+
throw new Error('Time value cannot be empty');
|
|
7
|
+
}
|
|
8
|
+
if (/^\d+(?:\.\d+)?$/.test(trimmed)) {
|
|
9
|
+
return Number(trimmed);
|
|
10
|
+
}
|
|
11
|
+
const clockMatch = trimmed.match(DURATION_RE);
|
|
12
|
+
if (clockMatch) {
|
|
13
|
+
const hours = Number(clockMatch[1] ?? 0);
|
|
14
|
+
const minutes = Number(clockMatch[2] ?? 0);
|
|
15
|
+
const seconds = Number(clockMatch[3] ?? 0);
|
|
16
|
+
return hours * 3600 + minutes * 60 + seconds;
|
|
17
|
+
}
|
|
18
|
+
const unitMatch = trimmed.match(UNIT_RE);
|
|
19
|
+
if (unitMatch && unitMatch[0]) {
|
|
20
|
+
const hours = Number(unitMatch[1] ?? 0);
|
|
21
|
+
const minutes = Number(unitMatch[2] ?? 0);
|
|
22
|
+
const seconds = Number(unitMatch[3] ?? 0);
|
|
23
|
+
return hours * 3600 + minutes * 60 + seconds;
|
|
24
|
+
}
|
|
25
|
+
throw new Error(`Unsupported time format: ${value}`);
|
|
26
|
+
}
|
|
27
|
+
export function formatClock(seconds) {
|
|
28
|
+
const safeSeconds = Math.max(0, seconds);
|
|
29
|
+
const wholeSeconds = Math.floor(safeSeconds);
|
|
30
|
+
const hours = Math.floor(wholeSeconds / 3600);
|
|
31
|
+
const minutes = Math.floor((wholeSeconds % 3600) / 60);
|
|
32
|
+
const remainder = wholeSeconds % 60;
|
|
33
|
+
if (hours > 0) {
|
|
34
|
+
return `${hours}:${String(minutes).padStart(2, '0')}:${String(remainder).padStart(2, '0')}`;
|
|
35
|
+
}
|
|
36
|
+
return `${minutes}:${String(remainder).padStart(2, '0')}`;
|
|
37
|
+
}
|
|
38
|
+
export function formatYtDlpTimestamp(seconds) {
|
|
39
|
+
const safeSeconds = Math.max(0, seconds);
|
|
40
|
+
const hours = Math.floor(safeSeconds / 3600);
|
|
41
|
+
const minutes = Math.floor((safeSeconds % 3600) / 60);
|
|
42
|
+
const remainder = safeSeconds % 60;
|
|
43
|
+
return `${hours}:${String(minutes).padStart(2, '0')}:${remainder.toFixed(3).padStart(6, '0')}`;
|
|
44
|
+
}
|
|
45
|
+
export function formatFileTimestamp(seconds) {
|
|
46
|
+
const safeSeconds = Math.max(0, Math.floor(seconds));
|
|
47
|
+
const hours = Math.floor(safeSeconds / 3600);
|
|
48
|
+
const minutes = Math.floor((safeSeconds % 3600) / 60);
|
|
49
|
+
const remainder = safeSeconds % 60;
|
|
50
|
+
return [hours, minutes, remainder].map((part) => String(part).padStart(2, '0')).join('-');
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=time.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"time.js","sourceRoot":"","sources":["../../src/utils/time.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "gifvid",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Local-first CLI for turning online videos and local files into size-constrained GIFs.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Daniel G Wilson",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"gif",
|
|
9
|
+
"video",
|
|
10
|
+
"ffmpeg",
|
|
11
|
+
"youtube",
|
|
12
|
+
"cli",
|
|
13
|
+
"cloudflare-r2"
|
|
14
|
+
],
|
|
15
|
+
"homepage": "https://github.com/danielgwilson/gifvid/tree/main/packages/cli",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/danielgwilson/gifvid/issues"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/danielgwilson/gifvid.git",
|
|
22
|
+
"directory": "packages/cli"
|
|
23
|
+
},
|
|
24
|
+
"type": "module",
|
|
25
|
+
"bin": {
|
|
26
|
+
"gifvid": "dist/cli.js"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"dist",
|
|
30
|
+
"LICENSE",
|
|
31
|
+
"README.md",
|
|
32
|
+
"skills/gifvid-cli"
|
|
33
|
+
],
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=20.0.0"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"",
|
|
39
|
+
"build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.json && node -e \"require('node:fs').chmodSync('dist/cli.js',0o755)\"",
|
|
40
|
+
"check": "tsc --noEmit -p tsconfig.json",
|
|
41
|
+
"cli": "node dist/cli.js",
|
|
42
|
+
"dev": "tsx src/cli.ts",
|
|
43
|
+
"prepack": "npm run build",
|
|
44
|
+
"prepublishOnly": "npm test",
|
|
45
|
+
"test": "vitest run",
|
|
46
|
+
"test:watch": "vitest"
|
|
47
|
+
},
|
|
48
|
+
"packageManager": "pnpm@10.26.2",
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"@aws-sdk/client-s3": "^3.1009.0",
|
|
51
|
+
"commander": "^14.0.3",
|
|
52
|
+
"execa": "^9.6.1",
|
|
53
|
+
"zod": "^4.3.6"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@types/node": "^25.5.0",
|
|
57
|
+
"tsx": "^4.21.0",
|
|
58
|
+
"typescript": "^5.9.3",
|
|
59
|
+
"vitest": "^4.1.0"
|
|
60
|
+
},
|
|
61
|
+
"publishConfig": {
|
|
62
|
+
"access": "public"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: gifvid-cli
|
|
3
|
+
description: Use when a user wants to inspect a video source, cut a clip, render a GIF/MP4/WebM under a target profile, produce a manifest, or publish the result to Cloudflare R2 with the local `gifvid` CLI. Triggers include making a GIF from YouTube or another supported URL, clipping a local video file, estimating GIF size for Slack/web, generating multiple output formats from one clip, compressing a reaction GIF to fit a limit, or uploading a generated asset set for sharing.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Gifvid Cli
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
This skill drives the local `gifvid` CLI to turn a URL or a local media file into usable clip assets. It is for operational clip-making, not for building the product itself.
|
|
11
|
+
|
|
12
|
+
Use it when the user wants a result like:
|
|
13
|
+
|
|
14
|
+
- “Make this YouTube moment into a Slack GIF.”
|
|
15
|
+
- “Cut 4 seconds out of this local MP4 and keep it under 3 MB.”
|
|
16
|
+
- “Generate GIF, MP4, and WebM versions of this clip.”
|
|
17
|
+
- “Inspect this source and tell me which preset should fit.”
|
|
18
|
+
- “Publish the finished GIF to R2 and give me the link.”
|
|
19
|
+
|
|
20
|
+
## Workflow
|
|
21
|
+
|
|
22
|
+
1. Confirm the source.
|
|
23
|
+
Use a supported URL or a local media path.
|
|
24
|
+
|
|
25
|
+
2. Confirm the clip window.
|
|
26
|
+
Ask for `start` and either `end` or `duration` if they are not already clear.
|
|
27
|
+
|
|
28
|
+
3. Confirm the target.
|
|
29
|
+
Choose a preset, output format, or byte budget:
|
|
30
|
+
`tiny`, `slack`, `web`, or `hq`
|
|
31
|
+
`gif`, `mp4`, `webm`, or `all`
|
|
32
|
+
|
|
33
|
+
4. Run the CLI.
|
|
34
|
+
Prefer `--json` when the next step needs structured output.
|
|
35
|
+
|
|
36
|
+
5. Summarize the result.
|
|
37
|
+
Report output path, final size, dimensions, clip window, and public URL if published.
|
|
38
|
+
|
|
39
|
+
## Commands
|
|
40
|
+
|
|
41
|
+
Build first if `dist/` is missing or stale:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pnpm build
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Inspect a source:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pnpm cli inspect "<url-or-file>" --json
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Create a GIF:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pnpm cli make "<url-or-file>" \
|
|
57
|
+
--start 0:42 \
|
|
58
|
+
--duration 4 \
|
|
59
|
+
--preset slack \
|
|
60
|
+
--json
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Create all output variants plus a manifest:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
pnpm cli make "<url-or-file>" \
|
|
67
|
+
--start 0:42 \
|
|
68
|
+
--duration 4 \
|
|
69
|
+
--preset slack \
|
|
70
|
+
--format all \
|
|
71
|
+
--json
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
If a URL source needs local browser cookies:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
pnpm cli make "<url>" \
|
|
78
|
+
--browser chrome \
|
|
79
|
+
--start 0:42 \
|
|
80
|
+
--duration 4 \
|
|
81
|
+
--preset slack \
|
|
82
|
+
--json
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Publish an existing asset:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
pnpm cli publish ./outputs/example.gif --json
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Guardrails
|
|
92
|
+
|
|
93
|
+
- Prefer `inspect` before `make` when the source duration or likely size is unclear.
|
|
94
|
+
- For URL sources that fail with anti-bot or sign-in errors, retry with `--browser chrome` or another local browser profile.
|
|
95
|
+
- Prefer shorter clip durations before aggressively shrinking width or fps.
|
|
96
|
+
- Remember that every `make` run writes a manifest file; include it when a downstream step needs structured metadata.
|
|
97
|
+
- If the user wants a public URL, verify whether R2 env vars are configured first.
|
|
98
|
+
- Treat public redistribution of copyrighted media as a policy and rights question, not just a technical one.
|