mirror of
				https://github.com/KevinMidboe/spotify-downloader.git
				synced 2025-10-29 18:00:15 +00:00 
			
		
		
		
	Refactor exceptions
* Suffix names for custom exceptions with "Error" * Introduce exceptions for when the coressponding encoder isn't found
This commit is contained in:
		@@ -1,8 +1,11 @@
 | 
			
		||||
import shutil
 | 
			
		||||
import os
 | 
			
		||||
 | 
			
		||||
from abc import ABC
 | 
			
		||||
from abc import abstractmethod
 | 
			
		||||
 | 
			
		||||
from spotdl.encode.exceptions import EncoderNotFoundError
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
  NOTE ON ENCODERS
 | 
			
		||||
  ================
 | 
			
		||||
@@ -25,6 +28,12 @@ from abc import abstractmethod
 | 
			
		||||
class EncoderBase(ABC):
 | 
			
		||||
    @abstractmethod
 | 
			
		||||
    def __init__(self, encoder_path, loglevel, additional_arguments):
 | 
			
		||||
        if shutil.which(encoder_path) is None:
 | 
			
		||||
            raise EncoderNotFoundError(
 | 
			
		||||
                "{} executable does not exist or was not found in PATH.".format(
 | 
			
		||||
                    encoder_path
 | 
			
		||||
                )
 | 
			
		||||
            )
 | 
			
		||||
        self.encoder_path = encoder_path
 | 
			
		||||
        self._loglevel = loglevel
 | 
			
		||||
        self._additional_arguments = additional_arguments
 | 
			
		||||
 
 | 
			
		||||
@@ -2,16 +2,21 @@ import subprocess
 | 
			
		||||
import os
 | 
			
		||||
from logzero import logger as log
 | 
			
		||||
from spotdl.encode import EncoderBase
 | 
			
		||||
from spotdl.encode.exceptions import EncoderNotFoundError
 | 
			
		||||
from spotdl.encode.exceptions import AvconvNotFoundError
 | 
			
		||||
 | 
			
		||||
class EncoderAvconv(EncoderBase):
 | 
			
		||||
    def __init__(self, encoder_path="avconv"):
 | 
			
		||||
        print("Using avconv is deprecated and this will be removed in",
 | 
			
		||||
              "future versions. Use ffmpeg instead.")
 | 
			
		||||
        print("Using EncoderAvconv is deprecated and will be removed"
 | 
			
		||||
              "in future versions. Use EncoderFFmpeg instead.")
 | 
			
		||||
        encoder_path = encoder_path
 | 
			
		||||
        _loglevel = "-loglevel 0"
 | 
			
		||||
        _additional_arguments = ["-ab", "192k"]
 | 
			
		||||
 | 
			
		||||
        super().__init__(encoder_path, _loglevel, _additional_arguments)
 | 
			
		||||
        try:
 | 
			
		||||
            super().__init__(encoder_path, _loglevel, _additional_arguments)
 | 
			
		||||
        except EncoderNotFoundError as e:
 | 
			
		||||
            raise AvconvNotFoundError(e.args[0])
 | 
			
		||||
 | 
			
		||||
    def set_argument(self, argument):
 | 
			
		||||
        super().set_argument(argument)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,10 @@
 | 
			
		||||
import subprocess
 | 
			
		||||
import os
 | 
			
		||||
from logzero import logger as log
 | 
			
		||||
 | 
			
		||||
from spotdl.encode import EncoderBase
 | 
			
		||||
from spotdl.encode.exceptions import EncoderNotFoundError
 | 
			
		||||
from spotdl.encode.exceptions import FFmpegNotFoundError
 | 
			
		||||
 | 
			
		||||
