using System.Runtime.Serialization; using System.Text.Json; using System.Text.Json.Nodes; using System.Text.Json.Serialization; using DotBased.Logging; using Manager.YouTube.Models; using Manager.YouTube.Models.Innertube; using Manager.YouTube.Parsers.Json; namespace Manager.YouTube.Util.Converters; public class YouTubeVideoJsonConverter : JsonConverter { private readonly ILogger _logger = LogService.RegisterLogger(); private readonly JsonSerializerOptions _serializerOptions = new() { Converters = { new NumericJsonConverter(), new NumericJsonConverter(), new NumericJsonConverter(), new NumericJsonConverter(), new NumericJsonConverter() }, PropertyNameCaseInsensitive = true }; public override YouTubeVideo Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { var node = JsonNode.Parse(ref reader); if (node == null) { throw new SerializationException("Failed to parse JSON reader."); } var rootObject = node.AsObject(); var playabilityStatus = rootObject["playabilityStatus"]; var streamingDataJson = rootObject["streamingData"]; var videoDetails = rootObject["videoDetails"]; var playerConfigJson = rootObject["playerConfig"]; var microformat = rootObject["microformat"]?["playerMicroformatRenderer"]; var videoId = videoDetails?["videoId"]?.GetValue() ?? microformat?["externalVideoId"]?.GetValue(); if (string.IsNullOrEmpty(videoId)) { throw new SerializationException("Failed to get videoId"); } var thumbnails = JsonParser.ExtractWebImages(videoDetails?["thumbnail"]); thumbnails.AddRange(JsonParser.ExtractWebImages(microformat?["thumbnail"])); var streamingData = streamingDataJson.Deserialize(_serializerOptions); var playerConfig = ExtractPlayerConfig(playerConfigJson); var video = new YouTubeVideo { VideoId = videoId, Title = JsonParser.ExtractTextOrHtml(microformat?["title"]), Description = JsonParser.ExtractTextOrHtml(microformat?["description"]), ViewCount = long.TryParse(microformat?["viewCount"]?.GetValue(), out var viewCountParsed) ? viewCountParsed : -1, LikeCount = long.TryParse(microformat?["likeCount"]?.GetValue(), out var likeCountParsed) ? likeCountParsed : -1, ChannelId = videoDetails?["channelId"]?.GetValue() ?? "", Author = videoDetails?["author"]?.GetValue() ?? "", PlayabilityStatus = playabilityStatus?["status"]?.GetValue() ?? "", LengthSeconds = long.TryParse(videoDetails?["lengthSeconds"]?.GetValue(), out var lengthSecondsParsed) ? lengthSecondsParsed : -1, Keywords = videoDetails?["keywords"]?.AsArray().Select(v => v?.GetValue() ?? "").ToArray() ?? [], IsOwnerViewing = videoDetails?["isOwnerViewing"]?.GetValue() ?? false, AllowRating = videoDetails?["allowRating"]?.GetValue() ?? false, IsCrawlable = videoDetails?["isCrawlable"]?.GetValue() ?? false, IsPrivate = videoDetails?["isPrivate"]?.GetValue() ?? false, IsUnpluggedCorpus = videoDetails?["isUnpluggedCorpus"]?.GetValue() ?? false, IsLive = videoDetails?["isLiveContent"]?.GetValue() ?? false, IsFamilySave = microformat?["isFamilySave"]?.GetValue() ?? false, AvailableCountries = microformat?["availableCountries"]?.AsArray().Select(v => v?.GetValue() ?? "").ToArray() ?? [], IsUnlisted = microformat?["isUnlisted"]?.GetValue() ?? false, HasYpcMetadata = microformat?["hasYpcMetadata"]?.GetValue() ?? false, PublishDate = DateTime.TryParse(microformat?["publishDate"]?.GetValue(), out var parsedPublishDate) ? parsedPublishDate : DateTime.MinValue, UploadDate = DateTime.TryParse(microformat?["uploadDate"]?.GetValue(), out var parsedUploadDate) ? parsedUploadDate : DateTime.MinValue, IsShortsEligible = microformat?["isShortsEligible"]?.GetValue() ?? false, Category = microformat?["category"]?.GetValue() ?? "", StreamingData = streamingData, Thumbnails = thumbnails, PlayerConfig = playerConfig }; return video; } public override void Write(Utf8JsonWriter writer, YouTubeVideo value, JsonSerializerOptions options) { throw new NotImplementedException("Converter only supports reading."); } private PlayerConfig? ExtractPlayerConfig(JsonNode? playerConfigNode) { if (playerConfigNode == null) { return null; } try { var playerConfigObj = playerConfigNode.AsObject(); var playerConfig = new PlayerConfig { AudioLoudnessDb = playerConfigObj["audioConfig"]?["loudnessDb"]?.GetValue() ?? 0, AudioPerceptualLoudnessDb = playerConfigObj["audioConfig"]?["perceptualLoudnessDb"]?.GetValue() ?? 0, AudioEnablePerFormatLoudness = playerConfigObj["audioConfig"]?["enablePerFormatLoudness"]?.GetValue() ?? false, MaxBitrate = uint.TryParse(playerConfigObj["streamSelectionConfig"]?["maxBitrate"]?.GetValue(), out var parsedMaxBitrate) ? parsedMaxBitrate : 0, MaxReadAheadMediaTimeMs = playerConfigObj["mediaCommonConfig"]?["dynamicReadaheadConfig"]?["maxReadAheadMediaTimeMs"]?.GetValue() ?? 0, MinReadAheadMediaTimeMs = playerConfigObj["mediaCommonConfig"]?["dynamicReadaheadConfig"]?["minReadAheadMediaTimeMs"]?.GetValue() ?? 0, ReadAheadGrowthRateMs = playerConfigObj["mediaCommonConfig"]?["dynamicReadaheadConfig"]?["readAheadGrowthRateMs"]?.GetValue() ?? 0, }; return playerConfig; } catch (Exception e) { _logger.Error(e, "Failed to extract player config from JSON."); return null; } } }