Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@ env/
ENV/
.env
.env.*

# Mutable operator hotword files must stay out of image layers. Keep only the
# reviewed onboarding example in the source build context.
hot-server.txt
hot.txt
hot-rule.txt
hot-rectify.txt
!hot-server.example.txt

build/
dist/
logs/
Expand Down
10 changes: 7 additions & 3 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Public image
CAPSWRITER_SERVER_IMAGE=ghcr.io/df-wu/capswriter-offline-server:latest
# v1 is released as source. Compose builds this local image from the v1 tree.
# The public ghcr.io/...:latest tag belongs to v2 and must not be used here.
CAPSWRITER_SERVER_IMAGE=capswriter-offline-v1-local:source

# Model selection
# qwen_asr = 主預設; 對話精度高, HTTP API / 長段轉錄場景的首選
Expand Down Expand Up @@ -33,7 +34,10 @@ CAPSWRITER_LOG_LEVEL=INFO
# Expose POST /v1/audio/transcriptions in addition to WebSocket. The OpenAI
# Python/Node SDKs can use this endpoint by setting base_url. See docs/HTTP_API.md.
CAPSWRITER_HTTP_API_ENABLE=false
CAPSWRITER_HTTP_API_BIND=127.0.0.1
# Inside the container, listen on all interfaces for Docker port forwarding.
CAPSWRITER_HTTP_API_BIND=0.0.0.0
# Publish the HTTP port on host loopback unless a trusted proxy requires more.
CAPSWRITER_HTTP_API_HOST_BIND=127.0.0.1
CAPSWRITER_HTTP_API_PORT=6017
# Bearer token. Empty disables auth (only safe behind a private bind address).
CAPSWRITER_HTTP_API_KEY=
Expand Down
46 changes: 0 additions & 46 deletions .github/workflows/publish-server-image.yml

This file was deleted.

76 changes: 76 additions & 0 deletions .github/workflows/v1-maintenance.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
name: v1 maintenance checks

on:
pull_request:
branches:
- archive/v1-legacy
push:
branches:
- maintenance/v1
workflow_dispatch:

permissions:
contents: read

concurrency:
group: v1-maintenance-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
protocol-and-runtime-smoke:
name: ${{ matrix.os }} / Python ${{ matrix.python-version }}
runs-on: ${{ matrix.os }}
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
os:
- ubuntu-24.04
- windows-2022
python-version:
- "3.10"
- "3.12"
env:
PYTHONDONTWRITEBYTECODE: "1"
PYTHONNOUSERSITE: "1"

steps:
- name: Check out legacy maintenance line
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
persist-credentials: false

- name: Set up Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: ${{ matrix.python-version }}
cache: pip
cache-dependency-path: requirements-maintenance.txt

- name: Install maintenance test dependencies
run: >-
python -m pip install
--disable-pip-version-check
--only-binary=:all:
--requirement requirements-maintenance.txt

- name: Run protocol and server regression tests
env:
CAPSWRITER_LOG_DIR: ${{ runner.temp }}/capswriter-maintenance-logs
run: python -m unittest discover -s tests -p "test_*.py" -v

- name: Compile supported entry points
run: >-
python -m compileall -q
config_client.py config_server.py
core_client.py core_server.py
start_client.py start_server.py
util docker/server

- name: Validate container entrypoint syntax
if: runner.os == 'Linux' && matrix.python-version == '3.10'
run: bash -n docker/server/entrypoint.sh

- name: Validate Compose configuration
if: runner.os == 'Linux' && matrix.python-version == '3.10'
run: docker compose --env-file .env.example config --quiet
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,8 @@ test_*.py
test_*.ipynb
test_*.md
test_*.txt
!tests/
!tests/test_*.py
stocks.txt
example_*.py
example_.ipynb
Expand All @@ -187,4 +189,4 @@ file_*.txt

release
*.dll
*.exe
*.exe
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 Haujet Zhao

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
149 changes: 149 additions & 0 deletions README.en.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
# CapsWriter-Offline fork v1 — Legacy Server + Desktop Client

