Skip to content

Commit

Permalink
work in progress for #6, bleep instead of mute
Browse files Browse the repository at this point in the history
  • Loading branch information
mmguero committed Aug 29, 2024
1 parent a963e5c commit d02d3f9
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 24 deletions.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

[![Latest Version](https://img.shields.io/pypi/v/monkeyplug)](https://pypi.python.org/pypi/monkeyplug/) [![VOSK Docker Images](https://github.com/mmguero/monkeyplug/workflows/monkeyplug-build-push-vosk-ghcr/badge.svg)](https://github.com/mmguero/monkeyplug/pkgs/container/monkeyplug) [![Whisper Docker Images](https://github.com/mmguero/monkeyplug/workflows/monkeyplug-build-push-whisper-ghcr/badge.svg)](https://github.com/mmguero/monkeyplug/pkgs/container/monkeyplug)

**monkeyplug** is a little script to mute profanity in audio files (intended for podcasts, but YMMV) in a few simple steps:
**monkeyplug** is a little script to censor profanity in audio files (intended for podcasts, but YMMV) in a few simple steps:

1. The user provides a local audio file (or a URL pointing to an audio file which is downloaded)
2. Either [Whisper](https://openai.com/research/whisper) ([GitHub](https://github.com/openai/whisper)) or the [Vosk](https://alphacephei.com/vosk/)-[API](https://github.com/alphacep/vosk-api) is used to recognize speech in the audio file
Expand Down Expand Up @@ -79,6 +79,10 @@ options:
Milliseconds to pad before muted segments (default: 0)
--pad-milliseconds-post <int>
Milliseconds to pad after muted segments (default: 0)
-b [true|false], --beep [true|false]
Beep instead of silence
-h <int>, --beep-hertz <int>
Beep frequency hertz (default: 1000)
--force [true|false] Process file despite existence of embedded tag
VOSK Options:
Expand Down
4 changes: 2 additions & 2 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
[metadata]
name = monkeyplug
version = 2.0.1
version = 2.1.0
author = Seth Grover
author_email = mero.mero.guero@gmail.com
description = monkeyplug is a little script to mute profanity in audio files.
description = monkeyplug is a little script to censor profanity in audio files.
long_description = file: README.md
long_description_content_type = text/markdown
url = https://github.com/mmguero/monkeyplug
Expand Down
4 changes: 2 additions & 2 deletions src/monkeyplug/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""monkeyplug is a little script to mute profanity in audio files."""
"""monkeyplug is a little script to censor profanity in audio files."""

__version__ = "2.0.1"
__version__ = "2.1.0"
__author__ = "Seth Grover <mero.mero.guero@gmail.com>"
__all__ = []

Expand Down
97 changes: 78 additions & 19 deletions src/monkeyplug/monkeyplug.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
AUDIO_MATCH_FORMAT = "MATCH"
AUDIO_INTERMEDIATE_PARAMS = ["-c:a", "pcm_s16le", "-ac", "1", "-ar", "16000"]
AUDIO_DEFAULT_WAV_FRAMES_CHUNK = 8000
BEEP_HERTZ_DEFAULT = 1000
SWEARS_FILENAME_DEFAULT = 'swears.txt'
MUTAGEN_METADATA_TAGS = ['encodedby', 'comment']
MUTAGEN_METADATA_TAG_VALUE = u'monkeyplug'
Expand Down Expand Up @@ -201,9 +202,15 @@ class Plugger(object):
swearsMap = {}
wordList = []
naughtyWordList = []
# for beep and mute
muteTimeList = []
# for beep only
sineTimeList = []
beepDelayList = []
padSecPre = 0.0
padSecPost = 0.0
beep = False
beepHertz = BEEP_HERTZ_DEFAULT
forceDespiteTag = False
aParams = None
tags = None
Expand All @@ -220,11 +227,15 @@ def __init__(
aChannels=AUDIO_DEFAULT_CHANNELS,
padMsecPre=0,
padMsecPost=0,
beep=False,
beepHertz=BEEP_HERTZ_DEFAULT,
force=False,
dbug=False,
):
self.padSecPre = padMsecPre / 1000.0
self.padSecPost = padMsecPost / 1000.0
self.beep = beep
self.beepHertz = beepHertz
self.forceDespiteTag = force
self.debug = dbug
self.outputJson = outputJson
Expand Down Expand Up @@ -335,6 +346,8 @@ def __init__(
mmguero.eprint(f'Encode parameters: {self.aParams}')
mmguero.eprint(f'Profanity file: {self.swearsFileSpec}')
mmguero.eprint(f'Intermediate downloaded file: {self.tmpDownloadedFileSpec}')
mmguero.eprint(f'Beep instead of mute: {self.beep}')
mmguero.eprint(f'Beep hertz: {self.beepHertz}')
mmguero.eprint(f'Force despite tags: {self.forceDespiteTag}')

######## del ##################################################################
Expand Down Expand Up @@ -365,28 +378,32 @@ def CreateCleanMuteList(self):
mmguero.eprint(self.naughtyWordList)

self.muteTimeList = []
self.sineTimeList = []
self.beepDelayList = []
for word, wordPeek in pairwise(self.naughtyWordList):
self.muteTimeList.append(
"afade=enable='between(t,"
+ format(word["start"] - self.padSecPre, ".3f")
+ ","
+ format(word["end"] + self.padSecPost, ".3f")
+ ")':t=out:st="
+ format(word["start"] - self.padSecPre, ".3f")
+ ":d=5ms"
)
self.muteTimeList.append(
"afade=enable='between(t,"
+ format(word["end"] + self.padSecPost, ".3f")
+ ","
+ format(wordPeek["start"] - self.padSecPre, ".3f")
+ ")':t=in:st="
+ format(word["end"] + self.padSecPost, ".3f")
+ ":d=5ms"
)
wordStart = format(word["start"] - self.padSecPre, ".3f")
wordEnd = format(word["end"] + self.padSecPost, ".3f")
wordDuration = format(float(wordEnd) - float(wordStart), ".3f")
wordPeekStart = format(wordPeek["start"] - self.padSecPre, ".3f")
if self.beep:
self.muteTimeList.append(f"volume=enable='between(t,{wordStart},{wordEnd})':volume=0")
self.sineTimeList.append(f"sine=f={self.beepHertz}:duration={wordDuration}")
self.beepDelayList.append(
f"atrim=0:{wordDuration},adelay={'|'.join([str(int(float(wordStart) * 1000))] * 2)}"
)
else:
self.muteTimeList.append(
"afade=enable='between(t," + wordStart + "," + wordEnd + ")':t=out:st=" + wordStart + ":d=5ms"
)
self.muteTimeList.append(
"afade=enable='between(t," + wordEnd + "," + wordPeekStart + ")':t=in:st=" + wordEnd + ":d=5ms"
)

if self.debug:
mmguero.eprint(self.muteTimeList)
if self.beep:
mmguero.eprint(self.sineTimeList)
mmguero.eprint(self.beepDelayList)

return self.muteTimeList

Expand All @@ -396,7 +413,17 @@ def EncodeCleanAudio(self):
self.CreateCleanMuteList()

if len(self.muteTimeList) > 0:
audioArgs = ['-af', ",".join(self.muteTimeList)]
if self.beep:
muteTimeListStr = ','.join(self.muteTimeList)
sineTimeListStr = ';'.join([f'{val}[beep{i+1}]' for i, val in enumerate(self.sineTimeList)])
beepDelayList = ';'.join(
[f'[beep{i+1}]{val}[beep{i+1}_delayed]' for i, val in enumerate(self.beepDelayList)]
)
beepMixList = ''.join([f'[beep{i+1}_delayed]' for i in range(len(self.beepDelayList))])
filterStr = f"[0:a]{muteTimeListStr}[mute];{sineTimeListStr};{beepDelayList};[mute]{beepMixList}amix=inputs={len(self.beepDelayList)+1}"
audioArgs = ['-filter_complex', filterStr]
else:
audioArgs = ['-af', ",".join(self.muteTimeList)]
else:
audioArgs = []

Expand Down Expand Up @@ -477,6 +504,8 @@ def __init__(
wChunk=AUDIO_DEFAULT_WAV_FRAMES_CHUNK,
padMsecPre=0,
padMsecPost=0,
beep=False,
beepHertz=BEEP_HERTZ_DEFAULT,
force=False,
dbug=False,
):
Expand Down Expand Up @@ -508,6 +537,8 @@ def __init__(
aChannels=aChannels,
padMsecPre=padMsecPre,
padMsecPost=padMsecPost,
beep=beep,
beepHertz=beepHertz,
force=force,
dbug=dbug,
)
Expand Down Expand Up @@ -622,6 +653,8 @@ def __init__(
aChannels=AUDIO_DEFAULT_CHANNELS,
padMsecPre=0,
padMsecPost=0,
beep=False,
beepHertz=BEEP_HERTZ_DEFAULT,
force=False,
dbug=False,
):
Expand All @@ -643,6 +676,8 @@ def __init__(
aChannels=aChannels,
padMsecPre=padMsecPre,
padMsecPost=padMsecPost,
beep=beep,
beepHertz=beepHertz,
force=force,
dbug=dbug,
)
Expand Down Expand Up @@ -793,6 +828,26 @@ def RunMonkeyPlug():
default=0,
help=f"Milliseconds to pad after muted segments (default: 0)",
)
parser.add_argument(
"-b",
"--beep",
dest="beep",
type=mmguero.str2bool,
nargs="?",
const=True,
default=False,
metavar="true|false",
help="Beep instead of silence",
)
parser.add_argument(
"-h",
"--beep-hertz",
dest="beepHertz",
metavar="<int>",
type=int,
default=BEEP_HERTZ_DEFAULT,
help=f"Beep frequency hertz (default: {BEEP_HERTZ_DEFAULT})",
)
parser.add_argument(
"--force",
dest="forceDespiteTag",
Expand Down Expand Up @@ -869,6 +924,8 @@ def RunMonkeyPlug():
wChunk=args.voskReadFramesChunk,
padMsecPre=args.padMsecPre if args.padMsecPre > 0 else args.padMsec,
padMsecPost=args.padMsecPost if args.padMsecPost > 0 else args.padMsec,
beep=args.beep,
beepHertz=args.beepHertz,
force=args.forceDespiteTag,
dbug=args.debug,
)
Expand All @@ -887,6 +944,8 @@ def RunMonkeyPlug():
aChannels=args.aChannels,
padMsecPre=args.padMsecPre if args.padMsecPre > 0 else args.padMsec,
padMsecPost=args.padMsecPost if args.padMsecPost > 0 else args.padMsec,
beep=args.beep,
beepHertz=args.beepHertz,
force=args.forceDespiteTag,
dbug=args.debug,
)
Expand Down

0 comments on commit d02d3f9

Please # to comment.