diff --git a/electron/electron-env.d.ts b/electron/electron-env.d.ts index 573aee8af..83442b9bd 100644 --- a/electron/electron-env.d.ts +++ b/electron/electron-env.d.ts @@ -137,6 +137,14 @@ interface Window { setHasUnsavedChanges: (hasChanges: boolean) => void; onRequestSaveBeforeClose: (callback: () => Promise | boolean) => () => void; setLocale: (locale: string) => Promise; + generateSubtitles: ( + videoPath: string, + lang?: string, + ) => Promise<{ + success: boolean; + subtitles?: Array<{ id: string; startMs: number; endMs: number; text: string }>; + error?: string; + }>; }; } diff --git a/electron/ipc/handlers.ts b/electron/ipc/handlers.ts index 78d83448a..b6fc5aa87 100644 --- a/electron/ipc/handlers.ts +++ b/electron/ipc/handlers.ts @@ -1,3 +1,4 @@ +import { execFile } from "node:child_process"; import fs from "node:fs/promises"; import path from "node:path"; import { fileURLToPath, pathToFileURL } from "node:url"; @@ -781,4 +782,65 @@ export function registerIpcHandlers( return { success: false, error: String(error) }; } }); + + ipcMain.handle("generate-subtitles", async (_, videoPath: string, lang = "pt") => { + const scriptPath = path.join(app.getAppPath(), "scripts", "extract-subtitles.mjs"); + try { + await fs.access(scriptPath); + } catch { + return { success: false, error: "extract-subtitles.mjs script not found" }; + } + + const nodeBin = + process.env.NODE_BINARY || + (process.platform === "win32" ? "node.exe" : "node"); + + return new Promise<{ + success: boolean; + subtitles?: Array<{ id: string; startMs: number; endMs: number; text: string }>; + error?: string; + }>((resolve) => { + const child = execFile( + nodeBin, + [scriptPath, videoPath, "--json", "--lang", lang], + { + maxBuffer: 10 * 1024 * 1024, + timeout: 300_000, + cwd: app.getAppPath(), + }, + (error, stdout, stderr) => { + if (error) { + console.error("Subtitle generation error:", stderr || error.message); + resolve({ success: false, error: error.message }); + return; + } + try { + const jsonStart = stdout.indexOf("{"); + const jsonEnd = stdout.lastIndexOf("}"); + if (jsonStart === -1 || jsonEnd === -1) { + resolve({ success: false, error: "No JSON output from script" }); + return; + } + const config = JSON.parse(stdout.slice(jsonStart, jsonEnd + 1)); + const items = config?.subtitles?.items ?? []; + const FPS = 30; + const subtitles = items.map( + (item: { text: string; startFrame: number; endFrame: number }, idx: number) => ({ + id: `sub-${idx + 1}`, + startMs: Math.round((item.startFrame / FPS) * 1000), + endMs: Math.round((item.endFrame / FPS) * 1000), + text: item.text, + }), + ); + resolve({ success: true, subtitles }); + } catch (parseError) { + resolve({ success: false, error: `Failed to parse output: ${String(parseError)}` }); + } + }, + ); + child.stderr?.on("data", (data) => { + console.log("[subtitle-gen]", String(data).trim()); + }); + }); + }); } diff --git a/electron/preload.ts b/electron/preload.ts index 8f1836bd8..97042c1fd 100644 --- a/electron/preload.ts +++ b/electron/preload.ts @@ -118,6 +118,9 @@ contextBridge.exposeInMainWorld("electronAPI", { setLocale: (locale: string) => { return ipcRenderer.invoke("set-locale", locale); }, + generateSubtitles: (videoPath: string, lang?: string) => { + return ipcRenderer.invoke("generate-subtitles", videoPath, lang ?? "pt"); + }, setMicrophoneExpanded: (expanded: boolean) => { ipcRenderer.send("hud:setMicrophoneExpanded", expanded); }, diff --git a/package-lock.json b/package-lock.json index 70e33952e..06bd6b365 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "openscreen", - "version": "1.2.0", + "version": "1.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "openscreen", - "version": "1.2.0", + "version": "1.3.0", "dependencies": { "@fix-webm-duration/fix": "^1.0.1", "@pixi/filter-drop-shadow": "^5.2.0", @@ -60,6 +60,7 @@ "@types/react-dom": "^18.2.21", "@types/uuid": "^10.0.0", "@vitejs/plugin-react": "^4.2.1", + "@xenova/transformers": "^2.17.2", "autoprefixer": "^10.4.21", "electron": "^39.2.7", "electron-builder": "^26.7.0", @@ -76,7 +77,8 @@ "vite": "^5.1.6", "vite-plugin-electron": "^0.28.6", "vite-plugin-electron-renderer": "^0.14.5", - "vitest": "^4.0.16" + "vitest": "^4.0.16", + "wavefile": "^11.0.0" }, "engines": { "node": "22.22.1", @@ -2132,6 +2134,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@huggingface/jinja": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@huggingface/jinja/-/jinja-0.2.2.tgz", + "integrity": "sha512-/KPde26khDUIPkTGU82jdtTW9UAuvUTumCAbFs/7giR0SxsvZC4hru51PBvpijH6BVkHcROcvZM/lpy5h1jRRA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -3205,6 +3217,80 @@ "node": ">=18" } }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", + "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.1.tgz", + "integrity": "sha512-mnzgDV26ueAvk7rsbt9L7bE0SuAoqyuys/sMMrmVcN5x9VsxpcG3rqAUSgDyLp0UZlmNfIbQ4fHfCtreVBk8Ew==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz", + "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/@radix-ui/number": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", @@ -4770,6 +4856,13 @@ "@types/node": "*" } }, + "node_modules/@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/ms": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", @@ -5036,6 +5129,21 @@ "integrity": "sha512-YA2hLrwLpDsRueNDXIMqN9NTzD6bCDkuXbOSe0heS+f8YE8usA6Gbv1prj81pzVHrbaAma7zObnIC+I6/sXJgA==", "license": "BSD-3-Clause" }, + "node_modules/@xenova/transformers": { + "version": "2.17.2", + "resolved": "https://registry.npmjs.org/@xenova/transformers/-/transformers-2.17.2.tgz", + "integrity": "sha512-lZmHqzrVIkSvZdKZEx7IYY51TK0WDrC8eR0c5IMnBsO8di8are1zzw8BlLhyO2TklZKLN5UffNGs1IJwT6oOqQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@huggingface/jinja": "^0.2.2", + "onnxruntime-web": "1.14.0", + "sharp": "^0.32.0" + }, + "optionalDependencies": { + "onnxruntime-node": "1.14.0" + } + }, "node_modules/@xmldom/xmldom": { "version": "0.8.11", "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.11.tgz", @@ -5839,6 +5947,103 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT" }, + "node_modules/bare-events": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", + "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "bare-abort-controller": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + } + } + }, + "node_modules/bare-fs": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.7.1.tgz", + "integrity": "sha512-WDRsyVN52eAx/lBamKD6uyw8H4228h/x0sGGGegOamM2cd7Pag88GfMQalobXI+HaEUxpCkbKQUDOQqt9wawRw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4", + "bare-url": "^2.2.2", + "fast-fifo": "^1.3.2" + }, + "engines": { + "bare": ">=1.16.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } + } + }, + "node_modules/bare-os": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.9.0.tgz", + "integrity": "sha512-JTjuZyNIDpw+GytMO4a6TK1VXdVKKJr6DRxEHasyuYyShV2deuiHJK/ahGZlebc+SG0/wJCB9XK8gprBGDFi/Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "bare": ">=1.14.0" + } + }, + "node_modules/bare-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", + "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bare-os": "^3.0.1" + } + }, + "node_modules/bare-stream": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.1.tgz", + "integrity": "sha512-Vp0cnjYyrEC4whYTymQ+YZi6pBpfiICZO3cfRG8sy67ZNWe951urv1x4eW1BKNngw3U+3fPYb5JQvHbCtxH7Ow==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "streamx": "^2.25.0", + "teex": "^1.0.1" + }, + "peerDependencies": { + "bare-abort-controller": "*", + "bare-buffer": "*", + "bare-events": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + }, + "bare-buffer": { + "optional": true + }, + "bare-events": { + "optional": true + } + } + }, + "node_modules/bare-url": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.2.tgz", + "integrity": "sha512-/9a2j4ac6ckpmAHvod/ob7x439OAHst/drc2Clnq+reRYd/ovddwcF4LfoxHyNk5AuGBnPg+HqFjmE/Zpq6v0A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bare-path": "^3.0.0" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -6571,6 +6776,20 @@ "node": ">=0.10.0" } }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -6589,6 +6808,17 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, "node_modules/color-support": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", @@ -6881,6 +7111,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/defaults": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", @@ -7826,12 +8066,32 @@ "node": ">=0.8.x" } }, + "node_modules/events-universal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", + "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.7.0" + } + }, "node_modules/exif-parser": { "version": "0.1.12", "resolved": "https://registry.npmjs.org/exif-parser/-/exif-parser-0.1.12.tgz", "integrity": "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw==", "dev": true }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "dev": true, + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, "node_modules/expect-type": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", @@ -7918,6 +8178,13 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true, + "license": "MIT" + }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", @@ -8047,6 +8314,13 @@ "integrity": "sha512-IKlE+pNvL2R+kVL1kEhUYqRxVqeFnjiIvHWDMLFXNaqyUdFXQM2wte44EfMYJNHkW16X991t2Zg8apKkhv7OBA==", "license": "MIT" }, + "node_modules/flatbuffers": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/flatbuffers/-/flatbuffers-1.12.0.tgz", + "integrity": "sha512-c7CZADjRcl6j0PlvFy0ZqXQ67qSEZfrVPynmnL+2zPc+NtMvrF8Y0QceMo7QqnSPc7+uWjUIAbvCQ5WIKlMVdQ==", + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt" + }, "node_modules/follow-redirects": { "version": "1.15.11", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", @@ -8152,6 +8426,13 @@ } } }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true, + "license": "MIT" + }, "node_modules/fs-extra": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", @@ -8389,6 +8670,13 @@ "omggif": "^1.0.10" } }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "dev": true, + "license": "MIT" + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -8567,6 +8855,13 @@ "integrity": "sha512-QL7MJ2WMjm1PHWsoFrAQH/J8wUeqZvMtHO58qdekHpCfhvhSL4gSiz6vJf5EeMP0LOn3ZCprL2ki/gjED8ghVw==", "license": "Standard 'no charge' license: https://gsap.com/standard-license." }, + "node_modules/guid-typescript": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/guid-typescript/-/guid-typescript-1.0.9.tgz", + "integrity": "sha512-Y8T4vYhEfwJOTbouREvG+3XDsjr8E3kIr7uf+JZ0BYloFsttiHU0WfvANVsR7TxNUJa/WpCnw/Ino/p+DeBhBQ==", + "dev": true, + "license": "ISC" + }, "node_modules/har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -8981,6 +9276,13 @@ "dev": true, "license": "ISC" }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, "node_modules/invert-kv": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", @@ -10026,6 +10328,13 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -10587,6 +10896,13 @@ "node": ">=10" } }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true, + "license": "MIT" + }, "node_modules/motion": { "version": "12.23.24", "resolved": "https://registry.npmjs.org/motion/-/motion-12.23.24.tgz", @@ -10683,6 +10999,13 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "dev": true, + "license": "MIT" + }, "node_modules/negotiator": { "version": "0.6.4", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", @@ -10970,6 +11293,54 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/onnx-proto": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/onnx-proto/-/onnx-proto-4.0.4.tgz", + "integrity": "sha512-aldMOB3HRoo6q/phyB6QRQxSt895HNNw82BNyZ2CMh4bjeKv7g/c+VpAFtJuEMVfYLMbRx61hbuqnKceLeDcDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "protobufjs": "^6.8.8" + } + }, + "node_modules/onnxruntime-common": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/onnxruntime-common/-/onnxruntime-common-1.14.0.tgz", + "integrity": "sha512-3LJpegM2iMNRX2wUmtYfeX/ytfOzNwAWKSq1HbRrKc9+uqG/FsEA0bbKZl1btQeZaXhC26l44NWpNUeXPII7Ew==", + "dev": true, + "license": "MIT" + }, + "node_modules/onnxruntime-node": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/onnxruntime-node/-/onnxruntime-node-1.14.0.tgz", + "integrity": "sha512-5ba7TWomIV/9b6NH/1x/8QEeowsb+jBEvFzU6z0T4mNsFwdPqXeFUM7uxC6QeSRkEbWu3qEB0VMjrvzN/0S9+w==", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32", + "darwin", + "linux" + ], + "dependencies": { + "onnxruntime-common": "~1.14.0" + } + }, + "node_modules/onnxruntime-web": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/onnxruntime-web/-/onnxruntime-web-1.14.0.tgz", + "integrity": "sha512-Kcqf43UMfW8mCydVGcX9OMXI2VN17c0p6XvR7IPSZzBf/6lteBzXHvcEVWDPmCKuGombl997HgLqj91F11DzXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatbuffers": "^1.12.0", + "guid-typescript": "^1.0.9", + "long": "^4.0.0", + "onnx-proto": "^4.0.4", + "onnxruntime-common": "~1.14.0", + "platform": "^1.3.6" + } + }, "node_modules/ora": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", @@ -11471,6 +11842,13 @@ "url": "https://opencollective.com/pixijs" } }, + "node_modules/platform": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz", + "integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==", + "dev": true, + "license": "MIT" + }, "node_modules/playwright": { "version": "1.58.2", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz", @@ -11733,6 +12111,71 @@ "node": "^12.20.0 || >=14" } }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/prebuild-install/node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true, + "license": "ISC" + }, + "node_modules/prebuild-install/node_modules/tar-fs": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/prebuild-install/node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/pretty-format": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", @@ -11859,6 +12302,33 @@ "dev": true, "license": "ISC" }, + "node_modules/protobufjs": { + "version": "6.11.5", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.5.tgz", + "integrity": "sha512-OKjVH3hDoXdIZ/s5MLv8O2X0s+wOxGfV7ar6WFSKGaSAxi/6gYn3px5POS4vi+mc/0zCOdL7Jkwrj0oT1Yst2A==", + "dev": true, + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": ">=13.7.0", + "long": "^4.0.0" + }, + "bin": { + "pbjs": "bin/pbjs", + "pbts": "bin/pbts" + } + }, "node_modules/psl": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", @@ -11959,6 +12429,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, "node_modules/re-resizable": { "version": "6.11.2", "resolved": "https://registry.npmjs.org/re-resizable/-/re-resizable-6.11.2.tgz", @@ -12787,6 +13273,37 @@ "dev": true, "license": "ISC" }, + "node_modules/sharp": { + "version": "0.32.6", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.32.6.tgz", + "integrity": "sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.2", + "node-addon-api": "^6.1.0", + "prebuild-install": "^7.1.1", + "semver": "^7.5.4", + "simple-get": "^4.0.1", + "tar-fs": "^3.0.4", + "tunnel-agent": "^0.6.0" + }, + "engines": { + "node": ">=14.15.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/sharp/node_modules/node-addon-api": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", + "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==", + "dev": true, + "license": "MIT" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -12903,6 +13420,70 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz", + "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/simple-swizzle/node_modules/is-arrayish": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz", + "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", + "dev": true, + "license": "MIT" + }, "node_modules/simple-update-notifier": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", @@ -13150,6 +13731,18 @@ "dev": true, "license": "MIT" }, + "node_modules/streamx": { + "version": "2.25.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.25.0.tgz", + "integrity": "sha512-0nQuG6jf1w+wddNEEXCF4nTg3LtufWINB5eFEN+5TNZW7KWJp6x87+JFL43vaAUPyCfH1wID+mNVyW6OHtFamg==", + "dev": true, + "license": "MIT", + "dependencies": { + "events-universal": "^1.0.0", + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + } + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -13250,6 +13843,16 @@ "node": ">=8" } }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/strtok3": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz", @@ -13596,6 +14199,49 @@ "node": ">=10" } }, + "node_modules/tar-fs": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.2.tgz", + "integrity": "sha512-QGxxTxxyleAdyM3kpFs14ymbYmNFrfY+pHj7Z8FgtbZ7w2//VAgLMac7sT6nRpIHjppXO2AwwEOg0bPFVRcmXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^4.0.1", + "bare-path": "^3.0.0" + } + }, + "node_modules/tar-stream": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.8.tgz", + "integrity": "sha512-U6QpVRyCGHva435KoNWy9PRoi2IFYCgtEhq9nmrPPpbRacPs9IH4aJ3gbrFC8dPcXvdSZ4XXfXT5Fshbp2MtlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "bare-fs": "^4.5.5", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/tar-stream/node_modules/b4a": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.0.tgz", + "integrity": "sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, "node_modules/tar/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -13603,6 +14249,16 @@ "dev": true, "license": "ISC" }, + "node_modules/teex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", + "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "streamx": "^2.12.5" + } + }, "node_modules/temp": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/temp/-/temp-0.9.4.tgz", @@ -13722,6 +14378,31 @@ "dev": true, "license": "MIT" }, + "node_modules/text-decoder": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz", + "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, + "node_modules/text-decoder/node_modules/b4a": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.0.tgz", + "integrity": "sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -14991,6 +15672,19 @@ "node": ">=18" } }, + "node_modules/wavefile": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/wavefile/-/wavefile-11.0.0.tgz", + "integrity": "sha512-/OBiAALgWU24IG7sC84cDO/KfFuvajWc5Uec0oV2zrpOOZZDgGdOwHwgEzOrwh8jkubBk7PtZfQBIcI1OaE5Ng==", + "dev": true, + "license": "MIT", + "bin": { + "wavefile": "bin/wavefile.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wcwidth": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", diff --git a/package.json b/package.json index c367f9ed1..5e90b0a4a 100644 --- a/package.json +++ b/package.json @@ -78,6 +78,7 @@ "@types/react-dom": "^18.2.21", "@types/uuid": "^10.0.0", "@vitejs/plugin-react": "^4.2.1", + "@xenova/transformers": "^2.17.2", "autoprefixer": "^10.4.21", "electron": "^39.2.7", "electron-builder": "^26.7.0", @@ -94,7 +95,8 @@ "vite": "^5.1.6", "vite-plugin-electron": "^0.28.6", "vite-plugin-electron-renderer": "^0.14.5", - "vitest": "^4.0.16" + "vitest": "^4.0.16", + "wavefile": "^11.0.0" }, "main": "dist-electron/main.js", "lint-staged": { diff --git a/public/Saira_Stencil/OFL.txt b/public/Saira_Stencil/OFL.txt new file mode 100644 index 000000000..26d124f13 --- /dev/null +++ b/public/Saira_Stencil/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2020 The Saira Project Authors (https://github.com/Omnibus-Type/Saira) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/public/Saira_Stencil/README.txt b/public/Saira_Stencil/README.txt new file mode 100644 index 000000000..a4f6e7757 --- /dev/null +++ b/public/Saira_Stencil/README.txt @@ -0,0 +1,190 @@ +Saira Stencil Variable Font +=========================== + +This download contains Saira Stencil as both variable fonts and static fonts. + +Saira Stencil is a variable font with these axes: + wdth + wght + +This means all the styles are contained in these files: + SairaStencil-VariableFont_wdth,wght.ttf + SairaStencil-Italic-VariableFont_wdth,wght.ttf + +If your app fully supports variable fonts, you can now pick intermediate styles +that aren’t available as static fonts. Not all apps support variable fonts, and +in those cases you can use the static font files for Saira Stencil: + static/SairaStencil_UltraCondensed-Thin.ttf + static/SairaStencil_UltraCondensed-ExtraLight.ttf + static/SairaStencil_UltraCondensed-Light.ttf + static/SairaStencil_UltraCondensed-Regular.ttf + static/SairaStencil_UltraCondensed-Medium.ttf + static/SairaStencil_UltraCondensed-SemiBold.ttf + static/SairaStencil_UltraCondensed-Bold.ttf + static/SairaStencil_UltraCondensed-ExtraBold.ttf + static/SairaStencil_UltraCondensed-Black.ttf + static/SairaStencil_ExtraCondensed-Thin.ttf + static/SairaStencil_ExtraCondensed-ExtraLight.ttf + static/SairaStencil_ExtraCondensed-Light.ttf + static/SairaStencil_ExtraCondensed-Regular.ttf + static/SairaStencil_ExtraCondensed-Medium.ttf + static/SairaStencil_ExtraCondensed-SemiBold.ttf + static/SairaStencil_ExtraCondensed-Bold.ttf + static/SairaStencil_ExtraCondensed-ExtraBold.ttf + static/SairaStencil_ExtraCondensed-Black.ttf + static/SairaStencil_Condensed-Thin.ttf + static/SairaStencil_Condensed-ExtraLight.ttf + static/SairaStencil_Condensed-Light.ttf + static/SairaStencil_Condensed-Regular.ttf + static/SairaStencil_Condensed-Medium.ttf + static/SairaStencil_Condensed-SemiBold.ttf + static/SairaStencil_Condensed-Bold.ttf + static/SairaStencil_Condensed-ExtraBold.ttf + static/SairaStencil_Condensed-Black.ttf + static/SairaStencil_SemiCondensed-Thin.ttf + static/SairaStencil_SemiCondensed-ExtraLight.ttf + static/SairaStencil_SemiCondensed-Light.ttf + static/SairaStencil_SemiCondensed-Regular.ttf + static/SairaStencil_SemiCondensed-Medium.ttf + static/SairaStencil_SemiCondensed-SemiBold.ttf + static/SairaStencil_SemiCondensed-Bold.ttf + static/SairaStencil_SemiCondensed-ExtraBold.ttf + static/SairaStencil_SemiCondensed-Black.ttf + static/SairaStencil-Thin.ttf + static/SairaStencil-ExtraLight.ttf + static/SairaStencil-Light.ttf + static/SairaStencil-Regular.ttf + static/SairaStencil-Medium.ttf + static/SairaStencil-SemiBold.ttf + static/SairaStencil-Bold.ttf + static/SairaStencil-ExtraBold.ttf + static/SairaStencil-Black.ttf + static/SairaStencil_SemiExpanded-Thin.ttf + static/SairaStencil_SemiExpanded-ExtraLight.ttf + static/SairaStencil_SemiExpanded-Light.ttf + static/SairaStencil_SemiExpanded-Regular.ttf + static/SairaStencil_SemiExpanded-Medium.ttf + static/SairaStencil_SemiExpanded-SemiBold.ttf + static/SairaStencil_SemiExpanded-Bold.ttf + static/SairaStencil_SemiExpanded-ExtraBold.ttf + static/SairaStencil_SemiExpanded-Black.ttf + static/SairaStencil_Expanded-Thin.ttf + static/SairaStencil_Expanded-ExtraLight.ttf + static/SairaStencil_Expanded-Light.ttf + static/SairaStencil_Expanded-Regular.ttf + static/SairaStencil_Expanded-Medium.ttf + static/SairaStencil_Expanded-SemiBold.ttf + static/SairaStencil_Expanded-Bold.ttf + static/SairaStencil_Expanded-ExtraBold.ttf + static/SairaStencil_Expanded-Black.ttf + static/SairaStencil_UltraCondensed-ThinItalic.ttf + static/SairaStencil_UltraCondensed-ExtraLightItalic.ttf + static/SairaStencil_UltraCondensed-LightItalic.ttf + static/SairaStencil_UltraCondensed-Italic.ttf + static/SairaStencil_UltraCondensed-MediumItalic.ttf + static/SairaStencil_UltraCondensed-SemiBoldItalic.ttf + static/SairaStencil_UltraCondensed-BoldItalic.ttf + static/SairaStencil_UltraCondensed-ExtraBoldItalic.ttf + static/SairaStencil_UltraCondensed-BlackItalic.ttf + static/SairaStencil_ExtraCondensed-ThinItalic.ttf + static/SairaStencil_ExtraCondensed-ExtraLightItalic.ttf + static/SairaStencil_ExtraCondensed-LightItalic.ttf + static/SairaStencil_ExtraCondensed-Italic.ttf + static/SairaStencil_ExtraCondensed-MediumItalic.ttf + static/SairaStencil_ExtraCondensed-SemiBoldItalic.ttf + static/SairaStencil_ExtraCondensed-BoldItalic.ttf + static/SairaStencil_ExtraCondensed-ExtraBoldItalic.ttf + static/SairaStencil_ExtraCondensed-BlackItalic.ttf + static/SairaStencil_Condensed-ThinItalic.ttf + static/SairaStencil_Condensed-ExtraLightItalic.ttf + static/SairaStencil_Condensed-LightItalic.ttf + static/SairaStencil_Condensed-Italic.ttf + static/SairaStencil_Condensed-MediumItalic.ttf + static/SairaStencil_Condensed-SemiBoldItalic.ttf + static/SairaStencil_Condensed-BoldItalic.ttf + static/SairaStencil_Condensed-ExtraBoldItalic.ttf + static/SairaStencil_Condensed-BlackItalic.ttf + static/SairaStencil_SemiCondensed-ThinItalic.ttf + static/SairaStencil_SemiCondensed-ExtraLightItalic.ttf + static/SairaStencil_SemiCondensed-LightItalic.ttf + static/SairaStencil_SemiCondensed-Italic.ttf + static/SairaStencil_SemiCondensed-MediumItalic.ttf + static/SairaStencil_SemiCondensed-SemiBoldItalic.ttf + static/SairaStencil_SemiCondensed-BoldItalic.ttf + static/SairaStencil_SemiCondensed-ExtraBoldItalic.ttf + static/SairaStencil_SemiCondensed-BlackItalic.ttf + static/SairaStencil-ThinItalic.ttf + static/SairaStencil-ExtraLightItalic.ttf + static/SairaStencil-LightItalic.ttf + static/SairaStencil-Italic.ttf + static/SairaStencil-MediumItalic.ttf + static/SairaStencil-SemiBoldItalic.ttf + static/SairaStencil-BoldItalic.ttf + static/SairaStencil-ExtraBoldItalic.ttf + static/SairaStencil-BlackItalic.ttf + static/SairaStencil_SemiExpanded-ThinItalic.ttf + static/SairaStencil_SemiExpanded-ExtraLightItalic.ttf + static/SairaStencil_SemiExpanded-LightItalic.ttf + static/SairaStencil_SemiExpanded-Italic.ttf + static/SairaStencil_SemiExpanded-MediumItalic.ttf + static/SairaStencil_SemiExpanded-SemiBoldItalic.ttf + static/SairaStencil_SemiExpanded-BoldItalic.ttf + static/SairaStencil_SemiExpanded-ExtraBoldItalic.ttf + static/SairaStencil_SemiExpanded-BlackItalic.ttf + static/SairaStencil_Expanded-ThinItalic.ttf + static/SairaStencil_Expanded-ExtraLightItalic.ttf + static/SairaStencil_Expanded-LightItalic.ttf + static/SairaStencil_Expanded-Italic.ttf + static/SairaStencil_Expanded-MediumItalic.ttf + static/SairaStencil_Expanded-SemiBoldItalic.ttf + static/SairaStencil_Expanded-BoldItalic.ttf + static/SairaStencil_Expanded-ExtraBoldItalic.ttf + static/SairaStencil_Expanded-BlackItalic.ttf + +Get started +----------- + +1. Install the font files you want to use + +2. Use your app's font picker to view the font family and all the +available styles + +Learn more about variable fonts +------------------------------- + + https://developers.google.com/web/fundamentals/design-and-ux/typography/variable-fonts + https://variablefonts.typenetwork.com + https://medium.com/variable-fonts + +In desktop apps + + https://theblog.adobe.com/can-variable-fonts-illustrator-cc + https://helpx.adobe.com/nz/photoshop/using/fonts.html#variable_fonts + +Online + + https://developers.google.com/fonts/docs/getting_started + https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide + https://developer.microsoft.com/en-us/microsoft-edge/testdrive/demos/variable-fonts + +Installing fonts + + MacOS: https://support.apple.com/en-us/HT201749 + Linux: https://www.google.com/search?q=how+to+install+a+font+on+gnu%2Blinux + Windows: https://support.microsoft.com/en-us/help/314960/how-to-install-or-remove-a-font-in-windows + +Android Apps + + https://developers.google.com/fonts/docs/android + https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts + +License +------- +Please read the full license text (OFL.txt) to understand the permissions, +restrictions and requirements for usage, redistribution, and modification. + +You can use them in your products & projects – print or digital, +commercial or otherwise. + +This isn't legal advice, please consider consulting a lawyer and see the full +license for all details. diff --git a/public/Saira_Stencil/SairaStencil-Italic-VariableFont_wdth,wght.ttf b/public/Saira_Stencil/SairaStencil-Italic-VariableFont_wdth,wght.ttf new file mode 100644 index 000000000..1210d6893 Binary files /dev/null and b/public/Saira_Stencil/SairaStencil-Italic-VariableFont_wdth,wght.ttf differ diff --git a/public/Saira_Stencil/SairaStencil-VariableFont_wdth,wght.ttf b/public/Saira_Stencil/SairaStencil-VariableFont_wdth,wght.ttf new file mode 100644 index 000000000..571e8860b Binary files /dev/null and b/public/Saira_Stencil/SairaStencil-VariableFont_wdth,wght.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil-Black.ttf b/public/Saira_Stencil/static/SairaStencil-Black.ttf new file mode 100644 index 000000000..d0b21a78f Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil-Black.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil-BlackItalic.ttf b/public/Saira_Stencil/static/SairaStencil-BlackItalic.ttf new file mode 100644 index 000000000..816856dc4 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil-BlackItalic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil-Bold.ttf b/public/Saira_Stencil/static/SairaStencil-Bold.ttf new file mode 100644 index 000000000..70d1dac46 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil-Bold.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil-BoldItalic.ttf b/public/Saira_Stencil/static/SairaStencil-BoldItalic.ttf new file mode 100644 index 000000000..68b8828d5 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil-BoldItalic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil-ExtraBold.ttf b/public/Saira_Stencil/static/SairaStencil-ExtraBold.ttf new file mode 100644 index 000000000..f466000a3 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil-ExtraBold.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil-ExtraBoldItalic.ttf b/public/Saira_Stencil/static/SairaStencil-ExtraBoldItalic.ttf new file mode 100644 index 000000000..59c0d7026 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil-ExtraBoldItalic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil-ExtraLight.ttf b/public/Saira_Stencil/static/SairaStencil-ExtraLight.ttf new file mode 100644 index 000000000..ade3743ef Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil-ExtraLight.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil-ExtraLightItalic.ttf b/public/Saira_Stencil/static/SairaStencil-ExtraLightItalic.ttf new file mode 100644 index 000000000..447350053 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil-ExtraLightItalic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil-Italic.ttf b/public/Saira_Stencil/static/SairaStencil-Italic.ttf new file mode 100644 index 000000000..3c1d3b9db Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil-Italic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil-Light.ttf b/public/Saira_Stencil/static/SairaStencil-Light.ttf new file mode 100644 index 000000000..3c26433cc Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil-Light.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil-LightItalic.ttf b/public/Saira_Stencil/static/SairaStencil-LightItalic.ttf new file mode 100644 index 000000000..b57cdf736 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil-LightItalic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil-Medium.ttf b/public/Saira_Stencil/static/SairaStencil-Medium.ttf new file mode 100644 index 000000000..fac70adc7 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil-Medium.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil-MediumItalic.ttf b/public/Saira_Stencil/static/SairaStencil-MediumItalic.ttf new file mode 100644 index 000000000..7755faabb Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil-MediumItalic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil-Regular.ttf b/public/Saira_Stencil/static/SairaStencil-Regular.ttf new file mode 100644 index 000000000..d6855bea2 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil-Regular.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil-SemiBold.ttf b/public/Saira_Stencil/static/SairaStencil-SemiBold.ttf new file mode 100644 index 000000000..c6d4edaf8 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil-SemiBold.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil-SemiBoldItalic.ttf b/public/Saira_Stencil/static/SairaStencil-SemiBoldItalic.ttf new file mode 100644 index 000000000..decf7e8ae Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil-SemiBoldItalic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil-Thin.ttf b/public/Saira_Stencil/static/SairaStencil-Thin.ttf new file mode 100644 index 000000000..4062d581e Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil-Thin.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil-ThinItalic.ttf b/public/Saira_Stencil/static/SairaStencil-ThinItalic.ttf new file mode 100644 index 000000000..6b51a67b8 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil-ThinItalic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_Condensed-Black.ttf b/public/Saira_Stencil/static/SairaStencil_Condensed-Black.ttf new file mode 100644 index 000000000..ce53e7e96 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_Condensed-Black.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_Condensed-BlackItalic.ttf b/public/Saira_Stencil/static/SairaStencil_Condensed-BlackItalic.ttf new file mode 100644 index 000000000..cff9c1af6 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_Condensed-BlackItalic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_Condensed-Bold.ttf b/public/Saira_Stencil/static/SairaStencil_Condensed-Bold.ttf new file mode 100644 index 000000000..92333931f Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_Condensed-Bold.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_Condensed-BoldItalic.ttf b/public/Saira_Stencil/static/SairaStencil_Condensed-BoldItalic.ttf new file mode 100644 index 000000000..77a682b5f Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_Condensed-BoldItalic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_Condensed-ExtraBold.ttf b/public/Saira_Stencil/static/SairaStencil_Condensed-ExtraBold.ttf new file mode 100644 index 000000000..796f8ff19 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_Condensed-ExtraBold.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_Condensed-ExtraBoldItalic.ttf b/public/Saira_Stencil/static/SairaStencil_Condensed-ExtraBoldItalic.ttf new file mode 100644 index 000000000..fe2a093e7 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_Condensed-ExtraBoldItalic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_Condensed-ExtraLight.ttf b/public/Saira_Stencil/static/SairaStencil_Condensed-ExtraLight.ttf new file mode 100644 index 000000000..f5ae16d39 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_Condensed-ExtraLight.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_Condensed-ExtraLightItalic.ttf b/public/Saira_Stencil/static/SairaStencil_Condensed-ExtraLightItalic.ttf new file mode 100644 index 000000000..7f0d948dc Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_Condensed-ExtraLightItalic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_Condensed-Italic.ttf b/public/Saira_Stencil/static/SairaStencil_Condensed-Italic.ttf new file mode 100644 index 000000000..ef8d4b14f Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_Condensed-Italic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_Condensed-Light.ttf b/public/Saira_Stencil/static/SairaStencil_Condensed-Light.ttf new file mode 100644 index 000000000..2a5ec4c1c Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_Condensed-Light.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_Condensed-LightItalic.ttf b/public/Saira_Stencil/static/SairaStencil_Condensed-LightItalic.ttf new file mode 100644 index 000000000..76877ff4f Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_Condensed-LightItalic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_Condensed-Medium.ttf b/public/Saira_Stencil/static/SairaStencil_Condensed-Medium.ttf new file mode 100644 index 000000000..7daa64570 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_Condensed-Medium.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_Condensed-MediumItalic.ttf b/public/Saira_Stencil/static/SairaStencil_Condensed-MediumItalic.ttf new file mode 100644 index 000000000..de9b6efc3 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_Condensed-MediumItalic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_Condensed-Regular.ttf b/public/Saira_Stencil/static/SairaStencil_Condensed-Regular.ttf new file mode 100644 index 000000000..633adc087 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_Condensed-Regular.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_Condensed-SemiBold.ttf b/public/Saira_Stencil/static/SairaStencil_Condensed-SemiBold.ttf new file mode 100644 index 000000000..8ac37fc0b Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_Condensed-SemiBold.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_Condensed-SemiBoldItalic.ttf b/public/Saira_Stencil/static/SairaStencil_Condensed-SemiBoldItalic.ttf new file mode 100644 index 000000000..d0b887277 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_Condensed-SemiBoldItalic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_Condensed-Thin.ttf b/public/Saira_Stencil/static/SairaStencil_Condensed-Thin.ttf new file mode 100644 index 000000000..22685e2e4 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_Condensed-Thin.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_Condensed-ThinItalic.ttf b/public/Saira_Stencil/static/SairaStencil_Condensed-ThinItalic.ttf new file mode 100644 index 000000000..80bab7a56 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_Condensed-ThinItalic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_Expanded-Black.ttf b/public/Saira_Stencil/static/SairaStencil_Expanded-Black.ttf new file mode 100644 index 000000000..f48f38a1b Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_Expanded-Black.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_Expanded-BlackItalic.ttf b/public/Saira_Stencil/static/SairaStencil_Expanded-BlackItalic.ttf new file mode 100644 index 000000000..aafe2b318 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_Expanded-BlackItalic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_Expanded-Bold.ttf b/public/Saira_Stencil/static/SairaStencil_Expanded-Bold.ttf new file mode 100644 index 000000000..34290d283 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_Expanded-Bold.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_Expanded-BoldItalic.ttf b/public/Saira_Stencil/static/SairaStencil_Expanded-BoldItalic.ttf new file mode 100644 index 000000000..88c8962e8 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_Expanded-BoldItalic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_Expanded-ExtraBold.ttf b/public/Saira_Stencil/static/SairaStencil_Expanded-ExtraBold.ttf new file mode 100644 index 000000000..3ea9140ac Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_Expanded-ExtraBold.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_Expanded-ExtraBoldItalic.ttf b/public/Saira_Stencil/static/SairaStencil_Expanded-ExtraBoldItalic.ttf new file mode 100644 index 000000000..1c3f152de Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_Expanded-ExtraBoldItalic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_Expanded-ExtraLight.ttf b/public/Saira_Stencil/static/SairaStencil_Expanded-ExtraLight.ttf new file mode 100644 index 000000000..f81e63d58 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_Expanded-ExtraLight.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_Expanded-ExtraLightItalic.ttf b/public/Saira_Stencil/static/SairaStencil_Expanded-ExtraLightItalic.ttf new file mode 100644 index 000000000..30633007c Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_Expanded-ExtraLightItalic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_Expanded-Italic.ttf b/public/Saira_Stencil/static/SairaStencil_Expanded-Italic.ttf new file mode 100644 index 000000000..c040b8707 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_Expanded-Italic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_Expanded-Light.ttf b/public/Saira_Stencil/static/SairaStencil_Expanded-Light.ttf new file mode 100644 index 000000000..d5d2d92e4 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_Expanded-Light.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_Expanded-LightItalic.ttf b/public/Saira_Stencil/static/SairaStencil_Expanded-LightItalic.ttf new file mode 100644 index 000000000..a312c0e51 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_Expanded-LightItalic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_Expanded-Medium.ttf b/public/Saira_Stencil/static/SairaStencil_Expanded-Medium.ttf new file mode 100644 index 000000000..22d6ef4f3 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_Expanded-Medium.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_Expanded-MediumItalic.ttf b/public/Saira_Stencil/static/SairaStencil_Expanded-MediumItalic.ttf new file mode 100644 index 000000000..4bd9e6882 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_Expanded-MediumItalic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_Expanded-Regular.ttf b/public/Saira_Stencil/static/SairaStencil_Expanded-Regular.ttf new file mode 100644 index 000000000..c7594f05a Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_Expanded-Regular.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_Expanded-SemiBold.ttf b/public/Saira_Stencil/static/SairaStencil_Expanded-SemiBold.ttf new file mode 100644 index 000000000..f86892555 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_Expanded-SemiBold.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_Expanded-SemiBoldItalic.ttf b/public/Saira_Stencil/static/SairaStencil_Expanded-SemiBoldItalic.ttf new file mode 100644 index 000000000..310076ea3 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_Expanded-SemiBoldItalic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_Expanded-Thin.ttf b/public/Saira_Stencil/static/SairaStencil_Expanded-Thin.ttf new file mode 100644 index 000000000..95f6e8a3a Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_Expanded-Thin.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_Expanded-ThinItalic.ttf b/public/Saira_Stencil/static/SairaStencil_Expanded-ThinItalic.ttf new file mode 100644 index 000000000..9153c32db Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_Expanded-ThinItalic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_ExtraCondensed-Black.ttf b/public/Saira_Stencil/static/SairaStencil_ExtraCondensed-Black.ttf new file mode 100644 index 000000000..4d994c14e Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_ExtraCondensed-Black.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_ExtraCondensed-BlackItalic.ttf b/public/Saira_Stencil/static/SairaStencil_ExtraCondensed-BlackItalic.ttf new file mode 100644 index 000000000..6f53751e9 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_ExtraCondensed-BlackItalic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_ExtraCondensed-Bold.ttf b/public/Saira_Stencil/static/SairaStencil_ExtraCondensed-Bold.ttf new file mode 100644 index 000000000..02074257f Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_ExtraCondensed-Bold.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_ExtraCondensed-BoldItalic.ttf b/public/Saira_Stencil/static/SairaStencil_ExtraCondensed-BoldItalic.ttf new file mode 100644 index 000000000..4031db594 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_ExtraCondensed-BoldItalic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_ExtraCondensed-ExtraBold.ttf b/public/Saira_Stencil/static/SairaStencil_ExtraCondensed-ExtraBold.ttf new file mode 100644 index 000000000..52a21d3c8 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_ExtraCondensed-ExtraBold.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_ExtraCondensed-ExtraBoldItalic.ttf b/public/Saira_Stencil/static/SairaStencil_ExtraCondensed-ExtraBoldItalic.ttf new file mode 100644 index 000000000..bb079267e Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_ExtraCondensed-ExtraBoldItalic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_ExtraCondensed-ExtraLight.ttf b/public/Saira_Stencil/static/SairaStencil_ExtraCondensed-ExtraLight.ttf new file mode 100644 index 000000000..bb4d1921d Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_ExtraCondensed-ExtraLight.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_ExtraCondensed-ExtraLightItalic.ttf b/public/Saira_Stencil/static/SairaStencil_ExtraCondensed-ExtraLightItalic.ttf new file mode 100644 index 000000000..66a061274 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_ExtraCondensed-ExtraLightItalic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_ExtraCondensed-Italic.ttf b/public/Saira_Stencil/static/SairaStencil_ExtraCondensed-Italic.ttf new file mode 100644 index 000000000..c3d184a96 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_ExtraCondensed-Italic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_ExtraCondensed-Light.ttf b/public/Saira_Stencil/static/SairaStencil_ExtraCondensed-Light.ttf new file mode 100644 index 000000000..ac4ed08d2 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_ExtraCondensed-Light.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_ExtraCondensed-LightItalic.ttf b/public/Saira_Stencil/static/SairaStencil_ExtraCondensed-LightItalic.ttf new file mode 100644 index 000000000..63705bfb4 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_ExtraCondensed-LightItalic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_ExtraCondensed-Medium.ttf b/public/Saira_Stencil/static/SairaStencil_ExtraCondensed-Medium.ttf new file mode 100644 index 000000000..fefb43d58 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_ExtraCondensed-Medium.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_ExtraCondensed-MediumItalic.ttf b/public/Saira_Stencil/static/SairaStencil_ExtraCondensed-MediumItalic.ttf new file mode 100644 index 000000000..164554f43 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_ExtraCondensed-MediumItalic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_ExtraCondensed-Regular.ttf b/public/Saira_Stencil/static/SairaStencil_ExtraCondensed-Regular.ttf new file mode 100644 index 000000000..e458ddadd Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_ExtraCondensed-Regular.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_ExtraCondensed-SemiBold.ttf b/public/Saira_Stencil/static/SairaStencil_ExtraCondensed-SemiBold.ttf new file mode 100644 index 000000000..a06700a56 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_ExtraCondensed-SemiBold.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_ExtraCondensed-SemiBoldItalic.ttf b/public/Saira_Stencil/static/SairaStencil_ExtraCondensed-SemiBoldItalic.ttf new file mode 100644 index 000000000..a651b56d9 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_ExtraCondensed-SemiBoldItalic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_ExtraCondensed-Thin.ttf b/public/Saira_Stencil/static/SairaStencil_ExtraCondensed-Thin.ttf new file mode 100644 index 000000000..d966c52c9 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_ExtraCondensed-Thin.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_ExtraCondensed-ThinItalic.ttf b/public/Saira_Stencil/static/SairaStencil_ExtraCondensed-ThinItalic.ttf new file mode 100644 index 000000000..cb9f6c2a6 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_ExtraCondensed-ThinItalic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_SemiCondensed-Black.ttf b/public/Saira_Stencil/static/SairaStencil_SemiCondensed-Black.ttf new file mode 100644 index 000000000..5df30f64a Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_SemiCondensed-Black.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_SemiCondensed-BlackItalic.ttf b/public/Saira_Stencil/static/SairaStencil_SemiCondensed-BlackItalic.ttf new file mode 100644 index 000000000..e16fc61f3 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_SemiCondensed-BlackItalic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_SemiCondensed-Bold.ttf b/public/Saira_Stencil/static/SairaStencil_SemiCondensed-Bold.ttf new file mode 100644 index 000000000..8e3883725 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_SemiCondensed-Bold.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_SemiCondensed-BoldItalic.ttf b/public/Saira_Stencil/static/SairaStencil_SemiCondensed-BoldItalic.ttf new file mode 100644 index 000000000..925863058 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_SemiCondensed-BoldItalic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_SemiCondensed-ExtraBold.ttf b/public/Saira_Stencil/static/SairaStencil_SemiCondensed-ExtraBold.ttf new file mode 100644 index 000000000..166a1affd Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_SemiCondensed-ExtraBold.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_SemiCondensed-ExtraBoldItalic.ttf b/public/Saira_Stencil/static/SairaStencil_SemiCondensed-ExtraBoldItalic.ttf new file mode 100644 index 000000000..3df57302c Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_SemiCondensed-ExtraBoldItalic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_SemiCondensed-ExtraLight.ttf b/public/Saira_Stencil/static/SairaStencil_SemiCondensed-ExtraLight.ttf new file mode 100644 index 000000000..da2586d95 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_SemiCondensed-ExtraLight.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_SemiCondensed-ExtraLightItalic.ttf b/public/Saira_Stencil/static/SairaStencil_SemiCondensed-ExtraLightItalic.ttf new file mode 100644 index 000000000..9c4769a91 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_SemiCondensed-ExtraLightItalic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_SemiCondensed-Italic.ttf b/public/Saira_Stencil/static/SairaStencil_SemiCondensed-Italic.ttf new file mode 100644 index 000000000..5a2379657 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_SemiCondensed-Italic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_SemiCondensed-Light.ttf b/public/Saira_Stencil/static/SairaStencil_SemiCondensed-Light.ttf new file mode 100644 index 000000000..e271744c3 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_SemiCondensed-Light.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_SemiCondensed-LightItalic.ttf b/public/Saira_Stencil/static/SairaStencil_SemiCondensed-LightItalic.ttf new file mode 100644 index 000000000..eae2d1ebd Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_SemiCondensed-LightItalic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_SemiCondensed-Medium.ttf b/public/Saira_Stencil/static/SairaStencil_SemiCondensed-Medium.ttf new file mode 100644 index 000000000..45a510b57 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_SemiCondensed-Medium.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_SemiCondensed-MediumItalic.ttf b/public/Saira_Stencil/static/SairaStencil_SemiCondensed-MediumItalic.ttf new file mode 100644 index 000000000..a7b47e9d7 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_SemiCondensed-MediumItalic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_SemiCondensed-Regular.ttf b/public/Saira_Stencil/static/SairaStencil_SemiCondensed-Regular.ttf new file mode 100644 index 000000000..bd24220ec Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_SemiCondensed-Regular.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_SemiCondensed-SemiBold.ttf b/public/Saira_Stencil/static/SairaStencil_SemiCondensed-SemiBold.ttf new file mode 100644 index 000000000..c45ee7bf1 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_SemiCondensed-SemiBold.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_SemiCondensed-SemiBoldItalic.ttf b/public/Saira_Stencil/static/SairaStencil_SemiCondensed-SemiBoldItalic.ttf new file mode 100644 index 000000000..786c06e23 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_SemiCondensed-SemiBoldItalic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_SemiCondensed-Thin.ttf b/public/Saira_Stencil/static/SairaStencil_SemiCondensed-Thin.ttf new file mode 100644 index 000000000..256eae69b Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_SemiCondensed-Thin.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_SemiCondensed-ThinItalic.ttf b/public/Saira_Stencil/static/SairaStencil_SemiCondensed-ThinItalic.ttf new file mode 100644 index 000000000..02b4dcd89 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_SemiCondensed-ThinItalic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_SemiExpanded-Black.ttf b/public/Saira_Stencil/static/SairaStencil_SemiExpanded-Black.ttf new file mode 100644 index 000000000..2068cbaea Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_SemiExpanded-Black.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_SemiExpanded-BlackItalic.ttf b/public/Saira_Stencil/static/SairaStencil_SemiExpanded-BlackItalic.ttf new file mode 100644 index 000000000..8ecb82164 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_SemiExpanded-BlackItalic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_SemiExpanded-Bold.ttf b/public/Saira_Stencil/static/SairaStencil_SemiExpanded-Bold.ttf new file mode 100644 index 000000000..55c05ab7f Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_SemiExpanded-Bold.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_SemiExpanded-BoldItalic.ttf b/public/Saira_Stencil/static/SairaStencil_SemiExpanded-BoldItalic.ttf new file mode 100644 index 000000000..aec43b67d Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_SemiExpanded-BoldItalic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_SemiExpanded-ExtraBold.ttf b/public/Saira_Stencil/static/SairaStencil_SemiExpanded-ExtraBold.ttf new file mode 100644 index 000000000..ed5ab3085 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_SemiExpanded-ExtraBold.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_SemiExpanded-ExtraBoldItalic.ttf b/public/Saira_Stencil/static/SairaStencil_SemiExpanded-ExtraBoldItalic.ttf new file mode 100644 index 000000000..8068ecac7 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_SemiExpanded-ExtraBoldItalic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_SemiExpanded-ExtraLight.ttf b/public/Saira_Stencil/static/SairaStencil_SemiExpanded-ExtraLight.ttf new file mode 100644 index 000000000..fec8abf67 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_SemiExpanded-ExtraLight.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_SemiExpanded-ExtraLightItalic.ttf b/public/Saira_Stencil/static/SairaStencil_SemiExpanded-ExtraLightItalic.ttf new file mode 100644 index 000000000..28d62669d Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_SemiExpanded-ExtraLightItalic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_SemiExpanded-Italic.ttf b/public/Saira_Stencil/static/SairaStencil_SemiExpanded-Italic.ttf new file mode 100644 index 000000000..dcd81612d Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_SemiExpanded-Italic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_SemiExpanded-Light.ttf b/public/Saira_Stencil/static/SairaStencil_SemiExpanded-Light.ttf new file mode 100644 index 000000000..50020af96 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_SemiExpanded-Light.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_SemiExpanded-LightItalic.ttf b/public/Saira_Stencil/static/SairaStencil_SemiExpanded-LightItalic.ttf new file mode 100644 index 000000000..02e4e3445 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_SemiExpanded-LightItalic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_SemiExpanded-Medium.ttf b/public/Saira_Stencil/static/SairaStencil_SemiExpanded-Medium.ttf new file mode 100644 index 000000000..06956cddc Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_SemiExpanded-Medium.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_SemiExpanded-MediumItalic.ttf b/public/Saira_Stencil/static/SairaStencil_SemiExpanded-MediumItalic.ttf new file mode 100644 index 000000000..96a716c48 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_SemiExpanded-MediumItalic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_SemiExpanded-Regular.ttf b/public/Saira_Stencil/static/SairaStencil_SemiExpanded-Regular.ttf new file mode 100644 index 000000000..217a53b2e Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_SemiExpanded-Regular.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_SemiExpanded-SemiBold.ttf b/public/Saira_Stencil/static/SairaStencil_SemiExpanded-SemiBold.ttf new file mode 100644 index 000000000..20edfa32b Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_SemiExpanded-SemiBold.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_SemiExpanded-SemiBoldItalic.ttf b/public/Saira_Stencil/static/SairaStencil_SemiExpanded-SemiBoldItalic.ttf new file mode 100644 index 000000000..32291a6ef Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_SemiExpanded-SemiBoldItalic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_SemiExpanded-Thin.ttf b/public/Saira_Stencil/static/SairaStencil_SemiExpanded-Thin.ttf new file mode 100644 index 000000000..404a9e739 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_SemiExpanded-Thin.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_SemiExpanded-ThinItalic.ttf b/public/Saira_Stencil/static/SairaStencil_SemiExpanded-ThinItalic.ttf new file mode 100644 index 000000000..29ee64d81 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_SemiExpanded-ThinItalic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_UltraCondensed-Black.ttf b/public/Saira_Stencil/static/SairaStencil_UltraCondensed-Black.ttf new file mode 100644 index 000000000..ba98ecc1f Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_UltraCondensed-Black.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_UltraCondensed-BlackItalic.ttf b/public/Saira_Stencil/static/SairaStencil_UltraCondensed-BlackItalic.ttf new file mode 100644 index 000000000..46fdc3ba5 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_UltraCondensed-BlackItalic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_UltraCondensed-Bold.ttf b/public/Saira_Stencil/static/SairaStencil_UltraCondensed-Bold.ttf new file mode 100644 index 000000000..13b91033e Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_UltraCondensed-Bold.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_UltraCondensed-BoldItalic.ttf b/public/Saira_Stencil/static/SairaStencil_UltraCondensed-BoldItalic.ttf new file mode 100644 index 000000000..cd0a8ee22 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_UltraCondensed-BoldItalic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_UltraCondensed-ExtraBold.ttf b/public/Saira_Stencil/static/SairaStencil_UltraCondensed-ExtraBold.ttf new file mode 100644 index 000000000..93a8261e3 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_UltraCondensed-ExtraBold.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_UltraCondensed-ExtraBoldItalic.ttf b/public/Saira_Stencil/static/SairaStencil_UltraCondensed-ExtraBoldItalic.ttf new file mode 100644 index 000000000..bfa25dc50 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_UltraCondensed-ExtraBoldItalic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_UltraCondensed-ExtraLight.ttf b/public/Saira_Stencil/static/SairaStencil_UltraCondensed-ExtraLight.ttf new file mode 100644 index 000000000..b930d5a3b Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_UltraCondensed-ExtraLight.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_UltraCondensed-ExtraLightItalic.ttf b/public/Saira_Stencil/static/SairaStencil_UltraCondensed-ExtraLightItalic.ttf new file mode 100644 index 000000000..83041c6d2 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_UltraCondensed-ExtraLightItalic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_UltraCondensed-Italic.ttf b/public/Saira_Stencil/static/SairaStencil_UltraCondensed-Italic.ttf new file mode 100644 index 000000000..bcd47dcfd Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_UltraCondensed-Italic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_UltraCondensed-Light.ttf b/public/Saira_Stencil/static/SairaStencil_UltraCondensed-Light.ttf new file mode 100644 index 000000000..ebde6895d Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_UltraCondensed-Light.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_UltraCondensed-LightItalic.ttf b/public/Saira_Stencil/static/SairaStencil_UltraCondensed-LightItalic.ttf new file mode 100644 index 000000000..1c8a5c243 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_UltraCondensed-LightItalic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_UltraCondensed-Medium.ttf b/public/Saira_Stencil/static/SairaStencil_UltraCondensed-Medium.ttf new file mode 100644 index 000000000..521a6fc68 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_UltraCondensed-Medium.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_UltraCondensed-MediumItalic.ttf b/public/Saira_Stencil/static/SairaStencil_UltraCondensed-MediumItalic.ttf new file mode 100644 index 000000000..f73018668 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_UltraCondensed-MediumItalic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_UltraCondensed-Regular.ttf b/public/Saira_Stencil/static/SairaStencil_UltraCondensed-Regular.ttf new file mode 100644 index 000000000..32b984936 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_UltraCondensed-Regular.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_UltraCondensed-SemiBold.ttf b/public/Saira_Stencil/static/SairaStencil_UltraCondensed-SemiBold.ttf new file mode 100644 index 000000000..54c44a832 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_UltraCondensed-SemiBold.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_UltraCondensed-SemiBoldItalic.ttf b/public/Saira_Stencil/static/SairaStencil_UltraCondensed-SemiBoldItalic.ttf new file mode 100644 index 000000000..66e960af1 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_UltraCondensed-SemiBoldItalic.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_UltraCondensed-Thin.ttf b/public/Saira_Stencil/static/SairaStencil_UltraCondensed-Thin.ttf new file mode 100644 index 000000000..e1aa2cf44 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_UltraCondensed-Thin.ttf differ diff --git a/public/Saira_Stencil/static/SairaStencil_UltraCondensed-ThinItalic.ttf b/public/Saira_Stencil/static/SairaStencil_UltraCondensed-ThinItalic.ttf new file mode 100644 index 000000000..db374ecd0 Binary files /dev/null and b/public/Saira_Stencil/static/SairaStencil_UltraCondensed-ThinItalic.ttf differ diff --git a/public/short-example/IMG_5204.jpg b/public/short-example/IMG_5204.jpg new file mode 100644 index 000000000..0174f2413 Binary files /dev/null and b/public/short-example/IMG_5204.jpg differ diff --git a/public/short-example/IMG_5205.jpg b/public/short-example/IMG_5205.jpg new file mode 100644 index 000000000..2e8fd8bdb Binary files /dev/null and b/public/short-example/IMG_5205.jpg differ diff --git a/public/short-example/IMG_5206.jpg b/public/short-example/IMG_5206.jpg new file mode 100644 index 000000000..4d1d13a57 Binary files /dev/null and b/public/short-example/IMG_5206.jpg differ diff --git a/public/short-example/IMG_5207.jpg b/public/short-example/IMG_5207.jpg new file mode 100644 index 000000000..0719201e5 Binary files /dev/null and b/public/short-example/IMG_5207.jpg differ diff --git a/public/short-example/IMG_5208.jpg b/public/short-example/IMG_5208.jpg new file mode 100644 index 000000000..f72dace18 Binary files /dev/null and b/public/short-example/IMG_5208.jpg differ diff --git a/public/short-example/IMG_5209.jpg b/public/short-example/IMG_5209.jpg new file mode 100644 index 000000000..d62abd4d5 Binary files /dev/null and b/public/short-example/IMG_5209.jpg differ diff --git a/scripts/extract-subtitles.mjs b/scripts/extract-subtitles.mjs new file mode 100644 index 000000000..0c0cc8e18 --- /dev/null +++ b/scripts/extract-subtitles.mjs @@ -0,0 +1,603 @@ +#!/usr/bin/env node + +import { execSync } from "child_process"; +import { existsSync, mkdirSync, unlinkSync, writeFileSync, readFileSync } from "fs"; +import { join, basename, dirname } from "path"; +import { fileURLToPath } from "url"; +import wavefile from "wavefile"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const FPS = 30; + +// Words/patterns that suggest emphasis or impact (for zoom effects) +// Portuguese (Brazilian) impact patterns +const IMPACT_PATTERNS_PT = [ + // Exclamations + /!+$/, + /\?!$/, + // Numbers and statistics + /\d+%/, + /R\$\s?\d+/, + /\d+x/i, + // Portuguese action/emphasis words + /\b(incrível|inacreditável|insano|absurdo|bizarro|épico|lendário|surreal)\b/i, + /\b(urgente|importante|crítico|atenção|perigo|alerta|cuidado)\b/i, + /\b(segredo|revelado|exposto|verdade|finalmente|agora|hoje)\b/i, + /\b(melhor|pior|primeiro|último|único|nunca|sempre|jamais)\b/i, + /\b(ganhar|ganhei|ganhamos|perder|perdi|perdemos|vencer|sucesso|consegui|dominei)\b/i, + /\b(milhão|milhões|bilhão|bilhões|mil)\b/i, + /\b(grátis|gratuito|novo|nova|exclusivo|exclusiva|limitado|especial)\b/i, + /\b(olha|olhem|cara|mano|gente|galera|pessoal)\b/i, + /\b(demais|muito|super|mega|ultra|hiper)\b/i, + /\b(quebrou|explodiu|bombou|viralizou|estourou)\b/i, + // All caps words (emphasis) + /\b[A-Z]{3,}\b/, +]; + +// English impact patterns (fallback) +const IMPACT_PATTERNS_EN = [ + /!+$/, + /\?!$/, + /\d+%/, + /\$\d+/, + /\d+x/i, + /\b(amazing|incredible|unbelievable|insane|crazy|huge|massive|epic|legendary)\b/i, + /\b(breaking|urgent|important|critical|warning|danger|alert)\b/i, + /\b(secret|revealed|exposed|truth|finally|now|today)\b/i, + /\b(best|worst|top|first|last|only|never|always|ever)\b/i, + /\b(win|won|lose|lost|fail|success|achieve|dominate)\b/i, + /\b(million|billion|thousand|hundred)\b/i, + /\b(free|new|exclusive|limited|special)\b/i, + /\b[A-Z]{3,}\b/, +]; + +// Detect if text is impactful and deserves a zoom +function detectImpact(text, language = "pt") { + const patterns = language === "pt" ? IMPACT_PATTERNS_PT : IMPACT_PATTERNS_EN; + + const impactScore = patterns.reduce((score, pattern) => { + return score + (pattern.test(text) ? 1 : 0); + }, 0); + + if (impactScore >= 2) { + return { type: "zoomCut", intensity: 2 }; + } else if (impactScore === 1) { + return { type: "zoomHold", intensity: 1 }; + } + return null; +} + +// Convert seconds to frame number +function secondsToFrame(seconds, fps = FPS) { + return Math.round(seconds * fps); +} + +// Extract audio from video using ffmpeg +async function extractAudio(videoPath, outputPath) { + console.log("Extraindo áudio do vídeo..."); + + try { + execSync( + `ffmpeg -y -i "${videoPath}" -vn -acodec pcm_s16le -ar 16000 -ac 1 "${outputPath}"`, + { stdio: "pipe" } + ); + console.log("Áudio extraído com sucesso."); + return true; + } catch (error) { + console.error("Erro ao extrair áudio:", error.message); + return false; + } +} + +// Read WAV file and convert to Float32Array for Whisper +function readWavAsFloat32(audioPath) { + console.log("Lendo arquivo de áudio..."); + const buffer = readFileSync(audioPath); + const wav = new wavefile.WaveFile(buffer); + + // Ensure 16-bit PCM format + wav.toBitDepth("16"); + + // Get samples and convert to Float32Array normalized to [-1, 1] + const samples = wav.getSamples(false, Int16Array); + const float32 = new Float32Array(samples.length); + + for (let i = 0; i < samples.length; i++) { + float32[i] = samples[i] / 32768.0; + } + + return float32; +} + +// Transcribe audio using Whisper via transformers.js +async function transcribeAudio(audioPath, language = "portuguese", modelSize = "small") { + console.log("Carregando modelo Whisper (pode demorar na primeira execução)..."); + + const { pipeline } = await import("@xenova/transformers"); + + const modelName = `Xenova/whisper-${modelSize}`; + console.log(`Usando modelo: ${modelName}`); + + const transcriber = await pipeline("automatic-speech-recognition", modelName, { + chunk_length_s: 30, + stride_length_s: 5, + }); + + // Read audio file as Float32Array (required for Node.js) + const audioData = readWavAsFloat32(audioPath); + + console.log("Transcrevendo áudio em português..."); + + const result = await transcriber(audioData, { + return_timestamps: "word", + chunk_length_s: 30, + stride_length_s: 5, + language: language, + task: "transcribe", + sampling_rate: 16000, + }); + + return result; +} + +// Group words into subtitle chunks (by sentence or time gaps) +function groupIntoSubtitles(chunks, maxWordsPerSubtitle = 8, maxDuration = 3) { + const subtitles = []; + let currentSubtitle = { + words: [], + startTime: null, + endTime: null, + }; + + for (const chunk of chunks) { + if (!chunk.timestamp) continue; + + const [start, end] = chunk.timestamp; + const word = chunk.text.trim(); + + if (!word) continue; + + // Skip bracketed annotations like [Música], [Music], [Applause], etc. + if (/^\[.*\]$/.test(word)) continue; + + // Start new subtitle if: + // 1. Current is empty + // 2. Too many words + // 3. Gap > 0.5s between words + // 4. Duration would exceed max + // 5. Sentence ends (., !, ?) + const shouldStartNew = + currentSubtitle.words.length === 0 || + currentSubtitle.words.length >= maxWordsPerSubtitle || + (currentSubtitle.endTime && start - currentSubtitle.endTime > 0.5) || + (currentSubtitle.startTime && end - currentSubtitle.startTime > maxDuration) || + (currentSubtitle.words.length > 0 && + /[.!?]$/.test(currentSubtitle.words[currentSubtitle.words.length - 1])); + + if (shouldStartNew && currentSubtitle.words.length > 0) { + subtitles.push({ ...currentSubtitle }); + currentSubtitle = { words: [], startTime: null, endTime: null }; + } + + currentSubtitle.words.push(word); + if (currentSubtitle.startTime === null) { + currentSubtitle.startTime = start; + } + currentSubtitle.endTime = end; + } + + // Push the last subtitle + if (currentSubtitle.words.length > 0) { + subtitles.push(currentSubtitle); + } + + return subtitles; +} + +// Words that should not end a subtitle (orphan words) - only applies to short words (< 4 letters) +const ORPHAN_WORDS_PT = new Set([ + // Articles + "o", "a", "os", "as", "um", "uma", "uns", + // Prepositions + "de", "da", "do", "das", "dos", "em", "na", "no", "nas", "nos", + "por", "com", "sem", "sob", "ao", "aos", "à", "às", + // Conjunctions + "e", "ou", "mas", "que", "se", "nem", + // Pronouns + "eu", "tu", "ele", "ela", "nós", "vós", + "me", "te", "se", "nos", "vos", "lhe", + "meu", "teu", "seu", + // Other common short words + "não", "já", "só", +]); + +const ORPHAN_WORDS_EN = new Set([ + // Articles + "a", "an", "the", + // Prepositions + "of", "to", "in", "on", "at", "by", "for", + // Conjunctions + "and", "or", "but", "if", "as", "so", "yet", "nor", + // Pronouns + "i", "we", "he", "she", "it", "my", "our", "his", "her", "its", + // Other + "is", "are", "was", "be", "has", +]); + +// Check if text ends with punctuation +function endsWithPunctuation(text) { + return /[.,!?;:"""'']$/.test(text.trim()); +} + +// Fix orphan words at the end of subtitles +function fixOrphanWords(subtitles, language = "pt") { + const orphanWords = language === "pt" ? ORPHAN_WORDS_PT : ORPHAN_WORDS_EN; + const result = [...subtitles]; + + for (let i = 0; i < result.length - 1; i++) { + const current = result[i]; + const next = result[i + 1]; + + if (current.words.length <= 1) continue; + + // Keep moving words while the last word is an orphan + while (current.words.length > 1) { + const lastWord = current.words[current.words.length - 1]; + const lastWordClean = lastWord.replace(/[.,!?;:"""'']/g, ""); + const lastWordLower = lastWordClean.toLowerCase(); + + // Stop if word ends with punctuation (good break point) + if (endsWithPunctuation(lastWord)) break; + + // Words with 4+ letters are fine + if (lastWordClean.length >= 4) break; + + // Check if it's an orphan word + const isOrphan = orphanWords.has(lastWordLower); + + if (isOrphan) { + // Move word to next subtitle + const wordToMove = current.words.pop(); + next.words.unshift(wordToMove); + } else { + break; + } + } + } + + return result; +} + +// Format subtitles as TypeScript code +function formatAsTypeScript(subtitles, fps = FPS, language = "pt") { + const items = subtitles.map((sub) => { + const text = sub.words.join(" "); + const startFrame = secondsToFrame(sub.startTime, fps); + const endFrame = secondsToFrame(sub.endTime, fps); + const zoom = detectImpact(text, language); + + let item = ` { text: "${text}", startFrame: ${startFrame}, endFrame: ${endFrame}`; + + if (zoom) { + item += `, zoom: { type: "${zoom.type}" as ZoomType, intensity: ${zoom.intensity} }`; + } + + item += " }"; + return item; + }); + + return ` subtitles: { + transition: "slideUp" as TransitionType, + items: [ +${items.join(",\n")}, + ], + },`; +} + +// Group subtitles into sentences (for audio splitting) +function groupIntoSentences(subtitles) { + const sentences = []; + let currentSentence = []; + + for (let i = 0; i < subtitles.length; i++) { + currentSentence.push(i); + const text = subtitles[i].words.join(" "); + + // End sentence if we hit punctuation or it's the last subtitle + if (text.match(/[.!?]$/) || i === subtitles.length - 1) { + sentences.push([...currentSentence]); + currentSentence = []; + } + } + + // Push any remaining subtitles as a sentence + if (currentSentence.length > 0) { + sentences.push(currentSentence); + } + + return sentences; +} + +// Split audio file into sentence chunks +async function splitAudioIntoSentences(audioPath, subtitles, sentences, sessionDir, fps = FPS) { + const audioDir = join(sessionDir, 'audio'); + if (!existsSync(audioDir)) { + mkdirSync(audioDir, { recursive: true }); + } + + console.log(`\nDividindo áudio em ${sentences.length} sentenças...`); + + const sentenceAudioFiles = []; + + for (let sentenceIdx = 0; sentenceIdx < sentences.length; sentenceIdx++) { + const subtitleIndices = sentences[sentenceIdx]; + const firstSubIndex = subtitleIndices[0]; + const lastSubIndex = subtitleIndices[subtitleIndices.length - 1]; + + const startTime = subtitles[firstSubIndex].startTime; + const endTime = subtitles[lastSubIndex].endTime; + const duration = endTime - startTime; + + const outputPath = join(audioDir, `sentence_${sentenceIdx}.wav`); + + try { + // Extract audio chunk using ffmpeg + execSync( + `ffmpeg -y -i "${audioPath}" -ss ${startTime} -t ${duration} -acodec pcm_s16le -ar 44100 -ac 1 "${outputPath}"`, + { stdio: 'pipe' } + ); + + const durationInFrames = Math.round(duration * fps); + sentenceAudioFiles.push({ + sentenceId: sentenceIdx, + path: `/sessions/${basename(sessionDir)}/audio/sentence_${sentenceIdx}.wav`, + durationInFrames + }); + } catch (error) { + console.error(`Erro ao extrair áudio da sentença ${sentenceIdx}:`, error.message); + sentenceAudioFiles.push(null); + } + } + + return sentenceAudioFiles; +} + +// Format subtitles as JSON for video-subtitles.json with voice chunks +function formatAsJSON(subtitles, fps = FPS, language = "pt", backgroundVideo = null, titleText = null, template = "bold", sentenceAudioFiles = null) { + // Group subtitles into sentences + const sentences = groupIntoSentences(subtitles); + + const items = subtitles.map((sub, index) => { + const text = sub.words.join(" "); + const startFrame = secondsToFrame(sub.startTime, fps); + const endFrame = secondsToFrame(sub.endTime, fps); + const zoom = detectImpact(text, language); + + const item = { text, startFrame, endFrame }; + if (index === 0) { + item.zoom = { type: "zoomHold", intensity: 2 }; + } else if (zoom) { + item.zoom = zoom; + } + + // Find which sentence this subtitle belongs to + if (sentenceAudioFiles) { + for (let sentenceIdx = 0; sentenceIdx < sentences.length; sentenceIdx++) { + if (sentences[sentenceIdx].includes(index)) { + item.sentenceId = sentenceIdx; + + // Add voice reference to first chunk of sentence + const isFirstChunk = sentences[sentenceIdx][0] === index; + if (isFirstChunk && sentenceAudioFiles[sentenceIdx]) { + item.voice = { + src: sentenceAudioFiles[sentenceIdx].path, + volume: 1.0, + durationInFrames: sentenceAudioFiles[sentenceIdx].durationInFrames + }; + } + break; + } + } + } + + return item; + }); + + // Calculate last frame for title end + const lastFrame = items.length > 0 ? items[items.length - 1].endFrame : 90; + + const config = { + background: backgroundVideo + ? { type: "video", src: `/${backgroundVideo}` } + : { type: "color", color: "#1a1815" }, + colors: { text: "#ffffff" }, + title: { + show: !!titleText, + text: titleText || "", + startFrame: 0, + endFrame: 90, + transition: "slideDown", + template + }, + subtitles: { + transition: "slideUp", + template, + items + }, + style: { + position: "bottom", + bottomOffset: 80 + } + }; + + return config; +} + +// Main function +async function main() { + const args = process.argv.slice(2); + + if (args.length === 0) { + console.log(` +Uso: node scripts/extract-subtitles.mjs [opções] + +Opções: + --output, -o Caminho do arquivo de saída (padrão: exibe no console) + --json Saída em formato JSON (para video-subtitles.json) + --background Caminho do vídeo de fundo (relativo a public/) + --title, -t Título do vídeo (exibido no topo) + --template Template: bold, classic, minimal, stacked, fullWidthStacked, etc (padrão: bold) + --model, -m Tamanho do modelo Whisper: tiny, base, small, medium (padrão: small) + --max-words Máximo de palavras por legenda (padrão: 8) + --fps FPS do vídeo para cálculo de frames (padrão: 30) + --lang, -l Idioma: pt, en (padrão: pt para português brasileiro) + --split-audio Dividir áudio em sentenças e gerar arquivos separados (padrão: false) + --session-dir Diretório da sessão (necessário se --split-audio for usado) + +Exemplos: + node scripts/extract-subtitles.mjs public/video.mp4 + node scripts/extract-subtitles.mjs public/video.mp4 -o subtitles.ts + node scripts/extract-subtitles.mjs public/video.mp4 --json -o video-subtitles.json + node scripts/extract-subtitles.mjs public/video.mp4 --json --background uploads/bg.mp4 + node scripts/extract-subtitles.mjs public/video.mp4 --model tiny --fps 60 +`); + process.exit(1); + } + + const videoPath = args[0]; + + // Parse options + let outputPath = null; + let modelSize = "small"; + let maxWords = 8; + let fps = FPS; + let language = "pt"; + let jsonFormat = false; + let backgroundVideo = null; + let titleText = null; + let template = "bold"; + let splitAudio = false; + let sessionDir = null; + + for (let i = 1; i < args.length; i++) { + if (args[i] === "--output" || args[i] === "-o") { + outputPath = args[++i]; + } else if (args[i] === "--model" || args[i] === "-m") { + modelSize = args[++i]; + } else if (args[i] === "--max-words") { + maxWords = parseInt(args[++i]); + } else if (args[i] === "--fps") { + fps = parseInt(args[++i]); + } else if (args[i] === "--lang" || args[i] === "-l") { + language = args[++i]; + } else if (args[i] === "--json") { + jsonFormat = true; + } else if (args[i] === "--background") { + backgroundVideo = args[++i]; + } else if (args[i] === "--title" || args[i] === "-t") { + titleText = args[++i]; + } else if (args[i] === "--template") { + template = args[++i]; + } else if (args[i] === "--split-audio") { + splitAudio = true; + } else if (args[i] === "--session-dir") { + sessionDir = args[++i]; + } + } + + // Auto-detect session dir from output path if not provided + if (splitAudio && !sessionDir && outputPath) { + // Try to extract from path like /path/to/datalake/session-xxx/video-subtitles.json + const match = outputPath.match(/(.*\/session-[^/]+)/); + if (match) { + sessionDir = match[1]; + } + } + + const whisperLang = language === "pt" ? "portuguese" : "english"; + + if (!existsSync(videoPath)) { + console.error(`Erro: Arquivo de vídeo não encontrado: ${videoPath}`); + process.exit(1); + } + + // Create temp directory for audio + const tempDir = join(__dirname, "..", ".temp"); + if (!existsSync(tempDir)) { + mkdirSync(tempDir, { recursive: true }); + } + + const videoBasename = basename(videoPath).replace(/\.[^.]+$/, ""); + const audioPath = join(tempDir, `${videoBasename}.wav`); + + try { + // Step 1: Extract audio + const audioExtracted = await extractAudio(videoPath, audioPath); + if (!audioExtracted) { + process.exit(1); + } + + // Step 2: Transcribe + const transcription = await transcribeAudio(audioPath, whisperLang, modelSize); + + console.log("\nTranscrição:", transcription.text); + console.log(`\nEncontrados ${transcription.chunks?.length || 0} timestamps de palavras`); + + // Step 3: Group into subtitles + const rawSubtitles = groupIntoSubtitles(transcription.chunks || [], maxWords); + + // Step 3.5: Fix orphan words at the end of subtitles + const subtitles = fixOrphanWords(rawSubtitles, language); + + console.log(`\nAgrupados em ${subtitles.length} legendas`); + + // Step 4: Split audio into sentences if requested + let sentenceAudioFiles = null; + if (splitAudio && jsonFormat) { + if (!sessionDir) { + console.error('\nErro: --session-dir é necessário quando --split-audio é usado'); + process.exit(1); + } + + const sentences = groupIntoSentences(subtitles); + sentenceAudioFiles = await splitAudioIntoSentences(audioPath, subtitles, sentences, sessionDir, fps); + console.log(`\nCriados ${sentenceAudioFiles.filter(f => f).length} arquivos de áudio`); + } + + // Step 5: Format output + let output; + if (jsonFormat) { + const jsonConfig = formatAsJSON(subtitles, fps, language, backgroundVideo, titleText, template, sentenceAudioFiles); + output = JSON.stringify(jsonConfig, null, 2); + } else { + output = formatAsTypeScript(subtitles, fps, language); + } + + // Step 6: Output + if (outputPath) { + writeFileSync(outputPath, output); + console.log(`\nLegendas escritas em: ${outputPath}`); + } else { + if (jsonFormat) { + console.log("\n--- JSON Config ---\n"); + console.log(output); + console.log("\n--- Copie para video-subtitles.json ---\n"); + } else { + console.log("\n--- TypeScript Gerado ---\n"); + console.log(output); + console.log("\n--- Copie o código acima para seu constants.ts ---\n"); + } + } + + // Cleanup + if (existsSync(audioPath)) { + unlinkSync(audioPath); + } + } catch (error) { + console.error("Erro:", error.message); + console.error(error.stack); + process.exit(1); + } +} + +main(); diff --git a/src/components/video-editor/AnnotationOverlay.tsx b/src/components/video-editor/AnnotationOverlay.tsx index fd8433225..182e59881 100644 --- a/src/components/video-editor/AnnotationOverlay.tsx +++ b/src/components/video-editor/AnnotationOverlay.tsx @@ -165,7 +165,8 @@ export function AnnotationOverlay({ style={{ fontFamily: data.fontFamily, fontSize: `${data.primaryFontSize}px`, - fontWeight: "bold", + fontWeight: data.fontWeight ?? "700", + fontStretch: data.fontStretch ?? "normal", lineHeight: 1.1, letterSpacing: "0.02em", textTransform: "uppercase", @@ -179,7 +180,8 @@ export function AnnotationOverlay({ style={{ fontFamily: data.fontFamily, fontSize: `${data.secondaryFontSize}px`, - fontWeight: "bold", + fontWeight: data.fontWeight ?? "700", + fontStretch: data.fontStretch ?? "normal", lineHeight: 1.1, letterSpacing: "0.02em", textTransform: "uppercase", @@ -251,6 +253,7 @@ export function AnnotationOverlay({ fontSize: `${annotation.style.fontSize}px`, fontFamily: annotation.style.fontFamily, fontWeight: annotation.style.fontWeight, + fontStretch: annotation.style.fontStretch, fontStyle: annotation.style.fontStyle, textDecoration: annotation.style.textDecoration, textAlign: annotation.style.textAlign, @@ -268,15 +271,51 @@ export function AnnotationOverlay({ ); - case "image": + case "image": { + const radius = annotation.style.borderRadius ?? 0; + const imgData = annotation.imageData; + const timeIntoAnnotation = currentTimeMs - annotation.startMs; + const totalDuration = annotation.endMs - annotation.startMs; + const animDuration = imgData?.animationDuration ?? 500; + + // entrance + const rawProgress = Math.min(1, Math.max(0, timeIntoAnnotation / animDuration)); + const p = 1 - Math.pow(1 - rawProgress, 3); + + // fade-out + const fadeOutStart = Math.max(0, totalDuration - animDuration); + const exitRaw = imgData?.fadeOut + ? Math.min(1, Math.max(0, (timeIntoAnnotation - fadeOutStart) / animDuration)) + : 0; + const exitOpacity = imgData?.fadeOut ? 1 - exitRaw : 1; + + let animOpacity = exitOpacity; + let animTransform = "none"; + const animType = imgData?.animationType ?? "none"; + if (animType !== "none" && rawProgress < 1) { + animOpacity = p * exitOpacity; + if (animType === "slide-up") animTransform = `translateY(${(1 - p) * 50}%)`; + else if (animType === "slide-down") animTransform = `translateY(${-(1 - p) * 50}%)`; + else if (animType === "slide-left") animTransform = `translateX(${(1 - p) * 50}%)`; + else if (animType === "slide-right") animTransform = `translateX(${-(1 - p) * 50}%)`; + else if (animType === "zoom") animTransform = `scale(${0.75 + 0.25 * p})`; + } + if (annotation.content && annotation.content.startsWith("data:image")) { return ( - Annotation +
0 ? `${radius}px` : undefined, opacity: animOpacity }} + > +
+ Annotation +
+
); } return ( @@ -284,6 +323,7 @@ export function AnnotationOverlay({ No image ); + } case "figure": if (!annotation.figureData) { diff --git a/src/components/video-editor/AnnotationSettingsPanel.tsx b/src/components/video-editor/AnnotationSettingsPanel.tsx index bb891da3e..2b585de1a 100644 --- a/src/components/video-editor/AnnotationSettingsPanel.tsx +++ b/src/components/video-editor/AnnotationSettingsPanel.tsx @@ -3,7 +3,6 @@ import { AlignCenter, AlignLeft, AlignRight, - Bold, ChevronDown, Image as ImageIcon, Info, @@ -26,6 +25,7 @@ import { SelectValue, } from "@/components/ui/select"; import { Slider } from "@/components/ui/slider"; +import { Switch } from "@/components/ui/switch"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; import { useScopedT } from "@/contexts/I18nContext"; @@ -40,6 +40,8 @@ import type { CaptionData, CaptionGradientDirection, FigureData, + ImageAnimationType, + ImageData, MarkerData, MarkerDirection, } from "./types"; @@ -49,12 +51,39 @@ interface AnnotationSettingsPanelProps { onContentChange: (content: string) => void; onTypeChange: (type: AnnotationType) => void; onStyleChange: (style: Partial) => void; + onPositionChange?: (position: { x: number; y: number }) => void; + onSizeChange?: (size: { width: number; height: number }) => void; + onImageDataChange?: (imageData: ImageData) => void; onFigureDataChange?: (figureData: FigureData) => void; onCaptionDataChange?: (captionData: CaptionData) => void; onMarkerDataChange?: (markerData: MarkerData) => void; onDelete: () => void; } +const IMAGE_PRESETS = [ + { + label: "Full width", + icon: "▬", + position: { x: 5, y: 57 }, + size: { width: 90, height: 38 }, + borderRadius: 10, + }, + { + label: "Card", + icon: "▪", + position: { x: 12, y: 50 }, + size: { width: 76, height: 43 }, + borderRadius: 24, + }, + { + label: "Phone", + icon: "▯", + position: { x: 25, y: 37 }, + size: { width: 50, height: 55 }, + borderRadius: 28, + }, +]; + const FONT_FAMILIES = [ { value: "system-ui, -apple-system, sans-serif", labelKey: "classic" }, { value: "Georgia, serif", labelKey: "editor" }, @@ -64,15 +93,43 @@ const FONT_FAMILIES = [ { value: "Arial, sans-serif", labelKey: "simple" }, { value: "Verdana, sans-serif", labelKey: "modern" }, { value: "Trebuchet MS, sans-serif", labelKey: "clean" }, + { value: "'Saira Stencil', sans-serif", labelKey: "stencil" }, ]; const FONT_SIZES = [12, 14, 16, 18, 20, 24, 28, 32, 36, 40, 48, 56, 64, 72, 80, 96, 128]; +const FONT_WEIGHTS = [ + { value: "100", label: "Thin" }, + { value: "200", label: "Extra Light" }, + { value: "300", label: "Light" }, + { value: "400", label: "Regular" }, + { value: "500", label: "Medium" }, + { value: "600", label: "Semi Bold" }, + { value: "700", label: "Bold" }, + { value: "800", label: "Extra Bold" }, + { value: "900", label: "Black" }, +]; + +const FONT_STRETCHES = [ + { value: "ultra-condensed", label: "Ultra Condensed" }, + { value: "extra-condensed", label: "Extra Condensed" }, + { value: "condensed", label: "Condensed" }, + { value: "semi-condensed", label: "Semi Condensed" }, + { value: "normal", label: "Normal" }, + { value: "semi-expanded", label: "Semi Expanded" }, + { value: "expanded", label: "Expanded" }, + { value: "extra-expanded", label: "Extra Expanded" }, + { value: "ultra-expanded", label: "Ultra Expanded" }, +]; + export function AnnotationSettingsPanel({ annotation, onContentChange, onTypeChange, onStyleChange, + onPositionChange, + onSizeChange, + onImageDataChange, onFigureDataChange, onCaptionDataChange, onMarkerDataChange, @@ -91,6 +148,7 @@ export function AnnotationSettingsPanel({ simple: t("fontStyles.simple"), modern: t("fontStyles.modern"), clean: t("fontStyles.clean"), + stencil: t("fontStyles.stencil"), }; // Load custom fonts on mount @@ -308,25 +366,56 @@ export function AnnotationSettingsPanel({ /> + {/* Weight & Stretch */} +
+
+ + +
+
+ + +
+
+ {/* Formatting Toggles */}
- - onStyleChange({ - fontWeight: annotation.style.fontWeight === "bold" ? "normal" : "bold", - }) - } - className="h-8 w-8 data-[state=on]:bg-[#34B27B] data-[state=on]:text-white text-slate-400 hover:bg-white/5 hover:text-slate-200" - > - - )} + {/* Layout presets */} +
+ +
+ {IMAGE_PRESETS.map((preset) => ( + + ))} +
+
+ + {/* Border radius */} +
+ + onStyleChange({ borderRadius: v })} + className="w-full" + /> +
+ + {/* Entrance animation */} + {(() => { + const imgData = annotation.imageData ?? { animationType: "none" as ImageAnimationType, animationDuration: 500 }; + const update = (patch: Partial) => + onImageDataChange?.({ ...imgData, ...patch }); + const ANIM_PRESETS: { type: ImageAnimationType; label: string; icon: React.ReactNode }[] = [ + { + type: "none", + label: "None", + icon: ( + + + + ), + }, + { + type: "fade", + label: "Fade", + icon: ( + + + + + + + + + + ), + }, + { + type: "slide-up", + label: "Up", + icon: ( + + + + ), + }, + { + type: "slide-down", + label: "Down", + icon: ( + + + + ), + }, + { + type: "slide-left", + label: "Left", + icon: ( + + + + ), + }, + { + type: "slide-right", + label: "Right", + icon: ( + + + + ), + }, + { + type: "zoom", + label: "Zoom", + icon: ( + + + + + ), + }, + ]; + return ( +
+ +
+ {ANIM_PRESETS.map((anim) => ( + + ))} +
+ {imgData.animationType !== "none" && ( +
+ + update({ animationDuration: v })} + className="w-full" + /> +
+ )} +
+ Fade out + update({ fadeOut: v })} + /> +
+
+ ); + })()} +

{t("annotation.supportedFormats")}

@@ -655,6 +923,88 @@ export function AnnotationSettingsPanel({ className="w-full px-3 py-2 bg-white/5 border border-white/10 rounded-lg text-slate-200 text-sm placeholder:text-slate-500 focus:outline-none focus:ring-2 focus:ring-[#34B27B]" />
+
+ + +
+
+
+ + +
+
+ + +
+