Merge pull request #316 from akbad/feat/add-flac
feat: add FLAC lossless format support
This commit is contained in:
@@ -12,13 +12,13 @@ class AudioQuality(enum.Enum):
|
|||||||
NORMAL = 0x00
|
NORMAL = 0x00
|
||||||
HIGH = 0x01
|
HIGH = 0x01
|
||||||
VERY_HIGH = 0x02
|
VERY_HIGH = 0x02
|
||||||
|
LOSSLESS = 0x03
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_quality(audio_format: AudioFile.Format) -> AudioQuality:
|
def get_quality(audio_format: AudioFile.Format) -> AudioQuality:
|
||||||
if audio_format in [
|
if audio_format in [
|
||||||
AudioFile.MP3_96,
|
AudioFile.MP3_96,
|
||||||
AudioFile.OGG_VORBIS_96,
|
AudioFile.OGG_VORBIS_96,
|
||||||
AudioFile.AAC_24_NORM,
|
|
||||||
]:
|
]:
|
||||||
return AudioQuality.NORMAL
|
return AudioQuality.NORMAL
|
||||||
if audio_format in [
|
if audio_format in [
|
||||||
@@ -35,7 +35,12 @@ class AudioQuality(enum.Enum):
|
|||||||
AudioFile.AAC_48,
|
AudioFile.AAC_48,
|
||||||
]:
|
]:
|
||||||
return AudioQuality.VERY_HIGH
|
return AudioQuality.VERY_HIGH
|
||||||
raise RuntimeError("Unknown format: {}".format(format))
|
if audio_format in [
|
||||||
|
AudioFile.FLAC_FLAC,
|
||||||
|
AudioFile.FLAC_FLAC_24BIT,
|
||||||
|
]:
|
||||||
|
return AudioQuality.LOSSLESS
|
||||||
|
raise RuntimeError("Unknown format: {}".format(audio_format))
|
||||||
|
|
||||||
def get_matches(self,
|
def get_matches(self,
|
||||||
files: typing.List[AudioFile]) -> typing.List[AudioFile]:
|
files: typing.List[AudioFile]) -> typing.List[AudioFile]:
|
||||||
@@ -47,35 +52,71 @@ class AudioQuality(enum.Enum):
|
|||||||
return file_list
|
return file_list
|
||||||
|
|
||||||
|
|
||||||
class VorbisOnlyAudioQuality(AudioQualityPicker):
|
class FormatOnlyAudioQuality(AudioQualityPicker):
|
||||||
logger = logging.getLogger("Librespot:Player:VorbisOnlyAudioQuality")
|
# Generic quality picker; filters files by container format
|
||||||
preferred: AudioQuality
|
|
||||||
|
|
||||||
def __init__(self, preferred: AudioQuality):
|
logger = logging.getLogger("Librespot:Player:FormatOnlyAudioQuality")
|
||||||
|
preferred: AudioQuality
|
||||||
|
format_filter: SuperAudioFormat
|
||||||
|
|
||||||
|
def __init__(self, preferred: AudioQuality, format_filter: SuperAudioFormat):
|
||||||
self.preferred = preferred
|
self.preferred = preferred
|
||||||
|
self.format_filter = format_filter
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_vorbis_file(files: typing.List[Metadata.AudioFile]):
|
def get_file_by_format(files: typing.List[Metadata.AudioFile],
|
||||||
|
format_type: SuperAudioFormat) -> typing.Optional[Metadata.AudioFile]:
|
||||||
for file in files:
|
for file in files:
|
||||||
if file.HasField("format") and SuperAudioFormat.get(
|
if file.HasField("format") and SuperAudioFormat.get(
|
||||||
file.format) == SuperAudioFormat.VORBIS:
|
file.format) == format_type:
|
||||||
return file
|
return file
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_file(self, files: typing.List[Metadata.AudioFile]):
|
def get_file(self, files: typing.List[Metadata.AudioFile]) -> typing.Optional[Metadata.AudioFile]:
|
||||||
matches: typing.List[Metadata.AudioFile] = self.preferred.get_matches(
|
quality_matches: typing.List[Metadata.AudioFile] = self.preferred.get_matches(files)
|
||||||
files)
|
|
||||||
vorbis: Metadata.AudioFile = VorbisOnlyAudioQuality.get_vorbis_file(
|
selected_file = self.get_file_by_format(quality_matches, self.format_filter)
|
||||||
matches)
|
|
||||||
if vorbis is None:
|
if selected_file is None:
|
||||||
vorbis: Metadata.AudioFile = VorbisOnlyAudioQuality.get_vorbis_file(
|
# Try using any file matching the format, regardless of quality
|
||||||
files)
|
selected_file = self.get_file_by_format(files, self.format_filter)
|
||||||
if vorbis is not None:
|
|
||||||
|
if selected_file is not None:
|
||||||
|
# Found format match (different quality than preferred)
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
"Using {} because preferred {} couldn't be found.".format(
|
"Using {} format file with {} quality because preferred {} quality couldn't be found.".format(
|
||||||
Metadata.AudioFile.Format.Name(vorbis.format),
|
self.format_filter.name,
|
||||||
self.preferred))
|
AudioQuality.get_quality(selected_file.format).name,
|
||||||
|
self.preferred.name))
|
||||||
else:
|
else:
|
||||||
|
available_formats = [SuperAudioFormat.get(f.format).name
|
||||||
|
for f in files if f.HasField("format")]
|
||||||
self.logger.fatal(
|
self.logger.fatal(
|
||||||
"Couldn't find any Vorbis file, available: {}")
|
"Couldn't find any {} file. Available formats: {}".format(
|
||||||
return vorbis
|
self.format_filter.name,
|
||||||
|
", ".join(set(available_formats)) if available_formats else "none"))
|
||||||
|
|
||||||
|
return selected_file
|
||||||
|
|
||||||
|
|
||||||
|
# Backward-compatible wrapper classes
|
||||||
|
|
||||||
|
class VorbisOnlyAudioQuality(FormatOnlyAudioQuality):
|
||||||
|
logger = logging.getLogger("Librespot:Player:VorbisOnlyAudioQuality")
|
||||||
|
|
||||||
|
def __init__(self, preferred: AudioQuality):
|
||||||
|
super().__init__(preferred, SuperAudioFormat.VORBIS)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_vorbis_file(files: typing.List[Metadata.AudioFile]) -> typing.Optional[Metadata.AudioFile]:
|
||||||
|
return FormatOnlyAudioQuality.get_file_by_format(files, SuperAudioFormat.VORBIS)
|
||||||
|
|
||||||
|
class LosslessOnlyAudioQuality(FormatOnlyAudioQuality):
|
||||||
|
logger = logging.getLogger("Librespot:Player:LosslessOnlyAudioQuality")
|
||||||
|
|
||||||
|
def __init__(self, preferred: AudioQuality):
|
||||||
|
super().__init__(preferred, SuperAudioFormat.FLAC)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_flac_file(files: typing.List[Metadata.AudioFile]) -> typing.Optional[Metadata.AudioFile]:
|
||||||
|
return FormatOnlyAudioQuality.get_file_by_format(files, SuperAudioFormat.FLAC)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ class SuperAudioFormat(enum.Enum):
|
|||||||
MP3 = 0x00
|
MP3 = 0x00
|
||||||
VORBIS = 0x01
|
VORBIS = 0x01
|
||||||
AAC = 0x02
|
AAC = 0x02
|
||||||
|
FLAC = 0x03
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get(audio_format: Metadata.AudioFile.Format):
|
def get(audio_format: Metadata.AudioFile.Format):
|
||||||
@@ -26,7 +27,11 @@ class SuperAudioFormat(enum.Enum):
|
|||||||
if audio_format in [
|
if audio_format in [
|
||||||
Metadata.AudioFile.Format.AAC_24,
|
Metadata.AudioFile.Format.AAC_24,
|
||||||
Metadata.AudioFile.Format.AAC_48,
|
Metadata.AudioFile.Format.AAC_48,
|
||||||
Metadata.AudioFile.Format.AAC_24_NORM,
|
|
||||||
]:
|
]:
|
||||||
return SuperAudioFormat.AAC
|
return SuperAudioFormat.AAC
|
||||||
|
if audio_format in [
|
||||||
|
Metadata.AudioFile.Format.FLAC_FLAC,
|
||||||
|
Metadata.AudioFile.Format.FLAC_FLAC_24BIT,
|
||||||
|
]:
|
||||||
|
return SuperAudioFormat.FLAC
|
||||||
raise RuntimeError("Unknown audio format: {}".format(audio_format))
|
raise RuntimeError("Unknown audio format: {}".format(audio_format))
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -270,7 +270,8 @@ message AudioFile {
|
|||||||
MP3_160_ENC = 7;
|
MP3_160_ENC = 7;
|
||||||
AAC_24 = 8;
|
AAC_24 = 8;
|
||||||
AAC_48 = 9;
|
AAC_48 = 9;
|
||||||
AAC_24_NORM = 16;
|
FLAC_FLAC = 16;
|
||||||
|
FLAC_FLAC_24BIT = 22;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user