> **v1 is an isolated, best-effort maintenance line.** Its primary deliverable
> is the Linux/headless ASR server. The same source retains compatibility with
> the upstream 2.5-alpha-era Windows desktop client.
>
> [繁體中文](readme.md) · English

[![License](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
[![Track](https://img.shields.io/badge/track-fork--v1%20legacy-64748B)](docs/en/maintenance.md)
[![Server](https://img.shields.io/badge/server-Linux%20%7C%20Docker-2496ED?logo=docker&logoColor=white)](docs/docker-server.md)

## First understand the v1 server/client split

```mermaid
flowchart LR
C[Legacy Windows desktop client<br/>start_client.py] -->|WebSocket :6016| S[v1 ASR Server<br/>model・FFmpeg・inference]
O[OpenAI SDK / curl] -->|optional HTTP :6017| S
S --> R[transcript]
```

| Component | v1 content | Release status |
|---|---|---|
| **Server** | Linux bare-metal/Docker, WebSocket `6016`, optional transcription-only HTTP `6017`, model bootstrap, GPU preference/CPU fallback | Primary maintained v1 path; GitHub Releases provide source for local builds |
| **Desktop client** | Upstream-era `start_client.py`: Windows GUI, tray, hotkeys, microphone, clipboard/text injection | Source compatibility only; no v1 Windows EXE unless a release explicitly attaches a real-Windows-qualified artifact |
| **External API caller** | Compatible SDK/curl code may use the documented `whisper-1` transcription subset | API interface, not a bundled client application |

**v1 does not contain the v2 Web Console, no-GUI CLI, Textual TUI, or universal
Windows package.** Use v2 when you need those surfaces.

## Release and image boundary

- v1 GitHub Releases are **source-only pre-releases**.
- Source archives include both legacy server/API/container code and the
compatibility-preserved Windows desktop client source.
- No v1 container image or Windows executable is currently published.
- `ghcr.io/df-wu/capswriter-offline-server:latest` belongs to **v2** and must
not be used for v1.
- v1 Compose builds `capswriter-offline-v1-local:source` from the current tree.

## Quick start: v1 Linux server

Prerequisites: Linux, Docker Engine, the Compose plugin, and model storage.
NVIDIA GPU support is optional; CPU fallback is available.

```bash
cp .env.example .env
cp hot-server.example.txt hot-server.txt
docker compose build --pull capswriter-server
docker compose up -d capswriter-server
docker compose ps
docker compose logs -f capswriter-server
```

Default WebSocket endpoint:

```text
ws://127.0.0.1:6016
```

See [v1 Docker server](docs/docker-server.md) for models, GPU/CPU selection,
volumes, and troubleshooting.

## Optional OpenAI-compatible HTTP API

The HTTP API shares the recognizer with the WebSocket server and is disabled by
default. It implements only the documented file-transcription subset, not
translation or the complete OpenAI Audio API.

Enable it in `.env` with a token:

```dotenv
CAPSWRITER_HTTP_API_ENABLE=true
CAPSWRITER_HTTP_API_BIND=0.0.0.0
CAPSWRITER_HTTP_API_HOST_BIND=127.0.0.1
CAPSWRITER_HTTP_API_PORT=6017
CAPSWRITER_HTTP_API_KEY=replace-with-a-long-random-token
```

Recreate the server after changing `.env`. Compose passes these settings into
the container and publishes port `6017` on host loopback by default. Keep
`CAPSWRITER_HTTP_API_HOST_BIND=127.0.0.1` unless a trusted reverse proxy with
authentication and TLS requires a wider host bind. Compatible callers may
point their base URL at `http://127.0.0.1:6017/v1`; unsupported fields may be
rejected.

See the [English HTTP API guide](docs/en/http-api.md) for the exact contract,
security limits, and SDK/curl examples.

## Legacy Windows desktop client

v1 source retains the original desktop flow:

```text
start_server.py --WebSocket :6016--> start_client.py
```

The desktop client owns tray, hotkeys, microphone, clipboard, and text
injection. The server loads the model and performs inference. This is not the
v2 universal package, and the current v1 release does not include an EXE.

A self-built Windows artifact still needs real-host launch/exit, tray, hotkey,
microphone, clipboard, FFmpeg, model, known-audio, and child-cleanup validation.

## Support scope

| Path | Status | Automated evidence | Remaining real-host evidence |
|---|---|---|---|
| Linux Docker server | Primary legacy server path | Ubuntu tests, Compose config, entrypoint shell, protocol/API units | Disposable image build, model load, Mandarin/English known audio, GPU/CPU host |
| Linux bare-metal server | Best effort | Python 3.10/3.12 server tests | FFmpeg, native libraries, model, supervision |
| Windows desktop source | Compatibility-preserved | Windows Python 3.10/3.12 syntax/protocol tests | Tray, hotkeys, microphone, clipboard, PyInstaller artifact |
| Optional HTTP API | Legacy compatibility | Auth, upload bound, format, routing tests | Live authenticated model-backed transcription |
| macOS | Not release-qualified | No complete gate | No project-level support claim |

Passing CI does not certify model quality, a GPU backend, audio hardware, or a
Windows desktop release.

## Maintenance and branch rules

- Development branch: `maintenance/v1`
- Standing comparison PR base: `archive/v1-legacy`
- Never merge v1 into `master` or bulk-backport v2 into v1.
- Only critical security, compatibility, model-asset, and contract fixes belong
here.
- v1 tags use `fork-v1.<minor>.<patch>`; pre-releases may add `-rc.<n>`.

Policies:

- [English maintenance policy](docs/en/maintenance.md)
- [繁體中文維護政策](docs/zh-TW/maintenance.md)

## Documentation

| Document | Covers |
|---|---|
| [v1 Docker server](docs/docker-server.md) | Local source build, models, GPU/CPU, volumes, operations |
| [HTTP API](docs/en/http-api.md) | Transcription subset, auth, limits, SDK/curl |
| [v1 maintenance policy](docs/en/maintenance.md) | Branches, support, qualification, residual risks |
| [v1 release notes](docs/en/release-notes.md) | RC deliverables, server/client boundary, remaining qualification |
| [Upstream release history](https://github.com/HaujetZhao/CapsWriter-Offline/releases) | Upstream-era product history |

## Upstream and license

This line derives from the 2.5-alpha-era desktop/recognition code in
[HaujetZhao/CapsWriter-Offline](https://github.com/HaujetZhao/CapsWriter-Offline)
and adds the fork's maintained Linux server, Docker, and HTTP API changes. New
feature development belongs to fork v2.

License: [MIT](LICENSE).
6 changes: 6 additions & 0 deletions config_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,12 @@ class ServerConfig:
addr = _env_str("CAPSWRITER_SERVER_ADDR", "0.0.0.0")
port = _env_str("CAPSWRITER_SERVER_PORT", "6016")

# 单个 WebSocket JSON frame 上限。官方客户端的文件分块约 5.12 MiB
# (Base64 后),默认 8 MiB 可保留既有行为并阻止无限制 frame 分配。
websocket_max_message_mb = int(
_env_str("CAPSWRITER_WS_MAX_MESSAGE_MB", "8")
)

# 语音模型选择:'fun_asr_nano', 'sensevoice', 'paraformer', 'qwen_asr'
model_type = _env_str("CAPSWRITER_MODEL_TYPE", "qwen_asr")

Expand Down
6 changes: 5 additions & 1 deletion core_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,11 @@ async def run_websocket_server():
# 2. 启动服务器
logger.info(f"WebSocket 服务器正在启动,监听地址: {Config.addr}:{Config.port}")
async with websockets.serve(
ws_recv, Config.addr, Config.port, subprotocols=["binary"], max_size=None
ws_recv,
Config.addr,
Config.port,
subprotocols=["binary"],
max_size=max(1, Config.websocket_max_message_mb) * 1024 * 1024,
):
send_task = asyncio.create_task(ws_send())

Expand Down
Loading
Loading