diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index f2b6970..7005c2c 100755
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -18,9 +18,8 @@ cache:
before_script:
- python -V # Print out python version for debugging
- - apt update
- - apt install -y libchromaprint-tools --install-recommends
- - apt install -y ffmpeg --install-recommends
+ - apt update && apt upgrade -y
+ - apt install -y libchromaprint-tools ffmpeg --install-recommends
- pip install -r YouTubeMDBot/requirements.txt
test:pylint:
diff --git a/YouTubeMDBot/audio/__init__.py b/YouTubeMDBot/audio/__init__.py
index 49eab31..7970b6b 100755
--- a/YouTubeMDBot/audio/__init__.py
+++ b/YouTubeMDBot/audio/__init__.py
@@ -17,4 +17,5 @@
from ..audio.ffmpeg import FFmpegOGG
from ..audio.ffmpeg import FFmpegMP3
from ..audio.ffmpeg import ffmpeg_available
+from ..audio.ffmpeg import FFmpegProcessor
from ..audio.fpcalc import FPCalc
diff --git a/YouTubeMDBot/audio/ffmpeg.py b/YouTubeMDBot/audio/ffmpeg.py
index 0bf9930..bda56ef 100755
--- a/YouTubeMDBot/audio/ffmpeg.py
+++ b/YouTubeMDBot/audio/ffmpeg.py
@@ -13,10 +13,17 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+from abc import ABC
+from abc import abstractmethod
from io import BytesIO
+from typing import List
from subprocess import PIPE
from subprocess import Popen
+from ..constants import FFMPEG_OPENER
+from ..constants import FFMPEG_CONVERTER
+from ..constants import FFMPEG_PROCESSOR
+
def ffmpeg_available() -> bool:
try:
@@ -30,17 +37,23 @@ def ffmpeg_available() -> bool:
return proc.returncode == 0
-class FFmpegOpener(object):
- def __init__(self, data: bytes):
+class FFmpeg(ABC):
+ def __init__(self, data: bytes, command: List[str] = None):
self._data = data
- self.__ffmpeg_proc = Popen(["ffmpeg", "-i", "-", "-f", "s16le", "-"],
- stdout=PIPE, stderr=PIPE, stdin=PIPE)
+ self.__command = command
self.__out = None
self.__err = None
- def open(self) -> int:
- self.__out, self.__err = self.__ffmpeg_proc.communicate(self._data)
- return self.__ffmpeg_proc.returncode
+ def process(self) -> int:
+ 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]:
+ return self.__command
+
+ def set_command(self, command: List[str]):
+ self.__command = command
def get_output(self) -> bytes:
return self.__out
@@ -49,49 +62,49 @@ def get_extra(self) -> bytes:
return self.__err
-class FFmpegExporter:
- def __init__(self, data: BytesIO):
- self._data = data
- self.__command = ["ffmpeg", "-i", "-", "-vn", "-map_metadata", "0",
- "-movflags", "use_metadata_tags"]
- self.__out = None
- self.__err = None
+class FFmpegProcessor(FFmpeg):
+ def __init__(self, data: bytes):
+ super().__init__(data=data, command=FFMPEG_PROCESSOR.copy())
- def _call_ffmpeg(self):
- self._data.seek(0)
- proc = Popen(self.__command, stdout=PIPE, stderr=PIPE, stdin=PIPE)
- self.__out, self.__err = proc.communicate(self._data.read())
- def _get_command(self) -> list:
- return self.__command
+class FFmpegOpener(FFmpeg):
+ def __init__(self, data: bytes):
+ super().__init__(data=data, command=FFMPEG_OPENER.copy())
- def convert(self):
- raise NotImplementedError
- def get_output(self) -> bytes:
- return self.__out
+class FFmpegExporter(FFmpeg):
+ def __init__(self, data: bytes, bitrate: str = None):
+ super().__init__(data=data, command=FFMPEG_CONVERTER.copy())
+ self._bitrate = bitrate
- def get_err(self) -> bytes:
- return self.__err
+ @abstractmethod
+ def convert(self) -> int:
+ raise NotImplementedError
class FFmpegMP3(FFmpegExporter):
- def convert(self):
- command = super()._get_command()
+ def convert(self) -> int:
+ command = super().get_command()
+ if self._bitrate:
+ command.append("-b:a")
+ command.append(self._bitrate)
command.append("-acodec")
command.append("libmp3lame")
command.append("-f")
command.append("mp3")
command.append("-")
- self._call_ffmpeg()
+ return self.process()
class FFmpegOGG(FFmpegExporter):
- def convert(self):
- command = super()._get_command()
+ def convert(self) -> int:
+ command = super().get_command()
+ if self._bitrate:
+ command.append("-b:a")
+ command.append(self._bitrate)
command.append("-c:a")
command.append("libvorbis")
command.append("-f")
command.append("ogg")
command.append("-")
- self._call_ffmpeg()
+ return self.process()
diff --git a/YouTubeMDBot/constants/__init__.py b/YouTubeMDBot/constants/__init__.py
index 4b9bf4d..01e666d 100755
--- a/YouTubeMDBot/constants/__init__.py
+++ b/YouTubeMDBot/constants/__init__.py
@@ -17,3 +17,6 @@
from ..constants.app_constants import FPCALC
from ..constants.app_constants import YDL_CLI_OPTIONS
from ..constants.app_constants import YOUTUBE
+from ..constants.app_constants import FFMPEG_OPENER
+from ..constants.app_constants import FFMPEG_PROCESSOR
+from ..constants.app_constants import FFMPEG_CONVERTER
diff --git a/YouTubeMDBot/constants/app_constants.py b/YouTubeMDBot/constants/app_constants.py
index e05c7a6..5f1a98d 100755
--- a/YouTubeMDBot/constants/app_constants.py
+++ b/YouTubeMDBot/constants/app_constants.py
@@ -34,3 +34,9 @@
"https://www.googleapis.com/youtube/v3/videos?"
"part=id,snippet,contentDetails,statistics&id={0}&key={1}"
}
+
+# FFmpeg commands
+FFMPEG_OPENER = "ffmpeg -i - -f s16le -".split(" ")
+FFMPEG_PROCESSOR = "ffmpeg -i - -filter:a loudnorm -vn -b:a 128k -f m4a -".split(" ")
+FFMPEG_CONVERTER = ["ffmpeg", "-i", "-", "-vn", "-map_metadata", "0",
+ "-movflags", "use_metadata_tags"]
diff --git a/YouTubeMDBot/downloader/youtube_downloader.py b/YouTubeMDBot/downloader/youtube_downloader.py
index 1692b34..27552af 100755
--- a/YouTubeMDBot/downloader/youtube_downloader.py
+++ b/YouTubeMDBot/downloader/youtube_downloader.py
@@ -16,8 +16,9 @@
from io import BytesIO
from typing import Tuple
+from ..errors import ProcessorError
+from ..audio import FFmpegProcessor
from ..constants.app_constants import YDL_CLI_OPTIONS
-from ..audio.ffmpeg import FFmpegOpener
class YouTubeDownloader(object):
@@ -26,7 +27,7 @@ def __init__(self, url: str):
self.__options: list = YDL_CLI_OPTIONS.copy()
self.__options.append(self.__url)
- def download(self, ffmpeg: bool = False) -> Tuple[BytesIO, bytes]:
+ def download(self) -> Tuple[BytesIO, bytes]:
import subprocess
proc = subprocess.Popen(self.__options,
@@ -35,7 +36,13 @@ def download(self, ffmpeg: bool = False) -> Tuple[BytesIO, bytes]:
stdout, stderr = proc.communicate()
retcode = proc.returncode
if retcode == 0:
- return BytesIO(stdout), stdout
+ processor = FFmpegProcessor(data=stdout)
+ if processor.process() == 0:
+ return BytesIO(processor.get_output()), processor.get_output()
+ else:
+ raise ProcessorError(
+ "ffmpeg failed: " + str(processor.get_extra().decode("utf-8"))
+ )
else:
raise RuntimeError("youtube-dl downloader exception - more info: " +
str(stderr.decode("utf-8")))
diff --git a/YouTubeMDBot/errors/ProcessorError.py b/YouTubeMDBot/errors/ProcessorError.py
new file mode 100644
index 0000000..703bd49
--- /dev/null
+++ b/YouTubeMDBot/errors/ProcessorError.py
@@ -0,0 +1,20 @@
+# 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 9fc7566..d9dd503 100755
--- a/YouTubeMDBot/errors/__init__.py
+++ b/YouTubeMDBot/errors/__init__.py
@@ -15,3 +15,4 @@
# 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/tests/converter.py b/YouTubeMDBot/tests/converter.py
index 9d7db7f..4901445 100644
--- a/YouTubeMDBot/tests/converter.py
+++ b/YouTubeMDBot/tests/converter.py
@@ -14,11 +14,10 @@ class MyTestCase(TaggerTest):
def find_metadata(self, downloader: YouTubeDownloader) -> Tuple[BytesIO, bytes]:
io, data = super().find_metadata(downloader)
io.seek(0)
- mp3 = FFmpegMP3(data=io)
- ogg = FFmpegOGG(data=io)
+ mp3 = FFmpegMP3(data=data, bitrate="96k") # downrate
+ ogg = FFmpegOGG(data=data, bitrate="256k") # uprate
mp3.convert()
- io.seek(0)
ogg.convert()
mp3_container = BytesIO(mp3.get_output())