diff --git a/TwitchDownloaderCLI/Modes/InfoHandler.cs b/TwitchDownloaderCLI/Modes/InfoHandler.cs index 7c8e2785..7d4ed846 100644 --- a/TwitchDownloaderCLI/Modes/InfoHandler.cs +++ b/TwitchDownloaderCLI/Modes/InfoHandler.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; @@ -149,7 +150,7 @@ private static void HandleVodTable(GqlVideoResponse videoInfo, GqlVideoChapterRe var name = stream.GetResolutionFramerateString(); var resolution = stream.StreamInfo.Resolution.StringifyOrDefault(x => x.ToString(), DEFAULT_STRING); var fps = stream.StreamInfo.Framerate.StringifyOrDefault(x => $"{x:F0}", DEFAULT_STRING); - var codecs = stream.StreamInfo.Codecs.StringifyOrDefault(x => x, DEFAULT_STRING); + var codecs = stream.StreamInfo.Codecs.StringifyOrDefault(x => string.Join(", ", x), DEFAULT_STRING); if (hasBitrate) { @@ -364,6 +365,16 @@ private static string StringifyOrDefault(this T? value, Func strin return defaultString; } + private static string StringifyOrDefault(this IEnumerable values, Func, string> stringify, string defaultString) + { + if (values.Any()) + { + return stringify(values); + } + + return defaultString; + } + private static string StringifyTimestamp(TimeSpan timeSpan) { return timeSpan.Ticks switch diff --git a/TwitchDownloaderCore.Tests/ExtensionTests/M3U8ExtensionTests.cs b/TwitchDownloaderCore.Tests/ExtensionTests/M3U8ExtensionTests.cs index 6f93149b..e0f41e1b 100644 --- a/TwitchDownloaderCore.Tests/ExtensionTests/M3U8ExtensionTests.cs +++ b/TwitchDownloaderCore.Tests/ExtensionTests/M3U8ExtensionTests.cs @@ -30,15 +30,15 @@ public static void CorrectlyFindsStreamOfQualityFromLiveM3U8Response(string qual { new M3U8.Stream( new M3U8.Stream.ExtMediaInfo(M3U8.Stream.ExtMediaInfo.MediaType.Video, "chunked", "1080p60 (source)", true, true), - new M3U8.Stream.ExtStreamInfo(0, 1, "avc1.4D401F,mp4a.40.2", (1920, 1080), "chunked", 60), + new M3U8.Stream.ExtStreamInfo(0, 1, new[] { "avc1.4D401F", "mp4a.40.2" }, (1920, 1080), "chunked", 60), "1080p60"), new M3U8.Stream( new M3U8.Stream.ExtMediaInfo(M3U8.Stream.ExtMediaInfo.MediaType.Video, "720p60", "720p60", true, true), - new M3U8.Stream.ExtStreamInfo(0, 1, "avc1.4D401F,mp4a.40.2", (1280, 720), "720p60", 60), + new M3U8.Stream.ExtStreamInfo(0, 1, new[] { "avc1.4D401F", "mp4a.40.2" }, (1280, 720), "720p60", 60), "720p60"), new M3U8.Stream( new M3U8.Stream.ExtMediaInfo(M3U8.Stream.ExtMediaInfo.MediaType.Video, "720p30", "720p", true, true), - new M3U8.Stream.ExtStreamInfo(0, 1, "avc1.4D401F,mp4a.40.2", (1280, 720), "720p30", 30), + new M3U8.Stream.ExtStreamInfo(0, 1, new[] { "avc1.4D401F", "mp4a.40.2" }, (1280, 720), "720p30", 30), "720p30") }); @@ -74,31 +74,31 @@ public static void CorrectlyFindsStreamOfQualityFromOldM3U8Response(string quali { new M3U8.Stream( new M3U8.Stream.ExtMediaInfo(M3U8.Stream.ExtMediaInfo.MediaType.Video, "chunked", "Source", true, true), - new M3U8.Stream.ExtStreamInfo(0, 1, "avc1.4D401F,mp4a.40.2", (1920, 1080), "chunked", 58.644M), + new M3U8.Stream.ExtStreamInfo(0, 1, new[] { "avc1.4D401F", "mp4a.40.2" }, (1920, 1080), "chunked", 58.644M), "1080p60"), new M3U8.Stream( new M3U8.Stream.ExtMediaInfo(M3U8.Stream.ExtMediaInfo.MediaType.Video, "720p60", "720p60", true, true), - new M3U8.Stream.ExtStreamInfo(0, 1, "avc1.4D401F,mp4a.40.2", (1280, 720), "720p60", 58.644M), + new M3U8.Stream.ExtStreamInfo(0, 1, new[] { "avc1.4D401F", "mp4a.40.2" }, (1280, 720), "720p60", 58.644M), "720p60"), new M3U8.Stream( new M3U8.Stream.ExtMediaInfo(M3U8.Stream.ExtMediaInfo.MediaType.Video, "720p30", "720p", true, true), - new M3U8.Stream.ExtStreamInfo(0, 1, "avc1.4D401F,mp4a.40.2", (1280, 720), "720p30", 28.814M), + new M3U8.Stream.ExtStreamInfo(0, 1, new[] { "avc1.4D401F", "mp4a.40.2" }, (1280, 720), "720p30", 28.814M), "720p30"), new M3U8.Stream( new M3U8.Stream.ExtMediaInfo(M3U8.Stream.ExtMediaInfo.MediaType.Video, "480p30", "480p", true, true), - new M3U8.Stream.ExtStreamInfo(0, 1, "avc1.42C01E,mp4a.40.2", (852, 480), "480p30", 30.159M), + new M3U8.Stream.ExtStreamInfo(0, 1, new[] { "avc1.42C01E", "mp4a.40.2" }, (852, 480), "480p30", 30.159M), "480p30"), new M3U8.Stream( new M3U8.Stream.ExtMediaInfo(M3U8.Stream.ExtMediaInfo.MediaType.Video, "360p30", "360p", true, true), - new M3U8.Stream.ExtStreamInfo(0, 1, "avc1.42C01E,mp4a.40.2", (640, 360), "360p30", 30.159M), + new M3U8.Stream.ExtStreamInfo(0, 1, new[] { "avc1.42C01E", "mp4a.40.2" }, (640, 360), "360p30", 30.159M), "360p30"), new M3U8.Stream( new M3U8.Stream.ExtMediaInfo(M3U8.Stream.ExtMediaInfo.MediaType.Video, "144p30", "144p", true, true), - new M3U8.Stream.ExtStreamInfo(0, 1, "avc1.42C00C,mp4a.40.2", (256, 144), "144p30", 30.159M), + new M3U8.Stream.ExtStreamInfo(0, 1, new[] { "avc1.42C00C", "mp4a.40.2" }, (256, 144), "144p30", 30.159M), "144p30"), new M3U8.Stream( new M3U8.Stream.ExtMediaInfo(M3U8.Stream.ExtMediaInfo.MediaType.Video, "audio_only", "Audio Only", false, false), - new M3U8.Stream.ExtStreamInfo(0, 1, "mp4a.40.2", (256, 144), "audio_only", 0), + new M3U8.Stream.ExtStreamInfo(0, 1, new[] { "mp4a.40.2" }, (256, 144), "audio_only", 0), "audio_only") }); @@ -122,11 +122,11 @@ public static void CorrectlyFindsStreamOfQualityFromM3U8ResponseWithoutFramerate { new M3U8.Stream( new M3U8.Stream.ExtMediaInfo(M3U8.Stream.ExtMediaInfo.MediaType.Video, "chunked", "Source", true, true), - new M3U8.Stream.ExtStreamInfo(0, 1, "avc1.4D401F,mp4a.40.2", (1920, 1080), "chunked", 0), + new M3U8.Stream.ExtStreamInfo(0, 1, new[] { "avc1.4D401F", "mp4a.40.2" }, (1920, 1080), "chunked", 0), "1080p60"), new M3U8.Stream( new M3U8.Stream.ExtMediaInfo(M3U8.Stream.ExtMediaInfo.MediaType.Video, "720p60", "720p60", true, true), - new M3U8.Stream.ExtStreamInfo(0, 1, "avc1.4D401F,mp4a.40.2", (1280, 720), "720p60", 58.644M), + new M3U8.Stream.ExtStreamInfo(0, 1, new[] { "avc1.4D401F", "mp4a.40.2" }, (1280, 720), "720p60", 58.644M), "720p60"), }); @@ -146,31 +146,31 @@ public static void ReturnsHighestQualityWhenDesiredQualityNotFoundForOldM3U8Resp { new M3U8.Stream( new M3U8.Stream.ExtMediaInfo(M3U8.Stream.ExtMediaInfo.MediaType.Video, "chunked", "Source", true, true), - new M3U8.Stream.ExtStreamInfo(0, 1, "avc1.4D401F,mp4a.40.2", (1920, 1080), "chunked", 58.644M), + new M3U8.Stream.ExtStreamInfo(0, 1, new[] { "avc1.4D401F", "mp4a.40.2" }, (1920, 1080), "chunked", 58.644M), "1080p60"), new M3U8.Stream( new M3U8.Stream.ExtMediaInfo(M3U8.Stream.ExtMediaInfo.MediaType.Video, "720p60", "720p60", true, true), - new M3U8.Stream.ExtStreamInfo(0, 1, "avc1.4D401F,mp4a.40.2", (1280, 720), "720p60", 58.644M), + new M3U8.Stream.ExtStreamInfo(0, 1, new[] { "avc1.4D401F", "mp4a.40.2" }, (1280, 720), "720p60", 58.644M), "720p60"), new M3U8.Stream( new M3U8.Stream.ExtMediaInfo(M3U8.Stream.ExtMediaInfo.MediaType.Video, "720p30", "720p", true, true), - new M3U8.Stream.ExtStreamInfo(0, 1, "avc1.4D401F,mp4a.40.2", (1280, 720), "720p30", 28.814M), + new M3U8.Stream.ExtStreamInfo(0, 1, new[] { "avc1.4D401F", "mp4a.40.2" }, (1280, 720), "720p30", 28.814M), "720p30"), new M3U8.Stream( new M3U8.Stream.ExtMediaInfo(M3U8.Stream.ExtMediaInfo.MediaType.Video, "480p30", "480p", true, true), - new M3U8.Stream.ExtStreamInfo(0, 1, "avc1.42C01E,mp4a.40.2", (852, 480), "480p30", 30.159M), + new M3U8.Stream.ExtStreamInfo(0, 1, new[] { "avc1.42C01E", "mp4a.40.2" }, (852, 480), "480p30", 30.159M), "480p30"), new M3U8.Stream( new M3U8.Stream.ExtMediaInfo(M3U8.Stream.ExtMediaInfo.MediaType.Video, "360p30", "360p", true, true), - new M3U8.Stream.ExtStreamInfo(0, 1, "avc1.42C01E,mp4a.40.2", (640, 360), "360p30", 30.159M), + new M3U8.Stream.ExtStreamInfo(0, 1, new[] { "avc1.42C01E", "mp4a.40.2" }, (640, 360), "360p30", 30.159M), "360p30"), new M3U8.Stream( new M3U8.Stream.ExtMediaInfo(M3U8.Stream.ExtMediaInfo.MediaType.Video, "144p30", "144p", true, true), - new M3U8.Stream.ExtStreamInfo(0, 1, "avc1.42C00C,mp4a.40.2", (256, 144), "144p30", 30.159M), + new M3U8.Stream.ExtStreamInfo(0, 1, new[] { "avc1.42C00C", "mp4a.40.2" }, (256, 144), "144p30", 30.159M), "144p30"), new M3U8.Stream( new M3U8.Stream.ExtMediaInfo(M3U8.Stream.ExtMediaInfo.MediaType.Video, "audio_only", "Audio Only", false, false), - new M3U8.Stream.ExtStreamInfo(0, 1, "mp4a.40.2", (256, 144), "audio_only", 0), + new M3U8.Stream.ExtStreamInfo(0, 1, new[] { "mp4a.40.2" }, (256, 144), "audio_only", 0), "audio_only") }); diff --git a/TwitchDownloaderCore.Tests/ToolTests/M3U8Tests.cs b/TwitchDownloaderCore.Tests/ToolTests/M3U8Tests.cs index c3bd288f..b8b302c4 100644 --- a/TwitchDownloaderCore.Tests/ToolTests/M3U8Tests.cs +++ b/TwitchDownloaderCore.Tests/ToolTests/M3U8Tests.cs @@ -191,22 +191,22 @@ public void CorrectlyParsesTwitchM3U8OfPlaylists(bool useStream, string culture) var streams = new M3U8.Stream[] { new(new M3U8.Stream.ExtMediaInfo(M3U8.Stream.ExtMediaInfo.MediaType.Video, "chunked", "1080p60", false, false), - new M3U8.Stream.ExtStreamInfo(0, 5898203, "avc1.64002A,mp4a.40.2", (1920, 1080), "chunked", 59.995m), + new M3U8.Stream.ExtStreamInfo(0, 5898203, new[] { "avc1.64002A", "mp4a.40.2" }, (1920, 1080), "chunked", 59.995m), "https://abc123def456gh.cloudfront.net/123abc456def789ghi01_streamer42_12345678901_1234567890/chunked/index-dvr.m3u8"), new(new M3U8.Stream.ExtMediaInfo(M3U8.Stream.ExtMediaInfo.MediaType.Video, "720p60", "720p60", true, true), - new M3U8.Stream.ExtStreamInfo(0, 3443956, "avc1.4D0020,mp4a.40.2", (1280, 720), "720p60", 59.995m), + new M3U8.Stream.ExtStreamInfo(0, 3443956, new[] { "avc1.4D0020", "mp4a.40.2" }, (1280, 720), "720p60", 59.995m), "https://abc123def456gh.cloudfront.net/123abc456def789ghi01_streamer42_12345678901_1234567890/720p60/index-dvr.m3u8"), - new(new M3U8.Stream.ExtMediaInfo (M3U8.Stream.ExtMediaInfo.MediaType.Video, "480p30", "480p", true, true), - new M3U8.Stream.ExtStreamInfo (0, 1454397, "avc1.4D001F,mp4a.40.2", (852, 480), "480p30", 29.998m), + new(new M3U8.Stream.ExtMediaInfo(M3U8.Stream.ExtMediaInfo.MediaType.Video, "480p30", "480p", true, true), + new M3U8.Stream.ExtStreamInfo(0, 1454397, new[] { "avc1.4D001F", "mp4a.40.2" }, (852, 480), "480p30", 29.998m), "https://abc123def456gh.cloudfront.net/123abc456def789ghi01_streamer42_12345678901_1234567890/480p30/index-dvr.m3u8"), new(new M3U8.Stream.ExtMediaInfo(M3U8.Stream.ExtMediaInfo.MediaType.Video, "audio_only", "Audio Only", false, false), - new M3U8.Stream.ExtStreamInfo(0, 220328, "mp4a.40.2", (0, 0), "audio_only", 0m), + new M3U8.Stream.ExtStreamInfo(0, 220328, new[] { "mp4a.40.2" }, (0, 0), "audio_only", 0m), "https://abc123def456gh.cloudfront.net/123abc456def789ghi01_streamer42_12345678901_1234567890/audio_only/index-dvr.m3u8"), new(new M3U8.Stream.ExtMediaInfo(M3U8.Stream.ExtMediaInfo.MediaType.Video, "360p30", "360p", true, true), - new M3U8.Stream.ExtStreamInfo(0, 708016, "avc1.4D001E,mp4a.40.2", (640, 360), "360p30", 29.998m), + new M3U8.Stream.ExtStreamInfo(0, 708016, new[] { "avc1.4D001E", "mp4a.40.2" }, (640, 360), "360p30", 29.998m), "https://abc123def456gh.cloudfront.net/123abc456def789ghi01_streamer42_12345678901_1234567890/360p30/index-dvr.m3u8"), new(new M3U8.Stream.ExtMediaInfo(M3U8.Stream.ExtMediaInfo.MediaType.Video, "160p30", "160p", true, true), - new M3U8.Stream.ExtStreamInfo(0, 288409, "avc1.4D000C,mp4a.40.2", (284, 160), "160p30", 29.998m), + new M3U8.Stream.ExtStreamInfo(0, 288409, new[] { "avc1.4D000C", "mp4a.40.2" }, (284, 160), "160p30", 29.998m), "https://abc123def456gh.cloudfront.net/123abc456def789ghi01_streamer42_12345678901_1234567890/160p30/index-dvr.m3u8") }; @@ -255,13 +255,13 @@ public void CorrectlyParsesTwitchM3U8MediaInfo(string mediaInfoString, M3U8.Stre } [Theory] - [InlineData("#EXT-X-STREAM-INF:BANDWIDTH=5898203,CODECS=\"avc1.64002A,mp4a.40.2\",RESOLUTION=1920x1080,VIDEO=\"chunked\",FRAME-RATE=59.995", 5898203, "avc1.64002A,mp4a.40.2", 1920, 1080, "chunked", 59.995)] - [InlineData("#EXT-X-STREAM-INF:BANDWIDTH=3443956,CODECS=\"avc1.4D0020,mp4a.40.2\",RESOLUTION=1280x720,VIDEO=\"720p60\",FRAME-RATE=59.995", 3443956, "avc1.4D0020,mp4a.40.2", 1280, 720, "720p60", 59.995)] - [InlineData("#EXT-X-STREAM-INF:BANDWIDTH=1454397,CODECS=\"avc1.4D001F,mp4a.40.2\",RESOLUTION=852x480,VIDEO=\"480p30\",FRAME-RATE=29.998", 1454397, "avc1.4D001F,mp4a.40.2", 852, 480, "480p30", 29.998)] - [InlineData("#EXT-X-STREAM-INF:BANDWIDTH=220328,CODECS=\"mp4a.40.2\",VIDEO=\"audio_only\"", 220328, "mp4a.40.2", 0, 0, "audio_only", 0)] - [InlineData("#EXT-X-STREAM-INF:BANDWIDTH=708016,CODECS=\"avc1.4D001E,mp4a.40.2\",RESOLUTION=640x360,VIDEO=\"360p30\",FRAME-RATE=29.998", 708016, "avc1.4D001E,mp4a.40.2", 640, 360, "360p30", 29.998)] - [InlineData("#EXT-X-STREAM-INF:BANDWIDTH=288409,CODECS=\"avc1.4D000C,mp4a.40.2\",RESOLUTION=284x160,VIDEO=\"160p30\",FRAME-RATE=29.998", 288409, "avc1.4D000C,mp4a.40.2", 284, 160, "160p30", 29.998)] - public void CorrectlyParsesTwitchM3U8StreamInfo(string streamInfoString, int bandwidth, string codecs, uint videoWidth, uint videoHeight, string video, decimal framerate) + [InlineData("#EXT-X-STREAM-INF:BANDWIDTH=5898203,CODECS=\"avc1.64002A,mp4a.40.2\",RESOLUTION=1920x1080,VIDEO=\"chunked\",FRAME-RATE=59.995", 5898203, new[] { "avc1.64002A", "mp4a.40.2" }, 1920, 1080, "chunked", 59.995)] + [InlineData("#EXT-X-STREAM-INF:BANDWIDTH=3443956,CODECS=\"avc1.4D0020,mp4a.40.2\",RESOLUTION=1280x720,VIDEO=\"720p60\",FRAME-RATE=59.995", 3443956, new[] { "avc1.4D0020", "mp4a.40.2" }, 1280, 720, "720p60", 59.995)] + [InlineData("#EXT-X-STREAM-INF:BANDWIDTH=1454397,CODECS=\"avc1.4D001F,mp4a.40.2\",RESOLUTION=852x480,VIDEO=\"480p30\",FRAME-RATE=29.998", 1454397, new[] { "avc1.4D001F", "mp4a.40.2" }, 852, 480, "480p30", 29.998)] + [InlineData("#EXT-X-STREAM-INF:BANDWIDTH=220328,CODECS=\"mp4a.40.2\",VIDEO=\"audio_only\"", 220328, new[] { "mp4a.40.2" }, 0, 0, "audio_only", 0)] + [InlineData("#EXT-X-STREAM-INF:BANDWIDTH=708016,CODECS=\"avc1.4D001E,mp4a.40.2\",RESOLUTION=640x360,VIDEO=\"360p30\",FRAME-RATE=29.998", 708016, new[] { "avc1.4D001E", "mp4a.40.2" }, 640, 360, "360p30", 29.998)] + [InlineData("#EXT-X-STREAM-INF:BANDWIDTH=288409,CODECS=\"avc1.4D000C,mp4a.40.2\",RESOLUTION=284x160,VIDEO=\"160p30\",FRAME-RATE=29.998", 288409, new[] { "avc1.4D000C", "mp4a.40.2" }, 284, 160, "160p30", 29.998)] + public void CorrectlyParsesTwitchM3U8StreamInfo(string streamInfoString, int bandwidth, string[] codecs, uint videoWidth, uint videoHeight, string video, decimal framerate) { var streamInfo = M3U8.Stream.ExtStreamInfo.Parse(streamInfoString); @@ -399,19 +399,19 @@ public void CorrectlyParsesKickM3U8OfPlaylists(bool useStream, string culture) var streams = new M3U8.Stream[] { new(new M3U8.Stream.ExtMediaInfo(M3U8.Stream.ExtMediaInfo.MediaType.Video, "1080p60", "1080p60", true, true), - new M3U8.Stream.ExtStreamInfo(1, 9878400, "avc1.64002A,mp4a.40.2", (1920, 1080), "1080p60", 60m), + new M3U8.Stream.ExtStreamInfo(1, 9878400, new[] { "avc1.64002A", "mp4a.40.2" }, (1920, 1080), "1080p60", 60m), "1080p60/playlist.m3u8"), new(new M3U8.Stream.ExtMediaInfo(M3U8.Stream.ExtMediaInfo.MediaType.Video, "720p60", "720p60", true, true), - new M3U8.Stream.ExtStreamInfo(1, 3330599, "avc1.4D401F,mp4a.40.2", (1280, 720), "720p60", 60m), + new M3U8.Stream.ExtStreamInfo(1, 3330599, new[] { "avc1.4D401F", "mp4a.40.2" }, (1280, 720), "720p60", 60m), "720p60/playlist.m3u8"), new(new M3U8.Stream.ExtMediaInfo(M3U8.Stream.ExtMediaInfo.MediaType.Video, "480p30", "480p", true, true), - new M3U8.Stream.ExtStreamInfo(1, 1335600, "avc1.4D401F,mp4a.40.2", (852, 480), "480p30", 30m), + new M3U8.Stream.ExtStreamInfo(1, 1335600, new[] { "avc1.4D401F", "mp4a.40.2" }, (852, 480), "480p30", 30m), "480p30/playlist.m3u8"), new(new M3U8.Stream.ExtMediaInfo(M3U8.Stream.ExtMediaInfo.MediaType.Video, "360p30", "360p", true, true), - new M3U8.Stream.ExtStreamInfo(1, 630000, "avc1.4D401F,mp4a.40.2", (640, 360), "360p30", 30m), + new M3U8.Stream.ExtStreamInfo(1, 630000, new[] { "avc1.4D401F", "mp4a.40.2" }, (640, 360), "360p30", 30m), "360p30/playlist.m3u8"), new(new M3U8.Stream.ExtMediaInfo(M3U8.Stream.ExtMediaInfo.MediaType.Video, "160p30", "160p", true, true), - new M3U8.Stream.ExtStreamInfo(1, 230000, "avc1.4D401F,mp4a.40.2", (284, 160), "160p30", 30m), + new M3U8.Stream.ExtStreamInfo(1, 230000, new[] { "avc1.4D401F", "mp4a.40.2" }, (284, 160), "160p30", 30m), "160p30/playlist.m3u8") }; @@ -458,12 +458,12 @@ public void CorrectlyParsesKickM3U8MediaInfo(string mediaInfoString, M3U8.Stream } [Theory] - [InlineData("#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=9878400,CODECS=\"avc1.64002A,mp4a.40.2\",RESOLUTION=1920x1080,VIDEO=\"1080p60\"", 9878400, "avc1.64002A,mp4a.40.2", 1920, 1080, "1080p60")] - [InlineData("#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=3330599,CODECS=\"avc1.4D401F,mp4a.40.2\",RESOLUTION=1280x720,VIDEO=\"720p60\"", 3330599, "avc1.4D401F,mp4a.40.2", 1280, 720, "720p60")] - [InlineData("#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1335600,CODECS=\"avc1.4D401F,mp4a.40.2\",RESOLUTION=852x480,VIDEO=\"480p30\"", 1335600, "avc1.4D401F,mp4a.40.2", 852, 480, "480p30")] - [InlineData("#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=630000,CODECS=\"avc1.4D401F,mp4a.40.2\",RESOLUTION=640x360,VIDEO=\"360p30\"", 630000, "avc1.4D401F,mp4a.40.2", 640, 360, "360p30")] - [InlineData("#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=230000,CODECS=\"avc1.4D401F,mp4a.40.2\",RESOLUTION=284x160,VIDEO=\"160p30\"", 230000, "avc1.4D401F,mp4a.40.2", 284, 160, "160p30")] - public void CorrectlyParsesKickM3U8StreamInfo(string streamInfoString, int bandwidth, string codecs, uint videoWidth, uint videoHeight, string video) + [InlineData("#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=9878400,CODECS=\"avc1.64002A,mp4a.40.2\",RESOLUTION=1920x1080,VIDEO=\"1080p60\"", 9878400, new[] { "avc1.64002A", "mp4a.40.2" }, 1920, 1080, "1080p60")] + [InlineData("#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=3330599,CODECS=\"avc1.4D401F,mp4a.40.2\",RESOLUTION=1280x720,VIDEO=\"720p60\"", 3330599, new[] { "avc1.4D401F", "mp4a.40.2" }, 1280, 720, "720p60")] + [InlineData("#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1335600,CODECS=\"avc1.4D401F,mp4a.40.2\",RESOLUTION=852x480,VIDEO=\"480p30\"", 1335600, new[] { "avc1.4D401F", "mp4a.40.2" }, 852, 480, "480p30")] + [InlineData("#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=630000,CODECS=\"avc1.4D401F,mp4a.40.2\",RESOLUTION=640x360,VIDEO=\"360p30\"", 630000, new[] { "avc1.4D401F", "mp4a.40.2" }, 640, 360, "360p30")] + [InlineData("#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=230000,CODECS=\"avc1.4D401F,mp4a.40.2\",RESOLUTION=284x160,VIDEO=\"160p30\"", 230000, new[] { "avc1.4D401F", "mp4a.40.2" }, 284, 160, "160p30")] + public void CorrectlyParsesKickM3U8StreamInfo(string streamInfoString, int bandwidth, string[] codecs, uint videoWidth, uint videoHeight, string video) { var streamInfo = M3U8.Stream.ExtStreamInfo.Parse(streamInfoString); diff --git a/TwitchDownloaderCore/Tools/M3U8.cs b/TwitchDownloaderCore/Tools/M3U8.cs index 5e26ecfe..064fbe17 100644 --- a/TwitchDownloaderCore/Tools/M3U8.cs +++ b/TwitchDownloaderCore/Tools/M3U8.cs @@ -224,7 +224,7 @@ public readonly partial record struct StreamResolution(uint Width, uint Height) private ExtStreamInfo() { } - public ExtStreamInfo(int programId, int bandwidth, string codecs, StreamResolution resolution, string video, decimal framerate) + public ExtStreamInfo(int programId, int bandwidth, string[] codecs, StreamResolution resolution, string video, decimal framerate) { ProgramId = programId; Bandwidth = bandwidth; @@ -236,7 +236,7 @@ public ExtStreamInfo(int programId, int bandwidth, string codecs, StreamResoluti public int ProgramId { get; internal set; } public int Bandwidth { get; internal set; } - public string Codecs { get; internal set; } + public IReadOnlyList Codecs { get; internal set; } public StreamResolution Resolution { get; internal set; } public string Video { get; internal set; } public decimal Framerate { get; internal set; } @@ -252,8 +252,8 @@ public override string ToString() if (Bandwidth != default) sb.AppendKeyValue("BANDWIDTH=", Bandwidth, keyValueSeparator); - if (!string.IsNullOrWhiteSpace(Codecs)) - sb.AppendKeyQuoteValue("CODECS=", Codecs, keyValueSeparator); + if (Codecs is { Count: > 0 }) + sb.AppendKeyQuoteValue("CODECS=", string.Join(',', Codecs), keyValueSeparator); if (Resolution != default) sb.AppendKeyValue("RESOLUTION=", Resolution, keyValueSeparator); diff --git a/TwitchDownloaderCore/Tools/M3U8Parse.cs b/TwitchDownloaderCore/Tools/M3U8Parse.cs index 27886c78..ee1f2379 100644 --- a/TwitchDownloaderCore/Tools/M3U8Parse.cs +++ b/TwitchDownloaderCore/Tools/M3U8Parse.cs @@ -400,7 +400,8 @@ public static ExtStreamInfo Parse(ReadOnlySpan text) } else if (text.StartsWith(KEY_CODECS)) { - streamInfo.Codecs = ParsingHelpers.ParseStringValue(text, KEY_CODECS); + var codecsString = ParsingHelpers.ParseStringValue(text, KEY_CODECS); + streamInfo.Codecs = codecsString.Split(','); } else if (text.StartsWith(KEY_RESOLUTION)) {