From 38fe5cccea560732739cb6c16890d913d21842ba Mon Sep 17 00:00:00 2001 From: Parth Parikh Date: Tue, 16 May 2023 15:15:24 -0400 Subject: [PATCH 1/5] Added basic logging #19 Suppressed logs from TTS and FFmpeg as they were adding in noise to the output --- radio.py | 106 ++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 78 insertions(+), 28 deletions(-) diff --git a/radio.py b/radio.py index bad5960..fe53e28 100644 --- a/radio.py +++ b/radio.py @@ -12,11 +12,13 @@ import datetime import glob import json +import logging import random import os from pathlib import Path import re import subprocess +import sys import time import urllib.request import uuid @@ -51,6 +53,26 @@ PATH = _CONFIG["PATH"] TTS = _CONFIG["TTS"] +_logger = logging.getLogger() +_logger.setLevel(logging.INFO) + + +class SuppressThirdPartyLogs: + """ + Suppresses print statements from third party libraries like + Coqui-ai's TTS and FFmpeg + From: https://stackoverflow.com/a/45669280/7543474 + Licensed under CC BY-SA 4.0 + """ + + def __enter__(self): + self._original_stdout = sys.stdout + sys.stdout = open(os.devnull, "w") + + def __exit__(self, exc_type, exc_val, exc_tb): + sys.stdout.close() + sys.stdout = self._original_stdout + class Recommend: """ @@ -326,9 +348,11 @@ def sprinkle_gpt(self): """ company, speech = self.rec.advertisement() if speech is not None: + logging.info(f"Generating fictional advertisement for {company}.") end = f" Thank you {company} for sponsoring today's broadcast. " return speech + end if self.rec.question is None: + logging.info("Generating daily question.") ques = self.rec.daily_question() speech = ( "And now it is time for today's daily question. " @@ -338,6 +362,7 @@ def sprinkle_gpt(self): ) return speech if self.rec.question: + logging.info("Generating daily question response.") ans = self.rec.daily_question(question=False) fname, lname, loc = self.rec.person() speech = ( @@ -364,6 +389,7 @@ def news(self, category, k): """ Speech to convey the daily news for a specific category """ + logging.info(f"Fetching {k} news items for {category}.") articles = self.rec.news(category, k) start = f"Now for today's {category} news. " end = f"And that's the {category} news." @@ -378,6 +404,7 @@ def weather(self, location): """ Speech for the weather forecast """ + logging.info(f"Fetching weather forecast for {location}.") forecast = self.rec.weather(location) loc = location if location is not None else "your region" speech = ( @@ -409,6 +436,7 @@ def music(self, song, artist): args.choice = 1 args.quiet = True url, _ = ytmdl.core.search(args.SONG_NAME[0], args) + logging.info(f"Fetching song from {url}.") # Download the song with metadata ydl_opts = { @@ -483,6 +511,9 @@ def podcast_clip(self, rss_feed, duration): """ # Download the podcast parsed = podcastparser.parse(rss_feed, urllib.request.urlopen(rss_feed)) + logging.info( + f"Finding a relevant clip from podcast - {parsed['title']} from {parsed['itunes_author']}." + ) podcast_link = parsed["episodes"][0]["enclosures"][0]["url"] audio_file = f"{self.audio_dir}/a{self.index}.mp3" subprocess.run( @@ -566,6 +597,9 @@ def podcast_clip(self, rss_feed, duration): optimal_clip = podcast_audio[optimal_start * 1000 : optimal_end * 1000] except IndexError: # If no silence is found, just take the first "duration" minutes + logging.warning( + f"No relevant podcast clip found. Using the first {duration} minutes." + ) optimal_start, optimal_end = None, None optimal_clip = AudioSegment.from_mp3(audio_file)[: duration_sec * 1000] @@ -593,6 +627,9 @@ def music_meta(self, song, artist, is_local, start=True): try: itunes_metadata = itunespy.search_track(song, country="US", limit=100) except: + logging.warning( + "Metadata search failed. Trying again after 80 seconds." + ) time.sleep(80) itunes_metadata = itunespy.search_track(song, country="US", limit=100) most_accurate = sorted( @@ -659,13 +696,17 @@ def flow(self): This includes generating segments, synthesizing it, merging it into one mp3, and cleaning up the temporary files """ + logging.info("Creating a broadcast.") self.synthesizer = self.init_speech() for action, meta in self.schema: + logging.info(f"Generating {action} segment.") speech = None if action == "no-ads": self.rec.ad_prob = 0 + logging.info("Disabled ads.") elif action == "no-qna": self.rec.question = False + logging.info("Disabled QnA.") elif action == "up": speech = self.wakeup() self.speak(speech, announce=True) @@ -678,9 +719,7 @@ def flow(self): if not is_local: error = self.music(song, artist) if error: - # At this point, it is likely that the - # song name is garbage. It is best to skip this song, - # than to show the user some random song + logging.warning(f"Failed to download {song}. Skipping.") continue speech = self.music_meta(song, artist, is_local) self.speak(speech, announce=True) @@ -692,6 +731,7 @@ def flow(self): elif action == "podcast": rss_feed, duration = meta if duration is None: + logging.warning("Duration not specified. Setting it to 15 mins.") duration = 15 speech = self.podcast_dialogue(rss_feed) self.speak(speech, announce=True) @@ -713,6 +753,7 @@ def flow(self): self.speak(speech, announce=True) self.radio() self.cleanup() + logging.info("Broadcast created.") return 0 def radio(self): @@ -741,6 +782,7 @@ def cleanup(self): if Path(dest).is_file(): os.remove(dest) convert = FFmpeg( + global_options=["-hide_banner", "-loglevel", "error"], inputs={src: None}, outputs={dest: ["-acodec", "libmp3lame", "-b:a", "128k"]}, ) @@ -846,6 +888,7 @@ def background_music(self): """ Background music is added during announcements """ + logging.info("Adding background music in this announcement.") background = AudioSegment.from_wav(PATH["backg_music"]) background -= 25 * (1 / TTS["backg_music_vol"]) # reduce the volume speech = AudioSegment.from_wav(f"{self.audio_dir}/a{self.index - 1}.wav") @@ -864,27 +907,30 @@ def init_speech(self): """ Initializes the synthesizer for tts """ - args = create_argparser().parse_args() - args.model_name = "tts_models/en/vctk/vits" - path = Path(tts_path).parent / "./.models.json" - manager = ModelManager(path) - model_path, config_path, model_item = manager.download_model(args.model_name) - args.vocoder_name = ( - model_item["default_vocoder"] - if args.vocoder_name is None - else args.vocoder_name - ) - synthesizer = Synthesizer( - tts_checkpoint=model_path, - tts_config_path=config_path, - tts_speakers_file=None, - tts_languages_file=None, - vocoder_checkpoint=None, - vocoder_config=None, - encoder_checkpoint="", - encoder_config="", - use_cuda=args.use_cuda, - ) + with SuppressThirdPartyLogs(): + args = create_argparser().parse_args() + args.model_name = "tts_models/en/vctk/vits" + path = Path(tts_path).parent / "./.models.json" + manager = ModelManager(path) + model_path, config_path, model_item = manager.download_model( + args.model_name + ) + args.vocoder_name = ( + model_item["default_vocoder"] + if args.vocoder_name is None + else args.vocoder_name + ) + synthesizer = Synthesizer( + tts_checkpoint=model_path, + tts_config_path=config_path, + tts_speakers_file=None, + tts_languages_file=None, + vocoder_checkpoint=None, + vocoder_config=None, + encoder_checkpoint="", + encoder_config="", + use_cuda=args.use_cuda, + ) return synthesizer def save_speech(self, text): @@ -895,12 +941,16 @@ def save_speech(self, text): the sound is a bit monotonic. To mitigate this, it is nice to have a deep voice. """ + if text.strip() != "": + logging.info(f"Synthesizing speech for - {text}") if not hasattr(self, "synthesizer"): - self.synthesizer = self.init_speech() + with SuppressThirdPartyLogs(): + self.synthesizer = self.init_speech() if text: - wavs = self.synthesizer.tts( - text, speaker_name=TTS["speaker_name"], style_wav="" - ) + with SuppressThirdPartyLogs(): + wavs = self.synthesizer.tts( + text, speaker_name=TTS["speaker_name"], style_wav="" + ) self.synthesizer.save_wav(wavs, f"{self.audio_dir}/a{self.index}.wav") self.index += 1 From 8209b94eef79432ed2981899e55f9883b0c6d2c6 Mon Sep 17 00:00:00 2001 From: Parth Parikh Date: Tue, 16 May 2023 15:21:20 -0400 Subject: [PATCH 2/5] Rename to SuppressTTSLogs #19 --- radio.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/radio.py b/radio.py index fe53e28..00fa112 100644 --- a/radio.py +++ b/radio.py @@ -57,10 +57,9 @@ _logger.setLevel(logging.INFO) -class SuppressThirdPartyLogs: +class _SuppressTTSLogs: """ - Suppresses print statements from third party libraries like - Coqui-ai's TTS and FFmpeg + Suppresses print statements from Coqui-ai's TTS From: https://stackoverflow.com/a/45669280/7543474 Licensed under CC BY-SA 4.0 """ @@ -907,7 +906,7 @@ def init_speech(self): """ Initializes the synthesizer for tts """ - with SuppressThirdPartyLogs(): + with _SuppressTTSLogs(): args = create_argparser().parse_args() args.model_name = "tts_models/en/vctk/vits" path = Path(tts_path).parent / "./.models.json" @@ -944,10 +943,10 @@ def save_speech(self, text): if text.strip() != "": logging.info(f"Synthesizing speech for - {text}") if not hasattr(self, "synthesizer"): - with SuppressThirdPartyLogs(): + with _SuppressTTSLogs(): self.synthesizer = self.init_speech() if text: - with SuppressThirdPartyLogs(): + with _SuppressTTSLogs(): wavs = self.synthesizer.tts( text, speaker_name=TTS["speaker_name"], style_wav="" ) From d0bf21e70008c4f2e22ee3fbdd365ccd2827f6b4 Mon Sep 17 00:00:00 2001 From: Parth Parikh Date: Tue, 16 May 2023 16:17:09 -0400 Subject: [PATCH 3/5] Remove noise from musicbrainzngs, ffmpeg logs #19 --- radio.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/radio.py b/radio.py index 00fa112..393672f 100644 --- a/radio.py +++ b/radio.py @@ -55,7 +55,7 @@ _logger = logging.getLogger() _logger.setLevel(logging.INFO) - +logging.getLogger('musicbrainzngs').setLevel(logging.WARNING) class _SuppressTTSLogs: """ @@ -498,7 +498,7 @@ def podcast_dialogue(self, rss_feed, start=True): else: speech = ( "Wow! That was something, wasn't it? " - "The podcast you just listened to was" + "The podcast you just listened to was " f"{parsed['title']} from {parsed['itunes_author']}. " "If you enjoyed it, please do check them out." ) @@ -549,6 +549,9 @@ def podcast_clip(self, rss_feed, duration): pipe = subprocess.Popen( [ "ffmpeg", + "-hide_banner", + "-loglevel", + "error", "-i", f"{audio_file}", "-f", @@ -874,7 +877,7 @@ def slow_it_down(self, start_index): src = f"{self.audio_dir}/a{index}.wav" dest = f"{self.audio_dir}/out.wav" slowit = FFmpeg( - global_options=["-y"], + global_options=["-y", "-hide_banner", "-loglevel", "error"], inputs={src: None}, outputs={dest: ["-filter:a", "atempo=0.85"]}, ) From 0a4492dbeba4a8d0f5c67d7ef0f4db7ffa7033e1 Mon Sep 17 00:00:00 2001 From: Parth Parikh Date: Tue, 16 May 2023 16:35:47 -0400 Subject: [PATCH 4/5] Add log for post-processing part #19 --- radio.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/radio.py b/radio.py index 393672f..4a9eb85 100644 --- a/radio.py +++ b/radio.py @@ -55,7 +55,8 @@ _logger = logging.getLogger() _logger.setLevel(logging.INFO) -logging.getLogger('musicbrainzngs').setLevel(logging.WARNING) +logging.getLogger("musicbrainzngs").setLevel(logging.WARNING) + class _SuppressTTSLogs: """ @@ -549,8 +550,8 @@ def podcast_clip(self, rss_feed, duration): pipe = subprocess.Popen( [ "ffmpeg", - "-hide_banner", - "-loglevel", + "-hide_banner", + "-loglevel", "error", "-i", f"{audio_file}", @@ -753,6 +754,10 @@ def flow(self): elif action == "end": speech = self.over() self.speak(speech, announce=True) + logging.info( + "Starting post-processing to create the final broadcast. " + "This may take a while." + ) self.radio() self.cleanup() logging.info("Broadcast created.") @@ -784,7 +789,7 @@ def cleanup(self): if Path(dest).is_file(): os.remove(dest) convert = FFmpeg( - global_options=["-hide_banner", "-loglevel", "error"], + global_options=["-loglevel", "error"], inputs={src: None}, outputs={dest: ["-acodec", "libmp3lame", "-b:a", "128k"]}, ) From aa8fd7839c1b93f9bdc723444048ea42f2b6a866 Mon Sep 17 00:00:00 2001 From: Parth Parikh Date: Wed, 17 May 2023 14:03:40 -0400 Subject: [PATCH 5/5] Synthesizing speech log is more distinct #19 --- radio.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/radio.py b/radio.py index 4a9eb85..35f4c02 100644 --- a/radio.py +++ b/radio.py @@ -949,7 +949,7 @@ def save_speech(self, text): To mitigate this, it is nice to have a deep voice. """ if text.strip() != "": - logging.info(f"Synthesizing speech for - {text}") + logging.info(f"Synthesizing speech for => {text}") if not hasattr(self, "synthesizer"): with _SuppressTTSLogs(): self.synthesizer = self.init_speech()