Metadata-Version: 2.4
Name: kfn2mp4
Version: 0.1.1
Summary: Convert KaraFun KFN karaoke files to MP4 with synchronized lyrics
Author-email: Łukasz Nowak <lukasz@nowak.io>
License-Expression: GPL-3.0-or-later
Project-URL: Homepage, https://github.com/lukenowak/kfn2mp4
Project-URL: Repository, https://github.com/lukenowak/kfn2mp4
Project-URL: Issues, https://github.com/lukenowak/kfn2mp4/issues
Project-URL: Changelog, https://github.com/lukenowak/kfn2mp4/blob/main/CHANGELOG.md
Keywords: karaoke,kfn,karafun,mp4,ffmpeg,subtitles,ass,lyrics
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: End Users/Desktop
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Multimedia :: Video
Classifier: Topic :: Multimedia :: Sound/Audio
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: cryptography>=3.4
Requires-Dist: tqdm>=4
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Requires-Dist: pytest-cov>=4; extra == "dev"
Dynamic: license-file

<!-- SPDX-License-Identifier: GPL-3.0-or-later -->
# kfn2mp4

[![CI](https://github.com/lukenowak/kfn2mp4/actions/workflows/ci.yml/badge.svg)](https://github.com/lukenowak/kfn2mp4/actions/workflows/ci.yml)
[![Release](https://img.shields.io/github/v/release/lukenowak/kfn2mp4?include_prereleases)](https://github.com/lukenowak/kfn2mp4/releases)
[![License: GPL-3.0-or-later](https://img.shields.io/github/license/lukenowak/kfn2mp4)](https://github.com/lukenowak/kfn2mp4/blob/main/LICENSE)
![Python 3.9+](https://img.shields.io/badge/python-3.9%2B-blue)
![Coverage ≥85%](https://img.shields.io/badge/coverage-%E2%89%A585%25%20enforced-brightgreen)

Convert **KFN** karaoke files (the format used by the KaraFun player) into standalone **MP4**
videos with synchronized, highlighted lyrics.

It parses the KFN container, reads lyric timing and background from `Song.ini`, and renders
with `ffmpeg` — a word-by-word wipe over the song's background and audio. CD+Graphics karaoke
(`.cdg` + audio) converts too.

## Why such tool

`.kfn` is a widely used file format for creating karaoke versions of songs, but it is only well
supported by one application — KaraFun. The desktop player that plays local KFN files, KaraFun
Player 2, is [officially no longer updated](https://www.karafun.com/help/features_102.html), and
the current KaraFun offering is built around its subscription streaming catalogue rather than
local files.

There is no open format for karaoke songs — and more and more creators simply publish videos
instead.

So rather than relying on the unknown future of KaraFun's local-file support, it seems better to
convert KFN files to plain videos — MP4 — that play anywhere. And even better: to convert a whole
library at once.

That's why this is here.

## Requirements

- **Python 3.9+** (not needed for the standalone Windows `.exe`)
- **ffmpeg**/**ffprobe** with **libass**, **libx264** and **AAC** — verified up front
  (`kfn2mp4 --check`); found on `PATH`, via `KFN2MP4_FFMPEG` (a directory or the ffmpeg
  binary path), or next to the Windows `.exe`
- **fluidsynth** + a General-MIDI soundfont — optional, only for **MIDI-only** files
  (`KFN2MP4_SOUNDFONT` overrides the auto-detected soundfont)

## Install

```bash
pip install kfn2mp4            # from PyPI
```

Windows users can instead grab the standalone `kfn2mp4.exe` from the
[releases page](https://github.com/lukenowak/kfn2mp4/releases) (no Python needed;
put `ffmpeg.exe` on `PATH` or next to the exe). From a checkout: `pip install -e .`

### External tools on Windows

```powershell
winget install Gyan.FFmpeg     # full build: includes libass, libx264, AAC
```

or download a build from [gyan.dev](https://www.gyan.dev/ffmpeg/builds/) and drop
`ffmpeg.exe` + `ffprobe.exe` next to `kfn2mp4.exe`. For MIDI-only files, install
[FluidSynth](https://github.com/FluidSynth/fluidsynth/releases) and point
`KFN2MP4_SOUNDFONT` at a General-MIDI `.sf2` soundfont. Verify with `kfn2mp4 --check`.

## Usage

```bash
kfn2mp4 "song.kfn" -o song.mp4                 # convert to a specific file
kfn2mp4 "song.kfn" -O out/                     # write "<song name>.mp4" into out/
kfn2mp4 *.kfn -O out/                          # convert many; re-runs skip finished files
kfn2mp4 *.kfn -O out/ --overwrite              # force re-render
kfn2mp4 "song.kfn" --extract-only out/         # just unpack audio/images/Song.ini
kfn2mp4 "song.kfn" -O out/ --file-colors       # use the KFN's own lyric colours
kfn2mp4 "song.kfn" -O out/ --duration 40 -v    # quick verbose preview (first 40s)

kfn2mp4 "song.cdg" -o song.mp4                 # CD+Graphics: pairs the sibling song.mp3
kfn2mp4 *.cdg -O out/                           # convert a folder of .cdg+.mp3 pairs
kfn2mp4 "song.cdg" --audio backing.mp3 -o s.mp4 # override the paired audio track

kfn2mp4 "song.kfn"                             # NO output given -> report the file
kfn2mp4 *.kfn                                  # ...report many files (like ffprobe)
kfn2mp4 "song.kfn" --json                      # ...as machine-readable JSON

kfn2mp4 *.kfn -o "{artist} - {title}.mp4"      # name outputs from KFN metadata
kfn2mp4 "song.kfn" -O out/ --sung-color "#00FF00"   # custom lyric colours
kfn2mp4 "song.kfn" -O out/ --logo brand.png    # watermark, bottom-left
kfn2mp4 "song.kfn" -O out/ --slots 3           # 3 on-screen lines (default: auto)
kfn2mp4 --check                                # verify ffmpeg & features (doctor)
kfn2mp4 "broken.kfn" --report                  # paste-ready diagnostic for a bug
```

Encoding is intentionally opinionated: a fixed **1280×720** canvas (background fitted, black
bars as needed), **crf 28**, **25 fps**, `medium` x264 preset. Lyrics use a **brand palette**
over a **translucent box** (colour flags and `--no-text-box` override), in **2–3 slots**
auto-picked from the song's pace; **duets** get an accent colour for the second voice. The MP4
is tagged with the song's title/artist/album/year and a converted-by marker (`comment` tag):

```bash
ffprobe -v error -show_entries format_tags -of default=nw=1 song.mp4
```

Skipping already-converted files makes a re-run resume a batch cheaply. A **progress bar** shows
per-file and batch progress on a terminal. Stopping a run (Ctrl-C or `kill`) removes the
unfinished output and temp files. Globs like `*.kfn` work even on Windows shells.

## How it works

1. **Parse** the KFN container — `KFNB` binary (AES-128-ECB entries), old `PK` ZIP, or legacy
   `KFN3` (chunked deflate).
2. **Read** `Song.ini` into a syllable-level lyric timeline + style + background references.
3. **Generate** an `.ass` subtitle with karaoke wipe (`\kf`) highlighting.
4. **Render** background + lyrics + audio to MP4 with ffmpeg.

## Supported formats

| Signature | Format | Support |
|-----------|--------|---------|
| `KFNB` | New KaraFun binary container (AES-128-ECB) | full |
| `PK` | Old KaraFun (plain ZIP) | full |
| `KFN3` | Legacy KaraFun (chunked deflate) | files with a readable body; see below |
| `.cdg` | CD+Graphics stream + sibling audio (`.mp3`/`.wav`/…) | full (graphics decoded by ffmpeg) |
| MIDI-only KFN | `.mid`/`.kar` track, no MP3 | synthesized via fluidsynth + GM soundfont |

The KFNB container specification is in **[`docs/kfn-format.md`](https://github.com/lukenowak/kfn2mp4/blob/main/docs/kfn-format.md)**.

### KFN3 (legacy container)

Read natively; files with an encrypted first chunk cannot be fully decoded (metadata is still
reported, `--extract-only` salvages what validates). Details:
[`docs/kfn3-format.md`](https://github.com/lukenowak/kfn2mp4/blob/main/docs/kfn3-format.md).

## Status

Faithful-ish fidelity: image / video / slideshow background, embedded font, auto 2–3 line slot
layout, smooth wipe highlighting, duet second voice, MIDI-only synthesis, CD+Graphics. Not yet:
full KaraFun effect layers (trajectories, sprites, Milkdrop transitions) and encrypted-head
KFN3 (above) — see the [open issues](https://github.com/lukenowak/kfn2mp4/issues).

## Development

```bash
pip install -e .[dev]
pytest
```

The suite is built entirely from synthetic in-memory fixtures — no sample library is required, and
no real (copyrighted) `.kfn` file is ever needed to run or pass the tests.

## Legal & disclaimer

This project is an **independent, unofficial** tool for **format interoperability**. It is not
affiliated with, endorsed by, or connected to KaraFun or Recisio; "KaraFun" is a trademark of its
respective owners.

`kfn2mp4` only reads a container format so you can convert **files you already have** for personal
use. It ships **no** copyrighted content. The musical recordings, lyrics, and artwork inside a KFN
file remain the property of their rights holders — do not use this tool to redistribute copyrighted
material.

## License

Released under the **GNU General Public License v3.0 or later** — see [`LICENSE`](https://github.com/lukenowak/kfn2mp4/blob/main/LICENSE).

## Acknowledgements

Reverse-engineering of the KFN formats built on prior open work:

- **UlduzSoft blog** — the [reverse-engineering series](https://www.ulduzsoft.com/2012/10/reverse-engineering-the-karafun-file-format-part-1-the-header/)
  (header, directory, `Song.ini`, encryption): the authoritative narrative.
- **WinslowJosiah** — [`unlock_kfn` gist](https://gist.github.com/WinslowJosiah/2bffe00b5abf1817a512988c87903225)
  (Python): the cleanest read/decrypt reference (AES-128-ECB with the `FLID` key).
- **gyunaev/spivak** — [Spivak](https://github.com/gyunaev/spivak) (C++/Qt): a production
  karaoke player; `lyricsparser_kfn.cpp` is the reference for the Sync/Text merge + tokenization.
- **MadLord80/KFN_Viewer** — [KFN_Viewer](https://github.com/MadLord80/KFN_Viewer) (C#): a
  GUI viewer/extractor with the richest field table and an extended-LRC export.
- **mazsibazsi/kfn-rs** — [kfn-rs](https://github.com/mazsibazsi/kfn-rs) (Rust): the
  best-modeled `[Eff#]` effects/animation layer.

[Croonify](https://croonify.com/) convinced the author that MP4 is the proper format for karaoke.

This project was written with the help of **AI tools** — the code, tests, and documentation were
developed with Anthropic's Claude (Claude Code).
