From ff78d7a062ceb0e5eafb95ffa3bc8fa1e110864c Mon Sep 17 00:00:00 2001 From: Vladimir Date: Tue, 24 Sep 2024 19:57:07 +0300 Subject: [PATCH] parse list from another source, make file guide --- TV Player WPF/PlaylistWorker/M3UParser.cs | 113 +++++++++++++++----- TV Player WPF/ViewModels/PlayerViewModel.cs | 4 +- TV Player WPF/ViewModels/ProgramsData.cs | 29 +++-- 3 files changed, 100 insertions(+), 46 deletions(-) diff --git a/TV Player WPF/PlaylistWorker/M3UParser.cs b/TV Player WPF/PlaylistWorker/M3UParser.cs index 639b2cf..e833c94 100644 --- a/TV Player WPF/PlaylistWorker/M3UParser.cs +++ b/TV Player WPF/PlaylistWorker/M3UParser.cs @@ -1,6 +1,9 @@ using System.IO; +using System.IO.Compression; using System.Net.Http; using System.Net.Http.Headers; +using System.Security.Policy; +using System.Text; using System.Text.RegularExpressions; using System.Xml; @@ -28,9 +31,36 @@ namespace TV_Player public static class M3UParser { - public static async Task> DownloadGuideFromWebAsync(string url) + public static async Task DownloadGuideFromWebAsync(string url) + { + + var filePath = "guide.xml"; + if (File.Exists(filePath)) + { + DateTime creationTime = File.GetCreationTime(filePath); + DateTime modificationTime = File.GetLastWriteTime(filePath); + DateTime currentTime = DateTime.Now; + + if ((currentTime - creationTime).TotalDays < 3 || (currentTime - modificationTime).TotalDays < 3) + { + return; + } + }; + var channelsContent = string.Empty; + + if (url.Contains(".gz")) + { + channelsContent = await DownloadGzip(url); + } + else + { + channelsContent = await DownloadXMLProgram(url); + } + await File.WriteAllTextAsync(filePath, channelsContent); + } + + private static async Task DownloadXMLProgram(string url) { - List epgChannels = new List(); ; using (var client = new HttpClient()) using (var request = new HttpRequestMessage()) { @@ -40,53 +70,82 @@ namespace TV_Player var response = await client.GetAsync(url); response.EnsureSuccessStatusCode(); string responseBody = await response.Content.ReadAsStringAsync(); - epgChannels = ParseEpg(responseBody); + return responseBody; } - return epgChannels; } - private static List ParseEpg(string epgData) + public static async Task DownloadGzip(string url) + { + try + { + using HttpClient httpClient = new HttpClient(); + byte[] compressedData = await httpClient.GetByteArrayAsync(url); + + using MemoryStream compressedStream = new MemoryStream(compressedData); + using GZipStream decompressionStream = new GZipStream(compressedStream, CompressionMode.Decompress); + using MemoryStream decompressedStream = new MemoryStream(); + await decompressionStream.CopyToAsync(decompressedStream); + + // Step 3: Get the inner XML as a string + string xmlContent = Encoding.UTF8.GetString(decompressedStream.ToArray()); + + // Output the XML content + return xmlContent; + } + catch (Exception ex) + { + Console.WriteLine($"An error occurred: {ex.Message}"); + return null; + } + } + + public static async Task ParseEpg(string channelId) { - List epgChannels = new List(); XmlReaderSettings settings = new XmlReaderSettings(); settings.IgnoreWhitespace = true; settings.IgnoreComments = true; - - using (XmlReader reader = XmlReader.Create(new System.IO.StringReader(epgData), settings)) + settings.DtdProcessing = DtdProcessing.Parse; + settings.Async = true; + ProgramGuide channel = null; + using (XmlReader reader = XmlReader.Create("guide.xml", settings)) { try { - while (reader.Read()) + while (await reader.ReadAsync()) { if (reader.NodeType == XmlNodeType.Element && reader.Name == "channel") { - ProgramGuide channel = new ProgramGuide(); - channel.Id = reader.GetAttribute("id"); - reader.Read(); - channel.DisplayName = reader.ReadElementContentAsString(); + var id = reader.GetAttribute("id"); + if (id == channelId) + { + channel = new ProgramGuide + { + Id = id + }; + reader.Read(); + channel.DisplayName = reader.ReadElementContentAsString(); - epgChannels.Add(channel); + } continue; } - if (reader.NodeType == XmlNodeType.Element && reader.Name == "programme") + if (channel != null && reader.NodeType == XmlNodeType.Element && reader.Name == "programme") { ProgramInfo program = new ProgramInfo(); var id = reader.GetAttribute("channel"); - var channel = epgChannels.FirstOrDefault(x => x.Id == id); + if (id != channelId) continue; program.StartTime = DateTime.ParseExact(reader.GetAttribute("start"), "yyyyMMddHHmmss zzz", null); program.EndTime = DateTime.ParseExact(reader.GetAttribute("stop"), "yyyyMMddHHmmss zzz", null); reader.Read(); program.Title = reader.ReadElementContentAsString(); - channel.Programs.Add(program); } else if (reader.NodeType == XmlNodeType.EndElement && reader.Name == "channel") { - break; + // break; } } } @@ -94,7 +153,7 @@ namespace TV_Player { } } - return epgChannels; + return channel; } private static async Task ReadFile(string url) @@ -116,15 +175,15 @@ namespace TV_Player public static async Task<(List programList, string programGuide)> DownloadM3UFromWebAsync(string url) { string fileData; - if (Uri.IsWellFormedUriString(url,UriKind.Absolute)) + if (Uri.IsWellFormedUriString(url, UriKind.Absolute)) { fileData = await ReadFile(url); } else { - fileData= File.ReadAllText(url); + fileData = File.ReadAllText(url); } - + return ParseM3UFromString(fileData); } private static string[] SplitStringBeforeSeparator(string input, string separator) @@ -164,7 +223,7 @@ namespace TV_Player } if (line.StartsWith("#EXTM3U")) { - programGuideLink=ExtractXtvgUrl(line); + programGuideLink = ExtractXtvgUrl(line); } } } @@ -173,14 +232,14 @@ namespace TV_Player Console.WriteLine("Error reading M3U file: " + ex.Message); } - return (playlistItems,programGuideLink); + return (playlistItems, programGuideLink); } private static bool TryParseM3ULine(string m3uLine, out M3UInfo? info) { info = null; //string pattern = @"#EXTINF:\d+ CUID=""(?.*?)"" number=""(?.*?)"" tvg-id=""(?.*?)"" tvg-name=""(?.*?)"".*?tvg-logo=""(?.*?)"" group-title=""(?.*?)""[^,]*,(?.*)[^\r](?.*)$"; - string pattern = @"#EXTINF:(?:-?\d+)\s+?(?:CUID=""(?.*?)""\s+)?(?:number=""(?.*?)""\s+)?(?:tvg-id=""(?.*?)""\s+)?(?:tvg-name=""(?.*?)""\s+)?(?:tvg-logo=""(?.*?)""\s+)?(?:group-title=""(?.*?)"")[^,]*,(?.*)[^\r](?.*)$"; + string pattern = @"#EXTINF:(-?\d+)\s+?(?:timeshift=""(?.*?)""\s+)?(?:catchup-days=""(?.*?)""\s+)?(?:catchup-type=""(?.*?)""\s+)?(?:CUID=""(?.*?)""\s+)?(?:number=""(?.*?)""\s+)?(?:tvg-id=""(?.*?)""\s+)?(?:tvg-name=""(?.*?)""\s+)?(?:group-title=""(?.*?)""\s+)?(?:tvg-logo=""(?.*?)"")?,(?.*?)\s*\r?\n(?.*)"; Regex regex = new Regex(pattern, RegexOptions.IgnoreCase); Match match = regex.Match(m3uLine); @@ -206,7 +265,7 @@ namespace TV_Player private static string ExtractXtvgUrl(string m3uEntry) { // Define a regular expression pattern to match x-tvg-url attribute - string pattern = @"x-tvg-url=""(.*?)"""; + string pattern = @"(x-tvg-url=""(.*?)"")?(url-tvg=""(.*?)"")"; // Use Regex.Match to find the first match Match match = Regex.Match(m3uEntry, pattern); @@ -214,7 +273,7 @@ namespace TV_Player // Check if a match is found and get the value from the capturing group if (match.Success && match.Groups.Count > 1) { - return match.Groups[1].Value; + return match.Groups[4].Value; } // Return null or an empty string if no match is found diff --git a/TV Player WPF/ViewModels/PlayerViewModel.cs b/TV Player WPF/ViewModels/PlayerViewModel.cs index cb7a347..63f0cf4 100644 --- a/TV Player WPF/ViewModels/PlayerViewModel.cs +++ b/TV Player WPF/ViewModels/PlayerViewModel.cs @@ -116,11 +116,11 @@ namespace TV_Player TopPanelTitle = _currentProgram.Name; SourceUrlChangedEvent?.Invoke(_currentProgram.Url); - _programGuideDisposable = TVPlayerViewModel.Instance.PlaylistData.ProgramGuideInfo.Subscribe(x => + _programGuideDisposable = TVPlayerViewModel.Instance.PlaylistData.ProgramGuideInfo.Subscribe(async x => { try { - _currentGuide = x.FirstOrDefault(p => p.Id == _currentProgram.TvgID); + _currentGuide = await TVPlayerViewModel.Instance.PlaylistData.GetGuideByProgram(_currentProgram.TvgID); UpdateScreenInfo(); diff --git a/TV Player WPF/ViewModels/ProgramsData.cs b/TV Player WPF/ViewModels/ProgramsData.cs index 6672b0b..05ab133 100644 --- a/TV Player WPF/ViewModels/ProgramsData.cs +++ b/TV Player WPF/ViewModels/ProgramsData.cs @@ -1,4 +1,5 @@ -using System.Reactive.Subjects; +using System.Reactive; +using System.Reactive.Subjects; namespace TV_Player { @@ -6,11 +7,11 @@ namespace TV_Player { private readonly ReplaySubject> programsSubject = new ReplaySubject>(); private readonly ReplaySubject> groupsSubject = new ReplaySubject>(); - private readonly ReplaySubject> programGuideSubject = new ReplaySubject>(); + private readonly ReplaySubject programGuideSubject = new ReplaySubject(); public IObservable> AllPrograms => programsSubject; public IObservable> GroupsInformation => groupsSubject; - public IObservable> ProgramGuideInfo => programGuideSubject; + public IObservable ProgramGuideInfo => programGuideSubject; public ProgramsData() { } @@ -20,17 +21,6 @@ namespace TV_Player //string m3uLink = "http://pl.da-tv.vip/a71e77fa/835b3216/tv.m3u"; var result = await M3UParser.DownloadM3UFromWebAsync(m3uLink); - - // For how hardcoded add custom channel - result.programList.Add(new M3UInfo() - { - GroupTitle = "Познавательные", - Name = "Discovery science", - Logo = "http://ip.viks.tv/posts/2018-11/1543603622_discovery_science.png", - Number = Convert.ToString(result.programList.Count+1), - Url= "https://s2.viks.tv/571/index.m3u8?k=1715093638p791i991i86i78S9b9c0d8fefdcO74ff3ed2f4710c95b07" - });; - programsSubject.OnNext(result.programList); var groupping = result.programList.GroupBy(item => item.GroupTitle) @@ -41,11 +31,16 @@ namespace TV_Player await Task.Run(() => GetProgramGuide(result.programGuide)); } + public Task GetGuideByProgram(string channelID) + { + return M3UParser.ParseEpg(channelID); + } + private async Task GetProgramGuide(string guideLink) { - //string guideLink = "http://epg.da-tv.vip/107-light.xml"; - var programGuide = await M3UParser.DownloadGuideFromWebAsync(guideLink); - programGuideSubject.OnNext(programGuide); + //guideLink = "http://epg.da-tv.vip/107-light.xml"; + await M3UParser.DownloadGuideFromWebAsync(guideLink); + programGuideSubject.OnNext(Unit.Default); } internal void GetData(string playlistURL)