[FEATURE] Support best codec type in audio_extract (#700)
Adds support to the `audio_extract` plugin (https://ytdl-sub.readthedocs.io/en/latest/config.html#audio-extract) to the following to grab the best audio format available: ``` audio_extract: codec: "best" ```
This commit is contained in:
parent
9e8403f148
commit
da70c725fc
6 changed files with 117 additions and 7 deletions
|
|
@ -8,6 +8,8 @@ from ytdl_sub.config.preset_options import OptionsDictValidator
|
|||
from ytdl_sub.downloaders.ytdl_options_builder import YTDLOptionsBuilder
|
||||
from ytdl_sub.entries.entry import Entry
|
||||
from ytdl_sub.utils.exceptions import FileNotDownloadedException
|
||||
from ytdl_sub.utils.file_handler import FileMetadata
|
||||
from ytdl_sub.validators.audo_codec_validator import AUDIO_CODEC_EXTS
|
||||
from ytdl_sub.validators.audo_codec_validator import AUDIO_CODEC_TYPES_EXTENSION_MAPPING
|
||||
from ytdl_sub.validators.audo_codec_validator import AudioTypeValidator
|
||||
from ytdl_sub.validators.validators import FloatValidator
|
||||
|
|
@ -49,7 +51,7 @@ class AudioExtractOptions(OptionsDictValidator):
|
|||
def codec(self) -> str:
|
||||
"""
|
||||
The codec to output after extracting the audio. Supported codecs are aac, flac, mp3, m4a,
|
||||
opus, vorbis, wav.
|
||||
opus, vorbis, wav, and best to grab the best possible format at runtime.
|
||||
"""
|
||||
return self._codec
|
||||
|
||||
|
|
@ -105,12 +107,39 @@ class AudioExtractPlugin(Plugin[AudioExtractOptions]):
|
|||
FileNotDownloadedException
|
||||
If the audio file is not found
|
||||
"""
|
||||
new_ext = AUDIO_CODEC_TYPES_EXTENSION_MAPPING[self.plugin_options.codec]
|
||||
extracted_audio_file = entry.get_download_file_path().removesuffix(entry.ext) + new_ext
|
||||
extracted_audio_file = entry.get_download_file_path()
|
||||
|
||||
# yt-dlp doesn't record which ext is used, so try to find it
|
||||
if self.plugin_options.codec == "best":
|
||||
new_ext = "m4a"
|
||||
|
||||
for possible_ext in AUDIO_CODEC_EXTS:
|
||||
extracted_audio_file = (
|
||||
entry.get_download_file_path().removesuffix(entry.ext) + possible_ext
|
||||
)
|
||||
|
||||
if os.path.isfile(extracted_audio_file):
|
||||
new_ext = possible_ext
|
||||
break
|
||||
else:
|
||||
new_ext = AUDIO_CODEC_TYPES_EXTENSION_MAPPING[self.plugin_options.codec]
|
||||
extracted_audio_file = entry.get_download_file_path().removesuffix(entry.ext) + new_ext
|
||||
|
||||
entry.add_kwargs({"ext": new_ext})
|
||||
|
||||
if not self.is_dry_run:
|
||||
if not os.path.isfile(extracted_audio_file):
|
||||
raise FileNotDownloadedException("Failed to find the extracted audio file")
|
||||
|
||||
entry.add_kwargs({"ext": new_ext})
|
||||
|
||||
return entry
|
||||
|
||||
def post_process_entry(self, entry: Entry) -> Optional[FileMetadata]:
|
||||
"""
|
||||
Warn the user that best cannot infer the format that will be used at run-time.
|
||||
"""
|
||||
if self.plugin_options.codec == "best" and self.is_dry_run:
|
||||
return FileMetadata(
|
||||
"Caution: extracted audio with 'best' format is not known during dry-run. "
|
||||
"Defaulting to m4a."
|
||||
)
|
||||
return None
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ VIDEO_CODEC_EXTS: Set[str] = {"avi", "flv", "mkv", "mov", "mp4", "webm"}
|
|||
|
||||
class AudioTypeValidator(StringSelectValidator):
|
||||
_expected_value_type_name = "codec"
|
||||
_select_values = AUDIO_CODEC_TYPES
|
||||
_select_values = AUDIO_CODEC_TYPES.union({"best"}) # support 'best' in the audio extract plugin
|
||||
|
||||
|
||||
class FileTypeValidator(StringSelectValidator):
|
||||
|
|
|
|||
|
|
@ -43,6 +43,11 @@ def single_song_preset_dict(output_directory):
|
|||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def single_song_best_format_preset_dict(single_song_preset_dict):
|
||||
return dict(single_song_preset_dict, **{"audio_extract": {"codec": "best"}})
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def multiple_songs_preset_dict(output_directory):
|
||||
return {
|
||||
|
|
@ -88,7 +93,7 @@ class TestAudioExtract:
|
|||
)
|
||||
|
||||
@pytest.mark.parametrize("dry_run", [True, False])
|
||||
def test_audio_extract_single_song_new_format(
|
||||
def test_audio_extract_single_song(
|
||||
self,
|
||||
music_audio_config,
|
||||
single_song_preset_dict,
|
||||
|
|
@ -113,6 +118,32 @@ class TestAudioExtract:
|
|||
expected_download_summary_file_name="plugins/test_audio_extract_single.json",
|
||||
)
|
||||
|
||||
@pytest.mark.parametrize("dry_run", [True, False])
|
||||
def test_audio_extract_single_song_best_format(
|
||||
self,
|
||||
music_audio_config,
|
||||
single_song_best_format_preset_dict,
|
||||
output_directory,
|
||||
dry_run,
|
||||
):
|
||||
subscription = Subscription.from_dict(
|
||||
config=music_audio_config,
|
||||
preset_name="single_song_best_test",
|
||||
preset_dict=single_song_best_format_preset_dict,
|
||||
)
|
||||
|
||||
transaction_log = subscription.download(dry_run=dry_run)
|
||||
assert_transaction_log_matches(
|
||||
output_directory=output_directory,
|
||||
transaction_log=transaction_log,
|
||||
transaction_log_summary_file_name=f"plugins/test_audio_extract_single_best{'_dry_run' if dry_run else ''}.txt",
|
||||
)
|
||||
assert_expected_downloads(
|
||||
output_directory=output_directory,
|
||||
dry_run=dry_run,
|
||||
expected_download_summary_file_name=f"plugins/test_audio_extract_single_best{'_dry_run' if dry_run else ''}.json",
|
||||
)
|
||||
|
||||
@pytest.mark.parametrize("dry_run", [True, False])
|
||||
def test_audio_extract_multiple_songs(
|
||||
self,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
".ytdl-sub-single_song_best_test-download-archive.json": "f179ccfe0a9b3a76ae62122a3ccb58fd",
|
||||
"YouTube/[2019] YouTube Rewind 2019: For the Record | #YouTubeRewind/01 - YouTube Rewind 2019: For the Record | #YouTubeRewind.m4a": "4b03133e9422a9ce196d271beb15ceef",
|
||||
"YouTube/[2019] YouTube Rewind 2019: For the Record | #YouTubeRewind/folder.jpg": "50ee47c80f679029f5d3503bb91b045a"
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
Files created:
|
||||
----------------------------------------
|
||||
{output_directory}
|
||||
.ytdl-sub-single_song_best_test-download-archive.json
|
||||
{output_directory}/YouTube/[2019] YouTube Rewind 2019: For the Record | #YouTubeRewind
|
||||
01 - YouTube Rewind 2019: For the Record | #YouTubeRewind.m4a
|
||||
Music Tags:
|
||||
album: YouTube Rewind 2019: For the Record | #YouTubeRewind
|
||||
albumartist: YouTube
|
||||
albumartists: YouTube
|
||||
artist: YouTube
|
||||
artists: YouTube
|
||||
genre: Unset
|
||||
genres:
|
||||
- multi_tag_1
|
||||
- multi_tag_2
|
||||
title: YouTube Rewind 2019: For the Record | #YouTubeRewind
|
||||
track: 1
|
||||
tracktotal: 1
|
||||
year: 2019
|
||||
Embedded thumbnail
|
||||
folder.jpg
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
Files created:
|
||||
----------------------------------------
|
||||
{output_directory}
|
||||
.ytdl-sub-single_song_best_test-download-archive.json
|
||||
{output_directory}/YouTube/[2019] YouTube Rewind 2019: For the Record | #YouTubeRewind
|
||||
01 - YouTube Rewind 2019: For the Record | #YouTubeRewind.m4a
|
||||
Caution: extracted audio with 'best' format is not known during dry-run. Defaulting to m4a.
|
||||
Music Tags:
|
||||
album: YouTube Rewind 2019: For the Record | #YouTubeRewind
|
||||
albumartist: YouTube
|
||||
albumartists: YouTube
|
||||
artist: YouTube
|
||||
artists: YouTube
|
||||
genre: Unset
|
||||
genres:
|
||||
- multi_tag_1
|
||||
- multi_tag_2
|
||||
title: YouTube Rewind 2019: For the Record | #YouTubeRewind
|
||||
track: 1
|
||||
tracktotal: 1
|
||||
year: 2019
|
||||
Embedded thumbnail
|
||||
folder.jpg
|
||||
Loading…
Reference in a new issue