226 lines
6.3 KiB
Python
Executable File
226 lines
6.3 KiB
Python
Executable File
#!/usr/bin/python3
|
|
|
|
import datetime as dt
|
|
from threading import Timer
|
|
from pydub import AudioSegment
|
|
from time import sleep
|
|
from timeloop import Timeloop # type: ignore
|
|
import vlc # type: ignore
|
|
|
|
|
|
class RepeatedTimer(object):
|
|
def __init__(self, interval, function, *args, **kwargs):
|
|
self._timer = None
|
|
self.interval = interval
|
|
self.function = function
|
|
self.args = args
|
|
self.kwargs = kwargs
|
|
self.is_running = False
|
|
self.start()
|
|
|
|
def _run(self):
|
|
self.is_running = False
|
|
self.start()
|
|
self.function(*self.args, **self.kwargs)
|
|
|
|
def start(self):
|
|
if not self.is_running:
|
|
self._timer = Timer(self.interval, self._run)
|
|
self._timer.start()
|
|
self.is_running = True
|
|
|
|
def stop(self):
|
|
self._timer.cancel()
|
|
self.is_running = False
|
|
|
|
|
|
tl = Timeloop()
|
|
|
|
|
|
def leading_silence(audio_segment, silence_threshold=-50.0, chunk_size=10):
|
|
"""
|
|
Returns the millisecond/index that the leading silence ends.
|
|
audio_segment - the segment to find silence in
|
|
silence_threshold - the upper bound for how quiet is silent in dFBS
|
|
chunk_size - chunk size for interating over the segment in ms
|
|
|
|
https://github.com/jiaaro/pydub/blob/master/pydub/silence.py
|
|
"""
|
|
|
|
trim_ms = 0 # ms
|
|
assert chunk_size > 0 # to avoid infinite loop
|
|
while audio_segment[
|
|
trim_ms : trim_ms + chunk_size
|
|
].dBFS < silence_threshold and trim_ms < len(audio_segment):
|
|
trim_ms += chunk_size
|
|
|
|
# if there is no end it should return the length of the segment
|
|
return min(trim_ms, len(audio_segment))
|
|
|
|
|
|
def significant_fade(audio_segment, fade_threshold=-20.0, chunk_size=10):
|
|
"""
|
|
Returns the millisecond/index of the point where the fade is down to
|
|
fade_threshold and doesn't get louder again.
|
|
audio_segment - the segment to find silence in
|
|
fade_threshold - the upper bound for how quiet is silent in dFBS
|
|
chunk_size - chunk size for interating over the segment in ms
|
|
"""
|
|
|
|
assert chunk_size > 0 # to avoid infinite loop
|
|
|
|
segment_length = audio_segment.duration_seconds * 1000 # ms
|
|
trim_ms = segment_length - chunk_size
|
|
while (
|
|
audio_segment[trim_ms : trim_ms + chunk_size].dBFS < fade_threshold
|
|
and trim_ms > 0
|
|
):
|
|
trim_ms -= chunk_size
|
|
|
|
# if there is no trailing silence, return lenght of track (it's less
|
|
# the chunk_size, but for chunk_size = 10ms, this may be ignored)
|
|
return trim_ms
|
|
|
|
|
|
def trailing_silence(audio_segment, silence_threshold=-50.0, chunk_size=10):
|
|
"""
|
|
Returns the millisecond/index that the trailing silence starts.
|
|
audio_segment - the segment to find silence in
|
|
silence_threshold - the upper bound for how quiet is silent in dFBS
|
|
chunk_size - chunk size for interating over the segment in ms
|
|
"""
|
|
|
|
assert chunk_size > 0 # to avoid infinite loop
|
|
|
|
segment_length = audio_segment.duration_seconds * 1000 # ms
|
|
trim_ms = segment_length - chunk_size
|
|
while (
|
|
audio_segment[trim_ms : trim_ms + chunk_size].dBFS < silence_threshold
|
|
and trim_ms > 0
|
|
):
|
|
trim_ms -= chunk_size
|
|
|
|
# if there is no trailing silence, return lenght of track (it's less
|
|
# the chunk_size, but for chunk_size = 10ms, this may be ignored)
|
|
return trim_ms
|
|
|
|
|
|
def ms_to_mmss(ms, decimals=0):
|
|
if not ms:
|
|
return "-"
|
|
if ms < 0:
|
|
sign = "-"
|
|
else:
|
|
sign = ""
|
|
|
|
minutes, remainder = divmod(ms, 60 * 1000)
|
|
seconds = remainder / 1000
|
|
|
|
return f"{sign}{minutes:.0f}:{seconds:02.{decimals}f}"
|
|
|
|
|
|
# @tl.job(interval=timedelta(seconds=1))
|
|
def update_progress(player, talk_at, silent_at):
|
|
elapsed_time = player.get_time()
|
|
total_time = player.get_length()
|
|
remaining_time = total_time - elapsed_time
|
|
talk_time = remaining_time - (total_time - talk_at)
|
|
silent_time = remaining_time - (total_time - silent_at)
|
|
end_time = (dt.datetime.now() + timedelta(milliseconds=remaining_time)).strftime(
|
|
"%H:%M:%S"
|
|
)
|
|
print(
|
|
f"\t{ms_to_mmss(elapsed_time)}/"
|
|
f"{ms_to_mmss(total_time)}\t\t"
|
|
f"Talk in: {ms_to_mmss(talk_time)} "
|
|
f"Silent in: {ms_to_mmss(silent_time)} "
|
|
f"Ends at: {end_time} [{ms_to_mmss(remaining_time)}]",
|
|
end="\r",
|
|
)
|
|
|
|
|
|
# Print name of current song, print name of next song. Play current when
|
|
# return pressed, Pri--current-song-output-lengthnt remaining time every
|
|
# second. When it ends, print name of new current and next song.
|
|
|
|
|
|
def test():
|
|
track = "wibg.mp3"
|
|
segment = AudioSegment.from_mp3(track)
|
|
print(f"Track: {track}")
|
|
print(f"Leading silence: {ms_to_mmss(leading_silence(segment), decimals=1)}")
|
|
talk_at = significant_fade(segment)
|
|
silent_at = trailing_silence(segment)
|
|
print(f"Talkover fade: {ms_to_mmss(talk_at)}")
|
|
print(f"Track silent from: {ms_to_mmss(silent_at)}")
|
|
p = vlc.MediaPlayer("wibg.mp3")
|
|
_ = input("")
|
|
p.play()
|
|
print()
|
|
rt = RepeatedTimer(0.5, update_progress, p, talk_at, silent_at)
|
|
sleep(1)
|
|
while p.is_playing():
|
|
sleep(1)
|
|
rt.stop() # better in a try/finally block to make sure the program ends!
|
|
print("End")
|
|
|
|
|
|
test()
|
|
# next_song = get_next_song
|
|
#
|
|
# def play_track():
|
|
# r = run_aud_cmd("--current-song-length
|
|
#
|
|
#
|
|
#
|
|
# def play():
|
|
# play_track()
|
|
# songtimer_start()
|
|
#
|
|
#
|
|
# print("Start playing in 3 seconds")
|
|
#
|
|
# sleep(3)
|
|
#
|
|
# play()
|
|
|
|
|
|
# print("starting...")
|
|
# This auto-starts, no need of rt.start()
|
|
# rt = RepeatedTimer(0.3, hello, "World")
|
|
#
|
|
# count = 5
|
|
# while count > 0:
|
|
# print("main job here")
|
|
# sleep(2)
|
|
# rt.stop() # better in a try/finally block to make sure the program ends!
|
|
|
|
|
|
# #!/usr/bin/python3
|
|
#
|
|
# import time
|
|
# from timeloop import Timeloop
|
|
# from datetime import timedelta
|
|
#
|
|
# tl = Timeloop()
|
|
#
|
|
# @tl.job(interval=timedelta(seconds=2))
|
|
# def sample_job_every_2s():
|
|
# print(f"2s job current time: {time.ctime()}")
|
|
#
|
|
# @tl.job(interval=timedelta(seconds=5))
|
|
# def sample_job_every_5s():
|
|
# print(f"5s job current time: {time.ctime()}")
|
|
#
|
|
# @tl.job(interval=timedelta(seconds=10))
|
|
# def sample_job_every_10s():
|
|
# print(f"10s job current time: {time.ctime()}")
|
|
#
|
|
# @tl.job(interval=timedelta(seconds=0.2))
|
|
# def sample_job_every_10s():
|
|
# print(f"200ms job current time: {time.ctime()}")
|
|
#
|
|
# if __name__ == "__main__":
|
|
# tl.start(block=True)
|
|
#
|