diff --git a/YouTubeMDBot/__init__.py b/YouTubeMDBot/__init__.py
index 8a858f1..a7f5f48 100755
--- a/YouTubeMDBot/__init__.py
+++ b/YouTubeMDBot/__init__.py
@@ -13,3 +13,34 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+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
diff --git a/YouTubeMDBot/api/youtube_api.py b/YouTubeMDBot/api/youtube_api.py
index 1260023..d8ff82f 100755
--- a/YouTubeMDBot/api/youtube_api.py
+++ b/YouTubeMDBot/api/youtube_api.py
@@ -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 = ""
@@ -77,7 +98,12 @@ 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
@@ -85,7 +111,12 @@ def __init__(self):
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 @@ 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)
diff --git a/YouTubeMDBot/audio/ffmpeg.py b/YouTubeMDBot/audio/ffmpeg.py
index 53d199f..f94c5ae 100755
--- a/YouTubeMDBot/audio/ffmpeg.py
+++ b/YouTubeMDBot/audio/ffmpeg.py
@@ -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,
@@ -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 @@ def convert(self) -> int:
class FFmpegOGG(FFmpegExporter):
+ """
+ Exports audio data to OGG format.
+ """
def convert(self) -> int:
command = super().get_command()
if self._bitrate:
diff --git a/YouTubeMDBot/utils/__init__.py b/YouTubeMDBot/utils/__init__.py
index 1315c64..d94ea00 100755
--- a/YouTubeMDBot/utils/__init__.py
+++ b/YouTubeMDBot/utils/__init__.py
@@ -13,5 +13,5 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-from ..utils import youtube_utils
+from ..utils.youtube_utils import get_yt_video_id
from ..utils.timeout import timeout