Skip to content

Commit

Permalink
pylint improvement - completed API and ffmpeg audio classes & methods
Browse files Browse the repository at this point in the history
  • Loading branch information
Javinator9889 committed Oct 10, 2019
1 parent 9b5880d commit e55eb1a
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 6 deletions.
31 changes: 31 additions & 0 deletions YouTubeMDBot/__init__.py
Expand Up @@ -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
47 changes: 42 additions & 5 deletions YouTubeMDBot/api/youtube_api.py
Expand Up @@ -19,8 +19,29 @@
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 = ""
Expand Down Expand Up @@ -77,15 +98,25 @@ def __init__(self, data: dict, ignore_errors: bool = False):
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

self.__youtube = build(serviceName=YOUTUBE["api"]["name"],
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",
Expand All @@ -95,12 +126,18 @@ def search(self, term: str):

@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)
68 changes: 68 additions & 0 deletions YouTubeMDBot/audio/ffmpeg.py
Expand Up @@ -24,6 +24,10 @@


def ffmpeg_available() -> bool:
"""
Checks if "ffmpeg" is installed or not.
:return: True if installed, else False
"""
try:
proc = Popen(["ffmpeg", "-version"],
stdout=PIPE,
Expand All @@ -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:
Expand All @@ -90,6 +155,9 @@ def convert(self) -> int:


class FFmpegOGG(FFmpegExporter):
"""
Exports audio data to OGG format.
"""
def convert(self) -> int:
command = super().get_command()
if self._bitrate:
Expand Down
2 changes: 1 addition & 1 deletion YouTubeMDBot/utils/__init__.py
Expand Up @@ -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

0 comments on commit e55eb1a

Please sign in to comment.