Skip to content

Commit

Permalink
Improve handling of some source quality related edge cases (#953)
Browse files Browse the repository at this point in the history
* Always sort source quality to the top of the list regardless of video metadata

* Return best quality when quality string contains "source" or "chunked"

* Always include "Source" in the source stream's quality string because of Twitch API weirdness.
Distinguishes between 720p60 (source) and 720p30 (transcoded) when framerates are not included in the M3U8 response.

* Cleanup
  • Loading branch information
ScrubN authored Jan 21, 2024
1 parent bc5c391 commit 88635e9
Show file tree
Hide file tree
Showing 2 changed files with 34 additions and 6 deletions.
36 changes: 30 additions & 6 deletions TwitchDownloaderCore/Extensions/M3U8Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ public static M3U8.Stream GetStreamOfQuality(this M3U8 m3u8, string qualityStrin
return m3u8.BestQualityStream();
}

if (qualityString.Contains("source", StringComparison.OrdinalIgnoreCase) || qualityString.Contains("chunked", StringComparison.OrdinalIgnoreCase))
{
return m3u8.BestQualityStream();
}

if (qualityString.Contains("audio", StringComparison.OrdinalIgnoreCase) &&
streams.FirstOrDefault(x => x.MediaInfo.Name.Contains("audio", StringComparison.OrdinalIgnoreCase)) is { } audioStream)
{
Expand Down Expand Up @@ -84,38 +89,55 @@ public static M3U8.Stream GetStreamOfQuality(this M3U8 m3u8, string qualityStrin
/// </returns>
public static string GetResolutionFramerateString(this M3U8.Stream stream)
{
const string RESOLUTION_FRAMERATE_PATTERN = /*lang=regex*/@"\d{3,4}p\d{2,3}";

var mediaInfo = stream.MediaInfo;
if (mediaInfo.Name.Contains("audio", StringComparison.OrdinalIgnoreCase) || Regex.IsMatch(mediaInfo.Name, @"\d{3,4}p\d{2,3}"))
if (mediaInfo.Name.Contains("audio", StringComparison.OrdinalIgnoreCase) || Regex.IsMatch(mediaInfo.Name, RESOLUTION_FRAMERATE_PATTERN))
{
return mediaInfo.Name;
}

var streamInfo = stream.StreamInfo;
if (Regex.IsMatch(streamInfo.Video, @"\d{3,4}p\d{2,3}"))
if (Regex.IsMatch(streamInfo.Video, RESOLUTION_FRAMERATE_PATTERN))
{
return streamInfo.Video;
}

if (Regex.IsMatch(mediaInfo.GroupId, @"\d{3,4}p\d{2,3}"))
if (Regex.IsMatch(mediaInfo.GroupId, RESOLUTION_FRAMERATE_PATTERN))
{
return mediaInfo.GroupId;
}

if (streamInfo.Resolution == default)
{
if (stream.IsSource())
{
return "Source";
}

return "";
}

var frameHeight = streamInfo.Resolution.Height;

if (streamInfo.Framerate == default)
{
if (stream.IsSource())
{
return $"{frameHeight}p (Source)";
}

return $"{frameHeight}p";
}

// Some M3U8 responses have framerate values up to 2fps more/less than the typical framerate.
var frameRate = (uint)(Math.Round(streamInfo.Framerate / 10) * 10);

if (stream.IsSource())
{
return $"{frameHeight}p{frameRate} (Source)";
}

return $"{frameHeight}p{frameRate}";
}

Expand All @@ -124,10 +146,12 @@ public static string GetResolutionFramerateString(this M3U8.Stream stream)
/// </summary>
public static M3U8.Stream BestQualityStream(this M3U8 m3u8)
{
var source = Array.Find(
m3u8.Streams, x => x.MediaInfo.Name.Contains("source", StringComparison.OrdinalIgnoreCase) ||
x.MediaInfo.GroupId.Equals("chunked", StringComparison.OrdinalIgnoreCase));
var source = Array.Find(m3u8.Streams, x => x.IsSource());
return source ?? m3u8.Streams.MaxBy(x => x.StreamInfo.Resolution.Width * x.StreamInfo.Resolution.Height * x.StreamInfo.Framerate);
}

internal static bool IsSource(this M3U8.Stream stream)
=> stream.MediaInfo.Name.Contains("source", StringComparison.OrdinalIgnoreCase) ||
stream.MediaInfo.GroupId.Equals("chunked", StringComparison.OrdinalIgnoreCase);
}
}
4 changes: 4 additions & 0 deletions TwitchDownloaderCore/Tools/M3U8StreamQualityComparer.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using TwitchDownloaderCore.Extensions;

namespace TwitchDownloaderCore.Tools
{
Expand All @@ -14,6 +15,9 @@ public int Compare(M3U8.Stream x, M3U8.Stream y)

if (y?.StreamInfo is null) return 1;

if (x.IsSource()) return -1;
if (y.IsSource()) return 1;

var xResolution = x.StreamInfo.Resolution;
var yResolution = y.StreamInfo.Resolution;
var xTotalPixels = xResolution.Width * xResolution.Height;
Expand Down

0 comments on commit 88635e9

Please sign in to comment.