feat: add FLAC lossless format support and refactor quality pickers (while staying backward compatible)
- Add `FLAC_FLAC` (16), `FLAC_FLAC_24BIT` (22) format codes (based on librespot-org/librespot#796, librespot-org/librespot#1424) - Regenerate Metadata_pb2.py with protoc 3.20.1 - Add enums: `SuperAudioFormat.FLAC`, `AudioQuality.LOSSLESS` - Refactor to generic DRY `FormatOnlyAudioQuality` base class while maintaining existing `VorbisOnlyAudioQuality` as wrapper - Remove `AAC_24_NORM` (replaced by `FLAC_FLAC` at code 16)
This commit is contained in:
@@ -12,13 +12,13 @@ class AudioQuality(enum.Enum):
|
||||
NORMAL = 0x00
|
||||
HIGH = 0x01
|
||||
VERY_HIGH = 0x02
|
||||
LOSSLESS = 0x03
|
||||
|
||||
@staticmethod
|
||||
def get_quality(audio_format: AudioFile.Format) -> AudioQuality:
|
||||
if audio_format in [
|
||||
AudioFile.MP3_96,
|
||||
AudioFile.OGG_VORBIS_96,
|
||||
AudioFile.AAC_24_NORM,
|
||||
]:
|
||||
return AudioQuality.NORMAL
|
||||
if audio_format in [
|
||||
@@ -35,6 +35,11 @@ class AudioQuality(enum.Enum):
|
||||
AudioFile.AAC_48,
|
||||
]:
|
||||
return AudioQuality.VERY_HIGH
|
||||
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,
|
||||
@@ -47,35 +52,71 @@ class AudioQuality(enum.Enum):
|
||||
return file_list
|
||||
|
||||
|
||||
class VorbisOnlyAudioQuality(AudioQualityPicker):
|
||||
logger = logging.getLogger("Librespot:Player:VorbisOnlyAudioQuality")
|
||||
preferred: AudioQuality
|
||||
class FormatOnlyAudioQuality(AudioQualityPicker):
|
||||
# Generic quality picker; filters files by container format
|
||||
|
||||
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.format_filter = format_filter
|
||||
|
||||
@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:
|
||||
if file.HasField("format") and SuperAudioFormat.get(
|
||||
file.format) == SuperAudioFormat.VORBIS:
|
||||
file.format) == format_type:
|
||||
return file
|
||||
return None
|
||||
|
||||
def get_file(self, files: typing.List[Metadata.AudioFile]):
|
||||
matches: typing.List[Metadata.AudioFile] = self.preferred.get_matches(
|
||||
files)
|
||||
vorbis: Metadata.AudioFile = VorbisOnlyAudioQuality.get_vorbis_file(
|
||||
matches)
|
||||
if vorbis is None:
|
||||
vorbis: Metadata.AudioFile = VorbisOnlyAudioQuality.get_vorbis_file(
|
||||
files)
|
||||
if vorbis is not None:
|
||||
def get_file(self, files: typing.List[Metadata.AudioFile]) -> typing.Optional[Metadata.AudioFile]:
|
||||
quality_matches: typing.List[Metadata.AudioFile] = self.preferred.get_matches(files)
|
||||
|
||||
selected_file = self.get_file_by_format(quality_matches, self.format_filter)
|
||||
|
||||
if selected_file is None:
|
||||
# Try using any file matching the format, regardless of quality
|
||||
selected_file = self.get_file_by_format(files, self.format_filter)
|
||||
|
||||
if selected_file is not None:
|
||||
# Found format match (different quality than preferred)
|
||||
self.logger.warning(
|
||||
"Using {} because preferred {} couldn't be found.".format(
|
||||
Metadata.AudioFile.Format.Name(vorbis.format),
|
||||
self.preferred))
|
||||
"Using {} format file with {} quality because preferred {} quality couldn't be found.".format(
|
||||
self.format_filter.name,
|
||||
AudioQuality.get_quality(selected_file.format).name,
|
||||
self.preferred.name))
|
||||
else:
|
||||
available_formats = [SuperAudioFormat.get(f.format).name
|
||||
for f in files if f.HasField("format")]
|
||||
self.logger.fatal(
|
||||
"Couldn't find any Vorbis file, available: {}")
|
||||
return vorbis
|
||||
"Couldn't find any {} file. Available formats: {}".format(
|
||||
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
|
||||
VORBIS = 0x01
|
||||
AAC = 0x02
|
||||
FLAC = 0x03
|
||||
|
||||
@staticmethod
|
||||
def get(audio_format: Metadata.AudioFile.Format):
|
||||
@@ -26,7 +27,11 @@ class SuperAudioFormat(enum.Enum):
|
||||
if audio_format in [
|
||||
Metadata.AudioFile.Format.AAC_24,
|
||||
Metadata.AudioFile.Format.AAC_48,
|
||||
Metadata.AudioFile.Format.AAC_24_NORM,
|
||||
]:
|
||||
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))
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -270,7 +270,8 @@ message AudioFile {
|
||||
MP3_160_ENC = 7;
|
||||
AAC_24 = 8;
|
||||
AAC_48 = 9;
|
||||
AAC_24_NORM = 16;
|
||||
FLAC_FLAC = 16;
|
||||
FLAC_FLAC_24BIT = 22;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user