Skip to content

Commit e44c7f5

Browse files
committed
Prepare public release
1 parent 6202a27 commit e44c7f5

80 files changed

Lines changed: 13022 additions & 1 deletion

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.editorconfig‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
root = true
2+
3+
[*]
4+
charset = utf-8
5+
end_of_line = crlf
6+
insert_final_newline = true
7+
trim_trailing_whitespace = true
8+
9+
[*.cs]
10+
indent_style = space
11+
indent_size = 4
12+
13+
[*.{ps1,psm1,psd1}]
14+
indent_style = space
15+
indent_size = 4
16+
17+
[*.{yml,yaml,json,md}]
18+
indent_style = space
19+
indent_size = 2
20+
21+
[*.md]
22+
trim_trailing_whitespace = false

‎.env.example‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Optional runtime overrides. Copy values into your shell profile or set them before running examples.
2+
3+
# Python environment that can import faster_whisper and demucs.
4+
CENSOR_MEDIA_PYTHON=
5+
6+
# Hugging Face cache root for faster-whisper model downloads.
7+
CENSOR_MEDIA_HF_HOME=
8+
9+
# Optional replacement-mode runtimes.
10+
CENSOR_OPENVOICE_PYTHON=
11+
CENSOR_OPENVOICE_CHECKPOINTS=
12+
CENSOR_COSYVOICE_REPO=
13+
CENSOR_COSYVOICE_MODEL_DIR=

‎.github/workflows/ci.yml‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
9+
jobs:
10+
build:
11+
runs-on: windows-latest
12+
13+
steps:
14+
- name: Checkout
15+
uses: actions/checkout@v4
16+
17+
- name: Setup .NET
18+
uses: actions/setup-dotnet@v4
19+
with:
20+
dotnet-version: 10.0.x
21+
22+
- name: Restore
23+
run: dotnet restore .\src\LocalProfanityCensor.DotNet\LocalProfanityCensor.DotNet.csproj
24+
25+
- name: Build
26+
run: dotnet build .\src\LocalProfanityCensor.DotNet\LocalProfanityCensor.DotNet.csproj --configuration Release --no-restore
27+
28+
- name: CLI help smoke test
29+
run: dotnet run --project .\src\LocalProfanityCensor.DotNet --configuration Release -- --help
30+
31+
- name: Validate starter dictionary
32+
run: dotnet run --project .\src\LocalProfanityCensor.DotNet --configuration Release -- validate-dictionary .\src\LocalProfanityCensor.DotNet\Dictionaries\profanity.example.yml
33+
34+
- name: Health check smoke test
35+
run: dotnet run --project .\src\LocalProfanityCensor.DotNet --configuration Release -- health-check --mode mute --no-prompt
36+
37+
- name: Publish core payload smoke test
38+
shell: pwsh
39+
run: .\Deployment\Scripts\Publish-Core.ps1 -OutputRoot .\Deployment\artifacts-ci

