i think i got step one working
This commit is contained in:
52
FFmpeg_COMPLIANCE.md
Normal file
52
FFmpeg_COMPLIANCE.md
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
# FFmpeg Compliance Checklist
|
||||||
|
|
||||||
|
Purpose: quick, practical checklist to ensure your TalkEdit distribution complies with FFmpeg licensing and packaging requirements.
|
||||||
|
|
||||||
|
1) Choose the FFmpeg build strategy
|
||||||
|
- Prefer an LGPL-only build (no GPL-only encoders) for minimal obligations.
|
||||||
|
- If you require GPL encoders (x264/x265/fdk-aac), document the decision and prepare to comply with GPL obligations.
|
||||||
|
|
||||||
|
2) Linking vs external binary
|
||||||
|
- Prefer spawning an external `ffmpeg` binary from Rust (invoke process) rather than statically linking FFmpeg into your app.
|
||||||
|
- If you link or bundle as a library, treat it as a third-party component and follow license terms strictly.
|
||||||
|
|
||||||
|
3) Bundling binary in installers
|
||||||
|
- If bundling `ffmpeg` binaries in installers, include the appropriate license files (COPYING.LGPL, COPYING.GPL) in the installer and app About/Legal.
|
||||||
|
- Include a plain-language notice in the installer/readme that explains which codecs/encoders are present and any implications.
|
||||||
|
|
||||||
|
4) Source & build-info disclosure
|
||||||
|
- For GPL components, you must provide access to the corresponding source or provide a written offer. Record the exact FFmpeg commit/configure flags used.
|
||||||
|
- Add a `third_party/ffmpeg/BUILD_INFO.txt` in the repo (or in release artifacts) containing:
|
||||||
|
- FFmpeg git commit or version
|
||||||
|
- configure flags used
|
||||||
|
- date and builder identity (automated CI username)
|
||||||
|
- link to the exact source tarball or repo snapshot
|
||||||
|
|
||||||
|
5) Make GPL components opt-in
|
||||||
|
- Default distribution: ship LGPL-only binary or no binary and invoke system `ffmpeg` when available.
|
||||||
|
- Offer an optional "codec pack" download or advanced installer that includes GPL encoders; make users explicitly accept terms before download.
|
||||||
|
|
||||||
|
6) Patent/licensing notice for codecs
|
||||||
|
- Add a short note in the README/installer explaining that certain codecs (H.264/AAC) may be patent-encumbered and that distributors may require separate licensing.
|
||||||
|
|
||||||
|
7) Platform-specific recommendations
|
||||||
|
- Linux: Prefer calling system FFmpeg (packaged by distro) or instruct users to install via package manager. If bundling, consider AppImage guidance.
|
||||||
|
- macOS: Prefer Homebrew/optional download; if bundling, include license files and sign/ notarize appropriately.
|
||||||
|
- Windows: If shipping `ffmpeg.exe`, include license files and a link to the source/build info; include checksums for shipped binaries.
|
||||||
|
|
||||||
|
8) Build automation & compliance artifacts
|
||||||
|
- Add a CI step that builds or fetches the FFmpeg binary, captures `ffmpeg -buildconf`, and writes `BUILD_INFO.txt` into the release artifacts.
|
||||||
|
- Produce a LICENSES folder in each installer containing FFmpeg license text and any third-party license texts used by your chosen build.
|
||||||
|
|
||||||
|
9) User-visible legal UI
|
||||||
|
- Add an About > Legal pane listing third-party components and linked license files.
|
||||||
|
- If downloading binaries on first run, show an explicit notice with a link to license and source information and require an OK from the user.
|
||||||
|
|
||||||
|
10) Pre-release legal checklist
|
||||||
|
- Verify whether chosen build enables GPL libraries; if yes, prepare source or written offer before publishing.
|
||||||
|
- Ensure installer contains license files and links to source/build-info.
|
||||||
|
- Add a short FAQ entry about codec patents and user options.
|
||||||
|
|
||||||
|
Notes & Next Steps
|
||||||
|
- This checklist is practical guidance, not legal advice. For final release compliance, consult legal counsel experienced in open-source licensing.
|
||||||
|
- I can add a small CI script snippet that records `ffmpeg -buildconf` and uploads `BUILD_INFO.txt` to release assets — tell me which CI you use and I'll draft it.
|
||||||
94
backend/dev_main.py
Normal file
94
backend/dev_main.py
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
"""Lightweight development backend for UI work.
|
||||||
|
|
||||||
|
This avoids importing heavy ML dependencies so the UI can run during frontend
|
||||||
|
development without installing large Python packages (torch/whisperx/etc.).
|
||||||
|
Use this when you only need the health/file streaming endpoints.
|
||||||
|
"""
|
||||||
|
from fastapi import FastAPI, Request, HTTPException
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
from fastapi.responses import StreamingResponse
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
app = FastAPI(title="TalkEdit Dev Backend", version="0.0.1")
|
||||||
|
|
||||||
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=["*"],
|
||||||
|
allow_credentials=True,
|
||||||
|
allow_methods=["*"],
|
||||||
|
allow_headers=["*"],
|
||||||
|
expose_headers=["Content-Range", "Accept-Ranges", "Content-Length"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
MIME_MAP = {
|
||||||
|
".mp4": "video/mp4",
|
||||||
|
".mkv": "video/x-matroska",
|
||||||
|
".mov": "video/quicktime",
|
||||||
|
".avi": "video/x-msvideo",
|
||||||
|
".webm": "video/webm",
|
||||||
|
".m4a": "audio/mp4",
|
||||||
|
".wav": "audio/wav",
|
||||||
|
".mp3": "audio/mpeg",
|
||||||
|
".flac": "audio/flac",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/health")
|
||||||
|
async def health():
|
||||||
|
return {"status": "ok"}
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/file")
|
||||||
|
async def serve_local_file(request: Request, path: str):
|
||||||
|
file_path = Path(path)
|
||||||
|
if not file_path.is_file():
|
||||||
|
raise HTTPException(status_code=404, detail=f"File not found: {path}")
|
||||||
|
|
||||||
|
file_size = file_path.stat().st_size
|
||||||
|
content_type = MIME_MAP.get(file_path.suffix.lower(), "application/octet-stream")
|
||||||
|
|
||||||
|
range_header = request.headers.get("range")
|
||||||
|
if range_header:
|
||||||
|
range_spec = range_header.replace("bytes=", "")
|
||||||
|
start_str, end_str = range_spec.split("-")
|
||||||
|
start = int(start_str) if start_str else 0
|
||||||
|
end = int(end_str) if end_str else file_size - 1
|
||||||
|
end = min(end, file_size - 1)
|
||||||
|
content_length = end - start + 1
|
||||||
|
|
||||||
|
def iter_range():
|
||||||
|
with open(file_path, "rb") as f:
|
||||||
|
f.seek(start)
|
||||||
|
remaining = content_length
|
||||||
|
while remaining > 0:
|
||||||
|
chunk = f.read(min(65536, remaining))
|
||||||
|
if not chunk:
|
||||||
|
break
|
||||||
|
remaining -= len(chunk)
|
||||||
|
yield chunk
|
||||||
|
|
||||||
|
return StreamingResponse(
|
||||||
|
iter_range(),
|
||||||
|
status_code=206,
|
||||||
|
media_type=content_type,
|
||||||
|
headers={
|
||||||
|
"Content-Range": f"bytes {start}-{end}/{file_size}",
|
||||||
|
"Accept-Ranges": "bytes",
|
||||||
|
"Content-Length": str(content_length),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
def iter_file():
|
||||||
|
with open(file_path, "rb") as f:
|
||||||
|
while chunk := f.read(65536):
|
||||||
|
yield chunk
|
||||||
|
|
||||||
|
return StreamingResponse(
|
||||||
|
iter_file(),
|
||||||
|
media_type=content_type,
|
||||||
|
headers={
|
||||||
|
"Accept-Ranges": "bytes",
|
||||||
|
"Content-Length": str(file_size),
|
||||||
|
},
|
||||||
|
)
|
||||||
253
frontend/package-lock.json
generated
253
frontend/package-lock.json
generated
@ -1,13 +1,16 @@
|
|||||||
{
|
{
|
||||||
"name": "cutscript-frontend",
|
"name": "talkedit-frontend",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "cutscript-frontend",
|
"name": "talkedit-frontend",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@tauri-apps/api": "^2",
|
||||||
|
"@tauri-apps/plugin-dialog": "^2",
|
||||||
|
"@tauri-apps/plugin-fs": "^2",
|
||||||
"lucide-react": "^0.468.0",
|
"lucide-react": "^0.468.0",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
@ -17,6 +20,7 @@
|
|||||||
"zustand": "^5.0.0"
|
"zustand": "^5.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@tauri-apps/cli": "^2",
|
||||||
"@types/react": "^19.0.0",
|
"@types/react": "^19.0.0",
|
||||||
"@types/react-dom": "^19.0.0",
|
"@types/react-dom": "^19.0.0",
|
||||||
"@vitejs/plugin-react": "^4.3.0",
|
"@vitejs/plugin-react": "^4.3.0",
|
||||||
@ -1209,6 +1213,251 @@
|
|||||||
"win32"
|
"win32"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"node_modules/@tauri-apps/api": {
|
||||||
|
"version": "2.10.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.10.1.tgz",
|
||||||
|
"integrity": "sha512-hKL/jWf293UDSUN09rR69hrToyIXBb8CjGaWC7gfinvnQrBVvnLr08FeFi38gxtugAVyVcTa5/FD/Xnkb1siBw==",
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/tauri"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli": {
|
||||||
|
"version": "2.10.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.10.1.tgz",
|
||||||
|
"integrity": "sha512-jQNGF/5quwORdZSSLtTluyKQ+o6SMa/AUICfhf4egCGFdMHqWssApVgYSbg+jmrZoc8e1DscNvjTnXtlHLS11g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"bin": {
|
||||||
|
"tauri": "tauri.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/tauri"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@tauri-apps/cli-darwin-arm64": "2.10.1",
|
||||||
|
"@tauri-apps/cli-darwin-x64": "2.10.1",
|
||||||
|
"@tauri-apps/cli-linux-arm-gnueabihf": "2.10.1",
|
||||||
|
"@tauri-apps/cli-linux-arm64-gnu": "2.10.1",
|
||||||
|
"@tauri-apps/cli-linux-arm64-musl": "2.10.1",
|
||||||
|
"@tauri-apps/cli-linux-riscv64-gnu": "2.10.1",
|
||||||
|
"@tauri-apps/cli-linux-x64-gnu": "2.10.1",
|
||||||
|
"@tauri-apps/cli-linux-x64-musl": "2.10.1",
|
||||||
|
"@tauri-apps/cli-win32-arm64-msvc": "2.10.1",
|
||||||
|
"@tauri-apps/cli-win32-ia32-msvc": "2.10.1",
|
||||||
|
"@tauri-apps/cli-win32-x64-msvc": "2.10.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-darwin-arm64": {
|
||||||
|
"version": "2.10.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.10.1.tgz",
|
||||||
|
"integrity": "sha512-Z2OjCXiZ+fbYZy7PmP3WRnOpM9+Fy+oonKDEmUE6MwN4IGaYqgceTjwHucc/kEEYZos5GICve35f7ZiizgqEnQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-darwin-x64": {
|
||||||
|
"version": "2.10.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.10.1.tgz",
|
||||||
|
"integrity": "sha512-V/irQVvjPMGOTQqNj55PnQPVuH4VJP8vZCN7ajnj+ZS8Kom1tEM2hR3qbbIRoS3dBKs5mbG8yg1WC+97dq17Pw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-linux-arm-gnueabihf": {
|
||||||
|
"version": "2.10.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.10.1.tgz",
|
||||||
|
"integrity": "sha512-Hyzwsb4VnCWKGfTw+wSt15Z2pLw2f0JdFBfq2vHBOBhvg7oi6uhKiF87hmbXOBXUZaGkyRDkCHsdzJcIfoJC2w==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-linux-arm64-gnu": {
|
||||||
|
"version": "2.10.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.10.1.tgz",
|
||||||
|
"integrity": "sha512-OyOYs2t5GkBIvyWjA1+h4CZxTcdz1OZPCWAPz5DYEfB0cnWHERTnQ/SLayQzncrT0kwRoSfSz9KxenkyJoTelA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-linux-arm64-musl": {
|
||||||
|
"version": "2.10.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.10.1.tgz",
|
||||||
|
"integrity": "sha512-MIj78PDDGjkg3NqGptDOGgfXks7SYJwhiMh8SBoZS+vfdz7yP5jN18bNaLnDhsVIPARcAhE1TlsZe/8Yxo2zqg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-linux-riscv64-gnu": {
|
||||||
|
"version": "2.10.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.10.1.tgz",
|
||||||
|
"integrity": "sha512-X0lvOVUg8PCVaoEtEAnpxmnkwlE1gcMDTqfhbefICKDnOTJ5Est3qL0SrWxizDackIOKBcvtpejrSiVpuJI1kw==",
|
||||||
|
"cpu": [
|
||||||
|
"riscv64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-linux-x64-gnu": {
|
||||||
|
"version": "2.10.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.10.1.tgz",
|
||||||
|
"integrity": "sha512-2/12bEzsJS9fAKybxgicCDFxYD1WEI9kO+tlDwX5znWG2GwMBaiWcmhGlZ8fi+DMe9CXlcVarMTYc0L3REIRxw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-linux-x64-musl": {
|
||||||
|
"version": "2.10.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.10.1.tgz",
|
||||||
|
"integrity": "sha512-Y8J0ZzswPz50UcGOFuXGEMrxbjwKSPgXftx5qnkuMs2rmwQB5ssvLb6tn54wDSYxe7S6vlLob9vt0VKuNOaCIQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-win32-arm64-msvc": {
|
||||||
|
"version": "2.10.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.10.1.tgz",
|
||||||
|
"integrity": "sha512-iSt5B86jHYAPJa/IlYw++SXtFPGnWtFJriHn7X0NFBVunF6zu9+/zOn8OgqIWSl8RgzhLGXQEEtGBdR4wzpVgg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-win32-ia32-msvc": {
|
||||||
|
"version": "2.10.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.10.1.tgz",
|
||||||
|
"integrity": "sha512-gXyxgEzsFegmnWywYU5pEBURkcFN/Oo45EAwvZrHMh+zUSEAvO5E8TXsgPADYm31d1u7OQU3O3HsYfVBf2moHw==",
|
||||||
|
"cpu": [
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-win32-x64-msvc": {
|
||||||
|
"version": "2.10.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.10.1.tgz",
|
||||||
|
"integrity": "sha512-6Cn7YpPFwzChy0ERz6djKEmUehWrYlM+xTaNzGPgZocw3BD7OfwfWHKVWxXzdjEW2KfKkHddfdxK1XXTYqBRLg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/plugin-dialog": {
|
||||||
|
"version": "2.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-dialog/-/plugin-dialog-2.6.0.tgz",
|
||||||
|
"integrity": "sha512-q4Uq3eY87TdcYzXACiYSPhmpBA76shgmQswGkSVio4C82Sz2W4iehe9TnKYwbq7weHiL88Yw19XZm7v28+Micg==",
|
||||||
|
"license": "MIT OR Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@tauri-apps/api": "^2.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/plugin-fs": {
|
||||||
|
"version": "2.4.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-fs/-/plugin-fs-2.4.5.tgz",
|
||||||
|
"integrity": "sha512-dVxWWGE6VrOxC7/jlhyE+ON/Cc2REJlM35R3PJX3UvFw2XwYhLGQVAIyrehenDdKjotipjYEVc4YjOl3qq90fA==",
|
||||||
|
"license": "MIT OR Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@tauri-apps/api": "^2.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/babel__core": {
|
"node_modules/@types/babel__core": {
|
||||||
"version": "7.20.5",
|
"version": "7.20.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "cutscript-frontend",
|
"name": "talkedit-frontend",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@ -10,6 +10,9 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@tauri-apps/api": "^2",
|
||||||
|
"@tauri-apps/plugin-dialog": "^2",
|
||||||
|
"@tauri-apps/plugin-fs": "^2",
|
||||||
"lucide-react": "^0.468.0",
|
"lucide-react": "^0.468.0",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
@ -19,6 +22,7 @@
|
|||||||
"zustand": "^5.0.0"
|
"zustand": "^5.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@tauri-apps/cli": "^2",
|
||||||
"@types/react": "^19.0.0",
|
"@types/react": "^19.0.0",
|
||||||
"@types/react-dom": "^19.0.0",
|
"@types/react-dom": "^19.0.0",
|
||||||
"@vitejs/plugin-react": "^4.3.0",
|
"@vitejs/plugin-react": "^4.3.0",
|
||||||
|
|||||||
71
frontend/src/lib/tauri-bridge.ts
Normal file
71
frontend/src/lib/tauri-bridge.ts
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
/**
|
||||||
|
* tauri-bridge.ts
|
||||||
|
*
|
||||||
|
* Polyfills window.electronAPI with Tauri equivalents so all existing
|
||||||
|
* call-sites in App.tsx, hooks, and stores continue to work unchanged.
|
||||||
|
*
|
||||||
|
* Imported once at the top of main.tsx.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
|
import { open, save } from '@tauri-apps/plugin-dialog';
|
||||||
|
import { readTextFile, writeTextFile } from '@tauri-apps/plugin-fs';
|
||||||
|
|
||||||
|
const VIDEO_FILTERS = [
|
||||||
|
{ name: 'Video Files', extensions: ['mp4', 'avi', 'mov', 'mkv', 'webm'] },
|
||||||
|
{ name: 'Audio Files', extensions: ['m4a', 'wav', 'mp3', 'flac'] },
|
||||||
|
{ name: 'All Files', extensions: ['*'] },
|
||||||
|
];
|
||||||
|
|
||||||
|
const PROJECT_FILTERS = [
|
||||||
|
{ name: 'TalkEdit Project', extensions: ['aive'] },
|
||||||
|
];
|
||||||
|
|
||||||
|
const EXPORT_FILTERS = [
|
||||||
|
{ name: 'Video Files', extensions: ['mp4', 'mov', 'webm'] },
|
||||||
|
{ name: 'Project Files', extensions: ['aive'] },
|
||||||
|
];
|
||||||
|
|
||||||
|
window.electronAPI = {
|
||||||
|
openFile: async (_options?: Record<string, unknown>): Promise<string | null> => {
|
||||||
|
const result = await open({
|
||||||
|
multiple: false,
|
||||||
|
filters: VIDEO_FILTERS,
|
||||||
|
});
|
||||||
|
return typeof result === 'string' ? result : null;
|
||||||
|
},
|
||||||
|
|
||||||
|
saveFile: async (_options?: Record<string, unknown>): Promise<string | null> => {
|
||||||
|
const result = await save({ filters: EXPORT_FILTERS });
|
||||||
|
return result ?? null;
|
||||||
|
},
|
||||||
|
|
||||||
|
openProject: async (): Promise<string | null> => {
|
||||||
|
const result = await open({
|
||||||
|
multiple: false,
|
||||||
|
filters: PROJECT_FILTERS,
|
||||||
|
});
|
||||||
|
return typeof result === 'string' ? result : null;
|
||||||
|
},
|
||||||
|
|
||||||
|
getBackendUrl: (): Promise<string> => {
|
||||||
|
return invoke<string>('get_backend_url');
|
||||||
|
},
|
||||||
|
|
||||||
|
encryptString: (data: string): Promise<string> => {
|
||||||
|
return invoke<string>('encrypt_string', { data });
|
||||||
|
},
|
||||||
|
|
||||||
|
decryptString: (encrypted: string): Promise<string> => {
|
||||||
|
return invoke<string>('decrypt_string', { encrypted });
|
||||||
|
},
|
||||||
|
|
||||||
|
readFile: (path: string): Promise<string> => {
|
||||||
|
return readTextFile(path);
|
||||||
|
},
|
||||||
|
|
||||||
|
writeFile: async (path: string, content: string): Promise<boolean> => {
|
||||||
|
await writeTextFile(path, content);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -1,5 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom/client';
|
import ReactDOM from 'react-dom/client';
|
||||||
|
// Must be imported before App so window.electronAPI is patched before any component runs.
|
||||||
|
import './lib/tauri-bridge';
|
||||||
import App from './App';
|
import App from './App';
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|
||||||
|
|||||||
1
frontend/tsconfig.tsbuildinfo
Normal file
1
frontend/tsconfig.tsbuildinfo
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"root":["./src/App.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/components/AIPanel.tsx","./src/components/ExportDialog.tsx","./src/components/SettingsPanel.tsx","./src/components/TranscriptEditor.tsx","./src/components/VideoPlayer.tsx","./src/components/WaveformTimeline.tsx","./src/hooks/useKeyboardShortcuts.ts","./src/hooks/useVideoSync.ts","./src/lib/tauri-bridge.ts","./src/store/aiStore.ts","./src/store/editorStore.ts","./src/types/project.ts"],"version":"5.9.3"}
|
||||||
@ -1,15 +1,30 @@
|
|||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
import react from '@vitejs/plugin-react';
|
import react from '@vitejs/plugin-react';
|
||||||
|
|
||||||
|
// Use TAURI_DEV_HOST when running inside tauri dev, otherwise fall back to localhost.
|
||||||
|
const host = process.env.TAURI_DEV_HOST;
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
base: './',
|
// Prevent vite from obscuring Rust errors in tauri dev
|
||||||
|
clearScreen: false,
|
||||||
server: {
|
server: {
|
||||||
port: 5173,
|
port: 5173,
|
||||||
strictPort: true,
|
strictPort: true,
|
||||||
|
host: host || false,
|
||||||
|
hmr: host ? { protocol: 'ws', host, port: 5174 } : undefined,
|
||||||
|
watch: {
|
||||||
|
// Tauri expects a hot reloading mechanism; ignore Rust src changes in Vite
|
||||||
|
ignored: ['**/src-tauri/**'],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
outDir: 'dist',
|
outDir: 'dist',
|
||||||
emptyOutDir: true,
|
emptyOutDir: true,
|
||||||
|
// Produces smaller bundle sizes compatible with Tauri's webview
|
||||||
|
target: ['es2021', 'chrome105', 'safari13'],
|
||||||
|
minify: !process.env.TAURI_DEBUG ? 'esbuild' : false,
|
||||||
|
sourcemap: !!process.env.TAURI_DEBUG,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -5,11 +5,12 @@
|
|||||||
"description": "TalkEdit — Open-source AI-powered text-based video editor",
|
"description": "TalkEdit — Open-source AI-powered text-based video editor",
|
||||||
"main": "electron/main.js",
|
"main": "electron/main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "concurrently \"npm run dev:backend\" \"npm run dev:frontend\" \"wait-on http://localhost:5173 && npm run dev:electron\"",
|
"tauri": "tauri",
|
||||||
|
"dev": "cd frontend && npm run dev -- --host",
|
||||||
|
"dev:tauri": "cd backend && python -m uvicorn main:app --reload --port 8642 & cd frontend && cargo tauri dev",
|
||||||
|
"build:tauri": "cd frontend && cargo tauri build",
|
||||||
"dev:frontend": "cd frontend && npm run dev",
|
"dev:frontend": "cd frontend && npm run dev",
|
||||||
"dev:electron": "electron .",
|
|
||||||
"dev:backend": "cd backend && python -m uvicorn main:app --reload --port 8642",
|
"dev:backend": "cd backend && python -m uvicorn main:app --reload --port 8642",
|
||||||
"build": "cd frontend && npm run build && electron-builder",
|
|
||||||
"lint": "cd frontend && npm run lint"
|
"lint": "cd frontend && npm run lint"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
5347
src-tauri/Cargo.lock
generated
Normal file
5347
src-tauri/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -15,11 +15,13 @@ name = "app_lib"
|
|||||||
crate-type = ["staticlib", "cdylib", "rlib"]
|
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
tauri-build = { version = "2.5.6" }
|
tauri-build = { version = "2.5.6", features = [] }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
tauri = { version = "2.10.3" }
|
tauri = { version = "2.10.3", features = [] }
|
||||||
|
tauri-plugin-dialog = "2"
|
||||||
|
tauri-plugin-fs = "2"
|
||||||
tauri-plugin-log = "2"
|
tauri-plugin-log = "2"
|
||||||
|
|||||||
@ -1,11 +1,19 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../gen/schemas/desktop-schema.json",
|
"$schema": "../gen/schemas/desktop-schema.json",
|
||||||
"identifier": "default",
|
"identifier": "default",
|
||||||
"description": "enables the default permissions",
|
"description": "Default capabilities for TalkEdit desktop",
|
||||||
"windows": [
|
"windows": [
|
||||||
"main"
|
"main"
|
||||||
],
|
],
|
||||||
"permissions": [
|
"permissions": [
|
||||||
"core:default"
|
"core:default",
|
||||||
|
"dialog:default",
|
||||||
|
"dialog:allow-open",
|
||||||
|
"dialog:allow-save",
|
||||||
|
"fs:default",
|
||||||
|
"fs:allow-read-text-file",
|
||||||
|
"fs:allow-write-text-file",
|
||||||
|
"fs:allow-app-read-recursive",
|
||||||
|
"fs:allow-app-write-recursive"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,51 @@
|
|||||||
|
use tauri::Manager;
|
||||||
|
|
||||||
|
// --- Commands ---
|
||||||
|
|
||||||
|
/// Returns the backend URL. Stubbed for now; will be replaced once the
|
||||||
|
/// Python/Rust backend is fully wired up.
|
||||||
|
#[tauri::command]
|
||||||
|
fn get_backend_url() -> String {
|
||||||
|
// During development the Python backend still runs on 8642.
|
||||||
|
// In production this will be replaced with a local Rust server or IPC.
|
||||||
|
"http://localhost:8642".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Minimal encrypt: base64-encodes the string as a placeholder until a proper
|
||||||
|
/// OS keychain implementation is added (e.g. tauri-plugin-stronghold).
|
||||||
|
#[tauri::command]
|
||||||
|
fn encrypt_string(data: String) -> String {
|
||||||
|
use std::io::Write;
|
||||||
|
let encoded = data
|
||||||
|
.as_bytes()
|
||||||
|
.iter()
|
||||||
|
.fold(String::new(), |mut acc, b| {
|
||||||
|
use std::fmt::Write as FmtWrite;
|
||||||
|
let _ = write!(acc, "{:02x}", b);
|
||||||
|
acc
|
||||||
|
});
|
||||||
|
encoded
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Companion decode for encrypt_string.
|
||||||
|
#[tauri::command]
|
||||||
|
fn decrypt_string(encrypted: String) -> Result<String, String> {
|
||||||
|
let bytes: Result<Vec<u8>, _> = (0..encrypted.len())
|
||||||
|
.step_by(2)
|
||||||
|
.map(|i| u8::from_str_radix(&encrypted[i..i + 2], 16))
|
||||||
|
.collect();
|
||||||
|
bytes
|
||||||
|
.map_err(|e| format!("decrypt error: {e}"))
|
||||||
|
.and_then(|b| String::from_utf8(b).map_err(|e| format!("utf8 error: {e}")))
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- App entry point ---
|
||||||
|
|
||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
|
.plugin(tauri_plugin_dialog::init())
|
||||||
|
.plugin(tauri_plugin_fs::init())
|
||||||
.setup(|app| {
|
.setup(|app| {
|
||||||
if cfg!(debug_assertions) {
|
if cfg!(debug_assertions) {
|
||||||
app.handle().plugin(
|
app.handle().plugin(
|
||||||
@ -11,6 +56,11 @@ pub fn run() {
|
|||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
|
.invoke_handler(tauri::generate_handler![
|
||||||
|
get_backend_url,
|
||||||
|
encrypt_string,
|
||||||
|
decrypt_string,
|
||||||
|
])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,19 +1,23 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "cutscript",
|
"productName": "TalkEdit",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"identifier": "com.tauri.dev",
|
"identifier": "com.talkedit.app",
|
||||||
"build": {
|
"build": {
|
||||||
"frontendDist": "../dist",
|
"frontendDist": "../frontend/dist",
|
||||||
"beforeDevCommand": "npm run dev",
|
"devUrl": "http://localhost:5173",
|
||||||
"beforeBuildCommand": "npm run build"
|
"beforeDevCommand": "cd frontend && npm run dev",
|
||||||
|
"beforeBuildCommand": "cd frontend && npm run build"
|
||||||
},
|
},
|
||||||
"app": {
|
"app": {
|
||||||
"windows": [
|
"windows": [
|
||||||
{
|
{
|
||||||
"title": "cutscript",
|
"label": "main",
|
||||||
"width": 800,
|
"title": "TalkEdit",
|
||||||
"height": 600,
|
"width": 1400,
|
||||||
|
"height": 900,
|
||||||
|
"minWidth": 1024,
|
||||||
|
"minHeight": 700,
|
||||||
"resizable": true,
|
"resizable": true,
|
||||||
"fullscreen": false
|
"fullscreen": false
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user