Commit e55eb1a7 authored by Javinator9889's avatar Javinator9889 🎼

pylint improvement - completed API and ffmpeg audio classes & methods

parent 9b5880d8
Pipeline #87 failed with stage
in 13 minutes and 39 seconds
......@@ -13,3 +13,34 @@
#
# 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 .api import YouTubeAPI
from .api import YouTubeVideoData
from .audio import FPCalc
from .audio import FFmpegOGG
from .audio import FFmpegMP3
from .audio import FFmpegOpener
from .audio import ffmpeg_available
from .commands import StartHandler
from .constants import *
from .decorators import restricted
from .decorators import send_action
from .downloader import YouTubeDownloader
from .errors import ProcessorError
from .errors import EmptyBodyError
from .errors import NoMatchError
from .logging_utils import LoggingHandler
from .logging_utils import setup_logging
from .metadata import AudioMetadata
from .metadata import MetadataIdentifier
from .metadata import YouTubeMetadataIdentifier
from .utils import get_yt_video_id
from .utils import timeout
......@@ -19,8 +19,29 @@ from ..constants import YOUTUBE
from ..errors import EmptyBodyError
class YouTubeVideoData(object):
class YouTubeVideoData:
"""
Obtains YouTube video data and wraps it inside this class. All fields are direct
access available, so it is possible to access them directly:
- title
- id
- thumbnail
- artist
- duration
- views
- likes
- dislikes
"""
def __init__(self, data: dict, ignore_errors: bool = False):
"""
By passing a dict with the YouTube data (YouTube API v3), generate and obtain
the information available from the result.
:param data: a dictionary with the information obtained from YouTube API.
:param ignore_errors: whether to ignore or not errors (do not raise exceptions).
:raises EmptyBodyError when there is no information available and ignored
errors is False.
"""
if not data.get("items"):
raise EmptyBodyError("The data object has no items")
self.id: str = ""
......@@ -77,7 +98,12 @@ class YouTubeVideoData(object):
self.dislikes = int(statistics["dislikeCount"])
class YouTubeAPI(object):
class YouTubeAPI:
"""
Wrapper for the YouTube API data. Allows the developer searching for videos and,
with a given video ID, obtain its data.
"""
def __init__(self):
from googleapiclient.discovery import build
......@@ -85,7 +111,12 @@ class YouTubeAPI(object):
version=YOUTUBE["api"]["version"],
developerKey=YOUTUBE["key"])
def search(self, term: str):
def search(self, term: str) -> dict:
"""
Searchs for a video with the specified term.
:param term: the search term.
:return: dict with YouTube data - can be wrapped inside "YouTubeVideoData" class.
"""
return self.__youtube.search().list(
q=term,
type="video",
......@@ -95,12 +126,18 @@ class YouTubeAPI(object):
@staticmethod
def video_details(video_id: str) -> YouTubeVideoData:
"""
Generates a "YouTubeVideoData" object wrapper with the video ID information.
:param video_id: YouTube video ID.
:return: YouTubeVideoData object with the available metadata.
"""
try:
import ujson as json
except ImportError:
import json
from urllib.request import urlopen
api_url = YOUTUBE["endpoint"].format(video_id, YOUTUBE["key"])
youtube_information = YOUTUBE.copy()
api_url = youtube_information["endpoint"].format(video_id, YOUTUBE["key"])
data = urlopen(url=api_url)
return YouTubeVideoData(data=json.loads(data.read()))
return YouTubeVideoData(data=json.loads(data.read()), ignore_errors=True)
......@@ -24,6 +24,10 @@ from ..constants import FFMPEG_CONVERTER
def ffmpeg_available() -> bool:
"""
Checks if "ffmpeg" is installed or not.
:return: True if installed, else False
"""
try:
proc = Popen(["ffmpeg", "-version"],
stdout=PIPE,
......@@ -36,46 +40,107 @@ def ffmpeg_available() -> bool:
class FFmpeg(ABC):
"""
Base abstract class for the FFmpeg operators. All classes that works with FFmpeg
must inherit from this class in order to maintain readability and code optimization.
Allows execution of the ffmpeg command by using the subprocess module. Everything
is working with PIPEs, so there is no directly discs operations (everything is
loaded and working with RAM).
"""
def __init__(self, data: bytes, command: List[str] = None):
"""
Creates the class by passing the data which will be processed and the command (
by default, None).
:param data: audio data that will be processed.
:param command: the ffmpeg command.
"""
self._data = data
self.__command = command
self.__out = None
self.__err = None
def process(self) -> int:
"""
Runs the ffmpeg command in a separate process and pipes both stdout and stderr.
:return: the return code of the operation ('0' if everything is OK, > 0 if not).
"""
proc = Popen(self.__command, stdout=PIPE, stderr=PIPE, stdin=PIPE)
self.__out, self.__err = proc.communicate(self._data)
return proc.returncode
def get_command(self) -> List[str]:
"""
Get the command for editing.
:return: List[str] with the command - as this is a pointer, all editions done
to the list are directly changing the self object.
"""
return self.__command
def set_command(self, command: List[str]):
"""
Sets the new list, overriding every old implementation.
:param command: the new command.
"""
self.__command = command
def get_output(self) -> bytes:
"""
Gets the stdout of the process.
:return: bytes with the command output.
"""
return self.__out
def get_extra(self) -> bytes:
"""
Gets the stderr of the process.
:return: bytes with extra information.
"""
return self.__err
class FFmpegOpener(FFmpeg):
"""
Opens and produces and audio in PWM mode.
"""
def __init__(self, data: bytes):
super().__init__(data=data, command=FFMPEG_OPENER.copy())
class FFmpegExporter(FFmpeg):
"""
Base class for the exporter options available in ffmpeg.
All classes that are developed for converting audio files must inherit from this
class and implement the "convert" method.
"""
def __init__(self, data: bytes, bitrate: str = None):
"""
Generates a new instance of the class.
:param data: the audio data.
:param bitrate: the new bitrate of the audio data, or None for keeping its
default.
"""
super().__init__(data=data, command=FFMPEG_CONVERTER.copy())
self._bitrate = bitrate
@abstractmethod
def convert(self) -> int:
"""
Converts the audio to the desired format.
:return: the operation result code.
:raises NotImplementedError when trying to access this method directly on super
class.
"""
raise NotImplementedError
class FFmpegMP3(FFmpegExporter):
"""
Exports audio data to MP3 format.
"""
def convert(self) -> int:
command = super().get_command()
if self._bitrate:
......@@ -90,6 +155,9 @@ class FFmpegMP3(FFmpegExporter):
class FFmpegOGG(FFmpegExporter):
"""
Exports audio data to OGG format.
"""
def convert(self) -> int:
command = super().get_command()
if self._bitrate:
......
......@@ -13,5 +13,5 @@
#
# 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 import youtube_utils
from ..utils.youtube_utils import get_yt_video_id
from ..utils.timeout import timeout
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