‎.gitignore‎

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Build outputs
2+
bin/
3+
obj/
4+
artifacts/
5+
artifacts*/
6+
**/bin/
7+
**/obj/
8+
**/artifacts/
9+
**/artifacts*/
10+
11+
# Generated bundle outputs
12+
bundles/*/Payload/*
13+
!bundles/*/Payload/.gitkeep
14+
bundles/*/Configs/*
15+
!bundles/*/Configs/.gitkeep
16+
bundles/*/Dictionaries/*
17+
!bundles/*/Dictionaries/.gitkeep
18+
bundles/*/Scripts/*
19+
!bundles/*/Scripts/.gitkeep
20+
21+
# Local runtime and model caches
22+
.venv/
23+
venv/
24+
runtime/
25+
models/*
26+
!models/README.md
27+
!models/.gitkeep
28+
hf-home/
29+
huggingface-cache/
30+
transformers/
31+
32+
# Processing work and reports
33+
.work/
34+
.state/
35+
logs/
36+
tmp/
37+
temp/
38+
*.report.json
39+
*.report.csv
40+
transcript_resolution.json
41+
alignment_request.json
42+
aligned_transcript*.json
43+
44+
# Large media and generated audio/video
45+
*.mkv
46+
*.mp4
47+
*.m4v
48+
*.avi
49+
*.m2ts
50+
*.mov
51+
*.wav
52+
*.flac
53+
*.aac
54+
*.eac3
55+
*.mp3
56+
57+
# Python cache
58+
__pycache__/
59+
*.py[cod]
60+
61+
# OS/editor files
62+
.DS_Store
63+
Thumbs.db
64+
.vs/
65+
.vscode/
66+
*.user
67+
*.suo

‎CONTRIBUTING.md‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Contributing
2+
3+
Contributions are welcome. Keep changes focused, testable, and friendly to home users who may not be developers.
4+
5+
## Guidelines
6+
7+
- Do not commit media files, transcripts, generated reports, model weights, local cache folders, or personal paths.
8+
- Keep examples generic and path-agnostic.
9+
- Prefer clear configuration options over hard-coded local assumptions.
10+
- Update documentation when a user-facing command, config key, or workflow changes.
11+
- Preserve original media by default; destructive or replacement workflows must be explicit.
12+
13+
## Validation
14+
15+
Before opening a pull request, run:
16+
17+
```powershell
18+
dotnet build .\src\LocalProfanityCensor.DotNet\LocalProfanityCensor.DotNet.csproj
19+
dotnet run --project .\src\LocalProfanityCensor.DotNet -- validate-dictionary .\src\LocalProfanityCensor.DotNet\Dictionaries\profanity.example.yml
20+
```
21+
22+
If your change touches deployment scripts, also run:
23+
24+
```powershell
25+
pwsh .\Deployment\Scripts\Publish-Core.ps1
26+
pwsh .\Deployment\Scripts\Stage-Payload.ps1
27+
```

‎Deployment/Assets/LICENSE.rtf‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{\rtf1\ansi\deff0
2+
{\fonttbl{\f0 Segoe UI;}}
3+
\fs20 MIT License\par
4+
\par
5+
Copyright (c) 2026 TrueCodePoet\par
6+
\par
7+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\par
8+
\par
9+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\par
10+
\par
11+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\par
12+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<Project Sdk="WixToolset.Sdk/6.0.2">
2+
<PropertyGroup>
3+
<OutputType>Package</OutputType>
4+
<TargetFramework>net10.0</TargetFramework>
5+
<Platform>x64</Platform>
6+
<DefineConstants>StageDir=$(LPC_STAGE_DIR)</DefineConstants>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<PackageReference Include="WixToolset.UI.wixext" Version="6.0.2" />
11+
<PackageReference Include="WixToolset.Util.wixext" Version="6.0.2" />
12+
</ItemGroup>
13+
14+
<ItemGroup>
15+
<BindPath Include="$(LPC_STAGE_DIR)" />
16+
</ItemGroup>
17+
</Project>

‎Deployment/Installer/Package.wxs‎

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"
3+
xmlns:ui="http://wixtoolset.org/schemas/v4/wxs/ui">
4+
<Package
5+
Name="LocalProfanityCensor"
6+
Manufacturer="LocalProfanityCensor"
7+
Version="1.0.0"
8+
UpgradeCode="A6A4D0A8-8D0A-4E6A-9A2B-1D6A9A0B4C11"
9+
Language="1033"
10+
Scope="perMachine">
11+
12+
<MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
13+
<MediaTemplate EmbedCab="yes" />
14+
15+
<StandardDirectory Id="ProgramFiles64Folder">
16+
<Directory Id="INSTALLFOLDER" Name="LocalProfanityCensor">
17+
<Component Id="MainExecutableComponent" Guid="B1A4A4D0-1F2A-4C5D-9E7F-123456789001">
18+
<File Id="MainExecutableFile" Source="$(var.StageDir)\LocalProfanityCensor.DotNet.exe" KeyPath="yes" />
19+
</Component>
20+
21+
<Component Id="ConfigDirectoryComponent" Guid="B1A4A4D0-1F2A-4C5D-9E7F-123456789002">
22+
<CreateFolder />
23+
</Component>
24+
25+
<Directory Id="CONFIGSDIR" Name="Configs">
26+
<Component Id="ProductionConfigComponent" Guid="B1A4A4D0-1F2A-4C5D-9E7F-123456789003">
27+
<File Id="ProductionConfigFile" Source="$(var.StageDir)\Configs\production.demucs.depth.censored-option.yml" KeyPath="yes" />
28+
</Component>
29+
</Directory>
30+
31+
<Directory Id="DICTIONARYDIR" Name="Dictionaries">
32+
<Component Id="DictionaryComponent" Guid="B1A4A4D0-1F2A-4C5D-9E7F-123456789004">
33+
<File Id="DictionaryFile" Source="$(var.StageDir)\Dictionaries\profanity.example.yml" KeyPath="yes" />
34+
</Component>
35+
</Directory>
36+
</Directory>
37+
</StandardDirectory>
38+
39+
<Feature Id="MainFeature" Title="LocalProfanityCensor" Level="1">
40+
<ComponentRef Id="MainExecutableComponent" />
41+
<ComponentRef Id="ConfigDirectoryComponent" />
42+
<ComponentRef Id="ProductionConfigComponent" />
43+
<ComponentRef Id="DictionaryComponent" />
44+
</Feature>
45+
46+
<ui:WixUI Id="WixUI_InstallDir" InstallDirectory="INSTALLFOLDER" />
47+
<WixVariable Id="WixUILicenseRtf" Value="..\Assets\LICENSE.rtf" />
48+
</Package>
49+
</Wix>

‎Deployment/README.md‎

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# Deployment
2+
3+
The deployment folder contains Windows-oriented packaging and batch-processing helpers. The scripts use repo-relative defaults and can be run from any clone path.
4+
5+
## Scripts
6+
7+
- `Scripts\Publish-Core.ps1` publishes the .NET app as a self-contained Windows x64 single-file executable.
8+
- `Scripts\Stage-Payload.ps1` stages the published executable, configs, and starter dictionary under `Deployment\artifacts\staging\LocalProfanityCensor`.
9+
- `Scripts\Invoke-CensorMediaBatch.ps1` processes a media folder safely with shared state, lock files, retries, local work folders, and a holding output folder.
10+
- `Scripts\Setup-AI.ps1` creates a Python virtual environment with `faster-whisper`, `demucs`, and optional replacement-mode packages.
11+
- `Scripts\Build-Msi.ps1` builds the WiX installer from the staged payload.
12+
13+
## Safe Batch Mode
14+
15+
Start with holding mode. It leaves source files untouched and writes cleaned MKV files to a separate folder. MKV is the target output container because it can embed the original media streams, the clean censored audio track, and generated subtitle tracks together.
16+
17+
```powershell
18+
pwsh .\Deployment\Scripts\Publish-Core.ps1
19+
pwsh .\Deployment\Scripts\Stage-Payload.ps1
20+
21+
pwsh .\Deployment\Scripts\Invoke-CensorMediaBatch.ps1 `
22+
-InputRoot "D:\Media\Incoming" `
23+
-HoldingRoot "D:\Media\CensoredHolding" `
24+
-MaxFilesPerRun 1
25+
```
26+
27+
Only use `-ReplaceOriginal` after you have validated output quality on your own media. Replacement mode archives the original to `-ArchiveRoot` before moving the cleaned MKV into the source folder.
28+
29+
## AI Runtime
30+
31+
The batch runner uses `CENSOR_MEDIA_PYTHON` or `-MediaPythonPath` to locate a Python environment with the AI packages installed. It uses `CENSOR_MEDIA_HF_HOME` or `-HuggingFaceCacheRoot` for the Hugging Face model cache.
32+
33+
Model weights are not bundled in the installer. See `models\README.md` and `scripts\Download-FasterWhisperModel.ps1`.
34+
35+
For the normal mute/beep/duck path, run setup with `-SkipOpenVoice`. Only omit that switch when you are intentionally testing prototype replacement audio.
36+
37+
## State And Locks
38+
39+
By default, state is stored under `<HoldingRoot>\.state`. Each source-relative media file gets its own JSON state file and lock file so multiple workers can safely share the same input and holding folders.
40+
41+
The runner skips completed jobs when the source file size and timestamp still match. Failed jobs retry according to `-MaxAttempts` and `-RetryDelayMinutes`.
42+
43+
## Output Behavior
44+
45+
Default behavior is safe for testing:
46+
47+
- Originals stay in place.
48+
- Cleaned output goes to the holding folder.
49+
- Original audio remains the default audio track.
50+
- Existing original subtitles remain default.
51+
- If no retained normal subtitle exists, a generated normal subtitle can be embedded into the output MKV.
52+
- A generated censored subtitle can be embedded as a selectable clean subtitle option.
53+
- Censored audio and generated subtitles are added as selectable options.
54+
- Files with no detected profanity and no subtitle changes can be marked complete without writing a new MKV.
55+
56+
## Useful Parameters
57+
58+
- `-InputRoot`: source media folder to scan recursively.
59+
- `-HoldingRoot`: holding folder for completed MKV outputs in safe mode.
60+
- `-StateRoot`: shared JSON state and lock folder. Defaults to `<HoldingRoot>\.state`.
61+
- `-ArchiveRoot`: original-file archive folder used by `-ReplaceOriginal`.
62+
- `-LocalWorkRoot`: machine-local working directory. Defaults to `%TEMP%\LocalProfanityCensorBatch`.
63+
- `-MaxFilesPerRun`: limit for one pass. `0` means no explicit limit.
64+
- `-Repeat`: keep scanning after each pass.
65+
- `-DryRun`: pass `--dry-run` to the censor process and do not move a final output.
66+
- `-KeepWork`: preserve local work folders and pass `--keep-work` to the censor process.
67+
- `-RunSetupAI`: run `Scripts\Setup-AI.ps1` before scanning.
68+
- `-MediaPythonPath`: explicitly set the Python runtime used by ASR/Demucs.
69+
- `-HuggingFaceCacheRoot`: explicitly set the Hugging Face cache root used by ASR models.

‎Deployment/Scripts/Build-Msi.ps1‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
[CmdletBinding()]
2+
param(
3+
[string]$ArtifactsRoot,
4+
[string]$InstallerProject,
5+
[string]$Configuration = "Release"
6+
)
7+
8+
$ErrorActionPreference = 'Stop'
9+
10+
$ScriptRoot = Split-Path -Parent $PSCommandPath
11+
$DeploymentRoot = Split-Path -Parent $ScriptRoot
12+
13+
if ([string]::IsNullOrWhiteSpace($ArtifactsRoot)) {
14+
$ArtifactsRoot = Join-Path $DeploymentRoot 'artifacts'
15+
}
16+
17+
if ([string]::IsNullOrWhiteSpace($InstallerProject)) {
18+
$InstallerProject = Join-Path $DeploymentRoot 'Installer\LocalProfanityCensor.Installer.wixproj'
19+
}
20+
21+
$stageDir = Join-Path $ArtifactsRoot "staging\LocalProfanityCensor"
22+
if (-not (Test-Path $stageDir)) {
23+
throw "Staging payload not found: $stageDir. Run Publish-Core.ps1 and Stage-Payload.ps1 first."
24+
}
25+
26+
$env:LPC_STAGE_DIR = $stageDir
27+
28+
Write-Host "Building MSI from staged payload: $stageDir"
29+
dotnet build $InstallerProject -c $Configuration

0 commit comments

Comments
 (0)