← All projects
  • Voice creation
  • macOS app
  • Local data

VoiceCanvas

A MiniMax-powered macOS voice-over tool that brings text annotation, segment previews, voice management, full-text export, and usage reporting into a local interface.

View source on GitHub ↗

Why I made it

Producing a voice-over from an article often means moving between several tools and steps: preparing the source, adding emotion and interjection cues, selecting a voice, previewing segments, handling failures, joining the result, and reviewing API usage. VoiceCanvas brings those tasks into a personal macOS desktop application, allowing everyday voice-over work to move from source text to adjustment and export in one interface.

Application data is stored by default in ~/Library/Application Support/VoiceCanvas/, generated audio goes to ~/Music/VoiceCanvas, and the API key is kept in macOS Keychain rather than written in plaintext to the data directory. Local storage does not mean offline processing, however. Speech synthesis, voice cloning, voice design, and other MiniMax-powered features send the data required for each request to the API Base URL configured by the user.

The core experience

The workbench follows a clear sequence: enter the source, run AI-assisted annotation, review the result, parse it into segments, preview those segments, and export the full recording. Users can adjust [emotion] tags and (interjection) markers by hand, then choose a voice and set speed, volume, Pitch, and optional effects. Edits are automatically saved as a draft, reducing the risk of losing a longer session.

Segment previews let users inspect the result before export. If some segments fail, the initial exported file contains only the successful ones. Retrying a failed segment synthesises and plays that segment again but does not modify the file already exported. To produce a finished file containing every segment, the user must run full-text synthesis again, which regenerates all segments. This is not an automatic resume feature, but it keeps the boundary of each export explicit.

The voice library browses system voices, clones voices from uploaded audio, designs voices from written descriptions, and stores local notes. Usage reports provide daily or weekly trends, feature-level breakdowns, call details, filters, pagination, CSV export, and cost estimates based on configured unit prices.

What I contributed

I implemented the voice-over workflow, desktop interface, local service, and data management, organising configuration, annotation, previews, voice management, full-text export, and usage reporting into four focused modules. PyWebView provides the native macOS window, FastAPI runs the local service on a random background port, SQLite stores structured application data, and Keychain protects the API key.

I also integrated MiniMax speech synthesis, voice cloning, and voice design; implemented automatic draft saving, failed-segment markers and retry previews, usage logging, CSV export, and macOS packaging for arm64 and x86_64. Automated tests intercept external requests without consuming real quota, while GitHub Actions runs the complete suite on macOS with Python 3.13.

How it works

The frontend uses native HTML, CSS, and JavaScript and requires no separate frontend build chain. Audio output supports MP3, WAV, and FLAC. MP3 offers 32, 64, 128, and 256 kbps at constant bitrate, while the MP3 bitrate setting does not apply to WAV or FLAC. The ffmpeg binary needed for concatenation is supplied through imageio-ffmpeg, so users do not need a separate system-level installation.

Full-text synthesis generates separate segments and concatenates them locally. A fixed one second of silence is inserted between each pair of adjacent successful segments, with no extra silence at the beginning or end. Output format, sample rate, and bitrate are locked when a generation begins; segment synthesis, failed-segment retries, and final concatenation within that generation reuse the same settings to prevent inconsistent audio parameters inside one finished file.

Problems and lessons

This project taught me that the difficult part of long-form voice-over is not simply whether audio can be generated, but how a generation is defined. Segment-based processing makes previewing and locating failures easier, yet the relationship between a retry, an existing export, and a new full-text generation must be explicit. Fixed inter-segment silence and locked generation settings likewise trade ambiguity for a consistent final result.

The project also clarified the difference between storing data locally and processing content locally. A desktop interface, SQLite, and Keychain can reduce unnecessary exposure, but an external voice service still receives the text or audio required for each request. A dependable personal tool should explain the service address, data flow, recovery behaviour, and usage records as clearly as it presents the successful path.