LocalProfanityCensor is a local-first media profanity filtering tool for home media libraries. It detects configured words from subtitles or speech, then creates cleaned MKV outputs with muted, beeped, ducked, or prototype replacement audio, plus generated normal and censored subtitle tracks.
The project is designed for people who want private, offline processing and for developers who want a practical .NET, FFmpeg, and Whisper media pipeline they can inspect and extend.
Important
Goal: LocalProfanityCensor is a review-first assistive tool, not an authority. All generated output should be reviewed by a human before use, and users should not assume 100% detection, timing, subtitle, or replacement accuracy.
- Inspects media with
ffprobeand renders/remuxes withffmpeg. - Uses embedded or sidecar subtitles when available.
- Uses local faster-whisper transcription when subtitles are missing, incomplete, or need timing refinement.
- Supports Demucs dialogue/vocal isolation for better speech recognition on difficult audio.
- Adds a selectable
Clean Censoredaudio track while preserving the original audio track by default. - Creates a generated normal subtitle track when the source media does not already have a usable normal subtitle.
- Creates a generated censored subtitle track with configured words replaced.
- Targets MKV output so video, original audio, clean audio, original subtitles, generated normal subtitles, and generated censored subtitles can live in one container.
- Writes JSON/CSV reports so users can review what was detected and changed.
- Includes a Windows batch runner with safe holding-folder mode, retries, state files, and multi-machine locks.
The mute, beep, and duck paths are the practical modes today. replace mode exists as a prototype for AI-generated word replacement and requires extra optional models and setup.
The default public profile is conservative about the original media: original audio remains default, original subtitles remain default when present, and clean/censored outputs are added as selectable tracks. If a video has no retained normal subtitle, the default profile can generate one from the selected transcript and embed it into the output MKV.
inspectvalidate-dictionarytranscribehealth-checkprocess-fileprocess- Practical censor modes:
mute,beep, andduck - Subtitle recovery workflows such as generating a normal subtitle only when one is missing
These are the workflows the project is currently positioned to support for normal public use, subject to the environment and model prerequisites documented below.
replacemodeprepare-replace-prototypesynthesize-replace-prototype- alignment and replacement prototype workflows used for AI-generated replacement audio
Experimental features are included for testing and iteration. They should not be treated as production-ready, and all output from these paths should be reviewed carefully.
LocalProfanityCensor is currently a Windows-first project.
- The included scripts target PowerShell on Windows.
- CI currently runs on
windows-latest. - Packaging and installer assets are Windows-oriented.
The core .NET application may be portable in parts, but this repository should currently be treated as tested and documented for Windows workflows first.
LocalProfanityCensor targets MKV for video outputs because MKV is a practical archival container for this workflow. A single MKV can hold the copied video stream, original audio tracks, the added Clean Censored audio track, retained original subtitles, generated normal subtitles, generated censored subtitles, track titles, languages, and default-track flags.
The tool can read common video inputs such as .mkv, .mp4, .m4v, .avi, and .m2ts, but video outputs are normalized to .mkv so everything stays embedded together instead of being split across sidecar files.
src/LocalProfanityCensor.DotNet/ .NET 10 command-line application
Deployment/ Windows publish, staging, MSI, and batch scripts
bundles/ Opinionated public bundle targets for supported workflows
examples/ Small generic scripts for common workflows
models/ Placeholder and instructions for local model downloads
scripts/ Utility scripts, including model download helper
docs/ Setup guidance for users and AI coding agents
THIRD_PARTY_NOTICES.md Dependency and model attribution notes
For the first public release, the recommended starting point is the Windows mute bundle:
bundles\windows-mute-default
That bundle documents one supported workflow, provides bundle-local build/setup/run helpers, and keeps generated payload output out of source control. It is the best path for users who want the practical Windows workflow without choosing between every internal config and script.
Typical bundle flow:
pwsh .\bundles\windows-mute-default\Build-Bundle.ps1
pwsh .\bundles\windows-mute-default\Setup-This-Bundle.ps1 -Profile gpu
pwsh .\bundles\windows-mute-default\Run-This-Bundle.ps1 `
-InputRoot "D:\Media\Incoming" `
-HoldingRoot "D:\Media\CensoredHolding" `
-MaxFilesPerRun 1Use -Profile cpu on machines without a supported NVIDIA CUDA setup.
- Windows PowerShell 7+ for the included scripts.
- .NET 10 SDK for building from source.
ffmpegandffprobeavailable onPATH.- Python 3.10 or newer for faster-whisper and Demucs.
- Optional NVIDIA CUDA stack if you want GPU acceleration.
If you want the simplest supported starting point, use this baseline:
- Windows with PowerShell 7+
- .NET 10 SDK
ffmpegandffprobeonPATH- Python 3.10+
faster-whisper,demucs, andpyyamlinstalled throughDeployment\Scripts\Setup-AI.ps1- a downloaded Whisper model such as
large-v3ormedium - the example dictionary at
src\LocalProfanityCensor.DotNet\Dictionaries\profanity.example.yml
Recommended first-run workflow:
- Build the project.
- Run
Setup-AI.ps1with-SkipOpenVoice. - Download a Whisper model.
- Validate the dictionary.
- Run a
-DryRunor usemutemode on a single test file.
For the lowest-risk first success, start with one of these:
pwsh .\examples\Process-OneFile.ps1 -InputPath "D:\Media\ExampleMovie.mkv" -DryRunpwsh .\examples\Generate-SubtitleIfMissing.ps1 -InputPath "D:\Media\OlderMovie.mkv"
Clone the repo and build the app:
dotnet build .\src\LocalProfanityCensor.DotNet\LocalProfanityCensor.DotNet.csprojCreate the AI runtime:
pwsh .\Deployment\Scripts\Setup-AI.ps1 -Profile gpu -SkipOpenVoiceIf you do not have a compatible GPU, use -Profile cpu instead. Leave -SkipOpenVoice on unless you are intentionally testing prototype replacement audio.
Download the default faster-whisper model:
pwsh .\scripts\Download-FasterWhisperModel.ps1 -ModelName large-v3Validate the starter dictionary:
dotnet run --project .\src\LocalProfanityCensor.DotNet -- validate-dictionary .\src\LocalProfanityCensor.DotNet\Dictionaries\profanity.example.ymlProcess one file:
pwsh .\examples\Process-OneFile.ps1 `
-InputPath "D:\Media\ExampleMovie.mkv" `
-OutputPath "D:\Media\ExampleMovie.clean.mkv" `
-Mode muteStart with -DryRun if you want reports without writing a cleaned media file.
Generate a normal subtitle only when the source media is missing one:
pwsh .\examples\Generate-SubtitleIfMissing.ps1 `
-InputPath "D:\Media\OlderMovie.mkv"This is a conservative workflow for older media libraries where the main goal is subtitle recovery rather than censorship. It generates a normal subtitle track only if a usable one is missing.
For a home media folder, use holding mode first. This keeps originals in place and writes cleaned files to a separate folder.
pwsh .\examples\Batch-ProcessFolder.ps1 `
-InputRoot "D:\Media\Incoming" `
-HoldingRoot "D:\Media\CensoredHolding" `
-MaxFilesPerRun 1The batch runner publishes and stages the app, copies each claimed file into local work, validates the output, and records state so failed or completed jobs are handled predictably. Use -ReplaceOriginal only after you have reviewed outputs and provided an -ArchiveRoot.
Profiles live under src\LocalProfanityCensor.DotNet\Configs.
production.demucs.depth.censored-option.ymlis the recommended default. It uses faster-whisperlarge-v3, Demucs depth isolation, mute mode, generated normal subtitles when missing, generated censored subtitles, and original tracks as defaults.production.demucs.depth.medium.ymluses the same deeper Demucs path with the smallermediummodel.production.demucs.speed.medium.ymlfavors lower runtime cost.
The key subtitle settings are:
subtitles.generate_plain_subtitle_if_missing: create a normal generated subtitle when the media has no retained normal subtitle.subtitles.generate_censored_subtitle: create a censored subtitle track with configured words replaced.subtitles.default_censored_subtitle: whenfalse, original or generated normal subtitles remain default and censored subtitles stay selectable.
For a subtitle-recovery workflow, see src\LocalProfanityCensor.DotNet\Configs\subtitle-only.if-missing.medium.yml and examples\Generate-SubtitleIfMissing.ps1.
The starter dictionary lives at src\LocalProfanityCensor.DotNet\Dictionaries\profanity.example.yml. Treat it as an example, not a universal policy. Edit the terms, variants, severity, and replacement words to match your household or project requirements.
Warning
The example dictionary contains explicit and offensive terms because the tool needs real match configuration to validate detection, replacement, subtitles, and reports. You do not need to open that file unless you are reviewing or customizing the censor policy.
Model setup has enough moving pieces that an AI coding agent can be useful. See docs\AI_AGENT_SETUP.md for a copy/paste prompt and the exact checks an agent should perform. The guide tells agents to verify .NET, ffmpeg, ffprobe, Python packages, model cache paths, and dictionary validation without adding private paths or generated files to the repo.
- Confirm both tools are installed and available on
PATH. - Open a new shell after updating
PATH. - Run
dotnet run --project .\src\LocalProfanityCensor.DotNet -- health-check --mode muteto confirm the runtime can see them.
- Re-run
pwsh .\Deployment\Scripts\Setup-AI.ps1 -Profile cpu -SkipOpenVoiceor the GPU variant you intend to use. - Confirm
CENSOR_MEDIA_PYTHONpoints to the Python environment wherefaster-whisper,demucs, andpyyamlare installed. - If needed, activate the environment and verify imports manually before retrying the app.
- Download the model named in your selected config, such as
large-v3ormedium. - Make sure the config's
transcription.modelmatches the downloaded model. - Confirm the model cache path is consistent with
CENSOR_MEDIA_HF_HOME.
- This is expected unless you intentionally set up the optional prototype dependencies.
replacemode requires extra Python packages and model/checkpoint setup beyond the default supported workflow.- Start with
mute,beep,duck, or subtitle-only recovery first.
- Review the JSON/CSV reports and generated subtitle tracks.
- Try a conservative single-file run before batch processing.
- Use
-DryRunfirst when testing a new config or media type.
Model weights are not included in this repository. Users download them locally and are responsible for following the upstream model cards and licenses. See models\README.md and THIRD_PARTY_NOTICES.md before redistributing binaries, installers, or model bundles.
LocalProfanityCensor is intended to run locally. Media files, transcripts, and reports stay on your machine unless you choose to move or publish them. The application disables implicit Hugging Face token use in its bridge environment by default.
Do not commit media files, generated reports, work folders, model weights, API keys, tokens, or personal library paths. The included .gitignore is set up to exclude those by default.
dotnet build .\src\LocalProfanityCensor.DotNet\LocalProfanityCensor.DotNet.csproj
dotnet run --project .\src\LocalProfanityCensor.DotNet -- inspect "D:\Media\ExampleMovie.mkv"
dotnet run --project .\src\LocalProfanityCensor.DotNet -- validate-dictionary .\src\LocalProfanityCensor.DotNet\Dictionaries\profanity.example.ymlPublish and stage a Windows payload:
pwsh .\Deployment\Scripts\Publish-Core.ps1
pwsh .\Deployment\Scripts\Stage-Payload.ps1Build the optional MSI after staging:
pwsh .\Deployment\Scripts\Build-Msi.ps1- Always review generated output before replacing originals.
- This is a tooling aid, not a guarantee of correctness; do not assume 100% accuracy.
- Speech recognition quality depends on the audio, model, hardware, and config.
- Generated subtitles depend on transcript quality; ASR refinement helps, but subtitle wording and timing should still be reviewed.
- Replacement audio is experimental and requires separate model checkpoints.
- This tool does not grant rights to modify or redistribute media you do not own or have permission to process.
LocalProfanityCensor is released under the MIT License. Third-party tools, packages, and models keep their own licenses and terms.