https://github.com/kperreau/wordsubgen
A Go library and CLI tool for generating ASS (Advanced SubStation Alpha) subtitle files with word-by-word fade-in effects. Perfect for creating dynamic subtitles where words appear sequentially with smooth fade transitions.
https://github.com/kperreau/wordsubgen
advanced-substation-alpha ass cli fade-in go golang karaoke ssa subtitles text-animation word-by-word
Last synced: 5 months ago
JSON representation
A Go library and CLI tool for generating ASS (Advanced SubStation Alpha) subtitle files with word-by-word fade-in effects. Perfect for creating dynamic subtitles where words appear sequentially with smooth fade transitions.
- Host: GitHub
- URL: https://github.com/kperreau/wordsubgen
- Owner: kperreau
- License: mit
- Created: 2025-09-17T15:15:39.000Z (9 months ago)
- Default Branch: main
- Last Pushed: 2026-01-22T19:20:44.000Z (5 months ago)
- Last Synced: 2026-01-23T12:22:33.519Z (5 months ago)
- Topics: advanced-substation-alpha, ass, cli, fade-in, go, golang, karaoke, ssa, subtitles, text-animation, word-by-word
- Language: Go
- Homepage:
- Size: 6.01 MB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# wordsubgen
A Go library and CLI tool for generating ASS (Advanced SubStation Alpha) subtitle files with word-by-word fade-in effects. Perfect for creating dynamic subtitles where words appear sequentially with smooth fade transitions.
## Features
- **Word-by-word fade effects**: Each word fades in sequentially using ASS `\alpha` tags
- **Structured JSON input**: Generate from timed words (`word`, `start`, `end` in seconds) with `--json` / `--json-file`; `StartDelay` shifts all timings (e.g. 1500 ms = 1.5 s offset)
- **Karaoke support**: Optional karaoke mode with `\k` tags for word highlighting
- **Shadow effects**: Customizable shadow with directional control using `\xshad` and `\yshad` tags
- **Flexible configuration**: Customizable fonts, colors, timing, alignment, and styling
- **Library API**: Use as a Go library in your own projects
- **CLI tool**: Easy-to-use command-line interface with structured logging
## Demo
Here's a visual example of the word-by-word fade effect generated by wordsubgen:

