Download all supported audio languages (#556)
This commit is contained in:
parent
1b3f5cb623
commit
e0d95244b9
@ -56,6 +56,7 @@ To learn more about the war and how you can help, [click here](https://tyrrrz.me
|
||||
- Download videos from playlists or channels
|
||||
- Download videos by search query
|
||||
- Selectable video quality and format
|
||||
- Automatically embed audio tracks in alternative languages
|
||||
- Automatically embed subtitles
|
||||
- Automatically inject media tags
|
||||
- Log in with a YouTube account to access private content
|
||||
|
@ -18,7 +18,10 @@ public partial record VideoDownloadOption(
|
||||
|
||||
public partial record VideoDownloadOption
|
||||
{
|
||||
internal static IReadOnlyList<VideoDownloadOption> ResolveAll(StreamManifest manifest)
|
||||
internal static IReadOnlyList<VideoDownloadOption> ResolveAll(
|
||||
StreamManifest manifest,
|
||||
bool includeLanguageSpecificAudioStreams = true
|
||||
)
|
||||
{
|
||||
IEnumerable<VideoDownloadOption> GetVideoAndAudioOptions()
|
||||
{
|
||||
@ -40,22 +43,50 @@ public partial record VideoDownloadOption
|
||||
// Separate audio + video stream
|
||||
else
|
||||
{
|
||||
// Prefer audio stream with the same container
|
||||
var audioStreamInfo = manifest
|
||||
var audioStreamInfos = manifest
|
||||
.GetAudioStreams()
|
||||
// Prefer audio streams with the same container
|
||||
.OrderByDescending(s => s.Container == videoStreamInfo.Container)
|
||||
.ThenByDescending(s => s is AudioOnlyStreamInfo)
|
||||
.ThenByDescending(s => s.Bitrate)
|
||||
.FirstOrDefault();
|
||||
.ToArray();
|
||||
|
||||
if (audioStreamInfo is not null)
|
||||
// Prefer language-specific audio streams, if available and if allowed
|
||||
var languageSpecificAudioStreamInfos = includeLanguageSpecificAudioStreams
|
||||
? audioStreamInfos
|
||||
.Where(s => s.AudioLanguage is not null)
|
||||
.DistinctBy(s => s.AudioLanguage)
|
||||
// Default language first so it's encoded as the first audio track in the output file
|
||||
.OrderByDescending(s => s.IsAudioLanguageDefault)
|
||||
.ToArray()
|
||||
: [];
|
||||
|
||||
// If there are language-specific streams, include them all
|
||||
if (languageSpecificAudioStreamInfos.Any())
|
||||
{
|
||||
yield return new VideoDownloadOption(
|
||||
videoStreamInfo.Container,
|
||||
false,
|
||||
new IStreamInfo[] { videoStreamInfo, audioStreamInfo }
|
||||
[videoStreamInfo, .. languageSpecificAudioStreamInfos]
|
||||
);
|
||||
}
|
||||
// If there are no language-specific streams, download the single best quality audio stream
|
||||
else
|
||||
{
|
||||
var audioStreamInfo = audioStreamInfos
|
||||
// Prefer audio streams in the default language (or non-language-specific streams)
|
||||
.OrderByDescending(s => s.IsAudioLanguageDefault ?? true)
|
||||
.FirstOrDefault();
|
||||
|
||||
if (audioStreamInfo is not null)
|
||||
{
|
||||
yield return new VideoDownloadOption(
|
||||
videoStreamInfo.Container,
|
||||
false,
|
||||
[videoStreamInfo, audioStreamInfo]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -66,7 +97,10 @@ public partial record VideoDownloadOption
|
||||
{
|
||||
var audioStreamInfo = manifest
|
||||
.GetAudioStreams()
|
||||
.OrderByDescending(s => s.Container == Container.WebM)
|
||||
// Prefer audio streams in the default language (or non-language-specific streams)
|
||||
.OrderByDescending(s => s.IsAudioLanguageDefault ?? true)
|
||||
// Prefer audio streams with the same container
|
||||
.ThenByDescending(s => s.Container == Container.WebM)
|
||||
.ThenByDescending(s => s is AudioOnlyStreamInfo)
|
||||
.ThenByDescending(s => s.Bitrate)
|
||||
.FirstOrDefault();
|
||||
@ -89,7 +123,10 @@ public partial record VideoDownloadOption
|
||||
{
|
||||
var audioStreamInfo = manifest
|
||||
.GetAudioStreams()
|
||||
.OrderByDescending(s => s.Container == Container.Mp4)
|
||||
// Prefer audio streams in the default language (or non-language-specific streams)
|
||||
.OrderByDescending(s => s.IsAudioLanguageDefault ?? true)
|
||||
// Prefer audio streams with the same container
|
||||
.ThenByDescending(s => s.Container == Container.Mp4)
|
||||
.ThenByDescending(s => s is AudioOnlyStreamInfo)
|
||||
.ThenByDescending(s => s.Bitrate)
|
||||
.FirstOrDefault();
|
||||
|
@ -19,20 +19,26 @@ public class VideoDownloader(IReadOnlyList<Cookie>? initialCookies = null)
|
||||
|
||||
public async Task<IReadOnlyList<VideoDownloadOption>> GetDownloadOptionsAsync(
|
||||
VideoId videoId,
|
||||
bool includeLanguageSpecificAudioStreams = true,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
var manifest = await _youtube.Videos.Streams.GetManifestAsync(videoId, cancellationToken);
|
||||
return VideoDownloadOption.ResolveAll(manifest);
|
||||
return VideoDownloadOption.ResolveAll(manifest, includeLanguageSpecificAudioStreams);
|
||||
}
|
||||
|
||||
public async Task<VideoDownloadOption> GetBestDownloadOptionAsync(
|
||||
VideoId videoId,
|
||||
VideoDownloadPreference preference,
|
||||
bool includeLanguageSpecificAudioStreams = true,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
var options = await GetDownloadOptionsAsync(videoId, cancellationToken);
|
||||
var options = await GetDownloadOptionsAsync(
|
||||
videoId,
|
||||
includeLanguageSpecificAudioStreams,
|
||||
cancellationToken
|
||||
);
|
||||
|
||||
return preference.TryGetBestOption(options)
|
||||
?? throw new InvalidOperationException("No suitable download option found.");
|
||||
|
@ -5,8 +5,8 @@
|
||||
<PackageReference Include="Gress" Version="2.1.1" />
|
||||
<PackageReference Include="JsonExtensions" Version="1.2.0" />
|
||||
<PackageReference Include="TagLibSharp" Version="2.3.0" />
|
||||
<PackageReference Include="YoutubeExplode" Version="6.4.4" />
|
||||
<PackageReference Include="YoutubeExplode.Converter" Version="6.4.4" />
|
||||
<PackageReference Include="YoutubeExplode" Version="6.5.0" />
|
||||
<PackageReference Include="YoutubeExplode.Converter" Version="6.5.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
@ -49,6 +49,13 @@ public partial class SettingsService()
|
||||
set => SetProperty(ref _isAuthPersisted, value);
|
||||
}
|
||||
|
||||
private bool _shouldInjectLanguageSpecificAudioStreams = true;
|
||||
public bool ShouldInjectLanguageSpecificAudioStreams
|
||||
{
|
||||
get => _shouldInjectLanguageSpecificAudioStreams;
|
||||
set => SetProperty(ref _shouldInjectLanguageSpecificAudioStreams, value);
|
||||
}
|
||||
|
||||
private bool _shouldInjectSubtitles = true;
|
||||
public bool ShouldInjectSubtitles
|
||||
{
|
||||
|
@ -104,6 +104,7 @@ public partial class DashboardViewModel : ViewModelBase
|
||||
?? await downloader.GetBestDownloadOptionAsync(
|
||||
download.Video!.Id,
|
||||
download.DownloadPreference!,
|
||||
_settingsService.ShouldInjectLanguageSpecificAudioStreams,
|
||||
download.CancellationToken
|
||||
);
|
||||
|
||||
@ -190,7 +191,10 @@ public partial class DashboardViewModel : ViewModelBase
|
||||
if (result.Videos.Count == 1)
|
||||
{
|
||||
var video = result.Videos.Single();
|
||||
var downloadOptions = await downloader.GetDownloadOptionsAsync(video.Id);
|
||||
var downloadOptions = await downloader.GetDownloadOptionsAsync(
|
||||
video.Id,
|
||||
_settingsService.ShouldInjectLanguageSpecificAudioStreams
|
||||
);
|
||||
|
||||
var download = await _dialogManager.ShowDialogAsync(
|
||||
_viewModelManager.CreateDownloadSingleSetupViewModel(video, downloadOptions)
|
||||
|
@ -40,6 +40,12 @@ public class SettingsViewModel : DialogViewModelBase
|
||||
set => _settingsService.IsAuthPersisted = value;
|
||||
}
|
||||
|
||||
public bool ShouldInjectLanguageSpecificAudioStreams
|
||||
{
|
||||
get => _settingsService.ShouldInjectLanguageSpecificAudioStreams;
|
||||
set => _settingsService.ShouldInjectLanguageSpecificAudioStreams = value;
|
||||
}
|
||||
|
||||
public bool ShouldInjectSubtitles
|
||||
{
|
||||
get => _settingsService.ShouldInjectSubtitles;
|
||||
|
@ -70,11 +70,20 @@
|
||||
IsChecked="{Binding IsAuthPersisted}" />
|
||||
</DockPanel>
|
||||
|
||||
<!-- Inject language-specific audio streams -->
|
||||
<DockPanel
|
||||
Margin="16,8"
|
||||
LastChildFill="False"
|
||||
ToolTip.Tip="Inject audio tracks in alternative languages (if available) into downloaded files">
|
||||
<TextBlock DockPanel.Dock="Left" Text="Inject alternative languages" />
|
||||
<ToggleSwitch DockPanel.Dock="Right" IsChecked="{Binding ShouldInjectLanguageSpecificAudioStreams}" />
|
||||
</DockPanel>
|
||||
|
||||
<!-- Inject subtitles -->
|
||||
<DockPanel
|
||||
Margin="16,8"
|
||||
LastChildFill="False"
|
||||
ToolTip.Tip="Inject subtitles into downloaded files">
|
||||
ToolTip.Tip="Inject subtitles (if available) into downloaded files">
|
||||
<TextBlock DockPanel.Dock="Left" Text="Inject subtitles" />
|
||||
<ToggleSwitch DockPanel.Dock="Right" IsChecked="{Binding ShouldInjectSubtitles}" />
|
||||
</DockPanel>
|
||||
@ -83,7 +92,7 @@
|
||||
<DockPanel
|
||||
Margin="16,8"
|
||||
LastChildFill="False"
|
||||
ToolTip.Tip="Inject media tags into downloaded files">
|
||||
ToolTip.Tip="Inject media tags (if available) into downloaded files">
|
||||
<TextBlock DockPanel.Dock="Left" Text="Inject media tags" />
|
||||
<ToggleSwitch DockPanel.Dock="Right" IsChecked="{Binding ShouldInjectTags}" />
|
||||
</DockPanel>
|
||||
|
Loading…
x
Reference in New Issue
Block a user