# Adding a converter
A converter is one class implementing `IConverter` (`src/Filee.Core/Conversion/IConverter.cs`).
The route planner combines converters automatically, so a new edge can unlock many conversions at once
(for example, adding `epub → pdf` also gives `epub → png` through PDFium).
## 1. New format? Register it
If the format is new, add one line to `FormatRegistry.Known` (`src/Filee.Core/Formats/FormatRegistry.cs`):
```csharp
new("epub", "EPUB", FormatCategory.Document, ["epub"]),
```
## 2. Write the converter
Create `src/Filee.Engines//Converter.cs`:
```csharp
// EPUB → PDF with the (hypothetical) SuperEpub library (MIT).
using Filee.Core.Conversion;
namespace Filee.Engines.Documents;
public sealed class SuperEpubConverter : IConverter
{
public string Id => "superepub"; // stable, used in settings
public string DisplayName => "SuperEpub";
public int MaxParallelism => 0; // 0 = one per CPU core; 1 for non thread-safe engines
public IReadOnlyList Edges { get; } =
[
new("epub", "pdf"), // cost 10 = normal; higher for lossy/slow paths
];
public EngineStatus GetStatus() => EngineStatus.Available();
public Task> ConvertAsync(ConversionStep step, IProgress? progress, CancellationToken ct) =>
Task.Run>(() =>
{
var output = step.Output.Allocate("pdf"); // null = skip (file exists + "skip" policy)
if (output is null)
return [];
SuperEpub.Convert(step.InputPath, output); // honour ct in long loops
progress?.Report(1);
return [output];
}, ct);
}
```
Rules:
- Never modify `step.InputPath`.
- Write outputs only to paths from `step.Output.Allocate(...)`. For several outputs from one input
(pages), pass a suffix: `Allocate("png", $"_p{page}")`.
- Scratch files go to `step.WorkDirectory` (deleted after the job).
- External programs: use `ProcessRunner.RunAsync` (timeout + cancellation + no shell) and find them with
`EngineEnvironment.FindBundled("")` only. Filee never uses programs installed on the system (nothing from
`PATH`, no other application's install folder): a program is either bundled with the installer (small, pinned in
`engines.json` and fetched by `build/fetch-engines.ps1`) or an optional download the user installs in Settings →
Engines (an `EnginePackage` in `EngineDownloads.Packages`).
- Return `EngineStatus.Unavailable("engine.reason.not_installed")` when a dependency is missing; the UI then greys
out affected slices with a reason and names the package to download. Report a version with
`EngineStatus.Available(version: …)` (see `EngineVersions`) and add an `engine..description` text to the three
`i18n/*.json` files: both are shown in Settings → Engines.
## 3. Register it
Add one line to `EngineRegistry.CreateAll` (`src/Filee.Engines/EngineRegistry.cs`).
If it should be preferred over existing engines by default, add its id to `AppSettings.EnginePriority`.
## 4. Test it
Add a test to `tests/Filee.Engines.Tests`. Generate the input files in the test (see `EngineFixture`) so the test
runs everywhere without sample documents, and skip when the engine is not available:
```csharp
[Fact]
public async Task Epub_to_pdf()
{
var job = await fx.ConvertAsync([MakeEpub()], new Preset { TargetFormat = "pdf" });
Assert.Equal(JobState.Completed, job.State);
}
```
## 5. Ship it (optional)
Command line tools are pinned in `src/Filee.Engines/Infrastructure/engines.json` (URL, SHA-256, size, archive kind),
which both `build/fetch-engines.ps1` and the app read. Small tools can be bundled with the installer (add the id to
the `fetch-engines.ps1 -Only` list in `.github/workflows/release.yml`); large ones become an optional download by
adding an `EnginePackage` to `EngineDownloads.Packages` plus its name and description in the three `i18n/*.json`
files. Either way, add a line to `THIRD-PARTY-NOTICES.md`.