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)'''