Commit 2457dd7f authored by Javinator9889's avatar Javinator9889 🎼

Finished pylint - code refactored and removed unnecessary classes

parent 6e6ade3d
Pipeline #89 failed with stage
in 10 minutes and 28 seconds
......@@ -18,9 +18,6 @@ from functools import wraps
from ..constants import PROGRAM_ARGS
# logging = LoggingHandler()
def send_action(action):
"""
Sends an action while processing a command.
......
......@@ -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
# 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 <http://www.gnu.org/licenses/>.
class InvalidCredentialsError(Exception):
pass
# 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 <http://www.gnu.org/licenses/>.
class NoMatchError(Exception):
"""Raises an error when there is no match available"""
pass
# 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 <http://www.gnu.org/licenses/>.
class ProcessorError(Exception):
"""Raises an exception when FFmpeg processing fails"""
pass
......@@ -14,5 +14,3 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from ..errors.EmptyBodyError import EmptyBodyError
from ..errors.NoMatchError import NoMatchError
from ..errors.ProcessorError import ProcessorError
......@@ -17,6 +17,10 @@ import logging
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 @@ class LoggingHandler(object):
__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 @@ class LoggingHandler(object):
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()
......@@ -19,27 +19,60 @@ from io import BytesIO
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
......@@ -13,13 +13,12 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
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 ..constants import ACOUSTID_KEY
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 @@ class MetadataIdentifier(object):
@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 @@ class MetadataIdentifier(object):
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 @@ class MetadataIdentifier(object):
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 @@ class YouTubeMetadataIdentifier(MetadataIdentifier):
self.youtube_data = True
valid = True
return valid
......@@ -14,4 +14,3 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from ..utils.youtube_utils import get_yt_video_id
from ..utils.timeout import timeout
# 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 <http://www.gnu.org/licenses/>.
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)'''
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment