When downloading TV series, subtitle files often do not match the exact episode filenames expected by media servers like Plex or Jellyfin.
Different packagers release files with slightly different naming conventions, causing subtitles not to load automatically.
bulkrenamer solves this problem by bulk-renaming subtitle files so that they exactly match the corresponding episode filenames.
- POSIX-style CLI: follows the UNIX philosophy of “do one thing and do it well”.
- Written in C23: modern C standard, memory-safe and simple.
- Valgrind clean: no memory leaks (verified with
valgrind). - Lightweight: only one external header-only library is used,
uthash, for efficient hash map lookups.
br [-h] [-v] <directory> <source-extension> <target-extension> <pattern>-h: show usage help-v: print version
-
<directory>
The folder containing your files.
Use.to process the current directory. -
<source-extension>
The extension of the “source” files (the ones you want to match, e.g..mkv).
Must begin with a dot (.). -
<target-extension>
The extension of the “target” files (the ones you want to rename, e.g..srt).
Must begin with a dot (.). -
<pattern>
The filename pattern that identifies episodes, with%%representing numeric wildcards.
For example:- Pattern:
Series.Name.S%%E%% - Matches:
Series.Name.S01E01.WEBRip.mkvandSeries.Name.S01E01.ION.srt - Key:
Series.Name.S01E01
- Pattern:
Suppose you have:
Series.Name.S01E01.WEBRip.mkv
Series.Name.S01E02.WEBRip.mkv
Series.Name.S01E01.ION.srt
Series.Name.S01E02.ION.srt
Run:
./br . .mkv .srt 'Series.Name.S%%E%%'Result:
Series.Name.S01E01.WEBRip.mkv
Series.Name.S01E01.srt
Series.Name.S01E02.WEBRip.mkv
Series.Name.S01E02.srt
Subtitles are renamed to match the corresponding episode files.
bulkrenamer is intentionally simple and follows the UNIX “one tool, one job” principle.
-
Pattern parsing
- The user-provided pattern is split into literal segments around
%%. - Each
%%matches a run of digits in the filename. - From this, a key is extracted (e.g.
Series.Name.S01E02).
- The user-provided pattern is split into literal segments around
-
Directory scan
- The target directory is scanned for regular files.
- Each file is checked against the source and target extensions.
- If it matches the pattern, its key is derived.
-
Pairing with hash map (uthash)
- A hash table stores pairs keyed by the episode identifier.
- Each entry may hold a source file and/or a target file.
- When both are present, they are considered a matched pair.
-
Renaming phase
- For each matched pair, the subtitle filename is rewritten to match the video filename (only the extension differs).
- Renaming uses
renameat()with a directory file descriptor for safety. - Already-correct names are skipped.
- Existing files at the target path are not clobbered.
This design ensures:
- O(1) lookups while pairing (
uthash). - Minimal memory allocation.
- Predictable, POSIX-style behavior.
Standard CMake project:
mkdir -p build && cd build
cmake -DCMAKE_BUILD_TYPE=Release ..
cmake --build .The resulting binary will be br.