This demo was generated using the following command:
```bash
wordsubgen --lines "This is a super test|Hello world \!" --font "Outfit" --fontsize 85 --shadow-enabled --shadow-x 3 --shadow-y 8 --width 1080 --height 734 --out subtitle.ass
```
## Installation
### CLI Tool Installation
To install the command-line tool:
```bash
go install github.com/kperreau/wordsubgen/cmd/wordsubgen@latest
```
### Package Installation (for Go projects)
To use wordsubgen as a library in your Go project:
```bash
go get github.com/kperreau/wordsubgen@latest
```
Then import it in your Go code:
```go
import "github.com/kperreau/wordsubgen"
```
### Development Installation
For development work on the wordsubgen project itself:
```bash
git clone https://github.com/kperreau/wordsubgen.git
cd wordsubgen
make deps
make build
```
Or install the CLI to your GOPATH/bin:
```bash
make install # Installs to GOPATH/bin
```
### Requirements
- **Go 1.25.0+**: Required for building from source
- **Make**: Required for using the Makefile (optional, you can use `go build` directly)
## Quick Start
### CLI Usage
Generate subtitles from command line:
```bash
# Basic usage
wordsubgen --lines "Hello world|This is a test" --out subtitle.ass
# Custom styling
wordsubgen --lines "Hello world" --color "#FF0000" --fontsize 72 --delay 500
# Karaoke mode
wordsubgen --lines "Hello world" --karaoke --delay 400
# From file
wordsubgen --file input.txt --out subtitle.ass
# From structured JSON (words with start/end in seconds); --start-delay 1500 = 1.5 s shift
wordsubgen --json-file words.json --start-delay 1500 --out subtitle.ass
# Show help with current default values
wordsubgen --help
```
### Library Usage
```go
package main
import (
"log"
"github.com/kperreau/wordsubgen"
)
func main() {
// Create configuration
cfg := wordsubgen.DefaultConfig()
cfg.FontSize = 48
cfg.PrimaryColor = wordsubgen.ColorToASS("#FF0000")
cfg.PerWordDelay = 500
// Enable shadow with diagonal direction (more downward than sideways)
cfg.ShadowEnabled = true
cfg.ShadowX = 3 // Horizontal offset
cfg.ShadowY = 8 // Vertical offset (more downward)
// Generate ASS content
lines := []string{"Hello world", "This is a test"}
content, err := wordsubgen.GenerateASS(cfg, lines)
if err != nil {
log.Fatal(err)
}
// Write to file
err = wordsubgen.WriteASS("output.ass", content, cfg.Logger)
if err != nil {
log.Fatal(err)
}
}
```
## Structured JSON Input
You can generate subtitles from a JSON structure with a `segments` array. Each segment is a phrase with a `words` array; each word has `word`, `start` and `end` (in **seconds**). Extra fields anywhere are ignored.
```json
{
"segments": [
{
"words": [
{"word": "Découvrez", "start": 0.162, "end": 1.024},
{"word": "cette", "start": 1.064, "end": 1.344},
{"word": "superbe", "start": 1.404, "end": 1.845}
]
},
{
"words": [
{"word": "Deuxième", "start": 2.0, "end": 2.5},
{"word": "phrase", "start": 2.6, "end": 3.0}
]
}
]
}
```
- **StartDelay** (e.g. `--start-delay 1500`): adds 1.5 seconds to every `start` and `end` so subtitles start 1.5 s later in the video.
- **PerWordDelay** is ignored (timings come from the JSON).
- **FadeDuration** is still used for the word fade-in effect.
### CLI
```bash
wordsubgen --json-file words.json --start-delay 1500 --out subtitle.ass
wordsubgen --json '{"segments":[{"words":[{"word":"Hello","start":0,"end":0.5},{"word":"world","start":0.6,"end":1.2}]}]}' --out subtitle.ass
```
### Library
```go
data, _ := os.ReadFile("words.json")
phrases, err := wordsubgen.ParseStructuredJSON(data)
if err != nil {
log.Fatal(err)
}
cfg := wordsubgen.DefaultConfig()
cfg.StartDelay = 1500 // 1.5 s shift
content, err := wordsubgen.GenerateASSFromStructured(cfg, phrases)
// then wordsubgen.WriteASS(...)
```
## Flexible Logging Interface
wordsubgen features a flexible logging interface that allows you to inject your own logger implementation. The library comes with several built-in loggers and supports popular logging libraries like zerolog (recommended), logrus, and others.
### Built-in Loggers
```go
// Default logger (uses Go's standard log package)
cfg.Logger = wordsubgen.NewDefaultLogger()
// No-operation logger (silent, useful for tests)
cfg.Logger = wordsubgen.NewNoOpLogger()
```
### Custom Logger Implementation
```go
type MyCustomLogger struct{}
func (l *MyCustomLogger) Debug(msg string, fields ...wordsubgen.Field) {
// Your debug implementation
}
func (l *MyCustomLogger) Info(msg string, fields ...wordsubgen.Field) {
// Your info implementation
}
func (l *MyCustomLogger) Error(msg string, fields ...wordsubgen.Field) {
// Your error implementation
}
func (l *MyCustomLogger) Warn(msg string, fields ...wordsubgen.Field) {
// Your warning implementation
}
// Use your custom logger
cfg.Logger = &MyCustomLogger{}
```
### Logging Features
- **Structured logging**: Support for key-value fields
- **Multiple log levels**: Debug, Info, Error, Warn
- **Zero dependencies**: No forced logging library dependencies
- **Flexible**: Easy to integrate with any logging system
- **zerolog support**: Recommended for high-performance structured logging
## Configuration Reference
### Video Settings
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `Width` | int | 1080 | Video width in pixels |
| `Height` | int | 1920 | Video height in pixels |
### Font Settings
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `FontName` | string | "Arial" | Font family name |
| `FontSize` | int | 64 | Font size in points |
### Colors (ASS format: &H00BBGGRR)
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `PrimaryColor` | string | "&H00FFFFFF" | Text color (white) |
| `SecondaryColor` | string | "&H0000FFFF" | Karaoke highlight color (yellow) |
| `OutlineColor` | string | "&H00000000" | Outline color (black) |
| `BackColor` | string | "&H64000000" | Background color (semi-transparent) |
> **Color Conversion**: Use `ColorToASS()` to convert hex colors (#RRGGBB) to ASS format, and `ColorToHex()` to convert ASS colors back to hex format.
### Style Settings
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `Bold` | bool | true | Bold text |
| `Italic` | bool | false | Italic text |
| `Underline` | bool | false | Underlined text |
| `StrikeOut` | bool | false | Strikethrough text |
### Scaling and Spacing
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `ScaleX` | int | 100 | Horizontal scale (%) |
| `ScaleY` | int | 100 | Vertical scale (%) |
| `Spacing` | int | 0 | Character spacing |
| `Angle` | int | 0 | Text rotation angle |
### Border and Shadow
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `BorderStyle` | int | 1 | Border style (1=outline, 3=box) |
| `Outline` | int | 4 | Outline width |
| `Shadow` | int | 0 | Shadow width |
### Shadow Effects
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `ShadowEnabled` | bool | false | Enable/disable shadow effect |
| `ShadowX` | int | 3 | Horizontal shadow offset (-50 to 50) |
| `ShadowY` | int | 8 | Vertical shadow offset (-50 to 50) |
> **Shadow Direction**: Positive values move the shadow right (X) and down (Y). Negative values move left and up. The default values (X=3, Y=8) create a diagonal shadow that's more downward than sideways.
### Alignment
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `Alignment` | int | 2 | Text alignment (1=bottom-left, 2=bottom-center, 3=bottom-right, etc.) |
### Margins
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `MarginL` | int | 40 | Left margin |
| `MarginR` | int | 40 | Right margin |
| `MarginV` | int | 120 | Vertical margin |
### Timing Settings
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `StartDelay` | int | 0 | Delay before starting subtitles (milliseconds). In structured JSON mode: added to all word start/end times (e.g. 1500 = 1.5 s shift). |
| `PerWordDelay` | int | 300 | Delay between words (milliseconds). **Ignored** when using structured JSON (`GenerateASSFromStructured`). |
| `FadeDuration` | int | 140 | Fade duration (milliseconds) |
| `LineHold` | int | 2000 | Line hold duration (milliseconds). Not applied in structured JSON mode. |
| `LineGap` | int | 0 | Gap between lines (milliseconds). Not applied in structured JSON mode. |
### Features
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `Karaoke` | bool | false | Enable karaoke mode |
### Encoding
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `Encoding` | int | 1 | Text encoding (1=ANSI, 0=Unicode) |
## CLI Reference
> **Note**: All default values are dynamically generated from the `DefaultConfig()` function. Use `--help` to see the current default values.
### Input Options (mutually exclusive)
- `--lines string`: Input lines separated by `|` (e.g., 'Hello world|Second line')
- `--file string`: Input file with lines (one per line)
- `--json string`: Structured JSON: `{"segments":[{"words":[{"word":"...","start":0.1,"end":0.5},...]},...]}`. `start`/`end` in seconds. Extra fields ignored.
- `--json-file string`: Path to a structured JSON file (same format as `--json`).
With `--json` or `--json-file`: `--delay` (PerWordDelay) is ignored; `--start-delay` shifts all word timings (e.g. `--start-delay 1500` adds 1.5 s to every start/end).
### Output Options
- `--out string`: Output ASS file path (default: output.ass)
### Video Settings
- `--width int`: Video width (default: 1080)
- `--height int`: Video height (default: 1920)
### Font Settings
- `--font string`: Font name (default: Arial)
- `--fontsize int`: Font size (default: 64)
### Colors (hex format)
- `--color string`: Primary text color (default: #FFFFFF)
- `--secondary string`: Secondary color for karaoke (default: #FFFF00)
- `--outline string`: Outline color (default: #000000)
- `--background string`: Background color in ASS format (default: #64000000)
### Style Settings
- `--bold`: Bold text (default: true)
- `--italic`: Italic text (default: false)
- `--underline`: Underline text (default: false)
- `--strikeout`: Strikeout text (default: false)
### Scaling
- `--scalex int`: Horizontal scale % (default: 100)
- `--scaley int`: Vertical scale % (default: 100)
- `--spacing int`: Character spacing (default: 0)
- `--angle int`: Text angle (default: 0)
### Border and Shadow
- `--borderstyle int`: Border style (default: 1)
- `--outlinewidth int`: Outline width (default: 4)
- `--shadow int`: Shadow width (default: 0)
### Shadow Effects
- `--shadow-enabled`: Enable shadow effect (default: false)
- `--shadow-x int`: Horizontal shadow offset (default: 3)
- `--shadow-y int`: Vertical shadow offset (default: 8)
### Alignment
- `--alignment int`: Text alignment (1=bottom-left, 2=bottom-center, 3=bottom-right, etc.) (default: 2)
### Margins
- `--marginl int`: Left margin (default: 40)
- `--marginr int`: Right margin (default: 40)
- `--marginv int`: Vertical margin (default: 120)
### Timing
- `--start-delay int`: Delay before starting subtitles in ms (default: 0)
- `--delay int`: Delay between words in ms (default: 300)
- `--fade int`: Fade duration in ms (default: 140)
- `--hold int`: Line hold duration in ms (default: 2000)
- `--gap int`: Gap between lines in ms (default: 0)
### Features
- `--karaoke`: Enable karaoke mode (default: false)
### Other
- `--help`: Show help with current default values
## Examples
### Basic Example
```bash
wordsubgen --lines "Hello world|This is a test" --out subtitle.ass
```
### Custom Styling
```bash
wordsubgen --lines "Hello world" --fontsize 48 --color "#FF0000" --bold
```
### Karaoke Mode
```bash
wordsubgen --lines "Hello world" --karaoke --delay 500
```
### From File
```bash
echo -e "First line\nSecond line\nThird line" > input.txt
wordsubgen --file input.txt --out subtitle.ass
```
### Shadow Effects
```bash
# Diagonal shadow (more downward than sideways)
wordsubgen --lines "Hello world" --shadow-enabled --shadow-x 3 --shadow-y 8
# Horizontal shadow (more sideways)
wordsubgen --lines "Hello world" --shadow-enabled --shadow-x 8 --shadow-y 3
# No shadow (explicitly disabled)
wordsubgen --lines "Hello world" --shadow-enabled=false
```
### Start Delay
```bash
# Delay subtitles by 100ms
wordsubgen --lines "Hello world" --start-delay 100
# Delay subtitles by 1 second
wordsubgen --lines "Hello world" --start-delay 1000
# Delay subtitles by 2.5 seconds
wordsubgen --lines "Hello world" --start-delay 2500
```
### Structured JSON
```bash
# From file with 1.5 s global delay
wordsubgen --json-file words.json --start-delay 1500 --out subtitle.ass
# Inline JSON (one phrase)
wordsubgen --json '{"segments":[{"words":[{"word":"Hello","start":0,"end":0.5},{"word":"world","start":0.6,"end":1.2}]}]}' --out subtitle.ass
```
### Quick Demo with Makefile
```bash
# Generate sample files
make run-cli # Basic sample
make run-cli-custom # Custom styling
make run-cli-karaoke # Karaoke mode
```
## Development
### Project Structure
```
wordsubgen/
├── cmd/wordsubgen/
│ ├── main.go # CLI application entry point
│ └── wordsubgen # Built binary (after make build)
├── assets/ # Assets
│ └── example.gif # Demo animation
├── config.go # Configuration and validation
├── config_test.go # Configuration tests
├── generator.go # ASS generation logic
├── generator_test.go # Generator tests
├── integration_test.go # Integration tests
├── logger.go # Flexible logging interface
├── logger_test.go # Logger tests
├── writer.go # File I/O operations
├── writer_test.go # Writer tests
├── go.mod # Go module definition
├── go.sum # Go module checksums
├── Makefile # Build automation
├── README.md # Documentation
├── LICENSE # MIT License
├── input.txt # Sample input file
├── subs.exemple.ass # Example subtitle file
└── subtitle.ass # Generated subtitle file
```
### Building
```bash
make build # Build CLI binary
make install # Install CLI to GOPATH/bin
make clean # Clean build artifacts
```
### Testing
```bash
make test # Run all tests
make test-coverage # Run tests with coverage report
make check # Run fmt, lint, and test
```
### Running Examples
```bash
make run-cli # Run CLI with sample input
make run-cli-custom # Run CLI with custom styling
make run-cli-karaoke # Run CLI with karaoke mode
```
### Code Quality
```bash
make fmt # Format code
make lint # Lint code
make deps # Install dependencies
```
### Development Workflow
```bash
make dev # Complete dev workflow: deps, fmt, lint, test, build
make release # Build release binaries for multiple platforms
```
### Available Make Targets
```bash
make help # Show all available targets
```
### Testing
The project includes test coverage:
- **Unit tests**: Individual component testing (`*_test.go` files)
Run tests with:
```bash
make test # Run all tests
make test-coverage # Generate coverage report
make ci-test # Run all tests for ci
```
## Usage Examples
### Basic Library Usage with Custom Logger
```go
package main
import (
"log"
"github.com/kperreau/wordsubgen"
)
func main() {
// Create configuration with custom logger
cfg := wordsubgen.DefaultConfig()
cfg.Logger = wordsubgen.NewDefaultLogger() // or your custom logger
// Generate subtitles
lines := []string{"Hello world", "This is a test"}
content, err := wordsubgen.GenerateASS(cfg, lines)
if err != nil {
log.Fatal(err)
}
// Write to file
err = wordsubgen.WriteASS("output.ass", content, cfg.Logger)
if err != nil {
log.Fatal(err)
}
}
```
### CLI with Help
```bash
# Show help with current default values
wordsubgen --help
```
This will display all available options with their current default values, which are dynamically generated from the `DefaultConfig()` function.
## ASS Format Details
The generated ASS files follow the Advanced SubStation Alpha v4.00+ format with:
- **Script Info**: Basic metadata and video resolution
- **V4+ Styles**: Font, color, and styling definitions
- **Events**: Dialogue lines with timing and effects
### Word Fade Effect
Each word uses the `\alpha` tag for fade effects:
```
{\alpha&HFF&\t(start,end,\alpha&H00&)}word
```
Where:
- `\alpha&HFF&`: Start invisible (fully transparent)
- `\t(start,end,\alpha&H00&)`: Transition to visible over time
- `start,end`: Timing in milliseconds
### Karaoke Effect
When karaoke mode is enabled, words also include `\k` tags:
```
{\k100}word
```
Where `100` is the duration in centiseconds.
### Shadow Effect
When shadow is enabled, text includes `\xshad` and `\yshad` tags:
```
{\xshad3\yshad8}word
```
Where:
- `\xshad3`: Horizontal shadow offset of 3 pixels
- `\yshad8`: Vertical shadow offset of 8 pixels
This creates a diagonal shadow that's more downward than sideways, as requested in the original specification.
## Key Benefits
- **🎯 Simple Structure**: Clean, flat project structure with all source files at the root
- **🔌 Flexible Logging**: Injectable logger interface supporting multiple logging libraries
- **⚡ Zero Dependencies**: Core library has no forced external dependencies
- **📝 Structured Logging**: Built-in support for structured logging with key-value fields
- **🛠️ Easy Integration**: Simple API that works with any Go project
- **🎨 Rich Configuration**: Extensive customization options for fonts, colors, timing, and styling
- **📊 Performance**: Optimized for performance with zero-allocation logging options
- **🔧 Developer Friendly**: Comprehensive CLI with verbose logging and helpful error messages
- **🔄 Consistent Defaults**: CLI flags automatically use configuration defaults, ensuring consistency
- **🎨 Color Conversion**: Built-in functions to convert between hex and ASS color formats
- **🌍 Encoding Support**: Built-in support for different text encodings (ANSI/Unicode)
## License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
## Contributing
We welcome contributions! Please follow these steps:
1. Fork the repository
2. Create a feature branch (`git checkout -b feat/amazing-feature`)
3. Make your changes
4. Add tests if applicable
5. Run the test suite (`make test`)
6. Commit your changes (`git commit -m 'feat: add some amazing feature'`)
7. Push to the branch (`git push origin feat/amazing-feature`)
8. Open a Pull Request
### Development Guidelines
- Follow Go best practices and conventions
- Add tests for new functionality
- Update documentation as needed
- Use the provided Makefile targets for consistency
- Ensure all tests pass before submitting