diff --git a/YouTubeMDBot/decorators/decorators.py b/YouTubeMDBot/decorators/decorators.py index 71b67ac..b3463c4 100755 --- a/YouTubeMDBot/decorators/decorators.py +++ b/YouTubeMDBot/decorators/decorators.py @@ -18,9 +18,6 @@ from ..constants import PROGRAM_ARGS -# logging = LoggingHandler() - - def send_action(action): """ Sends an action while processing a command. diff --git a/YouTubeMDBot/errors/EmptyBodyError.py b/YouTubeMDBot/errors/EmptyBodyError.py index 4706bda..a2e8f81 100755 --- a/YouTubeMDBot/errors/EmptyBodyError.py +++ b/YouTubeMDBot/errors/EmptyBodyError.py @@ -16,4 +16,8 @@ class EmptyBodyError(Exception): + """ + Raises an exception when the body of the json data is empty (e.g.: there is no + video information) + """ pass diff --git a/YouTubeMDBot/errors/InvalidCredentialsError.py b/YouTubeMDBot/errors/InvalidCredentialsError.py deleted file mode 100755 index 9b3d8c3..0000000 --- a/YouTubeMDBot/errors/InvalidCredentialsError.py +++ /dev/null @@ -1,19 +0,0 @@ -# YouTubeMDBot -# Copyright (C) 2019 - Javinator9889 -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - - -class InvalidCredentialsError(Exception): - pass diff --git a/YouTubeMDBot/errors/NoMatchError.py b/YouTubeMDBot/errors/NoMatchError.py deleted file mode 100755 index 69c5c3f..0000000 --- a/YouTubeMDBot/errors/NoMatchError.py +++ /dev/null @@ -1,20 +0,0 @@ -# YouTubeMDBot -# Copyright (C) 2019 - Javinator9889 -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - - -class NoMatchError(Exception): - """Raises an error when there is no match available""" - pass diff --git a/YouTubeMDBot/errors/ProcessorError.py b/YouTubeMDBot/errors/ProcessorError.py deleted file mode 100644 index 703bd49..0000000 --- a/YouTubeMDBot/errors/ProcessorError.py +++ /dev/null @@ -1,20 +0,0 @@ -# YouTubeMDBot -# Copyright (C) 2019 - Javinator9889 -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - - -class ProcessorError(Exception): - """Raises an exception when FFmpeg processing fails""" - pass diff --git a/YouTubeMDBot/errors/__init__.py b/YouTubeMDBot/errors/__init__.py index d9dd503..98ce0a2 100755 --- a/YouTubeMDBot/errors/__init__.py +++ b/YouTubeMDBot/errors/__init__.py @@ -14,5 +14,3 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . from ..errors.EmptyBodyError import EmptyBodyError -from ..errors.NoMatchError import NoMatchError -from ..errors.ProcessorError import ProcessorError diff --git a/YouTubeMDBot/logging_utils/utils.py b/YouTubeMDBot/logging_utils/utils.py index 309a620..3b6ac76 100755 --- a/YouTubeMDBot/logging_utils/utils.py +++ b/YouTubeMDBot/logging_utils/utils.py @@ -17,6 +17,10 @@ def cleanup_old_logs(log_file: str): + """ + Cleans-up the old log files. + :param log_file: log filename that must be cleaned. + """ import tarfile import os @@ -33,6 +37,14 @@ def cleanup_old_logs(log_file: str): def setup_logging(logger_name: str, log_file: str, level=logging.DEBUG, formatter: str = "%(process)d - %(asctime)s | [%(levelname)s]: %(message)s"): + """ + Creates a new logging which can log to stdout or file. + :param logger_name: the logger name. + :param log_file: log filename. + :param level: logging level. + :param formatter: the logging formatter. + :return: the logging file handler. + """ from os import path from os import makedirs @@ -53,8 +65,12 @@ def setup_logging(logger_name: str, log_file: str, level=logging.DEBUG, return logging_file_handler -class LoggingHandler(object): - class __LoggingHandler(object): +class LoggingHandler: + """ + LoggingHandler singleton class that outputs to multiple logging instances. + """ + + class __LoggingHandler: def __init__(self, logs: list): self.__logs = logs @@ -84,10 +100,18 @@ def get_loggers(self) -> list: __instance = None def __new__(cls, *args, **kwargs): + """ + Generates a new instance. + :param args: not used. + :param kwargs: "logs" is a list instance that must be provided the first time + this class is created. + :return: the LoggingHandler instance. + """ if not LoggingHandler.__instance: logs = kwargs.get("logs") if not logs or len(logs) == 0: - raise AttributeError("At least kwarg \"log\" (a list of the loggers) must be provided") + raise AttributeError( + "At least kwarg \"log\" (a list of the loggers) must be provided") LoggingHandler.__instance = LoggingHandler.__LoggingHandler(logs) return LoggingHandler.__instance @@ -98,19 +122,43 @@ def __setattr__(self, key, value): return setattr(self.__instance, key, value) def debug(self, msg): + """ + Debugs to loggers + :param msg: message to debug + """ self.__instance.debug(msg) def info(self, msg): + """ + Info to loggers + :param msg: message to info + """ self.__instance.info(msg) def error(self, msg): + """ + Error to loggers + :param msg: message to error + """ self.__instance.error(msg) def warning(self, msg): + """ + Warns to loggers + :param msg: message to warn + """ self.__instance.warning(msg) def critical(self, msg): + """ + Critical to loggers + :param msg: message to critical + """ self.__instance.critical(msg) def get_loggers(self) -> list: + """ + Obtains the list of loggers. + :return: the list of loggers. + """ return self.__instance.get_loggers() diff --git a/YouTubeMDBot/metadata/AudioMetadata.py b/YouTubeMDBot/metadata/AudioMetadata.py index 179dc29..77754e1 100755 --- a/YouTubeMDBot/metadata/AudioMetadata.py +++ b/YouTubeMDBot/metadata/AudioMetadata.py @@ -19,27 +19,60 @@ class AudioMetadata: + """ + Wrapper class for setting the audio metadata to the downloaded YouTube video + object. By using this class, it is possible to set the required information by + using mutagen without having to remember the metadata keys. + """ def __init__(self, audio: BytesIO): + """ + Generates a new instance. + :param audio: the audio metadata, in BytesIO, in MP4 format. + """ self._audio = MP4(audio) self._data = audio def set_title(self, title: str): + """ + Sets the audio title. + :param title: the audio title. + """ self._audio[u"\xa9nam"] = title def set_artist(self, artist: str): + """ + Sets the audio artist. + :param artist: the audio artist. + """ self._audio[u"\xa9ART"] = artist def set_album(self, album: str): + """ + Sets the audio album. + :param album: the audio album + """ self._audio[u"\xa9alb"] = album def set_extras(self, extras: list): + """ + Sets the audio extras. + :param extras: a list of extras that will be added to the audio information. + """ self._audio[u"\xa9cmt"] = '; '.join(map(str, extras)) def set_cover(self, cover: bytes): + """ + Sets the audio cover. + :param cover: the audio cover. + """ mp4_cover = MP4Cover(cover, MP4Cover.FORMAT_JPEG) self._audio[u"covr"] = [mp4_cover] def save(self) -> BytesIO: + """ + Saves the new metadata into the audio file object. + :return: the audio file object with the new metadata. + """ self._data.seek(0) self._audio.save(self._data) return self._data diff --git a/YouTubeMDBot/metadata/MetadataIdentifier.py b/YouTubeMDBot/metadata/MetadataIdentifier.py index c724232..d3396aa 100755 --- a/YouTubeMDBot/metadata/MetadataIdentifier.py +++ b/YouTubeMDBot/metadata/MetadataIdentifier.py @@ -13,13 +13,12 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import acoustid -import musicbrainzngs - try: import ujson as json except ImportError: import json +import acoustid +import musicbrainzngs from ..audio import FPCalc from ..api import YouTubeAPI @@ -28,8 +27,30 @@ from ..downloader import YouTubeDownloader -class MetadataIdentifier(object): +class MetadataIdentifier: + """ + Base identifier class. By using the audio data, calculates and generates a + fingerprint for searching across the MusicBrainz database. + + Once the audio has been identified, the available params and information are: + - audio (bytes) + - result (json) + - artist (str) + - title (str) + - release_id (str) + - recording_id (str) + - score (float) + - cover (bytes) + - album (str) + - duration (int) + - youtube_data (bool) + - youtube_id (str) + """ def __init__(self, audio: bytes): + """ + Generates a new instance of the MetadataIdentifier class. + :param audio: the audio data, in bytes. + """ self.audio = audio self.result: json = None self.artist: str = "" @@ -45,6 +66,12 @@ def __init__(self, audio: bytes): @staticmethod def _is_valid_result(data: json) -> bool: + """ + Checks whether the obtained result, in json, is valid or not, by checking for + certain keys that must exist. + :param data: the result in json. + :return: 'True' if the result is valid, else 'False'. + """ if "results" not in data: return False elif data["status"] != "ok": @@ -58,6 +85,12 @@ def _is_valid_result(data: json) -> bool: return True def identify_audio(self) -> bool: + """ + Tries to identify the audio by using the audio fingerprint. If the audio has + been successfully identified, then obtains all the data related to it. + :return: 'True' if the result is valid (the audio was correctly identified), + else 'False'. + """ fingerprint = FPCalc(self.audio) data: json = acoustid.lookup(apikey=ACOUSTID_KEY, fingerprint=fingerprint.fingerprint(), @@ -91,7 +124,35 @@ def identify_audio(self) -> bool: class YouTubeMetadataIdentifier(MetadataIdentifier): + """ + Identifies YouTube metadata by using MusicBrainz database and YouTube metadata. If + the first identification (MusicBrainz) fails, then fallback to YouTube + identification if the "downloader" was provided. + + Once the audio has been identified, the available params and information are: + - audio (bytes) + - result (json) + - artist (str) + - title (str) + - release_id (str) + - recording_id (str) + - score (float) + - cover (bytes) + - album (str) + - duration (int) + - youtube_data (bool) + - youtube_id (str) + + If "youtube_data" is True, then only audio, title, artist, duration, cover and + youtube_id are available. + """ def __init__(self, audio: bytes, downloader: YouTubeDownloader = None): + """ + Generates a new instance of the MetadataIdentifier class. + :param audio: the audio data, in bytes. + :param downloader: a downloader object, for obtaining the video information if + MusicBrainz fails. + """ super().__init__(audio) self._downloader = downloader @@ -111,5 +172,4 @@ def identify_audio(self) -> bool: self.youtube_data = True valid = True - return valid diff --git a/YouTubeMDBot/utils/__init__.py b/YouTubeMDBot/utils/__init__.py index d94ea00..9fe1f3e 100755 --- a/YouTubeMDBot/utils/__init__.py +++ b/YouTubeMDBot/utils/__init__.py @@ -14,4 +14,3 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . from ..utils.youtube_utils import get_yt_video_id -from ..utils.timeout import timeout diff --git a/YouTubeMDBot/utils/timeout.py b/YouTubeMDBot/utils/timeout.py deleted file mode 100755 index 4ed87ae..0000000 --- a/YouTubeMDBot/utils/timeout.py +++ /dev/null @@ -1,56 +0,0 @@ -# YouTubeMDBot -# Copyright (C) 2019 - Javinator9889 -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -import signal - - -class timeout: - def __init__(self, secs: int): - self.__secs = secs - - def _raise_timeout(self, signum, frame): - raise TimeoutError("Function timeout! - {0}s".format(self.__secs)) - - def __enter__(self): - if self.__secs <= 0: - self._raise_timeout(0, 0) - - signal.signal(signal.SIGALRM, self._raise_timeout) - signal.alarm(self.__secs) - yield - - def __exit__(self, exc_type, exc_val, exc_tb): - signal.signal(signal.SIGALRM, signal.SIG_IGN) - return exc_val is not None - - -'''@contextmanager -def timeout(secs: int): - def raise_timeout(signum=None, frame=None): - raise TimeoutError("Function timeout! - {0}s".format(secs)) - - if secs <= 0: - secs = 0 - raise_timeout() - - signal.signal(signalnum=signal.SIGALRM, handler=raise_timeout) - signal.alarm(secs) - - try: - yield - except TimeoutError: - pass - finally: - signal.signal(signalnum=signal.SIGALRM, handler=signal.SIG_IGN)'''