Make vide work with prod
This commit is contained in:
parent
3debacab4f
commit
ee231d2ee5
7 changed files with 197 additions and 67 deletions
22
video/auth.prod.json
Normal file
22
video/auth.prod.json
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"cookies": [],
|
||||
"origins": [
|
||||
{
|
||||
"origin": "https://perfect-postcode.co.uk",
|
||||
"localStorage": [
|
||||
{
|
||||
"name": "theme",
|
||||
"value": "light"
|
||||
},
|
||||
{
|
||||
"name": "pocketbase_auth",
|
||||
"value": "{\"token\":\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2xsZWN0aW9uSWQiOiJfcGJfdXNlcnNfYXV0aF8iLCJleHAiOjE3Nzg5NDgxMzIsImlkIjoiZDZ5aG9odWh3MHh5dHhwIiwicmVmcmVzaGFibGUiOnRydWUsInR5cGUiOiJhdXRoIn0.bOR_vxK2MvqeKc_C9ao13416n_F-Ipmbn53NJy6L_JU\",\"record\":{\"ai_tokens_used\":0,\"ai_tokens_week\":0,\"avatar\":\"\",\"collectionId\":\"_pb_users_auth_\",\"collectionName\":\"users\",\"created\":\"2026-03-14 21:50:46.000Z\",\"email\":\"schmelczerandras+10@gmail.com\",\"emailVisibility\":false,\"id\":\"d6yhohuhw0xytxp\",\"is_admin\":false,\"name\":\"\",\"newsletter\":false,\"subscription\":\"licensed\",\"updated\":\"2026-03-14 21:50:48.430Z\",\"verified\":false}}"
|
||||
},
|
||||
{
|
||||
"name": "tutorial_completed",
|
||||
"value": "1"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
132
video/render.sh
132
video/render.sh
|
|
@ -2,30 +2,81 @@
|
|||
#
|
||||
# End-to-end re-render of the dashboard demo video.
|
||||
#
|
||||
# Defaults assume you run from inside this repo's vscode-server container
|
||||
# (where host.docker.internal reaches the docker-compose stack). Override
|
||||
# any URL/credential via env vars at the top.
|
||||
# Two targets:
|
||||
# local (default) — assumes the docker-compose stack on host.docker.internal,
|
||||
# bootstraps a recorder admin user automatically.
|
||||
# prod — points at https://perfect-postcode.co.uk and skips the
|
||||
# bootstrap step; you supply real account credentials.
|
||||
#
|
||||
# Usage:
|
||||
# ./render.sh # full pipeline (uses cached auth.json if fresh)
|
||||
# ./render.sh --fresh-auth # force re-auth even if auth.json exists
|
||||
# ./render.sh --no-encode # stop at WebM, skip MP4 encode
|
||||
# ./render.sh --no-audio # skip Qwen3-TTS narration; publish silent MP4
|
||||
# FORCE_AUTH=1 ./render.sh # same as --fresh-auth
|
||||
# ./render.sh # local stack
|
||||
# ./render.sh --prod # prod (requires LOGIN_EMAIL/LOGIN_PASSWORD)
|
||||
# ./render.sh --target prod # same as --prod
|
||||
# ./render.sh --fresh-auth # force re-auth even if cache is fresh
|
||||
# ./render.sh --no-encode # stop at WebM, skip MP4 encode
|
||||
# ./render.sh --no-audio # skip Qwen3-TTS narration; silent MP4
|
||||
# FORCE_AUTH=1 ./render.sh # same as --fresh-auth
|
||||
# APP_URL=http://localhost:3001 ./render.sh # override frontend URL
|
||||
# TTS_SPEAKER=aiden ./render.sh # override CustomVoice speaker
|
||||
#
|
||||
# Cred env vars (read for both targets, but prod has no fallback defaults):
|
||||
# LOGIN_EMAIL, LOGIN_PASSWORD — the dashboard account to record as
|
||||
# (same email/password you'd type into
|
||||
# the login modal)
|
||||
# PB_ADMIN_EMAIL, PB_ADMIN_PASSWORD — PocketBase superuser, only used by
|
||||
# --target local to bootstrap the
|
||||
# recorder user; ignored on --prod
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# -- target -------------------------------------------------------------------
|
||||
TARGET="${TARGET:-local}"
|
||||
parsed_args=()
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--prod) TARGET=prod; shift ;;
|
||||
--local) TARGET=local; shift ;;
|
||||
--target) TARGET="$2"; shift 2 ;;
|
||||
--target=*) TARGET="${1#--target=}"; shift ;;
|
||||
*) parsed_args+=("$1"); shift ;;
|
||||
esac
|
||||
done
|
||||
set -- "${parsed_args[@]+"${parsed_args[@]}"}"
|
||||
|
||||
case "$TARGET" in
|
||||
local|prod) ;;
|
||||
*) echo "Unknown --target: $TARGET (expected: local, prod)" >&2; exit 2 ;;
|
||||
esac
|
||||
|
||||
# -- config (override via env) -------------------------------------------------
|
||||
export APP_URL="${APP_URL:-http://host.docker.internal:3001}"
|
||||
export PB_URL="${PB_URL:-http://host.docker.internal:8090}"
|
||||
export API_URL="${API_URL:-http://host.docker.internal:8001}"
|
||||
PB_ADMIN_EMAIL="${PB_ADMIN_EMAIL:-admin@propertymap.local}"
|
||||
PB_ADMIN_PASSWORD="${PB_ADMIN_PASSWORD:-propertymap-dev-2024}"
|
||||
PB_EMAIL="${PB_EMAIL:-demo-video@local.test}"
|
||||
PB_PASSWORD="${PB_PASSWORD:-DemoVideoPass123!}"
|
||||
AUTH_TTL_HOURS="${AUTH_TTL_HOURS:-24}" # re-auth if auth.json older than this
|
||||
if [ "$TARGET" = "prod" ]; then
|
||||
# Prod serves frontend, /api/*, and /pb/* off the same domain.
|
||||
export APP_URL="${APP_URL:-https://perfect-postcode.co.uk}"
|
||||
export PB_URL="${PB_URL:-https://perfect-postcode.co.uk/pb}"
|
||||
export API_URL="${API_URL:-https://perfect-postcode.co.uk}"
|
||||
# Prod has no recorder-bootstrap path: the user supplies a real account.
|
||||
PB_BOOTSTRAP_ADMIN="${PB_BOOTSTRAP_ADMIN:-0}"
|
||||
if [ -z "${LOGIN_EMAIL:-}" ] || [ -z "${LOGIN_PASSWORD:-}" ]; then
|
||||
echo "FAIL: --prod requires LOGIN_EMAIL and LOGIN_PASSWORD (your dashboard login)." >&2
|
||||
echo " Example: LOGIN_EMAIL=you@example.com LOGIN_PASSWORD='...' $0 --prod" >&2
|
||||
exit 2
|
||||
fi
|
||||
else
|
||||
export APP_URL="${APP_URL:-http://host.docker.internal:3001}"
|
||||
export PB_URL="${PB_URL:-http://host.docker.internal:8090}"
|
||||
export API_URL="${API_URL:-http://host.docker.internal:8001}"
|
||||
PB_ADMIN_EMAIL="${PB_ADMIN_EMAIL:-admin@propertymap.local}"
|
||||
PB_ADMIN_PASSWORD="${PB_ADMIN_PASSWORD:-propertymap-dev-2024}"
|
||||
LOGIN_EMAIL="${LOGIN_EMAIL:-demo-video@local.test}"
|
||||
LOGIN_PASSWORD="${LOGIN_PASSWORD:-DemoVideoPass123!}"
|
||||
PB_BOOTSTRAP_ADMIN="${PB_BOOTSTRAP_ADMIN:-1}"
|
||||
fi
|
||||
export PB_BOOTSTRAP_ADMIN
|
||||
export LOGIN_EMAIL LOGIN_PASSWORD
|
||||
# Per-target storage state — switching targets must not reuse a stale token.
|
||||
# config.ts reads AUTH_STATE_FILE for AUTH_STATE_PATH.
|
||||
export AUTH_STATE_FILE="${AUTH_STATE_FILE:-auth.${TARGET}.json}"
|
||||
AUTH_TTL_HOURS="${AUTH_TTL_HOURS:-24}" # re-auth if cache older than this
|
||||
# Where the homepage <video> source lives. Vite copies frontend/public/* into
|
||||
# the built bundle, so updating this path is what makes the new clip appear
|
||||
# on the homepage. Override if the dashboard ever moves.
|
||||
|
|
@ -44,20 +95,21 @@ export CAPTURE_SCALE="${CAPTURE_SCALE:-1}"
|
|||
export WEBM_BITRATE="${WEBM_BITRATE:-$(awk -v s="$CAPTURE_SCALE" 'BEGIN{print (s+0>1)?"18M":"8M"}')}"
|
||||
export PROMPT_TEXT="${PROMPT_TEXT:-Flats or terraces <£450k, 35 min to Manchester, low crime}"
|
||||
export AI_ZOOM_SCALE="${AI_ZOOM_SCALE:-2.4}"
|
||||
export MAX_DURATION_S="${MAX_DURATION_S:-45}"
|
||||
export MAX_DURATION_S="${MAX_DURATION_S:-60}"
|
||||
export MIN_DURATION_S="${MIN_DURATION_S:-10}"
|
||||
export OUTPUT_FPS="${OUTPUT_FPS:-50}"
|
||||
|
||||
FRESH_AUTH="${FORCE_AUTH:-0}"
|
||||
DO_ENCODE=1
|
||||
DO_AUDIO=1
|
||||
for arg in "$@"; do
|
||||
for arg in "${@:-}"; do
|
||||
[ -z "$arg" ] && continue
|
||||
case "$arg" in
|
||||
--fresh-auth) FRESH_AUTH=1 ;;
|
||||
--no-encode) DO_ENCODE=0 ;;
|
||||
--no-audio) DO_AUDIO=0 ;;
|
||||
-h|--help)
|
||||
sed -n '3,20p' "$0"
|
||||
sed -n '3,30p' "$0"
|
||||
exit 0 ;;
|
||||
*) echo "Unknown arg: $arg" >&2; exit 2 ;;
|
||||
esac
|
||||
|
|
@ -94,7 +146,11 @@ pb_code="$(http_code "$PB_URL/api/health")"
|
|||
echo " frontend=$fe_code api=$api_code pocketbase=$pb_code"
|
||||
|
||||
if [ "$fe_code" != "200" ] || [ "$pb_code" != "200" ]; then
|
||||
fail "Stack down. From the repo root run: docker compose up -d"
|
||||
if [ "$TARGET" = "prod" ]; then
|
||||
fail "Cannot reach prod ($APP_URL / $PB_URL). Check the URLs and your network."
|
||||
else
|
||||
fail "Stack down. From the repo root run: docker compose up -d"
|
||||
fi
|
||||
fi
|
||||
if [ "$api_code" != "200" ]; then
|
||||
wait_for "$API_URL/api/features" "Rust API" 120
|
||||
|
|
@ -114,30 +170,41 @@ if ! npx --no-install playwright --version >/dev/null 2>&1 \
|
|||
npx playwright install chromium
|
||||
fi
|
||||
|
||||
# System libs Chromium dlopens (libnspr4, libnss3, libasound2…). Detect via
|
||||
# the canonical missing one and shell out to playwright's apt wrapper. Needs
|
||||
# sudo; gated on ldconfig so we don't prompt unnecessarily on every render.
|
||||
if ! ldconfig -p 2>/dev/null | grep -q 'libnspr4\.so'; then
|
||||
say "Installing Chromium system deps (sudo)"
|
||||
sudo npx playwright install-deps chromium
|
||||
fi
|
||||
|
||||
# -- build --------------------------------------------------------------------
|
||||
say "Compiling TypeScript"
|
||||
./node_modules/.bin/tsc
|
||||
|
||||
# -- auth ---------------------------------------------------------------------
|
||||
need_auth=0
|
||||
if [ "$FRESH_AUTH" = "1" ] || [ ! -f auth.json ]; then
|
||||
if [ "$FRESH_AUTH" = "1" ] || [ ! -f "$AUTH_STATE_FILE" ]; then
|
||||
need_auth=1
|
||||
else
|
||||
# File mtime check, portable: if older than TTL, refresh.
|
||||
if [ "$(find auth.json -mmin "+$((AUTH_TTL_HOURS * 60))" -print 2>/dev/null)" ]; then
|
||||
say "auth.json is older than ${AUTH_TTL_HOURS}h, will refresh"
|
||||
if [ "$(find "$AUTH_STATE_FILE" -mmin "+$((AUTH_TTL_HOURS * 60))" -print 2>/dev/null)" ]; then
|
||||
say "$AUTH_STATE_FILE is older than ${AUTH_TTL_HOURS}h, will refresh"
|
||||
need_auth=1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$need_auth" = "1" ]; then
|
||||
say "Minting fresh auth.json (user: $PB_EMAIL)"
|
||||
PB_URL="$PB_URL" PB_EMAIL="$PB_EMAIL" PB_PASSWORD="$PB_PASSWORD" \
|
||||
PB_ADMIN_EMAIL="$PB_ADMIN_EMAIL" PB_ADMIN_PASSWORD="$PB_ADMIN_PASSWORD" \
|
||||
say "Minting fresh $AUTH_STATE_FILE (target: $TARGET, user: $LOGIN_EMAIL)"
|
||||
PB_URL="$PB_URL" \
|
||||
LOGIN_EMAIL="$LOGIN_EMAIL" LOGIN_PASSWORD="$LOGIN_PASSWORD" \
|
||||
PB_ADMIN_EMAIL="${PB_ADMIN_EMAIL:-}" PB_ADMIN_PASSWORD="${PB_ADMIN_PASSWORD:-}" \
|
||||
PB_BOOTSTRAP_ADMIN="$PB_BOOTSTRAP_ADMIN" \
|
||||
AUTH_STATE_FILE="$AUTH_STATE_FILE" \
|
||||
APP_URL="$APP_URL" \
|
||||
node dist/auth.js
|
||||
else
|
||||
say "Reusing existing auth.json"
|
||||
say "Reusing existing $AUTH_STATE_FILE"
|
||||
fi
|
||||
|
||||
# -- preflight + synth (Qwen3-TTS) -------------------------------------------
|
||||
|
|
@ -160,8 +227,17 @@ if [ "$DO_AUDIO" = "1" ]; then
|
|||
if ! command -v uv >/dev/null 2>&1; then
|
||||
fail "uv not on PATH (required for Qwen3-TTS synth). Install uv or rerun with --no-audio."
|
||||
fi
|
||||
# The torch/cudnn wheels are ~700MB; uv's 30s default chokes on first sync.
|
||||
export UV_HTTP_TIMEOUT="${UV_HTTP_TIMEOUT:-600}"
|
||||
# Pull in the flash-attn prebuilt wheel (defined as the `gpu` extra) when
|
||||
# the host actually has a GPU. The wheel is bound to torch 2.6 + cu12 +
|
||||
# cp312 — see tts/pyproject.toml.
|
||||
uv_sync_extras=()
|
||||
if command -v nvidia-smi >/dev/null 2>&1 && nvidia-smi -L >/dev/null 2>&1; then
|
||||
uv_sync_extras+=(--extra gpu)
|
||||
fi
|
||||
say "Synthesising narration with Qwen3-TTS (speaker=${TTS_SPEAKER:-ryan}) — one batched call"
|
||||
uv sync --project tts || fail "uv sync failed in video/tts"
|
||||
uv sync --project tts ${uv_sync_extras[@]+"${uv_sync_extras[@]}"} || fail "uv sync failed in video/tts"
|
||||
uv run --project tts python tts/synth.py || fail "tts/synth.py failed"
|
||||
if [ ! -s output/audio/index.json ]; then
|
||||
fail "synth did not produce output/audio/index.json"
|
||||
|
|
|
|||
|
|
@ -6,63 +6,56 @@ import { ensureRecorderAdminUser } from './pb-admin.js';
|
|||
* Auth setup. Two modes:
|
||||
*
|
||||
* 1. Programmatic (preferred for CI / non-interactive runs): set
|
||||
* PB_URL, PB_EMAIL, PB_PASSWORD env vars. We hit the PocketBase REST
|
||||
* auth-with-password endpoint, then hand-write a Playwright storageState
|
||||
* file with the resulting token in localStorage["pb_auth"]. The PocketBase
|
||||
* JS SDK reads that key on boot and treats us as logged in — bit-equivalent
|
||||
* to a real UI login.
|
||||
* LOGIN_EMAIL and LOGIN_PASSWORD env vars (the same email/password you'd
|
||||
* type into the dashboard's login modal). We drive the actual login form
|
||||
* in a headless browser — same path a real user takes, no knowledge of
|
||||
* the PocketBase REST endpoint required.
|
||||
*
|
||||
* 2. Interactive: no env vars, we open a headed browser, you log in by hand,
|
||||
* press Enter, and we serialize the resulting cookies + localStorage.
|
||||
* Works on a developer laptop; doesn't work in headless environments.
|
||||
*/
|
||||
|
||||
interface PbAuthResponse {
|
||||
token: string;
|
||||
record: Record<string, unknown>;
|
||||
}
|
||||
|
||||
async function programmatic() {
|
||||
const email = process.env.PB_EMAIL!;
|
||||
const password = process.env.PB_PASSWORD!;
|
||||
const email = process.env.LOGIN_EMAIL!;
|
||||
const password = process.env.LOGIN_PASSWORD!;
|
||||
|
||||
if (process.env.PB_BOOTSTRAP_ADMIN !== '0') {
|
||||
await ensureRecorderAdminUser();
|
||||
}
|
||||
|
||||
// Driving the login through the app itself ensures the PocketBase SDK's
|
||||
// LocalAuthStore sees the token via its own write path. Hand-writing
|
||||
// localStorage["pb_auth"] sometimes races with the SDK's module-time read.
|
||||
const browser = await chromium.launch({ headless: true });
|
||||
const context = await browser.newContext({ viewport: { width: 1280, height: 800 } });
|
||||
const page = await context.newPage();
|
||||
await page.goto(APP_URL);
|
||||
|
||||
await page.evaluate(
|
||||
async ({ email, password }) => {
|
||||
const res = await fetch('/pb/api/collections/users/auth-with-password', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ identity: email, password }),
|
||||
});
|
||||
if (!res.ok) throw new Error(`login ${res.status} ${await res.text()}`);
|
||||
const data = await res.json();
|
||||
// The SDK's LocalAuthStore default storageKey is "pocketbase_auth",
|
||||
// not "pb_auth" (which is just the cookie name in BaseAuthStore).
|
||||
localStorage.setItem(
|
||||
'pocketbase_auth',
|
||||
JSON.stringify({ token: data.token, record: data.record })
|
||||
);
|
||||
// Skip the react-joyride product tour — its spotlight overlay
|
||||
// intercepts pointer events and breaks the recording.
|
||||
localStorage.setItem('tutorial_completed', '1');
|
||||
// Drive the dashboard's actual login modal. Scoping to <header> avoids the
|
||||
// modal's own "Log in" submit button (same text, different element).
|
||||
await page.locator('header').getByRole('button', { name: /log in/i }).click();
|
||||
await page.locator('input[type="email"]').fill(email);
|
||||
const pw = page.locator('input[type="password"]');
|
||||
await pw.fill(password);
|
||||
await pw.press('Enter');
|
||||
|
||||
// Settle: the SDK writes localStorage["pocketbase_auth"] once auth-with-
|
||||
// password resolves. Polling localStorage is more robust than waiting on
|
||||
// a UI signal (modal close + user-menu render varies by viewport/state).
|
||||
await page.waitForFunction(
|
||||
() => {
|
||||
const v = localStorage.getItem('pocketbase_auth');
|
||||
if (!v) return false;
|
||||
try { return !!(JSON.parse(v) as { token?: string }).token; } catch { return false; }
|
||||
},
|
||||
{ email, password }
|
||||
{ timeout: 15_000 }
|
||||
);
|
||||
|
||||
// Skip the react-joyride product tour — its spotlight overlay intercepts
|
||||
// pointer events and breaks the recording.
|
||||
await page.evaluate(() => { localStorage.setItem('tutorial_completed', '1'); });
|
||||
|
||||
await context.storageState({ path: AUTH_STATE_PATH });
|
||||
await browser.close();
|
||||
console.log(`Saved ${AUTH_STATE_PATH} via in-app PocketBase login (user: ${email}).`);
|
||||
console.log(`Saved ${AUTH_STATE_PATH} via dashboard login form (user: ${email}).`);
|
||||
}
|
||||
|
||||
async function interactive() {
|
||||
|
|
@ -88,7 +81,7 @@ async function interactive() {
|
|||
}
|
||||
|
||||
async function main() {
|
||||
if (process.env.PB_URL && process.env.PB_EMAIL && process.env.PB_PASSWORD) {
|
||||
if (process.env.LOGIN_EMAIL && process.env.LOGIN_PASSWORD) {
|
||||
await programmatic();
|
||||
process.exit(0);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,9 @@ function requiredNumberEnv(name: string): number {
|
|||
export const APP_URL = requiredEnv("APP_URL");
|
||||
export const DASHBOARD_PATH = "/dashboard";
|
||||
|
||||
export const AUTH_STATE_PATH = "auth.json";
|
||||
// Per-target storage state. render.sh sets AUTH_STATE_FILE to auth.local.json
|
||||
// or auth.prod.json so a stale local token can't be reused against prod.
|
||||
export const AUTH_STATE_PATH = process.env.AUTH_STATE_FILE ?? "auth.json";
|
||||
export const OUTPUT_DIR = "output";
|
||||
|
||||
const aspect = requiredEnv("ASPECT");
|
||||
|
|
|
|||
|
|
@ -64,8 +64,8 @@ function recorderUserBody(email: string, password: string): Record<string, unkno
|
|||
|
||||
export async function ensureRecorderAdminUser(): Promise<void> {
|
||||
const pbUrl = requireEnv('PB_URL').replace(/\/$/, '');
|
||||
const email = requireEnv('PB_EMAIL');
|
||||
const password = requireEnv('PB_PASSWORD');
|
||||
const email = requireEnv('LOGIN_EMAIL');
|
||||
const password = requireEnv('LOGIN_PASSWORD');
|
||||
const adminEmail = process.env.PB_ADMIN_EMAIL ?? process.env.POCKETBASE_ADMIN_EMAIL;
|
||||
const adminPassword = process.env.PB_ADMIN_PASSWORD ?? process.env.POCKETBASE_ADMIN_PASSWORD;
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,18 @@ dependencies = [
|
|||
"numpy>=1.26",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
# Flash-attention prebuilt wheel matched to torch 2.6 + cu12 + cp312, old
|
||||
# CXX ABI (PyTorch's cu124 wheel reports compiled_with_cxx11_abi() == False
|
||||
# and only exports the old-ABI c10::Error constructor). Pinned to
|
||||
# 2.7.4.post1 because 2.8.x's torch2.6/abiFALSE wheels were mislabelled —
|
||||
# they ship new-ABI symbols and fail to import. Building from source needs
|
||||
# nvcc which isn't on the host. Enable via `uv sync --extra gpu`; render.sh
|
||||
# does this automatically when nvidia-smi reports a GPU.
|
||||
gpu = [
|
||||
"flash-attn @ https://github.com/Dao-AILab/flash-attention/releases/download/v2.7.4.post1/flash_attn-2.7.4.post1%2Bcu12torch2.6cxx11abiFALSE-cp312-cp312-linux_x86_64.whl ; sys_platform == 'linux' and platform_machine == 'x86_64'",
|
||||
]
|
||||
|
||||
[tool.uv]
|
||||
environments = ["sys_platform == 'linux' and python_version < '3.13'"]
|
||||
|
||||
|
|
|
|||
25
video/tts/uv.lock
generated
25
video/tts/uv.lock
generated
|
|
@ -180,6 +180,24 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl", hash = "sha256:96f5f6344709aa1572bbf631c640e4ebeeb519e08da902c39a001882f30ac258", size = 39812, upload-time = "2026-04-19T15:39:08.752Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flash-attn"
|
||||
version = "2.7.4.post1"
|
||||
source = { url = "https://github.com/Dao-AILab/flash-attention/releases/download/v2.7.4.post1/flash_attn-2.7.4.post1%2Bcu12torch2.6cxx11abiFALSE-cp312-cp312-linux_x86_64.whl" }
|
||||
dependencies = [
|
||||
{ name = "einops", marker = "sys_platform == 'linux'" },
|
||||
{ name = "torch", marker = "sys_platform == 'linux'" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://github.com/Dao-AILab/flash-attention/releases/download/v2.7.4.post1/flash_attn-2.7.4.post1%2Bcu12torch2.6cxx11abiFALSE-cp312-cp312-linux_x86_64.whl", hash = "sha256:7e0b07913d56782d0f3f2ee76bd39587557742628983f6ab9ec2527c99437476" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "einops" },
|
||||
{ name = "torch" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flatbuffers"
|
||||
version = "25.12.19"
|
||||
|
|
@ -733,14 +751,21 @@ dependencies = [
|
|||
{ name = "torchaudio", marker = "sys_platform == 'linux'" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
gpu = [
|
||||
{ name = "flash-attn", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "flash-attn", marker = "platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'gpu'", url = "https://github.com/Dao-AILab/flash-attention/releases/download/v2.7.4.post1/flash_attn-2.7.4.post1%2Bcu12torch2.6cxx11abiFALSE-cp312-cp312-linux_x86_64.whl" },
|
||||
{ name = "numpy", specifier = ">=1.26" },
|
||||
{ name = "qwen-tts", specifier = ">=0.1.1" },
|
||||
{ name = "soundfile", specifier = ">=0.12" },
|
||||
{ name = "torch", specifier = ">=2.5,<2.7", index = "https://download.pytorch.org/whl/cu124" },
|
||||
{ name = "torchaudio", specifier = ">=2.5,<2.7", index = "https://download.pytorch.org/whl/cu124" },
|
||||
]
|
||||
provides-extras = ["gpu"]
|
||||
|
||||
[[package]]
|
||||
name = "protobuf"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue