FIRE is an API-first .NET 10 library for metadata-driven file reorganization.
The FIRE project is the core of this repository and provides the full
catalog pipeline (collect → generate → execute), metadata extraction,
localization, and progress/state events for direct integration into UI or
automation applications.
FIRE.Console is intentionally a lightweight adapter for API testing and
quick onboarding only. It exists to validate and demonstrate the API surface,
while product UI work is expected to be implemented later as a separate layer
that consumes the FIRE API directly.
- Features
- API-First Focus
- Project Structure
- Requirements
- Installation
- Quick Start
- Usage
- API Integration for UI/Host Apps
- Configuration Reference
- Template Placeholders
- Keyword Selection
- Metadata Sources
- Examples
- Building from Source
- Documentation
- Third-Party Credits
- License
- Metadata-driven file sorting — JPEG, RAW, video, and any other file type supported by ExifTool
- Three-step API pipeline —
collect→generate→execute - SQLite persistence — inspect and audit every decision before committing
- Flexible templates — arbitrary directory hierarchy and filename patterns
using
{Keyword}placeholders - Multi-keyword fallback — define ordered lists of EXIF tags; FIRE picks the lowest/highest value
- Sidecar file support —
.xmp,.pp3, or any companion extension follows its primary file automatically - String replacements — normalize camera model names or other metadata values globally
- Culture-aware — date formatting respects the
--cultureflag - Localized API messages — resource-based API messages with English fallback (
en-US) and translations forde-DE,fr-FR, andfil-PH - UI-ready API progress model —
FIRECatalogexposes progress/state events and properties for direct UI integration (WPF/WinUI/etc.)
FIRE (class library) is the product core and receives priority for
implementation and completion.
FIRE.Console is intentionally limited to these goals:
- API test runner for development workflows
- quick start for first-time users
- reproducible command examples for documentation
A future UI application should be implemented as a dedicated consumer of the
FIRE API. It should not depend on the console process as an intermediate
runtime layer.
FIRE/
├── .github/
│ └── copilot-instructions.md
├── ConfigurationFiles/
│ └── Configuration.yaml # Example configuration
├── docs/
│ ├── Doxyfile # Doxygen 1.17.0 configuration
│ └── mainpage.dox # Doxygen main page
├── FIRE/
│ ├── FIRE.csproj
│ ├── FIRECatalog.cs # Core orchestration engine
│ ├── FIRECatalogProgress.cs # Progress/state event contracts for UI integration
│ ├── FIREConfigration.cs # YAML configuration model
│ ├── FIREDatabase.cs # SQLite database abstraction
│ ├── Localization/
│ │ └── ApiLocalizer.cs # Resource-based API localization helper
│ └── Resources/
│ └── ApiStrings*.resx # API message translations (EN/DE/FR/fil-PH)
├── FIRE.Console/
│ ├── FIRE.Console.csproj
│ └── Program.cs # CLI test/demo adapter for the FIRE API
├── FIRE.Tests/
│ ├── FIRE.Tests.csproj
│ └── UnitTest1.cs # Initial unit tests for core logic
├── FIRE.slnx
├── LICENSE
└── README.md
| Component | Version |
|---|---|
| .NET SDK | 10.0 or later |
| ExifTool | bundled via SharpExifTool NuGet package |
| SQLite | bundled via Microsoft.EntityFrameworkCore.Sqlite |
| OS | Windows (NTFS file-ID APIs are used for duplicate detection) |
git clone https://github.com/Gemelon/FIRE.git
cd FIRE
dotnet build -c ReleaseThe console binary is then located at:
FIRE.Console\bin\Release\net10.0\FIRE.Console.exe
- Copy
ConfigurationFiles/Configuration.yamland adapt the paths. - Run the three pipeline steps in order:
# Step 1 — scan source directories and extract metadata
FIRE.Console collect --config Configuration.yaml --culture de-DE
# Step 2 — compute target paths from templates
FIRE.Console generate --config Configuration.yaml --culture de-DE
# Step 3 — copy/move/link files to their new locations
FIRE.Console execute --config Configuration.yaml --culture de-DEOptional metadata inspection command:
FIRE.Console inspect --config Configuration.yaml --culture en-US --file "D:\Photos\IMG_1234.jpg"FIRE.Console is a CLI adapter for testing and fast onboarding. UI applications should reference the FIRE library directly and consume FIRECatalog progress/state events instead of invoking the console process.
FIRE.Console <command> --config <path> --culture <culture> [options]
| Argument | Short | Description |
|---|---|---|
--config |
-c |
Path to the YAML configuration file (required) |
--culture |
-l |
Culture code for UI language and date formatting (required). Recommended: en-US, de-DE, fr-FR, fil-PH. |
--no-wrap |
- | Disable line wrapping and clip long console lines. Default is line wrapping enabled. |
| Command | Description |
|---|---|
collect |
Scans source directories and writes metadata to the database |
generate |
Computes target paths and file names from configured templates |
execute |
Applies the file operations (Copy / Move / Link) |
inspect |
Inspects one file and writes metadata to a Markdown report |
| Argument | Short | Description |
|---|---|---|
--file |
-f |
Source file to inspect (required for inspect) |
--output |
-o |
Optional path for the generated Markdown report |
--copy-path |
- | Copy the generated report path to the clipboard |
Reference only the FIRE class library in your host project (WPF/WinUI/MAUI/service):
- Project reference (same solution):
FIRE/FIRE.csproj - Binary reference (separate solution):
FIRE.dllbuilt from theFIREproject
Do not use FIRE.Console as integration layer. It is only a CLI adapter over the same API.
| Type | Purpose | Central members |
|---|---|---|
FIREConfigration |
Load and validate YAML configuration | Load(...), Parse(...), EnsureSupportedConfigurationVersion() |
FIREDatabase |
SQLite-backed pipeline state and records | constructor FIREDatabase(dbPath), Count, AsReadOnlyList() |
FIRECatalog |
Main orchestration engine | CollectFiles(...), GenerateTargetPaths(...), ExecuteFileOperations(...), ProgressChanged |
FIRECatalogProgressEventArgs |
Progress payload for UI binding | Stage, Level, Message, CurrentFilePath, ProcessedCount, TotalCount |
using System.Globalization;
using FIRE;
var config = FIREConfigration.Load(configPath);
config.EnsureSupportedConfigurationVersion();
var dbPath = Path.Combine(config.DataBasePath, config.DataBaseFileName);
using var database = new FIREDatabase(dbPath);
using var catalog = new FIRECatalog(config, database)
{
Culture = CultureInfo.GetCultureInfo("de-DE")
};
catalog.ProgressChanged += (_, e) =>
{
// UI: progress bar, current file text, warnings/errors
// e.Stage, e.Level, e.Message, e.ProcessedCount, e.TotalCount
};
catalog.CollectFiles();
catalog.GenerateTargetPaths();
catalog.ExecuteFileOperations();| Property | Meaning |
|---|---|
Culture |
Controls localized API messages and date formatting behavior |
FallbackDateTime |
Replacement value for implausible/unparseable date metadata |
CurrentStage |
Active stage (Collect, Generate, Execute, Inspect) |
CurrentFilePath |
File currently processed |
ProcessedFileCount / TotalFileCount |
Current progress counters |
LastCollectedSourcePaths |
Snapshot of files touched in last collect stage |
| Method | Use case |
|---|---|
ClearDatabase() |
Reset state before a clean collect run |
DiagnoseGeneration(sourcePath) |
Create detailed generation diagnosis report |
GetAllAvailableMetadata(filePath) |
Display metadata explorer in UI |
WriteMetadataToMarkdown(filePath, outputPath?) |
Export metadata report for users/support |
LogCancelled() |
Log user cancellation in long-running operations |
Run pipeline methods (CollectFiles, GenerateTargetPaths, ExecuteFileOperations) on a background task and marshal ProgressChanged updates to the UI thread.
The configuration is a single YAML file. Every key is case-sensitive.
ConfigurationVersion: 1.20 # Must be 1.20
FilesRootPath: # One or more source directories
- D:\Photos\Import
- D:\Videos\Import
DataBasePath: D:\Photos # Directory for the SQLite database file
DataBaseFileName: FIRE.db # Database filename
Action: Copy # Default action: Copy | Move | Link
MediaRootPath: D:\Photos\Sorted # Convenience root used in templates
# Global sorting template (overridden per extension)
SortingPatern: "{MediaRootPath}\\{MetaCreationTime.Year}\\{Make}\\{Model}"
FileNamePatern: "{MetaCreationTime.Year}-{MetaCreationTime.Month}-{MetaCreationTime.Day}_{FileName}"
# Normalize metadata values (e.g. camera model names)
StringReplacements:
SM-S938B: SM-S938B Galaxy S25 Ultra
FileExtensions:
.jpg:
FileType: Image
FileClass: RegularFile
Action: Copy
SortingPatern: "{MediaRootPath}\\{MetaCreationTime.Year}\\{MetaCreationTime.Month}\\{Make}\\{Model}"
FileNamePatern: "{MetaCreationTime.Year}-{MetaCreationTime.Month}-{MetaCreationTime.Day}_{FileName.Noext}.JPG"
SidecarFileExtensions:
- .xmp
AvailableKeyWords:
Make:
DataType: STRING
Source: EXIFTOOL
KeyWords:
- IFD0:Make
- DJI:Make
MetaCreationTime:
DataType: DATETIME
Source: EXIFTOOL
ValAttribute: LOWEST
KeyWords:
- ExifIFD:DateTimeOriginal
- IFD0:DateTime| Property | Type | Description |
|---|---|---|
FileType |
string | Logical type label (e.g. Image, Video) |
FileClass |
string | RegularFile or SidecarFile |
Action |
string | Copy, Move, or Link |
RootPath |
string | Override root path for this extension |
SortingPatern |
string | Target directory template |
FileNamePatern |
string | Target filename template |
SidecarFileExtensions |
list | Extensions that follow this file type |
AvailableKeyWords |
map | Keyword definitions (see below) |
| Property | Type | Default | Description |
|---|---|---|---|
DataType |
string | STRING |
STRING, INT, INTEGER, DATETIME, DATE, TIME |
Source |
string | FILEINFO |
FILEINFO or EXIFTOOL |
ValAttribute |
string | LOWEST |
LOWEST or HIGHEST |
Default |
string | — | Fallback value when no configured keyword is found. For DATETIME, supports a date string (e.g. 2024-12-31 00:00:00) or NOW. |
KeyWords |
list | — | Ordered list of metadata tag names to try |
Use {KeywordName} in SortingPatern and FileNamePatern.
Sub-properties are accessed with a dot.
| Placeholder | Example | Description |
|---|---|---|
{FileName} |
IMG_1234.jpg |
Original filename including extension |
{FileName.Noext} |
IMG_1234 |
Filename without extension |
{Make} |
Apple |
Camera manufacturer |
{Model} |
iPhone 15 Pro |
Camera model |
{MetaCreationTime.Year} |
2026 |
Four-digit year |
{MetaCreationTime.Month} |
07 |
Zero-padded month |
{MetaCreationTime.Day} |
04 |
Zero-padded day |
{MediaRootPath} |
D:\Photos\Sorted |
Value of MediaRootPath |
{Counter:D3} |
001, 002, 003 |
Persistent running number per target path with Dx formatting; only active when the placeholder is used in the template |
When a keyword lists multiple EXIF tags, FIRE queries each tag and picks one
value based on ValAttribute:
| ValAttribute | Behaviour |
|---|---|
LOWEST (default) |
Smallest numeric value or earliest date/time |
HIGHEST |
Largest numeric value or latest date/time |
If none of the listed tags yields a value, NA is written and a [WARN]
message is printed to the console.
| Source | Description |
|---|---|
FILEINFO |
File system timestamps: CreationTime, ModificationTime, AccessTime |
EXIFTOOL |
Any tag readable by ExifTool (EXIF, XMP, IPTC, QuickTime, ID3, …) |
FIRE automatically detects and processes sidecar files (e.g., .xmp, .pp3) alongside their primary files.
-
Configuration: Define sidecar extensions in your primary file's configuration:
FileExtensions: .jpg: SidecarFileExtensions: - .xmp - .pp3
-
Collection Phase: When a primary file is processed, FIRE searches for configured sidecar files in the same directory with the same base name.
-
Classification: Each file record is marked as either:
RegularFile(0): Primary filesSidecarFile(1): Sidecar files
-
Path Generation: Sidecar files automatically inherit the target directory and base filename from their primary file, preserving only their own extension. If the primary template uses
{Counter...}, the same persistent sequence is retained for the generated target path. -
Execution: Sidecar files are copied/moved alongside their primary files using the global action setting.
Source structure:
D:\Import\
IMG_1234.jpg
IMG_1234.xmp
After execution:
D:\Sorted\2026\07\Apple\
2026-07-04_001.jpg
2026-07-04_001.xmp
SortingPatern: "{MediaRootPath}\\{MetaCreationTime.Year}\\{MetaCreationTime.Month}\\{Make} {Model}"
FileNamePatern: "{MetaCreationTime.Year}{MetaCreationTime.Month}{MetaCreationTime.Day}_{FileName}"Result:
D:\Photos\Sorted\
2026\
07\
Apple iPhone 15 Pro\
20260704_IMG_1234.jpg
FileExtensions:
.jpg:
Action: Move
SidecarFileExtensions:
- .xmp
- .dngStringReplacements:
SM-S938B: Galaxy S25 Ultra # exact string replacement
DJI*: DJI # wildcard: replace match where * means any char sequence
*\[IS0 14496-12:2003\]: "" # wildcard can include surrounding text
"regex:\\bSM-(S938B|F766B)\\b": Samsung DeviceRules:
- Replacements are applied to each resolved placeholder value (e.g.
{Make},{Model}) before it is inserted into the final pattern string. - Only the first matching replacement rule is applied; processing stops after the first match.
- Rule order in
StringReplacementsis therefore significant. - Without wildcard and without
regex:prefix, an exact substring replacement is applied. - Wildcard mode is active when
*is present in the key (*= any character sequence). - Regex mode is active when the key starts with
regex:.
{Counter...} enables numbering only when the placeholder is present in the template. The last used values are stored in the database so numbers are not reused after an application restart.
FileNamePatern: "{MetaCreationTime.Year}-{MetaCreationTime.Month}-{MetaCreationTime.Day}_{Counter:D3}.JPG"Example output within the same target path:
2026-07-04_001.JPG2026-07-04_002.JPG2026-07-04_003.JPG
# Restore dependencies
dotnet restore FIRE.slnx
# Build (Release)
dotnet build FIRE.slnx -c Release
# Run the console
dotnet run --project FIRE.Console -- collect --config ConfigurationFiles\Configuration.yaml --culture de-DEAPI documentation is generated with Doxygen 1.17.0. The API is the canonical integration surface; the console documentation is provided as an auxiliary test/onboarding reference.
# Run from the repository root
doxygen docs/DoxyfileThe HTML output is written to docs/html/index.html.
For a local, non-synchronized GitHub Wiki draft, see docs/wiki-local/.
This folder is intentionally ignored by Git so you can copy pages 1:1 into the
GitHub Wiki later.
Tip: Install Graphviz and set
HAVE_DOT = YESindocs/Doxyfileto generate class and call graphs.
FIRECatalog provides two helper methods for metadata discovery and documentation.
Returns all metadata entries for one file as tuples:
Source(e.g.FILEINFO,EXIFTOOL)Key(metadata key/tag name)Value(string value)
Behavior:
- Returns an empty list if the file does not exist.
- Continues gracefully if one metadata source fails.
- Useful to discover keys for
AvailableKeyWords.
Example:
var metadata = catalog.GetAllAvailableMetadata(@"D:\Photos\IMG_1234.jpg");
foreach (var (source, key, value) in metadata)
{
Console.WriteLine($"[{source}] {key}: {value}");
}Creates a Markdown report for one file.
Report contains:
- File information (path, name, size, timestamps)
- Metadata grouped by source
- Key/value tables
- Summary statistics
Behavior:
- If
outputPathis omitted, a.mdfile is created next to the source file. - If no metadata is available, the report still contains a warning section.
Example:
// Creates "IMG_1234.md" in the same directory
catalog.WriteMetadataToMarkdown(@"D:\Photos\IMG_1234.jpg");
// Custom output location
catalog.WriteMetadataToMarkdown(
@"D:\Photos\IMG_1234.jpg",
@"D:\Reports\IMG_1234-metadata.md");This project uses third-party components:
- ExifTool by Phil Harvey — https://exiftool.org
License: Artistic License 2.0 (alternatively GNU GPL) - SharpExifTool by Junian Triajianto — https://www.nuget.org/packages/SharpExifTool
License: MIT License
For details, see THIRD-PARTY-NOTICES.md.
Copyright © 2026 by Thomas Stoll.
Released under the MIT License.
