From 908d6198679c1c8f1afcd18110ec506e72d2b886 Mon Sep 17 00:00:00 2001 From: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com> Date: Mon, 2 Dec 2024 16:20:54 +0200 Subject: [PATCH 1/7] Identify audio stream languages Closes #845 --- YoutubeExplode.Tests/StreamSpecs.cs | 56 +++++++++++++++++++ YoutubeExplode.Tests/TestData/VideoIds.cs | 1 + YoutubeExplode/Bridge/DashManifest.cs | 6 ++ YoutubeExplode/Bridge/IStreamData.cs | 6 ++ YoutubeExplode/Bridge/PlayerResponse.cs | 28 +++++++++- .../ClosedCaptions => Common}/Language.cs | 2 + .../Utils/Extensions/JsonExtensions.cs | 8 +++ .../Videos/Streams/AudioOnlyStreamInfo.cs | 16 +++++- .../Videos/Streams/IAudioStreamInfo.cs | 18 ++++++ .../Videos/Streams/MuxedStreamInfo.cs | 9 +++ YoutubeExplode/Videos/Streams/StreamClient.cs | 11 +++- 11 files changed, 155 insertions(+), 6 deletions(-) rename YoutubeExplode/{Videos/ClosedCaptions => Common}/Language.cs (94%) diff --git a/YoutubeExplode.Tests/StreamSpecs.cs b/YoutubeExplode.Tests/StreamSpecs.cs index 25ffc4de..ac357ab9 100644 --- a/YoutubeExplode.Tests/StreamSpecs.cs +++ b/YoutubeExplode.Tests/StreamSpecs.cs @@ -1,3 +1,4 @@ +using System; using System.Buffers; using System.IO; using System.Linq; @@ -57,6 +58,61 @@ public async Task I_can_get_the_list_of_available_streams_of_a_video() .Contain(s => s.VideoQuality.MaxHeight == 144 && !s.VideoQuality.IsHighDefinition); } + [Fact] + public async Task I_can_get_the_list_of_available_streams_of_a_video_with_multiple_audio_languages() + { + // Arrange + var youtube = new YoutubeClient(); + + // Act + var manifest = await youtube.Videos.Streams.GetManifestAsync( + VideoIds.WithMultipleAUdioLanguages + ); + + // Assert + manifest.Streams.Should().NotBeEmpty(); + + manifest + .GetAudioStreams() + .Should() + .Contain(t => + t.AudioLanguage != null + && t.AudioLanguage.Value.Code == "en-US" + && t.AudioLanguage.Value.Name == "English (United States) original" + && t.IsAudioLanguageDefault == true + ); + + manifest + .GetAudioStreams() + .Should() + .Contain(t => + t.AudioLanguage != null + && t.AudioLanguage.Value.Code == "fr-FR" + && t.AudioLanguage.Value.Name == "French (France)" + && t.IsAudioLanguageDefault == false + ); + + manifest + .GetAudioStreams() + .Should() + .Contain(t => + t.AudioLanguage != null + && t.AudioLanguage.Value.Code == "it" + && t.AudioLanguage.Value.Name == "Italian" + && t.IsAudioLanguageDefault == false + ); + + manifest + .GetAudioStreams() + .Should() + .Contain(t => + t.AudioLanguage != null + && t.AudioLanguage.Value.Code == "pt-BR" + && t.AudioLanguage.Value.Name == "Portuguese (Brazil)" + && t.IsAudioLanguageDefault == false + ); + } + [Theory] [InlineData(VideoIds.Normal)] [InlineData(VideoIds.Unlisted)] diff --git a/YoutubeExplode.Tests/TestData/VideoIds.cs b/YoutubeExplode.Tests/TestData/VideoIds.cs index 902256c9..b79682eb 100644 --- a/YoutubeExplode.Tests/TestData/VideoIds.cs +++ b/YoutubeExplode.Tests/TestData/VideoIds.cs @@ -21,4 +21,5 @@ internal static class VideoIds public const string WithHighDynamicRangeStreams = "vX2vsvdq8nw"; public const string WithClosedCaptions = "YltHGKX80Y8"; public const string WithBrokenClosedCaptions = "1VKIIw05JnE"; + public const string WithMultipleAUdioLanguages = "ngqcjXfggHQ"; } diff --git a/YoutubeExplode/Bridge/DashManifest.cs b/YoutubeExplode/Bridge/DashManifest.cs index 1892cd67..cb24589b 100644 --- a/YoutubeExplode/Bridge/DashManifest.cs +++ b/YoutubeExplode/Bridge/DashManifest.cs @@ -70,6 +70,12 @@ public class StreamData(XElement content) : IStreamData [Lazy] public string? AudioCodec => IsAudioOnly ? (string?)content.Attribute("codecs") : null; + public string? AudioLanguageCode => null; + + public string? AudioLanguageName => null; + + public bool? IsAudioLanguageDefault => null; + [Lazy] public string? VideoCodec => IsAudioOnly ? null : (string?)content.Attribute("codecs"); diff --git a/YoutubeExplode/Bridge/IStreamData.cs b/YoutubeExplode/Bridge/IStreamData.cs index f2385bab..067e91e1 100644 --- a/YoutubeExplode/Bridge/IStreamData.cs +++ b/YoutubeExplode/Bridge/IStreamData.cs @@ -18,6 +18,12 @@ internal interface IStreamData string? AudioCodec { get; } + string? AudioLanguageCode { get; } + + string? AudioLanguageName { get; } + + bool? IsAudioLanguageDefault { get; } + string? VideoCodec { get; } string? VideoQualityLabel { get; } diff --git a/YoutubeExplode/Bridge/PlayerResponse.cs b/YoutubeExplode/Bridge/PlayerResponse.cs index 8462bbb8..094b3964 100644 --- a/YoutubeExplode/Bridge/PlayerResponse.cs +++ b/YoutubeExplode/Bridge/PlayerResponse.cs @@ -236,16 +236,38 @@ public class StreamData(JsonElement content) : IStreamData public string? Container => MimeType?.SubstringUntil(";").SubstringAfter("/"); [Lazy] - private bool IsAudioOnly => - MimeType?.StartsWith("audio/", StringComparison.OrdinalIgnoreCase) ?? false; + public string? Codecs => MimeType?.SubstringAfter("codecs=\"").SubstringUntil("\""); [Lazy] - public string? Codecs => MimeType?.SubstringAfter("codecs=\"").SubstringUntil("\""); + private bool IsAudioOnly => + MimeType?.StartsWith("audio/", StringComparison.OrdinalIgnoreCase) ?? false; [Lazy] public string? AudioCodec => IsAudioOnly ? Codecs : Codecs?.SubstringAfter(", ").NullIfWhiteSpace(); + [Lazy] + public string? AudioLanguageCode => + content + .GetPropertyOrNull("audioTrack") + ?.GetPropertyOrNull("id") + ?.GetStringOrNull() + ?.SubstringUntil("."); + + [Lazy] + public string? AudioLanguageName => + content + .GetPropertyOrNull("audioTrack") + ?.GetPropertyOrNull("displayName") + ?.GetStringOrNull(); + + [Lazy] + public bool? IsAudioLanguageDefault => + content + .GetPropertyOrNull("audioTrack") + ?.GetPropertyOrNull("audioIsDefault") + ?.GetBooleanOrNull(); + [Lazy] public string? VideoCodec { diff --git a/YoutubeExplode/Videos/ClosedCaptions/Language.cs b/YoutubeExplode/Common/Language.cs similarity index 94% rename from YoutubeExplode/Videos/ClosedCaptions/Language.cs rename to YoutubeExplode/Common/Language.cs index 4d25af38..d7b552ec 100644 --- a/YoutubeExplode/Videos/ClosedCaptions/Language.cs +++ b/YoutubeExplode/Common/Language.cs @@ -1,6 +1,8 @@ using System; using System.Diagnostics.CodeAnalysis; +// TODO: breaking change: update the namespace +// ReSharper disable once CheckNamespace namespace YoutubeExplode.Videos.ClosedCaptions; /// diff --git a/YoutubeExplode/Utils/Extensions/JsonExtensions.cs b/YoutubeExplode/Utils/Extensions/JsonExtensions.cs index 70628a7b..501707f8 100644 --- a/YoutubeExplode/Utils/Extensions/JsonExtensions.cs +++ b/YoutubeExplode/Utils/Extensions/JsonExtensions.cs @@ -25,6 +25,14 @@ internal static class JsonExtensions return null; } + public static bool? GetBooleanOrNull(this JsonElement element) => + element.ValueKind switch + { + JsonValueKind.True => true, + JsonValueKind.False => false, + _ => null, + }; + public static string? GetStringOrNull(this JsonElement element) => element.ValueKind == JsonValueKind.String ? element.GetString() : null; diff --git a/YoutubeExplode/Videos/Streams/AudioOnlyStreamInfo.cs b/YoutubeExplode/Videos/Streams/AudioOnlyStreamInfo.cs index 59cb8e55..26ec59f5 100644 --- a/YoutubeExplode/Videos/Streams/AudioOnlyStreamInfo.cs +++ b/YoutubeExplode/Videos/Streams/AudioOnlyStreamInfo.cs @@ -1,4 +1,5 @@ using System.Diagnostics.CodeAnalysis; +using YoutubeExplode.Videos.ClosedCaptions; namespace YoutubeExplode.Videos.Streams; @@ -10,7 +11,9 @@ public class AudioOnlyStreamInfo( Container container, FileSize size, Bitrate bitrate, - string audioCodec + string audioCodec, + Language? audioLanguage, + bool? isAudioLanguageDefault ) : IAudioStreamInfo { /// @@ -28,7 +31,16 @@ string audioCodec /// public string AudioCodec { get; } = audioCodec; + /// + public Language? AudioLanguage { get; } = audioLanguage; + + /// + public bool? IsAudioLanguageDefault { get; } = isAudioLanguageDefault; + /// [ExcludeFromCodeCoverage] - public override string ToString() => $"Audio-only ({Container})"; + public override string ToString() => + AudioLanguage is not null + ? $"Audio-only ({Container} | {AudioLanguage})" + : $"Audio-only ({Container})"; } diff --git a/YoutubeExplode/Videos/Streams/IAudioStreamInfo.cs b/YoutubeExplode/Videos/Streams/IAudioStreamInfo.cs index 9de2853a..7bad3d0c 100644 --- a/YoutubeExplode/Videos/Streams/IAudioStreamInfo.cs +++ b/YoutubeExplode/Videos/Streams/IAudioStreamInfo.cs @@ -1,3 +1,5 @@ +using YoutubeExplode.Videos.ClosedCaptions; + namespace YoutubeExplode.Videos.Streams; /// @@ -9,4 +11,20 @@ public interface IAudioStreamInfo : IStreamInfo /// Audio codec. /// string AudioCodec { get; } + + /// + /// Audio language. + /// + /// + /// May be null if the audio stream does not have language information. + /// + Language? AudioLanguage { get; } + + /// + /// Whether the audio track language corresponds to the default language of the video. + /// + /// + /// May be null if the audio stream does not have language information. + /// + bool? IsAudioLanguageDefault { get; } } diff --git a/YoutubeExplode/Videos/Streams/MuxedStreamInfo.cs b/YoutubeExplode/Videos/Streams/MuxedStreamInfo.cs index 0983038b..88258441 100644 --- a/YoutubeExplode/Videos/Streams/MuxedStreamInfo.cs +++ b/YoutubeExplode/Videos/Streams/MuxedStreamInfo.cs @@ -1,5 +1,6 @@ using System.Diagnostics.CodeAnalysis; using YoutubeExplode.Common; +using YoutubeExplode.Videos.ClosedCaptions; namespace YoutubeExplode.Videos.Streams; @@ -12,6 +13,8 @@ public class MuxedStreamInfo( FileSize size, Bitrate bitrate, string audioCodec, + Language? audioLanguage, + bool? isAudioLanguageDefault, string videoCodec, VideoQuality videoQuality, Resolution videoResolution @@ -32,6 +35,12 @@ Resolution videoResolution /// public string AudioCodec { get; } = audioCodec; + /// + public Language? AudioLanguage { get; } = audioLanguage; + + /// + public bool? IsAudioLanguageDefault { get; } = isAudioLanguageDefault; + /// public string VideoCodec { get; } = videoCodec; diff --git a/YoutubeExplode/Videos/Streams/StreamClient.cs b/YoutubeExplode/Videos/Streams/StreamClient.cs index d6db8c58..ced02674 100644 --- a/YoutubeExplode/Videos/Streams/StreamClient.cs +++ b/YoutubeExplode/Videos/Streams/StreamClient.cs @@ -13,6 +13,7 @@ using YoutubeExplode.Exceptions; using YoutubeExplode.Utils; using YoutubeExplode.Utils.Extensions; +using YoutubeExplode.Videos.ClosedCaptions; namespace YoutubeExplode.Videos.Streams; @@ -123,6 +124,10 @@ private async IAsyncEnumerable GetStreamInfosAsync( streamData.Bitrate?.Pipe(s => new Bitrate(s)) ?? throw new YoutubeExplodeException("Failed to extract the stream bitrate."); + var audioLanguage = !string.IsNullOrWhiteSpace(streamData.AudioLanguageCode) + ? new Language(streamData.AudioLanguageCode, streamData.AudioLanguageName!) + : (Language?)null; + // Muxed or video-only stream if (!string.IsNullOrWhiteSpace(streamData.VideoCodec)) { @@ -146,6 +151,8 @@ streamData.VideoWidth is not null && streamData.VideoHeight is not null new FileSize(contentLength.Value), bitrate, streamData.AudioCodec, + audioLanguage, + streamData.IsAudioLanguageDefault, streamData.VideoCodec, videoQuality, videoResolution @@ -177,7 +184,9 @@ streamData.VideoWidth is not null && streamData.VideoHeight is not null container, new FileSize(contentLength.Value), bitrate, - streamData.AudioCodec + streamData.AudioCodec, + audioLanguage, + streamData.IsAudioLanguageDefault ); yield return streamInfo; From 77d03ba4a81b84bc154581784709885699efb4d9 Mon Sep 17 00:00:00 2001 From: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com> Date: Mon, 2 Dec 2024 16:24:25 +0200 Subject: [PATCH 2/7] asd --- YoutubeExplode/Bridge/PlayerResponse.cs | 6 +++--- YoutubeExplode/Videos/Streams/IAudioStreamInfo.cs | 2 +- YoutubeExplode/Videos/Streams/StreamClient.cs | 5 ++++- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/YoutubeExplode/Bridge/PlayerResponse.cs b/YoutubeExplode/Bridge/PlayerResponse.cs index 094b3964..caaa1948 100644 --- a/YoutubeExplode/Bridge/PlayerResponse.cs +++ b/YoutubeExplode/Bridge/PlayerResponse.cs @@ -235,13 +235,13 @@ public class StreamData(JsonElement content) : IStreamData [Lazy] public string? Container => MimeType?.SubstringUntil(";").SubstringAfter("/"); - [Lazy] - public string? Codecs => MimeType?.SubstringAfter("codecs=\"").SubstringUntil("\""); - [Lazy] private bool IsAudioOnly => MimeType?.StartsWith("audio/", StringComparison.OrdinalIgnoreCase) ?? false; + [Lazy] + public string? Codecs => MimeType?.SubstringAfter("codecs=\"").SubstringUntil("\""); + [Lazy] public string? AudioCodec => IsAudioOnly ? Codecs : Codecs?.SubstringAfter(", ").NullIfWhiteSpace(); diff --git a/YoutubeExplode/Videos/Streams/IAudioStreamInfo.cs b/YoutubeExplode/Videos/Streams/IAudioStreamInfo.cs index 7bad3d0c..4b77dad7 100644 --- a/YoutubeExplode/Videos/Streams/IAudioStreamInfo.cs +++ b/YoutubeExplode/Videos/Streams/IAudioStreamInfo.cs @@ -21,7 +21,7 @@ public interface IAudioStreamInfo : IStreamInfo Language? AudioLanguage { get; } /// - /// Whether the audio track language corresponds to the default language of the video. + /// Whether the audio stream's language corresponds to the default language of the video. /// /// /// May be null if the audio stream does not have language information. diff --git a/YoutubeExplode/Videos/Streams/StreamClient.cs b/YoutubeExplode/Videos/Streams/StreamClient.cs index ced02674..aeb3d66c 100644 --- a/YoutubeExplode/Videos/Streams/StreamClient.cs +++ b/YoutubeExplode/Videos/Streams/StreamClient.cs @@ -125,7 +125,10 @@ private async IAsyncEnumerable GetStreamInfosAsync( ?? throw new YoutubeExplodeException("Failed to extract the stream bitrate."); var audioLanguage = !string.IsNullOrWhiteSpace(streamData.AudioLanguageCode) - ? new Language(streamData.AudioLanguageCode, streamData.AudioLanguageName!) + ? new Language( + streamData.AudioLanguageCode, + streamData.AudioLanguageName ?? streamData.AudioLanguageCode + ) : (Language?)null; // Muxed or video-only stream From 8869b92dc5d4306bf759f998b11096ce32b9b259 Mon Sep 17 00:00:00 2001 From: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com> Date: Mon, 2 Dec 2024 16:28:48 +0200 Subject: [PATCH 3/7] Better FFmpeg integration --- YoutubeExplode.Converter/Converter.cs | 29 +++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/YoutubeExplode.Converter/Converter.cs b/YoutubeExplode.Converter/Converter.cs index fc13d457..e8dd5249 100644 --- a/YoutubeExplode.Converter/Converter.cs +++ b/YoutubeExplode.Converter/Converter.cs @@ -116,18 +116,39 @@ private async ValueTask ProcessAsync( if (streamInput.Info is IAudioStreamInfo audioStreamInfo) { - arguments - .Add($"-metadata:s:a:{lastAudioStreamIndex++}") - .Add($"title={audioStreamInfo.Bitrate}"); + // Contains language information + if (audioStreamInfo.AudioLanguage is { } language) + { + // Language codes can be stored in any format, but most players expect + // three-letter codes, so we'll try to convert to that first. + var languageCode = language.TryGetThreeLetterCode() ?? language.Code; + + arguments + .Add($"-metadata:s:a:{lastAudioStreamIndex}") + .Add($"language={languageCode}") + .Add($"-metadata:s:a:{lastAudioStreamIndex}") + .Add($"title={language.Name} | {audioStreamInfo.Bitrate}"); + } + // Does not contain language information + else + { + arguments + .Add($"-metadata:s:a:{lastAudioStreamIndex}") + .Add($"title={audioStreamInfo.Bitrate}"); + } + + lastAudioStreamIndex++; } if (streamInput.Info is IVideoStreamInfo videoStreamInfo) { arguments - .Add($"-metadata:s:v:{lastVideoStreamIndex++}") + .Add($"-metadata:s:v:{lastVideoStreamIndex}") .Add( $"title={videoStreamInfo.VideoQuality.Label} | {videoStreamInfo.Bitrate}" ); + + lastVideoStreamIndex++; } } } From b9f6a859418dc789b1de5dcc985a08518a47954c Mon Sep 17 00:00:00 2001 From: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com> Date: Mon, 2 Dec 2024 16:30:35 +0200 Subject: [PATCH 4/7] asd --- YoutubeExplode.Tests/StreamSpecs.cs | 2 +- YoutubeExplode.Tests/TestData/VideoIds.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/YoutubeExplode.Tests/StreamSpecs.cs b/YoutubeExplode.Tests/StreamSpecs.cs index ac357ab9..02d9ddfe 100644 --- a/YoutubeExplode.Tests/StreamSpecs.cs +++ b/YoutubeExplode.Tests/StreamSpecs.cs @@ -66,7 +66,7 @@ public async Task I_can_get_the_list_of_available_streams_of_a_video_with_multip // Act var manifest = await youtube.Videos.Streams.GetManifestAsync( - VideoIds.WithMultipleAUdioLanguages + VideoIds.WithMultipleAudioLanguages ); // Assert diff --git a/YoutubeExplode.Tests/TestData/VideoIds.cs b/YoutubeExplode.Tests/TestData/VideoIds.cs index b79682eb..b822e5a5 100644 --- a/YoutubeExplode.Tests/TestData/VideoIds.cs +++ b/YoutubeExplode.Tests/TestData/VideoIds.cs @@ -21,5 +21,5 @@ internal static class VideoIds public const string WithHighDynamicRangeStreams = "vX2vsvdq8nw"; public const string WithClosedCaptions = "YltHGKX80Y8"; public const string WithBrokenClosedCaptions = "1VKIIw05JnE"; - public const string WithMultipleAUdioLanguages = "ngqcjXfggHQ"; + public const string WithMultipleAudioLanguages = "ngqcjXfggHQ"; } From c6c10f272f95f62ab144000879de85a18c2a6992 Mon Sep 17 00:00:00 2001 From: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com> Date: Mon, 2 Dec 2024 16:31:16 +0200 Subject: [PATCH 5/7] asd --- YoutubeExplode/Videos/Streams/IAudioStreamInfo.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/YoutubeExplode/Videos/Streams/IAudioStreamInfo.cs b/YoutubeExplode/Videos/Streams/IAudioStreamInfo.cs index 4b77dad7..e8103f84 100644 --- a/YoutubeExplode/Videos/Streams/IAudioStreamInfo.cs +++ b/YoutubeExplode/Videos/Streams/IAudioStreamInfo.cs @@ -16,7 +16,7 @@ public interface IAudioStreamInfo : IStreamInfo /// Audio language. /// /// - /// May be null if the audio stream does not have language information. + /// May be null if the audio stream does not contain language information. /// Language? AudioLanguage { get; } @@ -24,7 +24,7 @@ public interface IAudioStreamInfo : IStreamInfo /// Whether the audio stream's language corresponds to the default language of the video. /// /// - /// May be null if the audio stream does not have language information. + /// May be null if the audio stream does not contain language information. /// bool? IsAudioLanguageDefault { get; } } From bd415594269f3b185358b37688e0e05fe1434e5e Mon Sep 17 00:00:00 2001 From: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com> Date: Mon, 2 Dec 2024 16:53:00 +0200 Subject: [PATCH 6/7] asd --- .../GeneralSpecs.cs | 30 ++++++++++++++++++- YoutubeExplode.Converter/Converter.cs | 10 +++++-- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/YoutubeExplode.Converter.Tests/GeneralSpecs.cs b/YoutubeExplode.Converter.Tests/GeneralSpecs.cs index 7657193f..63e9bf45 100644 --- a/YoutubeExplode.Converter.Tests/GeneralSpecs.cs +++ b/YoutubeExplode.Converter.Tests/GeneralSpecs.cs @@ -92,7 +92,7 @@ public async Task I_can_download_a_video_as_a_single_mp4_file_with_multiple_stre var filePath = Path.Combine(dir.Path, "video.mp4"); // Act - var manifest = await youtube.Videos.Streams.GetManifestAsync("9bZkp7q19f0"); + var manifest = await youtube.Videos.Streams.GetManifestAsync("ngqcjXfggHQ"); var audioStreamInfos = manifest .GetAudioOnlyStreams() @@ -117,6 +117,20 @@ await youtube.Videos.DownloadAsync( // Assert MediaFormat.IsMp4File(filePath).Should().BeTrue(); + foreach (var streamInfo in audioStreamInfos) + { + if (streamInfo.AudioLanguage is not null) + { + FileEx + .ContainsBytes( + filePath, + Encoding.ASCII.GetBytes(streamInfo.AudioLanguage.Value.Name) + ) + .Should() + .BeTrue(); + } + } + foreach (var streamInfo in videoStreamInfos) { FileEx @@ -161,6 +175,20 @@ await youtube.Videos.DownloadAsync( // Assert MediaFormat.IsWebMFile(filePath).Should().BeTrue(); + foreach (var streamInfo in audioStreamInfos) + { + if (streamInfo.AudioLanguage is not null) + { + FileEx + .ContainsBytes( + filePath, + Encoding.ASCII.GetBytes(streamInfo.AudioLanguage.Value.Name) + ) + .Should() + .BeTrue(); + } + } + foreach (var streamInfo in videoStreamInfos) { FileEx diff --git a/YoutubeExplode.Converter/Converter.cs b/YoutubeExplode.Converter/Converter.cs index e8dd5249..d63d3c2b 100644 --- a/YoutubeExplode.Converter/Converter.cs +++ b/YoutubeExplode.Converter/Converter.cs @@ -117,17 +117,21 @@ private async ValueTask ProcessAsync( if (streamInput.Info is IAudioStreamInfo audioStreamInfo) { // Contains language information - if (audioStreamInfo.AudioLanguage is { } language) + if (audioStreamInfo.AudioLanguage is not null) { // Language codes can be stored in any format, but most players expect // three-letter codes, so we'll try to convert to that first. - var languageCode = language.TryGetThreeLetterCode() ?? language.Code; + var languageCode = + audioStreamInfo.AudioLanguage.Value.TryGetThreeLetterCode() + ?? audioStreamInfo.AudioLanguage.Value.Code; arguments .Add($"-metadata:s:a:{lastAudioStreamIndex}") .Add($"language={languageCode}") .Add($"-metadata:s:a:{lastAudioStreamIndex}") - .Add($"title={language.Name} | {audioStreamInfo.Bitrate}"); + .Add( + $"title={audioStreamInfo.AudioLanguage.Value.Name} | {audioStreamInfo.Bitrate}" + ); } // Does not contain language information else From 3d08bc244b24c4b1077edbeb56f01fa55f634c23 Mon Sep 17 00:00:00 2001 From: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com> Date: Mon, 2 Dec 2024 16:56:39 +0200 Subject: [PATCH 7/7] zxc --- YoutubeExplode.Converter.Tests/GeneralSpecs.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/YoutubeExplode.Converter.Tests/GeneralSpecs.cs b/YoutubeExplode.Converter.Tests/GeneralSpecs.cs index 63e9bf45..6c161937 100644 --- a/YoutubeExplode.Converter.Tests/GeneralSpecs.cs +++ b/YoutubeExplode.Converter.Tests/GeneralSpecs.cs @@ -150,7 +150,7 @@ public async Task I_can_download_a_video_as_a_single_webm_file_with_multiple_str var filePath = Path.Combine(dir.Path, "video.webm"); // Act - var manifest = await youtube.Videos.Streams.GetManifestAsync("9bZkp7q19f0"); + var manifest = await youtube.Videos.Streams.GetManifestAsync("ngqcjXfggHQ"); var audioStreamInfos = manifest .GetAudioOnlyStreams()