RULES = {
 | 
			
		||||
    "m4a": {
 | 
			
		||||
@@ -17,14 +20,15 @@ RULES = {
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EncoderFFmpeg(EncoderBase):
 | 
			
		||||
    def __init__(self, encoder_path="ffmpeg"):
 | 
			
		||||
        encoder_path = encoder_path
 | 
			
		||||
        _loglevel = "-hide_banner -nostats -v panic"
 | 
			
		||||
        _additional_arguments = ["-b:a", "192k", "-vn"]
 | 
			
		||||
 | 
			
		||||
        super().__init__(encoder_path, _loglevel, _additional_arguments)
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            super().__init__(encoder_path, _loglevel, _additional_arguments)
 | 
			
		||||
        except EncoderNotFoundError as e:
 | 
			
		||||
            raise FFmpegNotFoundError(e.args[0])
 | 
			
		||||
        self._rules = RULES
 | 
			
		||||
 | 
			
		||||
    def set_argument(self, argument):
 | 
			
		||||
@@ -41,18 +45,16 @@ class EncoderFFmpeg(EncoderBase):
 | 
			
		||||
        if initial_arguments is None:
 | 
			
		||||
            raise TypeError(
 | 
			
		||||
                'The input format ("{}") is not supported.'.format(
 | 
			
		||||
                input_extension,
 | 
			
		||||
                input_encoding,
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        arguments = initial_arguments.get(output_encoding)
 | 
			
		||||
        if arguments is None:
 | 
			
		||||
            raise TypeError(
 | 
			
		||||
                'The output format ("{}") is not supported.'.format(
 | 
			
		||||
                output_extension,
 | 
			
		||||
                output_encoding,
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        return arguments
 | 
			
		||||
 | 
			
		||||
    def set_debuglog(self):
 | 
			
		||||
@@ -61,12 +63,10 @@ class EncoderFFmpeg(EncoderBase):
 | 
			
		||||
    def _generate_encode_command(self, input_file, output_file):
 | 
			
		||||
        input_encoding = self.get_encoding(input_file)
 | 
			
		||||
        output_encoding = self.get_encoding(output_file)
 | 
			
		||||
 | 
			
		||||
        arguments = self._generate_encoding_arguments(
 | 
			
		||||
            input_encoding,
 | 
			
		||||
            output_encoding
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        command = [self.encoder_path] \
 | 
			
		||||
            + ["-y", "-nostdin"] \
 | 
			
		||||
            + self._loglevel.split() \
 | 
			
		||||
@@ -82,10 +82,8 @@ class EncoderFFmpeg(EncoderBase):
 | 
			
		||||
            input_file,
 | 
			
		||||
            output_file
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        returncode = subprocess.call(encode_command)
 | 
			
		||||
        encode_successful = returncode == 0
 | 
			
		||||
 | 
			
		||||
        if encode_successful and delete_original:
 | 
			
		||||
            os.remove(input_file)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										0
									
								
								spotdl/encode/encoders/tests/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								spotdl/encode/encoders/tests/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										188
									
								
								spotdl/encode/encoders/tests/test_ffmpeg.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										188
									
								
								spotdl/encode/encoders/tests/test_ffmpeg.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,188 @@
 | 
			
		||||
from spotdl.encode import EncoderBase
 | 
			
		||||
# from spotdl.encode import exceptions
 | 
			
		||||
from spotdl.encode.encoders import EncoderFFmpeg
 | 
			
		||||
 | 
			
		||||
import pytest
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestEncoderFFmpeg:
 | 
			
		||||
    def test_subclass(self):
 | 
			
		||||
        assert issubclass(EncoderFFmpeg, EncoderBase)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestEncodingDefaults:
 | 
			
		||||
    def m4a_to_mp3_encoder(input_file, output_file):
 | 
			
		||||
        command = [
 | 
			
		||||
            'ffmpeg', '-y', '-nostdin', '-hide_banner', '-nostats', '-v', 'panic',
 | 
			
		||||
            '-i', input_file,
 | 
			
		||||
            '-codec:v', 'copy',
 | 
			
		||||
            '-codec:a', 'libmp3lame',
 | 
			
		||||
            '-ar', '48000',
 | 
			
		||||
            '-b:a', '192k',
 | 
			
		||||
            '-vn', output_file
 | 
			
		||||
        ]
 | 
			
		||||
        return command
 | 
			
		||||
 | 
			
		||||
    def m4a_to_webm_encoder(input_file, output_file):
 | 
			
		||||
        command = [
 | 
			
		||||
            'ffmpeg', '-y', '-nostdin', '-hide_banner', '-nostats', '-v', 'panic',
 | 
			
		||||
            '-i', input_file,
 | 
			
		||||
            '-codec:a', 'libopus',
 | 
			
		||||
            '-vbr', 'on',
 | 
			
		||||
            '-b:a', '192k',
 | 
			
		||||
            '-vn', output_file
 | 
			
		||||
        ]
 | 
			
		||||
        return command
 | 
			
		||||
 | 
			
		||||
    def m4a_to_m4a_encoder(input_file, output_file):
 | 
			
		||||
        command = [
 | 
			
		||||
            'ffmpeg', '-y', '-nostdin', '-hide_banner', '-nostats', '-v', 'panic',
 | 
			
		||||
            '-i', input_file,
 | 
			
		||||
            '-acodec', 'copy',
 | 
			
		||||
            '-b:a', '192k',
 | 
			
		||||
            '-vn', output_file
 | 
			
		||||
        ]
 | 
			
		||||
        return command
 | 
			
		||||
 | 
			
		||||
    def m4a_to_flac_encoder(input_file, output_file):
 | 
			
		||||
        command = [
 | 
			
		||||
            'ffmpeg', '-y', '-nostdin', '-hide_banner', '-nostats', '-v', 'panic',
 | 
			
		||||
            '-i', input_file,
 | 
			
		||||
            '-codec:a', 'flac',
 | 
			
		||||
            '-ar', '48000',
 | 
			
		||||
            '-b:a', '192k',
 | 
			
		||||
            '-vn', output_file
 | 
			
		||||
        ]
 | 
			
		||||
        return command
 | 
			
		||||
 | 
			
		||||
    @pytest.mark.parametrize("files, expected_command", [
 | 
			
		||||
        (("test.m4a", "test.mp3"), m4a_to_mp3_encoder("test.m4a", "test.mp3")),
 | 
			
		||||
        (("abc.m4a", "cba.webm"), m4a_to_webm_encoder("abc.m4a", "cba.webm")),
 | 
			
		||||
        (("bla bla.m4a", "ble ble.m4a"), m4a_to_m4a_encoder("bla bla.m4a", "ble ble.m4a")),
 | 
			
		||||
        (("😛.m4a", "• tongue.flac"), m4a_to_flac_encoder("😛.m4a", "• tongue.flac")),
 | 
			
		||||
    ])
 | 
			
		||||
    def test_generate_encode_command(self, files, expected_command):
 | 
			
		||||
        encoder = EncoderFFmpeg()
 | 
			
		||||
        assert encoder._generate_encode_command(*files) == expected_command
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestEncodingInDebugMode:
 | 
			
		||||
    def m4a_to_mp3_encoder_with_debug(input_file, output_file):
 | 
			
		||||
        command = [
 | 
			
		||||
            'ffmpeg', '-y', '-nostdin', '-loglevel', 'debug',
 | 
			
		||||
            '-i', input_file,
 | 
			
		||||
            '-codec:v', 'copy',
 | 
			
		||||
            '-codec:a', 'libmp3lame',
 | 
			
		||||
            '-ar', '48000',
 | 
			
		||||
            '-b:a', '192k',
 | 
			
		||||
            '-vn', output_file
 | 
			
		||||
        ]
 | 
			
		||||
        return command
 | 
			
		||||
 | 
			
		||||
    def m4a_to_webm_encoder_with_debug(input_file, output_file):
 | 
			
		||||
        command = [
 | 
			
		||||
            'ffmpeg', '-y', '-nostdin', '-loglevel', 'debug',
 | 
			
		||||
            '-i', input_file,
 | 
			
		||||
            '-codec:a', 'libopus',
 | 
			
		||||
            '-vbr', 'on',
 | 
			
		||||
            '-b:a', '192k',
 | 
			
		||||
            '-vn', output_file
 | 
			
		||||
        ]
 | 
			
		||||
        return command
 | 
			
		||||
 | 
			
		||||
    def m4a_to_m4a_encoder_with_debug(input_file, output_file):
 | 
			
		||||
        command = [
 | 
			
		||||
            'ffmpeg', '-y', '-nostdin', '-loglevel', 'debug',
 | 
			
		||||
            '-i', input_file,
 | 
			
		||||
            '-acodec', 'copy',
 | 
			
		||||
            '-b:a', '192k',
 | 
			
		||||
            '-vn', output_file
 | 
			
		||||
        ]
 | 
			
		||||
        return command
 | 
			
		||||
 | 
			
		||||
    def m4a_to_flac_encoder_with_debug(input_file, output_file):
 | 
			
		||||
        command = [
 | 
			
		||||
            'ffmpeg', '-y', '-nostdin', '-loglevel', 'debug',
 | 
			
		||||
            '-i', input_file,
 | 
			
		||||
            '-codec:a', 'flac',
 | 
			
		||||
            '-ar', '48000',
 | 
			
		||||
            '-b:a', '192k',
 | 
			
		||||
            '-vn', output_file
 | 
			
		||||
        ]
 | 
			
		||||
        return command
 | 
			
		||||
 | 
			
		||||
    @pytest.mark.parametrize("files, expected_command", [
 | 
			
		||||
        (("test.m4a", "test.mp3"), m4a_to_mp3_encoder_with_debug("test.m4a", "test.mp3")),
 | 
			
		||||
        (("abc.m4a", "cba.webm"), m4a_to_webm_encoder_with_debug("abc.m4a", "cba.webm")),
 | 
			
		||||
        (("bla bla.m4a", "ble ble.m4a"), m4a_to_m4a_encoder_with_debug("bla bla.m4a", "ble ble.m4a")),
 | 
			
		||||
        (("😛.m4a", "• tongue.flac"), m4a_to_flac_encoder_with_debug("😛.m4a", "• tongue.flac")),
 | 
			
		||||
    ])
 | 
			
		||||
    def test_generate_encode_command_with_debug(self, files, expected_command):
 | 
			
		||||
        encoder = EncoderFFmpeg()
 | 
			
		||||
        encoder.set_debuglog()
 | 
			
		||||
        assert encoder._generate_encode_command(*files) == expected_command
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestEncodingAndTrimSilence:
 | 
			
		||||
    def m4a_to_mp3_encoder_and_trim_silence(input_file, output_file):
 | 
			
		||||
        command = [
 | 
			
		||||
            'ffmpeg', '-y', '-nostdin', '-hide_banner', '-nostats', '-v', 'panic',
 | 
			
		||||
            '-i', input_file,
 | 
			
		||||
            '-codec:v', 'copy',
 | 
			
		||||
            '-codec:a', 'libmp3lame',
 | 
			
		||||
            '-ar', '48000',
 | 
			
		||||
            '-b:a', '192k',
 | 
			
		||||
            '-vn',
 | 
			
		||||
            '-af', 'silenceremove=start_periods=1',
 | 
			
		||||
            output_file
 | 
			
		||||
        ]
 | 
			
		||||
        return command
 | 
			
		||||
 | 
			
		||||
    def m4a_to_webm_encoder_and_trim_silence(input_file, output_file):
 | 
			
		||||
        command = [
 | 
			
		||||
            'ffmpeg', '-y', '-nostdin', '-hide_banner', '-nostats', '-v', 'panic',
 | 
			
		||||
            '-i', input_file,
 | 
			
		||||
            '-codec:a', 'libopus',
 | 
			
		||||
            '-vbr', 'on',
 | 
			
		||||
            '-b:a', '192k',
 | 
			
		||||
            '-vn',
 | 
			
		||||
            '-af', 'silenceremove=start_periods=1',
 | 
			
		||||
            output_file
 | 
			
		||||
        ]
 | 
			
		||||
        return command
 | 
			
		||||
 | 
			
		||||
    def m4a_to_m4a_encoder_and_trim_silence(input_file, output_file):
 | 
			
		||||
        command = [
 | 
			
		||||
            'ffmpeg', '-y', '-nostdin', '-hide_banner', '-nostats', '-v', 'panic',
 | 
			
		||||
            '-i', input_file,
 | 
			
		||||
            '-acodec', 'copy',
 | 
			
		||||
            '-b:a', '192k',
 | 
			
		||||
            '-vn',
 | 
			
		||||
            '-af', 'silenceremove=start_periods=1',
 | 
			
		||||
            output_file
 | 
			
		||||
        ]
 | 
			
		||||
        return command
 | 
			
		||||
 | 
			
		||||
    def m4a_to_flac_encoder_and_trim_silence(input_file, output_file):
 | 
			
		||||
        command = [
 | 
			
		||||
            'ffmpeg', '-y', '-nostdin', '-hide_banner', '-nostats', '-v', 'panic',
 | 
			
		||||
            '-i', input_file,
 | 
			
		||||
            '-codec:a', 'flac',
 | 
			
		||||
            '-ar', '48000',
 | 
			
		||||
            '-b:a', '192k',
 | 
			
		||||
            '-vn',
 | 
			
		||||
            '-af', 'silenceremove=start_periods=1',
 | 
			
		||||
            output_file
 | 
			
		||||
        ]
 | 
			
		||||
        return command
 | 
			
		||||
 | 
			
		||||
    @pytest.mark.parametrize("files, expected_command", [
 | 
			
		||||
        (("test.m4a", "test.mp3"), m4a_to_mp3_encoder_and_trim_silence("test.m4a", "test.mp3")),
 | 
			
		||||
        (("abc.m4a", "cba.webm"), m4a_to_webm_encoder_and_trim_silence("abc.m4a", "cba.webm")),
 | 
			
		||||
        (("bla bla.m4a", "ble ble.m4a"), m4a_to_m4a_encoder_and_trim_silence("bla bla.m4a", "ble ble.m4a")),
 | 
			
		||||
        (("😛.m4a", "• tongue.flac"), m4a_to_flac_encoder_and_trim_silence("😛.m4a", "• tongue.flac")),
 | 
			
		||||
    ])
 | 
			
		||||
    def test_generate_encode_command_and_trim_silence(self, files, expected_command):
 | 
			
		||||
        encoder = EncoderFFmpeg()
 | 
			
		||||
        encoder.set_trim_silence()
 | 
			
		||||
        assert encoder._generate_encode_command(*files) == expected_command
 | 
			
		||||
							
								
								
									
										20
									
								
								spotdl/encode/exceptions.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								spotdl/encode/exceptions.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
			
		||||
class EncoderNotFoundError(Exception):
 | 
			
		||||
    __module__ = Exception.__module__
 | 
			
		||||
 | 
			
		||||
    def __init__(self, message=None):
 | 
			
		||||
        super(EncoderNotFoundError, self).__init__(message)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FFmpegNotFoundError(EncoderNotFoundError):
 | 
			
		||||
    __module__ = Exception.__module__
 | 
			
		||||
 | 
			
		||||
    def __init__(self, message=None):
 | 
			
		||||
        super(FFmpegNotFoundError, self).__init__(message)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AvconvNotFoundError(EncoderNotFoundError):
 | 
			
		||||
    __module__ = Exception.__module__
 | 
			
		||||
 | 
			
		||||
    def __init__(self, message=None):
 | 
			
		||||
        super(AvconvNotFoundError, self).__init__(message)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										0
									
								
								spotdl/encode/tests/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								spotdl/encode/tests/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										13
									
								
								spotdl/encode/tests/test_encode_base.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								spotdl/encode/tests/test_encode_base.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
from spotdl.encode import EncoderBase
 | 
			
		||||
 | 
			
		||||
import pytest
 | 
			
		||||
 | 
			
		||||
def test_abstract_base_class_encoderbase():
 | 
			
		||||
    encoder_path = "ffmpeg"
 | 
			
		||||
    _loglevel = "-hide_banner -nostats -v panic"
 | 
			
		||||
    _additional_arguments = ["-b:a", "192k", "-vn"]
 | 
			
		||||
 | 
			
		||||
    with pytest.raises(TypeError):
 | 
			
		||||
        # This abstract base class must be inherited from
 | 
			
		||||
        # for instantiation
 | 
			
		||||
        EncoderBase(encoder_path, _loglevel, _additional_arguments)
 | 
			
		||||
							
								
								
									
										14
									
								
								spotdl/encode/tests/test_exceptions.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								spotdl/encode/tests/test_exceptions.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
from spotdl.encode.exceptions import EncoderNotFoundError
 | 
			
		||||
from spotdl.encode.exceptions import FFmpegNotFoundError
 | 
			
		||||
from spotdl.encode.exceptions import AvconvNotFoundError
 | 
			
		||||
 | 
			
		||||
import pytest
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestEncoderNotFoundSubclass:
 | 
			
		||||
    def test_ffmpeg_not_found_subclass(self):
 | 
			
		||||
        assert issubclass(FFmpegNotFoundError, EncoderNotFoundError)
 | 
			
		||||
 | 
			
		||||
    def test_avconv_not_found_subclass(self):
 | 
			
		||||
        assert issubclass(AvconvNotFoundError, EncoderNotFoundError)
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user