Merge pull request #316 from akbad/feat/add-flac

feat: add FLAC lossless format support
This commit is contained in:
碧舞すみほ
2025-10-11 09:58:09 +09:00
committed by GitHub
4 changed files with 143 additions and 3706 deletions

View File

@@ -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)

View File

@@ -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

View File

@@ -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;
} }
} }