private static void ProcessMd5ScheduleEntry(string md5) { // ensure cached file exists if (!epgCache.JsonFiles.ContainsKey(md5)) { return; } // read the cached file ScheduleResponse schedule; try { using (var reader = new StringReader(epgCache.GetAsset(md5))) { var serializer = new JsonSerializer(); schedule = (ScheduleResponse)serializer.Deserialize(reader, typeof(ScheduleResponse)); if (schedule == null) { Logger.WriteError("Failed to read Md5Schedule file in cache directory."); return; } } } catch (Exception ex) { Logger.WriteError("Error occurred when trying to read Md5Schedule file in cache directory. Message: " + ex.Message); return; } // determine which service entry applies to var mxfService = SdMxf.GetService(schedule.StationId); // process each program schedule entry foreach (var scheduleProgram in schedule.Programs) { // prepopulate some of the program var mxfProgram = SdMxf.GetProgram(scheduleProgram.ProgramId); if (mxfProgram.extras.Count == 0) { mxfProgram.ProgramId = scheduleProgram.ProgramId; mxfProgram.UidOverride = $"{scheduleProgram.ProgramId.Substring(0, 10)}_{scheduleProgram.ProgramId.Substring(10)}"; mxfProgram.extras.Add("md5", scheduleProgram.Md5); if (scheduleProgram.Multipart?.PartNumber > 0) { mxfProgram.extras.Add("multipart", $"{scheduleProgram.Multipart.PartNumber}/{scheduleProgram.Multipart.TotalParts}"); } if (config.OadOverride && scheduleProgram.New) { mxfProgram.extras.Add("newAirDate", scheduleProgram.AirDateTime.ToLocalTime()); } } mxfProgram.IsSeasonFinale |= Helper.StringContains(scheduleProgram.IsPremiereOrFinale, "Season Finale"); mxfProgram.IsSeasonPremiere |= Helper.StringContains(scheduleProgram.IsPremiereOrFinale, "Season Premiere"); mxfProgram.IsSeriesFinale |= Helper.StringContains(scheduleProgram.IsPremiereOrFinale, "Series Finale"); mxfProgram.IsSeriesPremiere |= Helper.StringContains(scheduleProgram.IsPremiereOrFinale, "Series Premiere"); if (!mxfProgram.extras.ContainsKey("premiere")) { mxfProgram.extras.Add("premiere", false); } if (scheduleProgram.Premiere) { mxfProgram.extras["premiere"] = true; // used only for movie and miniseries premieres } // grab any tvratings from desired countries var scheduleTvRatings = new Dictionary <string, string>(); if (scheduleProgram.Ratings != null) { var ratings = config.RatingsOrigin.Split(','); foreach (var rating in scheduleProgram.Ratings.Where(rating => string.IsNullOrEmpty(rating.Country) || Helper.TableContains(ratings, "ALL") || Helper.TableContains(ratings, rating.Country))) { scheduleTvRatings.Add(rating.Body, rating.Code); } } // populate the schedule entry and create program entry as required mxfService.MxfScheduleEntries.ScheduleEntry.Add(new MxfScheduleEntry { AudioFormat = EncodeAudioFormat(scheduleProgram.AudioProperties), Duration = scheduleProgram.Duration, Is3D = Helper.TableContains(scheduleProgram.VideoProperties, "3d"), IsBlackout = scheduleProgram.SubjectToBlackout, IsClassroom = scheduleProgram.CableInTheClassroom, IsCc = Helper.TableContains(scheduleProgram.AudioProperties, "cc"), IsDelay = Helper.StringContains(scheduleProgram.LiveTapeDelay, "delay"), IsDvs = Helper.TableContains(scheduleProgram.AudioProperties, "dvs"), IsEnhanced = Helper.TableContains(scheduleProgram.VideoProperties, "enhanced"), IsFinale = Helper.StringContains(scheduleProgram.IsPremiereOrFinale, "finale"), IsHdtv = CheckHdOverride(schedule.StationId) || !CheckSdOverride(schedule.StationId) && Helper.TableContains(scheduleProgram.VideoProperties, "hdtv"), //IsHdtvSimulCast = null, IsInProgress = scheduleProgram.JoinedInProgress, IsLetterbox = Helper.TableContains(scheduleProgram.VideoProperties, "letterbox"), IsLive = Helper.StringContains(scheduleProgram.LiveTapeDelay, "live"), //IsLiveSports = null, IsPremiere = scheduleProgram.Premiere || Helper.StringContains(scheduleProgram.IsPremiereOrFinale, "premiere"), IsRepeat = !scheduleProgram.New, IsSap = Helper.TableContains(scheduleProgram.AudioProperties, "sap"), IsSubtitled = Helper.TableContains(scheduleProgram.AudioProperties, "subtitled"), IsTape = Helper.StringContains(scheduleProgram.LiveTapeDelay, "tape"), Part = scheduleProgram.Multipart?.PartNumber ?? 0, Parts = scheduleProgram.Multipart?.TotalParts ?? 0, mxfProgram = mxfProgram, StartTime = scheduleProgram.AirDateTime, //TvRating is determined in the class itself to combine with the program content ratings IsSigned = scheduleProgram.Signed }); mxfService.MxfScheduleEntries.ScheduleEntry.Last().extras.Add("ratings", scheduleTvRatings); } }
private static bool GetMd5ScheduleEntries(string[] dates, int start) { // reject 0 requests if (SdMxf.With.Services.Count - start < 1) { return(true); } // build request for station schedules var requests = new ScheduleRequest[Math.Min(SdMxf.With.Services.Count - start, MaxQueries / dates.Length)]; for (var i = 0; i < requests.Length; ++i) { requests[i] = new ScheduleRequest() { StationId = SdMxf.With.Services[start + i].StationId, Date = dates }; } // request schedule md5s from Schedules Direct var stationResponses = SdApi.GetScheduleMd5s(requests); if (stationResponses == null) { return(false); } // build request of daily schedules not downloaded yet var newRequests = new List <ScheduleRequest>(); foreach (var request in requests) { var requestErrors = new Dictionary <int, string>(); var mxfService = SdMxf.GetService(request.StationId); if (stationResponses.TryGetValue(request.StationId, out var stationResponse)) { // if the station return is empty, go to next station if (stationResponse.Count == 0) { var comment = $"Failed to parse the schedule Md5 return for stationId {mxfService.StationId} ({mxfService.CallSign}) on {dates[0]} and after."; if (CheckSuppressWarnings(mxfService.CallSign)) { Logger.WriteInformation(comment); } else { Logger.WriteWarning(comment); } processedObjects += dates.Length; ReportProgress(); continue; } // scan through all the dates returned for the station and request dates that are not cached var newDateRequests = new List <string>(); var dupeMd5s = new HashSet <string>(); foreach (var day in dates) { if (stationResponse.TryGetValue(day, out var dayResponse) && (dayResponse.Code == 0) && !string.IsNullOrEmpty(dayResponse.Md5)) { var filepath = $"{Helper.Epg123CacheFolder}\\{SafeFilename(dayResponse.Md5)}"; var file = new FileInfo(filepath); if (file.Exists && (file.Length > 0) && !epgCache.JsonFiles.ContainsKey(dayResponse.Md5)) { using (var reader = File.OpenText(filepath)) { epgCache.AddAsset(dayResponse.Md5, reader.ReadToEnd()); } } if (epgCache.JsonFiles.ContainsKey(dayResponse.Md5)) { ++processedObjects; ReportProgress(); ++cachedSchedules; } else { newDateRequests.Add(day); } if (!ScheduleEntries.ContainsKey(dayResponse.Md5)) { ScheduleEntries.Add(dayResponse.Md5, new[] { request.StationId, day }); } else { var previous = ScheduleEntries[dayResponse.Md5][1]; var comment = $"Duplicate schedule Md5 return for stationId {mxfService.StationId} ({mxfService.CallSign}) on {day} with {previous}."; Logger.WriteWarning(comment); dupeMd5s.Add(dayResponse.Md5); } } else if ((dayResponse != null) && (dayResponse.Code != 0) && !requestErrors.ContainsKey(dayResponse.Code)) { requestErrors.Add(dayResponse.Code, dayResponse.Message); } } // clear out dupe entries foreach (var dupe in dupeMd5s) { var previous = ScheduleEntries[dupe][1]; var comment = $"Removing duplicate Md5 schedule entry for stationId {mxfService.StationId} ({mxfService.CallSign}) on {previous}."; Logger.WriteWarning(comment); ScheduleEntries.Remove(dupe); } // create the new request for the station if (newDateRequests.Count > 0) { newRequests.Add(new ScheduleRequest() { StationId = request.StationId, Date = newDateRequests.ToArray() }); } } else { // requested station was not in response Logger.WriteWarning($"Requested stationId {mxfService.StationId} ({mxfService.CallSign}) was not present in schedule Md5 response."); processedObjects += dates.Length; ReportProgress(); continue; } if (requestErrors.Count <= 0) { continue; } foreach (var keyValuePair in requestErrors) { Logger.WriteError($"Requests for MD5 schedule entries of station {request.StationId} returned error code {keyValuePair.Key} , message: {keyValuePair.Value}"); } } ReportProgress(); // download the remaining daily schedules to the cache directory if (newRequests.Count > 0) { // request daily schedules from Schedules Direct var responses = SdApi.GetScheduleListings(newRequests.ToArray()); if (responses == null) { return(false); } // process the responses foreach (var response in responses) { ++processedObjects; ReportProgress(); if (response?.Programs == null) { continue; } ++downloadedSchedules; // serialize JSON directly to a file if (ScheduleEntries.TryGetValue(response.Metadata.Md5, out var serviceDate)) { using (var writer = new StringWriter()) { try { var serializer = new JsonSerializer(); serializer.Serialize(writer, response); epgCache.AddAsset(response.Metadata.Md5, writer.ToString()); } catch { Logger.WriteInformation($"Failed to write station daily schedule file to cache file. station: {serviceDate[0]} ; date: {serviceDate[1]}"); } } } else { try { var compare = ScheduleEntries .Where(arg => arg.Value[0].Equals(response.StationId)) .Single(arg => arg.Value[1].Equals(response.Metadata.StartDate)); Logger.WriteWarning($"Md5 mismatch for station {compare.Value[0]} on {compare.Value[1]}. Expected: {compare.Key} - Downloaded {response.Metadata.Md5}"); } catch { Logger.WriteWarning($"Md5 mismatch for station {response.StationId} on {response.Metadata.StartDate}. Downloaded {response.Metadata.Md5}"); } } } } ReportProgress(); return(true); }
private static bool BuildLineupServices() { // query what lineups client is subscribed to var clientLineups = SdApi.GetSubscribedLineups(); if (clientLineups == null) { return(false); } // determine if there are custom lineups to consider if (File.Exists(Helper.Epg123CustomLineupsXmlPath)) { CustomLineups customLineups; using (var stream = new StreamReader(Helper.Epg123CustomLineupsXmlPath, Encoding.Default)) { var serializer = new XmlSerializer(typeof(CustomLineups)); TextReader reader = new StringReader(stream.ReadToEnd()); customLineups = (CustomLineups)serializer.Deserialize(reader); reader.Close(); } foreach (var lineup in customLineups.CustomLineup.Where(lineup => config.IncludedLineup.Contains(lineup.Lineup))) { customLineup = lineup; clientLineups.Lineups.Add(new SubscribedLineup { Lineup = lineup.Lineup, Name = lineup.Name, Transport = "CUSTOM", Location = lineup.Location, Uri = "CUSTOM", IsDeleted = false }); customMap = new StationChannelMap { Map = new List <LineupChannel>(), Stations = new List <LineupStation>(), Metadata = new LineupMetadata { Lineup = lineup.Lineup } }; } } // reset counters processedObjects = 0; totalObjects = clientLineups.Lineups.Count; ReportProgress(); // process lineups Logger.WriteMessage($"Entering BuildLineupServices() for {clientLineups.Lineups.Count} lineups."); foreach (var clientLineup in clientLineups.Lineups) { var flagCustom = !string.IsNullOrEmpty(clientLineup.Uri) && clientLineup.Uri.Equals("CUSTOM"); ++processedObjects; ReportProgress(); // request the lineup's station maps StationChannelMap lineupMap = null; if (!flagCustom) { lineupMap = SdApi.GetStationChannelMap(clientLineup.Lineup); if (lineupMap == null) { continue; } foreach (var station in lineupMap.Stations.Where(station => !AllStations.ContainsKey(station.StationId))) { AllStations.Add(station.StationId, station); } } if (!config.IncludedLineup.Contains(clientLineup.Lineup)) { Logger.WriteVerbose($"Subscribed lineup {clientLineup.Lineup} has been EXCLUDED from download and processing."); continue; } if (clientLineup.IsDeleted) { Logger.WriteWarning($"Subscribed lineup {clientLineup.Lineup} has been DELETED at the headend."); continue; } if (flagCustom) { foreach (var station in customLineup.Station.Where(station => station.StationId != null)) { if (AllStations.TryGetValue(station.StationId, out var lineupStation)) { customMap.Map.Add(new LineupChannel { StationId = station.StationId, AtscMajor = station.Number, AtscMinor = station.Subnumber, MatchName = station.MatchName }); CustomStations.Add(station.StationId); customMap.Stations.Add(lineupStation); } else if (!string.IsNullOrEmpty(station.Alternate) && AllStations.TryGetValue(station.Alternate, out lineupStation)) { customMap.Map.Add(new LineupChannel { StationId = station.Alternate, AtscMajor = station.Number, AtscMinor = station.Subnumber, MatchName = station.MatchName }); CustomStations.Add(station.Alternate); customMap.Stations.Add(lineupStation); } } lineupMap = customMap; Logger.WriteVerbose($"Successfully retrieved the station mapping for lineup {clientLineup.Lineup}."); } if (lineupMap == null) { return(false); } var lineupIndex = SdMxf.With.Lineups.Count; SdMxf.With.Lineups.Add(new MxfLineup { Index = lineupIndex + 1, LineupId = clientLineup.Lineup, Name = $"EPG123 {clientLineup.Name} ({clientLineup.Location})" }); // use hashset to make sure we don't duplicate channel entries for this station var channelNumbers = new HashSet <string>(); // build the services and lineup foreach (var station in lineupMap.Stations) { // check if station should be downloaded and processed if (!flagCustom) { if (station == null || (ExcludedStations.Contains(station.StationId) && !CustomStations.Contains(station.StationId))) { continue; } if (!IncludedStations.Contains(station.StationId) && !config.AutoAddNew) { Logger.WriteWarning($"**** Lineup {clientLineup.Name} ({clientLineup.Location}) has added station {station.StationId} ({station.Callsign}). ****"); continue; } } // build the service if necessary var mxfService = SdMxf.GetService(station.StationId); if (string.IsNullOrEmpty(mxfService.CallSign)) { // instantiate stationLogo and override uid StationImage stationLogo = null; mxfService.UidOverride = $"EPG123_{station.StationId}"; // determine station name for ATSC stations var atsc = false; var names = station.Name.Replace("-", "").Split(' '); if (!string.IsNullOrEmpty(station.Affiliate) && names.Length == 2 && names[0] == station.Callsign && $"({names[0]})" == $"{names[1]}") { atsc = true; } // add callsign and station name mxfService.CallSign = CheckCustomCallsign(station.StationId) ?? station.Callsign; mxfService.Name = CheckCustomServicename(station.StationId) ?? (atsc ? $"{station.Callsign} ({station.Affiliate})" : null) ?? station.Name; // add affiliate if available if (!string.IsNullOrEmpty(station.Affiliate)) { mxfService.mxfAffiliate = SdMxf.GetAffiliate(station.Affiliate); } // add station logo if available and allowed var logoPath = $"{Helper.Epg123LogosFolder}\\{station.Callsign}.png"; var urlLogoPath = logoPath.Replace($"{Helper.Epg123LogosFolder}\\", $"http://{Environment.MachineName}:{Helper.TcpPort}/logos/"); var customPath = $"{Helper.Epg123LogosFolder}\\{station.Callsign}_c.png"; if (config.IncludeSdLogos) { // make sure logos directory exists if (!Directory.Exists(Helper.Epg123LogosFolder)) { Directory.CreateDirectory(Helper.Epg123LogosFolder); } if (station.StationLogos != null) { stationLogo = station.StationLogos.FirstOrDefault(arg => arg.Category != null && arg.Category.Equals(config.PreferredLogoStyle, StringComparison.OrdinalIgnoreCase)) ?? station.StationLogos.FirstOrDefault(arg => arg.Category != null && arg.Category.Equals(config.AlternateLogoStyle, StringComparison.OrdinalIgnoreCase)); if (stationLogo != null) { switch (stationLogo.Category) { case "dark": logoPath = logoPath.Replace(".png", "_d.png"); break; case "gray": logoPath = logoPath.Replace(".png", "_g.png"); break; case "light": logoPath = logoPath.Replace(".png", "_l.png"); break; case "white": logoPath = logoPath.Replace(".png", "_w.png"); break; } } } if (stationLogo == null && !config.PreferredLogoStyle.Equals("none", StringComparison.OrdinalIgnoreCase) && !config.AlternateLogoStyle.Equals("none", StringComparison.OrdinalIgnoreCase)) { stationLogo = station.Logo; } // download the logo from SD if not present in the .\logos folder if (stationLogo != null && !File.Exists(logoPath)) { var url = stationLogo.Url; // download, crop & resize logo image, save and add if (!string.IsNullOrEmpty(url)) { StationLogosToDownload.Add(new KeyValuePair <MxfService, string[]>(mxfService, new[] { logoPath, url })); } } // add the existing logo; custom logo overrides downloaded logos if (File.Exists(customPath)) { logoPath = customPath; } urlLogoPath = logoPath.Replace($"{Helper.Epg123LogosFolder}\\", $"http://{Environment.MachineName}:{Helper.TcpPort}/logos/"); if (File.Exists(logoPath)) { mxfService.mxfGuideImage = SdMxf.GetGuideImage(Helper.Standalone ? $"file://{logoPath}" : urlLogoPath, GetStringEncodedImage(logoPath)); } } // handle xmltv logos if (config.XmltvIncludeChannelLogos.Equals("url")) { if (stationLogo != null) { mxfService.extras.Add("logo", stationLogo); } else if (station.Logo?.Url != null) { mxfService.extras.Add("logo", station.Logo); } } else if (config.XmltvIncludeChannelLogos.Equals("local") && config.IncludeSdLogos) { if (File.Exists(logoPath)) { var image = Image.FromFile(logoPath); mxfService.extras.Add("logo", new StationImage { Url = Helper.Standalone ? logoPath : urlLogoPath, Height = image.Height, Width = image.Width }); } else if (stationLogo != null) { mxfService.extras.Add("logo", new StationImage { Url = Helper.Standalone ? logoPath : urlLogoPath }); } } } // match station with mapping for lineup number and subnumbers foreach (var map in lineupMap.Map) { var number = -1; var subnumber = 0; if (!map.StationId.Equals(station.StationId)) { continue; } // QAM if (map.ChannelMajor != 0) { number = map.ChannelMajor; subnumber = map.ChannelMinor; } // ATSC (and CUSTOM) or NTSC else if (map.AtscMajor != 0) { number = map.AtscMajor; subnumber = map.AtscMinor; } else if (map.UhfVhf != 0) { number = map.UhfVhf; } // Cable or Satellite else if (!string.IsNullOrEmpty(map.Channel)) { subnumber = 0; if (Regex.Match(map.Channel, @"[A-Za-z]{1}[\d]{4}").Length > 0) { // 4dtv has channels starting with 2 character satellite identifier number = int.Parse(map.Channel.Substring(2)); } else if (!int.TryParse(Regex.Replace(map.Channel, "[^0-9.]", ""), out number)) { // if channel number is not a whole number, must be a decimal number var numbers = Regex.Replace(map.Channel, "[^0-9.]", "").Replace('_', '.').Replace('-', '.').Split('.'); if (numbers.Length == 2) { number = int.Parse(numbers[0]); subnumber = int.Parse(numbers[1]); } } } string matchName = null; switch (clientLineup.Transport) { case "CUSTOM": matchName = map.MatchName; break; case "DVB-S": var m = Regex.Match(lineupMap.Metadata.Lineup, @"\d+\.\d+"); if (m.Success && map.FrequencyHz > 0 && map.NetworkId > 0 && map.TransportId > 0 && map.ServiceId > 0) { while (map.FrequencyHz > 13000) { map.FrequencyHz /= 1000; } matchName = $"DVBS:{m.Value.Replace(".", "")}:{map.FrequencyHz}:{map.NetworkId}:{map.TransportId}:{map.ServiceId}"; } number = -1; subnumber = 0; break; case "DVB-T": if (map.NetworkId > 0 && map.TransportId > 0 && map.ServiceId > 0) { matchName = $"DVBT:{map.NetworkId}:{map.TransportId}:{map.ServiceId}"; } break; case "Antenna": if (map.AtscMajor > 0 && map.AtscMinor > 0) { matchName = $"OC:{map.AtscMajor}:{map.AtscMinor}"; } break; } var channelNumber = $"{number}{(subnumber > 0 ? $".{subnumber}" : "")}"; if (channelNumbers.Add($"{channelNumber}:{station.StationId}")) { SdMxf.With.Lineups[lineupIndex].channels.Add(new MxfChannel { mxfLineup = SdMxf.With.Lineups[lineupIndex], mxfService = mxfService, Number = number, SubNumber = subnumber, MatchName = matchName }); } } } } if (StationLogosToDownload.Count > 0) { StationLogosDownloadComplete = false; Logger.WriteInformation($"Kicking off background worker to download and process {StationLogosToDownload.Count} station logos."); BackgroundDownloader = new System.ComponentModel.BackgroundWorker(); BackgroundDownloader.DoWork += BackgroundDownloader_DoWork; BackgroundDownloader.RunWorkerCompleted += BackgroundDownloader_RunWorkerCompleted; BackgroundDownloader.WorkerSupportsCancellation = true; BackgroundDownloader.RunWorkerAsync(); } if (SdMxf.With.Services.Count > 0) { // report specific stations that are no longer available var missing = (from station in IncludedStations where SdMxf.With.Services.FirstOrDefault(arg => arg.StationId.Equals(station)) == null select config.StationId.Single(arg => arg.StationId.Equals(station)).CallSign).ToList(); if (missing.Count > 0) { MissingStations = missing.Count; Logger.WriteInformation($"Stations no longer available since last configuration save are: {string.Join(", ", missing)}"); } Logger.WriteMessage("Exiting BuildLineupServices(). SUCCESS."); return(true); } Logger.WriteError($"There are 0 stations queued for download from {clientLineups.Lineups.Count} subscribed lineups. Exiting."); Logger.WriteError("Check that lineups are 'INCLUDED' and stations are selected in the EPG123 GUI."); return(false); }