From 6302a25f2613f8c66b9c513af141714a8a36e525 Mon Sep 17 00:00:00 2001 From: feyris-tan <4116042+feyris-tan@users.noreply.github.com> Date: Mon, 14 Jul 2025 23:39:37 +0200 Subject: [PATCH] Added rudimentary (and very buggy!) support for reading MediaHighway2 EPG. --- .gitignore | 1 + .../skyscraper5.Data.PostgreSql/DvbNip.cs | 19 +- .../skyscraper5.Data.PostgreSql/Dvbi.cs | 2 +- .../PostgresqlDataStore.cs | 11 +- .../PostgresqlToken.cs | 29 ++ .../WorkerThread.cs | 8 +- .../DVBServices/Enumerations.cs | 219 +++++++++++++ .../MediaHighway2CategorySection.cs | 181 +++++++++++ .../MediaHighway2ChannelSection.cs | 170 ++++++++++ .../MediaHighway/MediaHighway2SummaryData.cs | 190 +++++++++++ .../MediaHighway2SummarySection.cs | 103 ++++++ .../MediaHighway/MediaHighway2TitleData.cs | 222 +++++++++++++ .../MediaHighway/MediaHighway2TitleSection.cs | 164 ++++++++++ .../MediaHighway/MediaHighwayCategoryEntry.cs | 70 +++++ .../MediaHighwayChanneInfoEntry.cs | 214 +++++++++++++ .../DVBServices/Mpeg2BasicHeader.cs | 134 ++++++++ .../EPGCollectorSide/DVBServices/Utils.cs | 77 ++++- .../EPGCollectorSide/DomainObjects/Logger.cs | 93 ++++++ .../{ => Freesat}/FreesatBatSdtContestant.cs | 6 +- .../{ => Freesat}/FreesatEpgContestant.cs | 6 +- .../{ => Freesat}/FreesatNitContestant.cs | 6 +- .../{ => Freesat}/FreesatTdtContestant.cs | 14 +- .../{ => Freesat}/FreesatTextDecoder.cs | 2 +- .../{ => Freesat}/FreesatTunnelDataStorage.cs | 6 +- .../FreesatTunnelDataStoragePostgresql.cs | 201 ++++++++++++ .../{ => Freesat}/FreesatTunnelScraper.cs | 24 +- .../MediaHighway2/Mhw2Contestant.cs | 95 ++++++ .../MediaHighway2/Mhw2EventBinder.cs | 32 ++ .../MediaHighway2/Mhw2EventHandler.cs | 18 ++ .../MediaHighway2/Mhw2Parser.cs | 95 ++++++ .../MediaHighway2/Mhw2Scraper.cs | 153 +++++++++ .../MediaHighway2/Mhw2Storage.cs | 297 ++++++++++++++++++ .../skyscraper8.EPGCollectorPort.csproj | 1 + skyscraper8/Program.cs | 11 +- skyscraper8/Properties/launchSettings.json | 2 +- .../Skyscraper/Plugins/ExperimentalPlugin.cs | 17 + .../Skyscraper/Plugins/PluginAppender.cs | 5 +- .../Skyscraper/Scraper/SkyscraperContext.cs | 5 + .../Scraper/Storage/NullObjectStorage.cs | 73 +++++ .../StreamTypeAutodetection.cs | 2 +- .../Scraper/Utils/PacketDiscarder.cs | 2 +- skyscraper8/Skyscraper/Text/AsciiTable.cs | 2 + .../Skyscraper/Text/Encodings/dvb-big5.cs | 33 ++ .../Text/Encodings/dvb-iso-8859-10.cs | 59 +--- .../Text/Encodings/dvb-iso-8859-11.cs | 86 +++++ .../Text/Encodings/dvb-iso-8859-13.cs | 9 + .../Text/Encodings/dvb-iso-8859-14.cs | 18 ++ .../Text/Encodings/dvb-iso-8859-15.cs | 1 + .../Text/Encodings/dvb-iso-8859-6.cs | 3 + .../Text/Encodings/dvb-iso-8859-8.cs | 3 + 50 files changed, 3096 insertions(+), 98 deletions(-) create mode 100644 DataTableStorages/skyscraper5.Data.PostgreSql/PostgresqlToken.cs create mode 100644 PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/EPGCollectorSide/DVBServices/Enumerations.cs create mode 100644 PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/EPGCollectorSide/DVBServices/MediaHighway/MediaHighway2CategorySection.cs create mode 100644 PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/EPGCollectorSide/DVBServices/MediaHighway/MediaHighway2ChannelSection.cs create mode 100644 PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/EPGCollectorSide/DVBServices/MediaHighway/MediaHighway2SummaryData.cs create mode 100644 PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/EPGCollectorSide/DVBServices/MediaHighway/MediaHighway2SummarySection.cs create mode 100644 PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/EPGCollectorSide/DVBServices/MediaHighway/MediaHighway2TitleData.cs create mode 100644 PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/EPGCollectorSide/DVBServices/MediaHighway/MediaHighway2TitleSection.cs create mode 100644 PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/EPGCollectorSide/DVBServices/MediaHighway/MediaHighwayCategoryEntry.cs create mode 100644 PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/EPGCollectorSide/DVBServices/MediaHighway/MediaHighwayChanneInfoEntry.cs create mode 100644 PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/EPGCollectorSide/DVBServices/Mpeg2BasicHeader.cs rename PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/{ => Freesat}/FreesatBatSdtContestant.cs (88%) rename PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/{ => Freesat}/FreesatEpgContestant.cs (88%) rename PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/{ => Freesat}/FreesatNitContestant.cs (88%) rename PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/{ => Freesat}/FreesatTdtContestant.cs (85%) rename PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/{ => Freesat}/FreesatTextDecoder.cs (93%) rename PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/{ => Freesat}/FreesatTunnelDataStorage.cs (93%) create mode 100644 PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/Freesat/FreesatTunnelDataStoragePostgresql.cs rename PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/{ => Freesat}/FreesatTunnelScraper.cs (87%) create mode 100644 PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/MediaHighway2/Mhw2Contestant.cs create mode 100644 PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/MediaHighway2/Mhw2EventBinder.cs create mode 100644 PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/MediaHighway2/Mhw2EventHandler.cs create mode 100644 PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/MediaHighway2/Mhw2Parser.cs create mode 100644 PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/MediaHighway2/Mhw2Scraper.cs create mode 100644 PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/MediaHighway2/Mhw2Storage.cs create mode 100644 skyscraper8/Skyscraper/Plugins/ExperimentalPlugin.cs create mode 100644 skyscraper8/Skyscraper/Scraper/Storage/NullObjectStorage.cs create mode 100644 skyscraper8/Skyscraper/Text/Encodings/dvb-big5.cs create mode 100644 skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-11.cs diff --git a/.gitignore b/.gitignore index aa15769..2662269 100644 --- a/.gitignore +++ b/.gitignore @@ -113,3 +113,4 @@ imgui.ini /PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/bin/Debug/net8.0 /PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/obj/Debug/net8.0 /PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/obj +/.vs/skyscraper8/CopilotIndices/17.14.827.52834 diff --git a/DataTableStorages/skyscraper5.Data.PostgreSql/DvbNip.cs b/DataTableStorages/skyscraper5.Data.PostgreSql/DvbNip.cs index 62c48e7..51b7589 100644 --- a/DataTableStorages/skyscraper5.Data.PostgreSql/DvbNip.cs +++ b/DataTableStorages/skyscraper5.Data.PostgreSql/DvbNip.cs @@ -178,9 +178,24 @@ namespace skyscraper5.Data.PostgreSql private void UpdateDvbNipPrivateDataSpecifier(NpgsqlConnection connection, Tuple coordinates, DateTime versionUpdate, List privateDataSessions) { - throw new NotImplementedException(); + DateTime localUpdate = new DateTime(versionUpdate.Year, versionUpdate.Month, versionUpdate.Day, + versionUpdate.Hour, versionUpdate.Minute, versionUpdate.Second, DateTimeKind.Local); + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "UPDATE dvbnip_private_data_specifiers SET version_update = @vu, sessions = @s WHERE nid = @nid AND cid = @cid AND lid=@lid AND sid=@sid AND private_data_specifier = @pds"; + command.Parameters.AddWithValue("@nid", NpgsqlDbType.Integer, (int)coordinates.Item1); + command.Parameters.AddWithValue("@cid", NpgsqlDbType.Integer, (int)coordinates.Item2); + command.Parameters.AddWithValue("@lid", NpgsqlDbType.Integer, (int)coordinates.Item3); + command.Parameters.AddWithValue("@sid", NpgsqlDbType.Integer, (int)coordinates.Item4); + command.Parameters.AddWithValue("@pds", NpgsqlDbType.Bigint, (long)coordinates.Item5); + command.Parameters.AddWithValue("@vu", NpgsqlDbType.Timestamp, localUpdate); + command.Parameters.AddWithValue("@s", NpgsqlDbType.Json, JsonConvert.SerializeObject(privateDataSessions)); + int executeNonQuery = command.ExecuteNonQuery(); + if (executeNonQuery != 1) + { + throw new DataException(String.Format("Expected to insert {0} rows, but it were {1}", 1, executeNonQuery)); + } } - + private bool[] _dvbNipKnownNetworks; public bool DvbNipTestForNetwork(BroadcastNetworkType network) { diff --git a/DataTableStorages/skyscraper5.Data.PostgreSql/Dvbi.cs b/DataTableStorages/skyscraper5.Data.PostgreSql/Dvbi.cs index d07a628..58531af 100644 --- a/DataTableStorages/skyscraper5.Data.PostgreSql/Dvbi.cs +++ b/DataTableStorages/skyscraper5.Data.PostgreSql/Dvbi.cs @@ -182,7 +182,7 @@ namespace skyscraper5.Data.PostgreSql private void AddDvbiServiceToServiceList(NpgsqlConnection x, string id, string serviceListId) { NpgsqlCommand checkCommand = x.CreateCommand(); - checkCommand.CommandText = "SELECT dateadded FROM dvbi_services_to_service_lists WHERE service = @service AND service_list = @service_list"; + checkCommand.CommandText = "SELECT dateadded FROM dvbi_services_to_service_lists WHERE service = @service AND service_list = @servicelist"; checkCommand.Parameters.AddWithValue("@service", NpgsqlDbType.Text, id); checkCommand.Parameters.AddWithValue("@servicelist", NpgsqlDbType.Text, serviceListId); NpgsqlDataReader dataReader = checkCommand.ExecuteReader(); diff --git a/DataTableStorages/skyscraper5.Data.PostgreSql/PostgresqlDataStore.cs b/DataTableStorages/skyscraper5.Data.PostgreSql/PostgresqlDataStore.cs index 5107809..b56c9ae 100644 --- a/DataTableStorages/skyscraper5.Data.PostgreSql/PostgresqlDataStore.cs +++ b/DataTableStorages/skyscraper5.Data.PostgreSql/PostgresqlDataStore.cs @@ -18,7 +18,7 @@ namespace skyscraper5.Data.PostgreSql connectionStringBuilder = stringBuilder; } - private NpgsqlConnectionStringBuilder connectionStringBuilder; + internal NpgsqlConnectionStringBuilder connectionStringBuilder; public bool TestForRelatedContent(EitEvent lEvent, RctLinkInfo rctLinkInfo) @@ -157,10 +157,15 @@ namespace skyscraper5.Data.PostgreSql connection.Close(); } } - + + private PostgresqlToken pluginToken; public object[] GetPluginConnector() { - return new object[] { connectionStringBuilder }; + if (pluginToken == null) + { + pluginToken = new PostgresqlToken(this); + } + return new object[] { pluginToken }; } diff --git a/DataTableStorages/skyscraper5.Data.PostgreSql/PostgresqlToken.cs b/DataTableStorages/skyscraper5.Data.PostgreSql/PostgresqlToken.cs new file mode 100644 index 0000000..dff4ec7 --- /dev/null +++ b/DataTableStorages/skyscraper5.Data.PostgreSql/PostgresqlToken.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Npgsql; + +namespace skyscraper5.Data.PostgreSql +{ + public class PostgresqlToken + { + private readonly PostgresqlDataStore _postgresqlDataStore; + + public PostgresqlToken(PostgresqlDataStore postgresqlDataStore) + { + _postgresqlDataStore = postgresqlDataStore; + } + + public NpgsqlConnection GetConnection() + { + return new NpgsqlConnection(_postgresqlDataStore.connectionStringBuilder.ToString()); + } + + public void EnqueueTask(WriterTask task) + { + _postgresqlDataStore.EnqueueTask(task); + } + } +} diff --git a/DataTableStorages/skyscraper5.Data.PostgreSql/WorkerThread.cs b/DataTableStorages/skyscraper5.Data.PostgreSql/WorkerThread.cs index 9846b49..86e6163 100644 --- a/DataTableStorages/skyscraper5.Data.PostgreSql/WorkerThread.cs +++ b/DataTableStorages/skyscraper5.Data.PostgreSql/WorkerThread.cs @@ -9,15 +9,17 @@ using Npgsql; namespace skyscraper5.Data.PostgreSql { - public partial class PostgresqlDataStore + public delegate void WriterTask(NpgsqlConnection connection); + + public partial class PostgresqlDataStore { private Thread _writerThread; - private delegate void WriterTask(NpgsqlConnection connection); + private Queue _writerTasks; - private void EnqueueTask(WriterTask task) + internal void EnqueueTask(WriterTask task) { if (_writerTasks == null) { diff --git a/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/EPGCollectorSide/DVBServices/Enumerations.cs b/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/EPGCollectorSide/DVBServices/Enumerations.cs new file mode 100644 index 0000000..a07a926 --- /dev/null +++ b/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/EPGCollectorSide/DVBServices/Enumerations.cs @@ -0,0 +1,219 @@ +////////////////////////////////////////////////////////////////////////////////// +// // +// Copyright © 2005-2020 nzsjb // +// // +// This Program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation; either version 2, or (at your option) // +// any later version. // +// // +// This Program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with GNU Make; see the file COPYING. If not, write to // +// the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. // +// http://www.gnu.org/copyleft/gpl.html // +// // +////////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace DVBServices +{ + /// + /// The reply codes from the protocol collectors. + /// + public enum CollectorReply + { + /// + /// The collection was successful. + /// + OK, + /// + /// The collection failed. + /// + GeneralFailure, + /// + /// There was a format error in the received data. + /// + FatalFormatError, + /// + /// The was an error loading the reference data. + /// + ReferenceDataError, + /// + /// There was an error in the broadcast data. + /// + BroadcastDataError, + /// + /// The collection was cancelled. + /// + Cancelled + } + + /// + /// The current scope + /// + [Flags] + public enum Scope + { + /// + /// No scope restrictions + /// + All = 0x7fffffff, + /// + /// Program Map table + /// + PMT = 0x0002, + /// + /// Program Association table + /// + PAT = 0x0004, + /// + /// Network Information table + /// + NIT = 0x0008, + /// + /// Bouquet Association table + /// + BAT = 0x0010, + /// + /// Service Description table + /// + SDT = 0x0020, + /// + /// Event Information table + /// + EIT = 0x0040, + /// + /// Time and Date table + /// + TDT = 0x0080, + /// + /// Time offset table + /// + TOT = 0x0100, + /// + /// Running Status table + /// + RST = 0x0200, + /// + /// Stuffing table + /// + ST = 0x0400, + /// + /// Discontinuity Information table + /// + DIT = 0x0800, + /// + /// Selection Information table + /// + SIT = 0x1000, + /// + /// Application Information table + /// + AIT = 0x2000, + /// + /// DSMCC sections + /// + DSMCC = 0x4000, + } + + /// + /// The action to take for non-Ascii characters in text strings. + /// + public enum ReplaceMode + { + /// + /// The non-ASCII character is removed. + /// + Ignore, + /// + /// The non-ASCII character is set to space. + /// + SetToSpace, + /// + /// The non-ASCII character is not changed in the output text. + /// + TransferUnchanged, + /// + /// The non-ASCII character is converted to its ASCII equivalent (EIT 0x8a only at present) + /// + Convert, + /// + /// The non-ASCII character is converted using the byte conversion table) + /// + ConvertUsingTable + } + + /// + /// The data broadcast ID values. + /// + public enum DataBroadcastId + { + /// + /// DVB data pipe. + /// + DvbDataPipe = 0x0001, // EN 301 192 section 4.2.1 + /// + /// Asynchronous data stream + /// + AsyncDataStream = 0x0002, // EN 301 192 section 5.2.1 + /// + /// Synchronous data stream + /// + SynchronousDataStream = 0x0003, // EN 301 192 section 6.2.1 + /// + /// Synchronous data streams + /// + SynchronousDataStreams = 0x0004, // EN 301 192 section 6.2.1 + /// + /// Multiprotocol encapsulation + /// + MultiProtocolEncapsulation = 0x0005, // EN 301 192 section 4.2.1 + /// + /// Data carousel + /// + DataCarousel = 0x0006, + /// + /// Object carousel + /// + ObjectCarousel = 0x0007, + /// + /// DVB ATM stream + /// + DvbAtmStream = 0x0008, + /// + /// Higher protocol asynchronous stream + /// + HigherProtcolAsyncStream = 0x0009, + /// + /// System software update service + /// + SsuService = 0x000a, + /// + /// IP/MAC notification service + /// + IpMacNotificationService = 0x000b, + /// + /// MHP object carousel + /// + MhpObjectCarousel = 0x00f0, // TS 101 812 + /// + /// MHP multiprotocol encapsulation + /// + MhpMultiProtoclEncapsulation = 0x000f1, // TS 101 812 + /// + /// MHP application presence + /// + MhpApplicationPresence = 0x00f2, + /// + /// MHEG5 stream + /// + Mheg5 = 0x0106 + } +} + diff --git a/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/EPGCollectorSide/DVBServices/MediaHighway/MediaHighway2CategorySection.cs b/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/EPGCollectorSide/DVBServices/MediaHighway/MediaHighway2CategorySection.cs new file mode 100644 index 0000000..80106a5 --- /dev/null +++ b/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/EPGCollectorSide/DVBServices/MediaHighway/MediaHighway2CategorySection.cs @@ -0,0 +1,181 @@ +////////////////////////////////////////////////////////////////////////////////// +// // +// Copyright © 2005-2020 nzsjb // +// // +// This Program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation; either version 2, or (at your option) // +// any later version. // +// // +// This Program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with GNU Make; see the file COPYING. If not, write to // +// the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. // +// http://www.gnu.org/copyleft/gpl.html // +// // +////////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.ObjectModel; + +using DomainObjects; + +namespace DVBServices +{ + /// + /// The class that describes a MediaHighway2 category section. + /// + public class MediaHighway2CategorySection + { + /// + /// Get the collection of categories. + /// + public Collection Categories + { + get + { + if (categories == null) + categories = new Collection(); + return (categories); + } + } + + private int categoryCount; + + private Collection categories; + + private int lastIndex = -1; + + /// + /// Initialize a new instance of the MediaHighway2CategorySection class. + /// + internal MediaHighway2CategorySection() { } + + /// + /// Parse the section. + /// + /// The MPEG2 section containing the section. + /// The index of the first byte of the data portion. + /// True if the section is an MHW2 category section; false otherwise. + internal bool Process(byte[] byteData, int index) + { + lastIndex = index; + + if (byteData[lastIndex] != 0x01) + return (false); + + lastIndex++; + + categoryCount = (int)byteData[lastIndex]; + lastIndex++; + + try + { + int p1 = 0; + int p2 = 0; + + string themeName = null; + + for (int themeIndex = 0; themeIndex < categoryCount; themeIndex++) + { + p1 = ((byteData[lastIndex + (themeIndex * 2)] << 8) | byteData[lastIndex + 1 + (themeIndex * 2)]) + 3; + + for (int descriptionIndex = 0; descriptionIndex <= (byteData[p1] & 0x3f); descriptionIndex++) + { + p2 = ((byteData[p1 + 1 + (descriptionIndex * 2)] << 8) | byteData[p1 + 2 + (descriptionIndex * 2)] + 3); + + MediaHighwayCategoryEntry categoryEntry = new MediaHighwayCategoryEntry(); + + categoryEntry.Number = ((themeIndex & 0x3f) << 6) | (descriptionIndex & 0x3f); + if (descriptionIndex == 0) + { + themeName = Utils.GetString(byteData, p2 + 1, byteData[p2] & 0x1f, ReplaceMode.TransferUnchanged).Trim(); + categoryEntry.Description = themeName; + } + else + categoryEntry.Description = themeName + " " + Utils.GetString(byteData, p2 + 1, byteData[p2] & 0x1f, ReplaceMode.TransferUnchanged).Trim(); + + Categories.Add(categoryEntry); + } + } + } + catch (IndexOutOfRangeException) + { + throw (new ArgumentOutOfRangeException("The MediaHighway2 Category Section message is short")); + } + + Validate(); + + return (true); + } + + + + /// + /// Validate the section fields. + /// + /// + /// A section field is not valid. + /// + public void Validate() { } + + /// + /// Log the section fields. + /// + public void LogMessage() + { + if (Logger.ProtocolLogger == null) + return; + + Logger.ProtocolLogger.Write(Logger.ProtocolIndent + "MHW2 CATEGORY SECTION"); + + if (categories != null) + { + foreach (MediaHighwayCategoryEntry categoryEntry in categories) + { + Logger.IncrementProtocolIndent(); + categoryEntry.LogMessage(); + Logger.DecrementProtocolIndent(); + } + } + } + + /// + /// Process an MPEG2 section from the Open TV Title table. + /// + /// The MPEG2 section. + public static MediaHighway2CategorySection ProcessMediaHighwayCategoryTable(byte[] byteData) + { + Mpeg2BasicHeader mpeg2Header = new Mpeg2BasicHeader(); + + try + { + mpeg2Header.Process(byteData); + + MediaHighway2CategorySection categorySection = new MediaHighway2CategorySection(); + bool process = categorySection.Process(byteData, mpeg2Header.Index); + if (process) + { + categorySection.LogMessage(); + return (categorySection); + } + else + return (null); + } + catch (ArgumentOutOfRangeException e) + { + Logger.Instance.Write(" Category section parsing failed: " + e.Message); + return (null); + } + catch (NotImplementedException e) + { + return null; + } + } + } +} + diff --git a/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/EPGCollectorSide/DVBServices/MediaHighway/MediaHighway2ChannelSection.cs b/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/EPGCollectorSide/DVBServices/MediaHighway/MediaHighway2ChannelSection.cs new file mode 100644 index 0000000..870a349 --- /dev/null +++ b/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/EPGCollectorSide/DVBServices/MediaHighway/MediaHighway2ChannelSection.cs @@ -0,0 +1,170 @@ +////////////////////////////////////////////////////////////////////////////////// +// // +// Copyright © 2005-2020 nzsjb // +// // +// This Program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation; either version 2, or (at your option) // +// any later version. // +// // +// This Program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with GNU Make; see the file COPYING. If not, write to // +// the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. // +// http://www.gnu.org/copyleft/gpl.html // +// // +////////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.ObjectModel; + +using DomainObjects; + +namespace DVBServices +{ + /// + /// The class that describes a MediaHighway2 channel section. + /// + public class MediaHighway2ChannelSection + { + /// + /// Get the collection of channels. + /// + public Collection Channels + { + get + { + if (channels == null) + channels = new Collection(); + return (channels); + } + } + + private byte[] fillBytes; + private int channelCount; + + private Collection channels; + + private int lastIndex = -1; + + /// + /// Initialize a new instance of the MediaHighway2ChannelSection class. + /// + internal MediaHighway2ChannelSection() { } + + /// + /// Parse the section. + /// + /// The MPEG2 section containing the section. + /// The index of the first byte of the data portion. + /// True if the section is an MHW2 channel section; false otherwise. + internal bool Process(byte[] byteData, int index) + { + lastIndex = index; + + /*if (byteData[lastIndex] != 0x00 && byteData[lastIndex] != 0x02 && byteData[lastIndex] != 0x03)*/ + if (byteData[lastIndex] != 0x00) + return (false); + + try + { + fillBytes = Utils.GetBytes(byteData, lastIndex, 117); + lastIndex += fillBytes.Length; + + channelCount = (int)byteData[lastIndex]; + lastIndex++; + + int nameIndex = lastIndex + (8 * channelCount); + + while (Channels.Count < channelCount) + { + MediaHighwayChannelInfoEntry channelEntry = new MediaHighwayChannelInfoEntry(); + channelEntry.Process(byteData, lastIndex, nameIndex); + Channels.Add(channelEntry); + + lastIndex = channelEntry.Index; + nameIndex = channelEntry.NameIndex; + } + } + catch (IndexOutOfRangeException) + { + throw (new ArgumentOutOfRangeException("The MediaHighway2 Channel Section message is short")); + } + catch (NotImplementedException e) + { + throw e; + } + + Validate(); + + return (true); + } + + /// + /// Validate the section fields. + /// + /// + /// A section field is not valid. + /// + public void Validate() { } + + /// + /// Log the section fields. + /// + public void LogMessage() + { + if (Logger.ProtocolLogger == null) + return; + + Logger.ProtocolLogger.Write(Logger.ProtocolIndent + "MHW2 CHANNEL SECTION"); + + if (channels != null) + { + foreach (MediaHighwayChannelInfoEntry channelInfoEntry in channels) + { + Logger.IncrementProtocolIndent(); + channelInfoEntry.LogMessage(); + Logger.DecrementProtocolIndent(); + } + } + } + + /// + /// Process an MPEG2 section from the Open TV Title table. + /// + /// The MPEG2 section. + public static MediaHighway2ChannelSection ProcessMediaHighwayChannelTable(byte[] byteData) + { + Mpeg2BasicHeader mpeg2Header = new Mpeg2BasicHeader(); + + try + { + mpeg2Header.Process(byteData); + + MediaHighway2ChannelSection channelSection = new MediaHighway2ChannelSection(); + bool process = channelSection.Process(byteData, mpeg2Header.Index); + if (process) + { + channelSection.LogMessage(); + return (channelSection); + } + else + return (null); + } + catch (ArgumentOutOfRangeException e) + { + Logger.Instance.Write(" Channel section parsing failed: " + e.Message); + return (null); + } + catch (NotImplementedException e) + { + return null; + } + } + } +} + diff --git a/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/EPGCollectorSide/DVBServices/MediaHighway/MediaHighway2SummaryData.cs b/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/EPGCollectorSide/DVBServices/MediaHighway/MediaHighway2SummaryData.cs new file mode 100644 index 0000000..30b17f1 --- /dev/null +++ b/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/EPGCollectorSide/DVBServices/MediaHighway/MediaHighway2SummaryData.cs @@ -0,0 +1,190 @@ +////////////////////////////////////////////////////////////////////////////////// +// // +// Copyright © 2005-2020 nzsjb // +// // +// This Program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation; either version 2, or (at your option) // +// any later version. // +// // +// This Program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with GNU Make; see the file COPYING. If not, write to // +// the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. // +// http://www.gnu.org/copyleft/gpl.html // +// // +////////////////////////////////////////////////////////////////////////////////// + +using System; + +using DomainObjects; + +namespace DVBServices +{ + /// + /// The class that describes MediaHighway2 summary data. + /// + public class MediaHighway2SummaryData + { + /// + /// Get the event identification. + /// + public int EventID { get { return (eventID); } } + + /// + /// Get the short description of the event. + /// + public string ShortDescription + { + get + { + return shortDescription; + } + } + + /// + /// Get the unidentified data. + /// + public byte[] Unknown { get { return (unknown); } } + + /// + /// Get the index of the next byte in the MPEG2 section following the summary data. + /// + /// + /// The summary data has not been processed. + /// + public int Index + { + get + { + if (lastIndex == -1) + throw (new InvalidOperationException("MediaHighway2SummaryData: Index requested before block processed")); + return (lastIndex); + } + } + + private int eventID; + private string shortDescription; + private byte[] unknown; + + private int lastIndex = -1; + + int summaryLength; + int lineCount; + int lineLength; + + /// + /// Initialize a new instance of the MediaHighway2SummaryData class. + /// + public MediaHighway2SummaryData() { } + + /// + /// Parse the summary data. + /// + /// The MPEG2 section containing the summary data. + /// Index of the first byte of the summary data in the MPEG2 section. + /// True if the block contains data; false otherwise. + internal bool Process(byte[] byteData, int index) + { + lastIndex = index; + + try + { + eventID = Utils.Convert2BytesToInt(byteData, lastIndex); + lastIndex += 2; + + unknown = Utils.GetBytes(byteData, lastIndex, 9); + lastIndex += unknown.Length; + + if (unknown[1] != 0x00) + { + if (DebugEntry.IsDefined(DebugName.Mhw2Unknown)) + { + Logger.Instance.Write("Index: " + index); + Logger.Instance.Dump("Unknown Data Block", byteData, byteData.Length); + } + return (false); + } + + if (unknown[2] != 0x00 && unknown[2] != 0x01) + { + if (DebugEntry.IsDefined(DebugName.Mhw2Unknown)) + { + Logger.Instance.Write("Index: " + index); + Logger.Instance.Dump("Unknown Data Block", byteData, byteData.Length); + } + return (false); + } + + summaryLength = (int)byteData[lastIndex]; + lastIndex++; + + if (summaryLength == 0) + { + if (DebugEntry.IsDefined(DebugName.Mhw2Unknown)) + { + Logger.Instance.Write("Index: " + index); + Logger.Instance.Dump("Summary Length Zero", byteData, byteData.Length); + } + return (false); + } + + shortDescription = Utils.GetString(byteData, lastIndex, summaryLength, ReplaceMode.TransferUnchanged); + lastIndex += summaryLength; + + lineCount = byteData[lastIndex] & 0x0f; + lastIndex++; + + while (lineCount > 0) + { + lineLength = (int)byteData[lastIndex]; + lastIndex++; + + if (lineLength > 0) + { + shortDescription += " " + Utils.GetString(byteData, lastIndex, lineLength, ReplaceMode.TransferUnchanged); + lastIndex += lineLength; + } + + lineCount--; + } + + Validate(); + + return (true); + } + catch (IndexOutOfRangeException) + { + byte[] data = (Utils.GetBytes(byteData, 0, byteData.Length)); + Logger.Instance.Dump("Exception Data", data, data.Length); + throw (new ArgumentOutOfRangeException("The MediaHighway2 Summary Data message is short")); + } + } + + /// + /// Validate the summary data fields. + /// + /// + /// A summary data field is not valid. + /// + public void Validate() { } + + /// + /// Log the summary data fields. + /// + public void LogMessage() + { + if (Logger.ProtocolLogger == null) + return; + + Logger.ProtocolLogger.Write(Logger.ProtocolIndent + "MHW2 SUMMARY DATA: Event ID: " + eventID + + " Unknown: " + Utils.ConvertToHex(unknown) + + " Short desc: " + shortDescription); + } + } +} + diff --git a/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/EPGCollectorSide/DVBServices/MediaHighway/MediaHighway2SummarySection.cs b/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/EPGCollectorSide/DVBServices/MediaHighway/MediaHighway2SummarySection.cs new file mode 100644 index 0000000..fe28fe5 --- /dev/null +++ b/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/EPGCollectorSide/DVBServices/MediaHighway/MediaHighway2SummarySection.cs @@ -0,0 +1,103 @@ +////////////////////////////////////////////////////////////////////////////////// +// // +// Copyright © 2005-2020 nzsjb // +// // +// This Program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation; either version 2, or (at your option) // +// any later version. // +// // +// This Program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with GNU Make; see the file COPYING. If not, write to // +// the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. // +// http://www.gnu.org/copyleft/gpl.html // +// // +////////////////////////////////////////////////////////////////////////////////// + +using System; + +using DomainObjects; + +namespace DVBServices +{ + /// + /// The class that describes a MediaHighway2 Summary section. + /// + public class MediaHighway2SummarySection + { + /// + /// Get the summary data for this section. + /// + public MediaHighway2SummaryData SummaryData { get { return (summaryData); } } + + private MediaHighway2SummaryData summaryData; + + private int lastIndex = -1; + + /// + /// Initialize a new instance of the MediaHighway2SummarySection class. + /// + internal MediaHighway2SummarySection() { } + + /// + /// Parse the section. + /// + /// The MPEG2 section containing the summary data. + /// Index of the first byte of the summary data in the MPEG2 section. + /// True if the block contains data; false otherwise. + internal bool Process(byte[] byteData, int index) + { + lastIndex = index; + + summaryData = new MediaHighway2SummaryData(); + return(summaryData.Process(byteData, lastIndex)); + } + + /// + /// Log the section fields. + /// + public void LogMessage() + { + if (Logger.ProtocolLogger == null) + return; + + summaryData.LogMessage(); + } + + /// + /// Process an MPEG2 section from the MediaHighway1 summary table. + /// + /// The MPEG2 section. + /// A MediaHighway2SummarySection instance. + public static MediaHighway2SummarySection ProcessMediaHighwaySummaryTable(byte[] byteData) + { + Mpeg2BasicHeader mpeg2Header = new Mpeg2BasicHeader(); + + try + { + mpeg2Header.Process(byteData); + + MediaHighway2SummarySection summarySection = new MediaHighway2SummarySection(); + bool process = summarySection.Process(byteData, mpeg2Header.Index); + if (process) + { + summarySection.LogMessage(); + return (summarySection); + } + else + return (null); + } + catch (ArgumentOutOfRangeException e) + { + Logger.Instance.Write(" Summary section parsing failed: " + e.Message); + return (null); + } + } + } +} + diff --git a/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/EPGCollectorSide/DVBServices/MediaHighway/MediaHighway2TitleData.cs b/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/EPGCollectorSide/DVBServices/MediaHighway/MediaHighway2TitleData.cs new file mode 100644 index 0000000..c920f4e --- /dev/null +++ b/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/EPGCollectorSide/DVBServices/MediaHighway/MediaHighway2TitleData.cs @@ -0,0 +1,222 @@ +////////////////////////////////////////////////////////////////////////////////// +// // +// Copyright © 2005-2020 nzsjb // +// // +// This Program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation; either version 2, or (at your option) // +// any later version. // +// // +// This Program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with GNU Make; see the file COPYING. If not, write to // +// the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. // +// http://www.gnu.org/copyleft/gpl.html // +// // +////////////////////////////////////////////////////////////////////////////////// + +using System; + +using DomainObjects; + +namespace DVBServices +{ + /// + /// The class that describes MediaHighway2 title data. + /// + public class MediaHighway2TitleData + { + /// + /// Get the channel identification. + /// + public int ChannelID { get { return (channelID); } } + /// + /// Get the event identification. + /// + public int EventID { get { return (eventID); } } + /// + /// Get the start time of the event. + /// + public DateTime StartTime { get { return (baseDate + new TimeSpan(hours, minutes, 0)); }} + /// + /// Get the duration of the event. + /// + public TimeSpan Duration { get { return (duration); } } + /// + /// Get the theme identification of the event. + /// + public int CategoryID { get { return ((mainCategory << 6) + subCategory); } } + /// + /// Get the theme group of the event. + /// + public int MainCategory { get { return (mainCategory); } } + /// + /// Get the theme subgroup of the event. + /// + public int SubCategory { get { return (subCategory); } } + /// + /// Get the name of the event. + /// + public string EventName { get { return (eventName); } } + /// + /// Get the unknown data. + /// + public byte[] Unknown + { + get + { + byte[] outputBytes = new byte[unknown0.Length + unknown1.Length + unknown2.Length]; + unknown0.CopyTo(outputBytes, 0); + unknown1.CopyTo(outputBytes, unknown0.Length); + unknown2.CopyTo(outputBytes, unknown0.Length + unknown1.Length); + return (outputBytes); + } + } + + /// + /// Get the index of the next byte in the MPEG2 section following the title data. + /// + /// + /// The title data has not been processed. + /// + public int Index + { + get + { + if (lastIndex == -1) + throw (new InvalidOperationException("MediaHighway2TitleData: Index requested before block processed")); + return (lastIndex); + } + } + + private byte[] unknown0; + private int channelID; + private byte[] unknown1; + private int mainCategory; + private DateTime baseDate; + private int hours; + private int minutes; + private byte[] unknown2; + private TimeSpan duration; + private string eventName; + private int subCategory; + private int eventID; + + private int lastIndex = -1; + + /// + /// Initialize a new instance of the MediaHighway2TitleData class. + /// + public MediaHighway2TitleData() { } + + /// + /// Parse the title data. + /// + /// The MPEG2 section containing the title data. + /// Index of the first byte of the title data in the MPEG2 section. + internal void Process(byte[] byteData, int index) + { + lastIndex = index; + + try + { + unknown0 = Utils.GetBytes(byteData, 3, 15); + + channelID = (int)byteData[lastIndex]; + lastIndex++; + + unknown1 = Utils.GetBytes(byteData, lastIndex, 10); + lastIndex += unknown1.Length; + + mainCategory = byteData[7] & 0x0f; + + baseDate = getDate(Utils.Convert2BytesToInt(byteData, lastIndex)); + lastIndex += 2; + + hours = ((byteData[lastIndex] >> 4) * 10) + (byteData[lastIndex] & 0x0f); + lastIndex++; + + minutes = ((byteData[lastIndex] >> 4) * 10) + (byteData[lastIndex] & 0x0f); + lastIndex++; + + unknown2 = Utils.GetBytes(byteData, lastIndex, 1); + lastIndex += unknown2.Length; + + int durationMinutes = (byteData[lastIndex] << 4) + (byteData[lastIndex + 1] >> 4); + duration = new TimeSpan(durationMinutes / 60, durationMinutes % 60, 0); + lastIndex += 2; + + int eventNameLength = byteData[lastIndex] & 0x3f; + lastIndex++; + + eventName = Utils.GetString(byteData, lastIndex, eventNameLength, ReplaceMode.TransferUnchanged); + lastIndex += eventNameLength; + + subCategory = byteData[lastIndex] & 0x3f; + lastIndex++; + + eventID = Utils.Convert2BytesToInt(byteData, lastIndex); + lastIndex += 2; + + Validate(); + } + catch (IndexOutOfRangeException) + { + throw (new ArgumentOutOfRangeException("lastIndex = " + lastIndex)); + } + } + + private DateTime getDate(int mjd) + { + int j = mjd + 2400001 + 68569; + int c = 4 * j / 146097; + j = j - (146097 * c + 3) / 4; + + int y = 4000 * (j + 1) / 1461001; + j = j - 1461 * y / 4 + 31; + int m = 80 * j / 2447; + + int day = j - 2447 * m / 80; + j = m / 11; + int month = m + 2 - (12 * j); + int year = 100 * (c - 49) + y + j; + + return (new DateTime(year, month, day)); + } + + /// + /// Validate the title data fields. + /// + /// + /// A title data field is not valid. + /// + public void Validate() { } + + /// + /// Log the title data fields. + /// + public void LogMessage() + { + if (Logger.ProtocolLogger == null) + return; + + Logger.ProtocolLogger.Write(Logger.ProtocolIndent + "MHW2 TITLE DATA: Channel ID: " + channelID + + " Unknown0: " + Utils.ConvertToHex(unknown0) + + " Unknown1: " + Utils.ConvertToHex(unknown1) + + " Main cat: " + mainCategory + + " Base date: " + baseDate + + " Hours: " + hours + + " Minutes: " + minutes + + " Unknown2: " + Utils.ConvertToHex(unknown2) + + " Duration: " + duration + + " Event name: " + eventName + + " Sub cat: " + mainCategory + + " Event ID: " + eventID); + } + } +} + diff --git a/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/EPGCollectorSide/DVBServices/MediaHighway/MediaHighway2TitleSection.cs b/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/EPGCollectorSide/DVBServices/MediaHighway/MediaHighway2TitleSection.cs new file mode 100644 index 0000000..62f97db --- /dev/null +++ b/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/EPGCollectorSide/DVBServices/MediaHighway/MediaHighway2TitleSection.cs @@ -0,0 +1,164 @@ +////////////////////////////////////////////////////////////////////////////////// +// // +// Copyright © 2005-2020 nzsjb // +// // +// This Program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation; either version 2, or (at your option) // +// any later version. // +// // +// This Program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with GNU Make; see the file COPYING. If not, write to // +// the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. // +// http://www.gnu.org/copyleft/gpl.html // +// // +////////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.ObjectModel; + +using DomainObjects; + +namespace DVBServices +{ + /// + /// The class that describes a MediaHighway2 Title section. + /// + public class MediaHighway2TitleSection + { + /// + /// Get the title data for this section. + /// + public Collection Titles { get { return (titles); } } + + private byte[] unknown; + private Collection titles; + + private int lastIndex = -1; + + /// + /// Initialize a new instance of the MediaHighway2TitleSection class. + /// + internal MediaHighway2TitleSection() + { + titles = new Collection(); + } + + /// + /// Parse the section. + /// + /// The MPEG2 section containing the section. + /// Index of the first byte of the title section in the MPEG2 section. + /// True if the section is an MHW2 title section; false otherwise. + internal bool Process(byte[] byteData, int index) + { + lastIndex = index; + + /*int checkIndex = 18; + bool process = false; + + while (checkIndex < byteData.Length) + { + process = false; + checkIndex += 7; + + if (checkIndex < byteData.Length) + { + checkIndex += 3; + + if (checkIndex < byteData.Length) + { + if (byteData[checkIndex] > 0xc0) + { + checkIndex += (byteData[checkIndex] - 0xc0); + checkIndex += 4; + + if (checkIndex < byteData.Length) + { + if (byteData[checkIndex] == 0xff) + { + checkIndex += 1; + process = true; + } + } + } + } + } + } + + if (!process) + return (false);*/ + + /*Logger.Instance.Dump("Title Section", byteData, byteData.Length);*/ + + unknown = Utils.GetBytes(byteData, lastIndex, 15); + lastIndex += unknown.Length; + + while (lastIndex < byteData.Length) + { + MediaHighway2TitleData title = new MediaHighway2TitleData(); + title.Process(byteData, lastIndex); + + titles.Add(title); + lastIndex = title.Index; + } + + return (true); + } + + /// + /// Log the section fields. + /// + public void LogMessage() + { + if (Logger.ProtocolLogger == null) + return; + + if (titles != null) + { + Logger.IncrementProtocolIndent(); + + foreach (MediaHighway2TitleData title in titles) + title.LogMessage(); + + Logger.DecrementProtocolIndent(); + } + } + + /// + /// Process an MPEG2 section from the MediaHighway title table. + /// + /// The MPEG2 section. + /// A MediaHighway2TitleSection instance. + public static MediaHighway2TitleSection ProcessMediaHighwayTitleTable(byte[] byteData) + { + Mpeg2BasicHeader mpeg2Header = new Mpeg2BasicHeader(); + + try + { + mpeg2Header.Process(byteData); + + MediaHighway2TitleSection titleSection = new MediaHighway2TitleSection(); + bool process = titleSection.Process(byteData, mpeg2Header.Index); + if (process) + { + titleSection.LogMessage(); + return (titleSection); + } + else + return (null); + } + catch (ArgumentOutOfRangeException e) + { + Logger.Instance.Write(" Title section parsing failed: " + e.Message); + return (null); + } + } + } +} + diff --git a/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/EPGCollectorSide/DVBServices/MediaHighway/MediaHighwayCategoryEntry.cs b/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/EPGCollectorSide/DVBServices/MediaHighway/MediaHighwayCategoryEntry.cs new file mode 100644 index 0000000..d3868f8 --- /dev/null +++ b/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/EPGCollectorSide/DVBServices/MediaHighway/MediaHighwayCategoryEntry.cs @@ -0,0 +1,70 @@ +////////////////////////////////////////////////////////////////////////////////// +// // +// Copyright © 2005-2020 nzsjb // +// // +// This Program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation; either version 2, or (at your option) // +// any later version. // +// // +// This Program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with GNU Make; see the file COPYING. If not, write to // +// the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. // +// http://www.gnu.org/copyleft/gpl.html // +// // +////////////////////////////////////////////////////////////////////////////////// + +using DomainObjects; + +namespace DVBServices +{ + /// + /// The class that describes a MediaHighway category entry. + /// + public class MediaHighwayCategoryEntry + { + /// + /// Get or set the category number. + /// + public int Number + { + get { return (number); } + set { number = value; } + } + + /// + /// Get or set the category description. + /// + public string Description + { + get { return (description); } + set { description = value; } + } + + private int number; + private string description; + + /// + /// Initialize a new instance of the MediaHighwayCategoryEntry class. + /// + public MediaHighwayCategoryEntry() { } + + /// + /// Log the section fields. + /// + public void LogMessage() + { + if (Logger.ProtocolLogger == null) + return; + + Logger.ProtocolLogger.Write(Logger.ProtocolIndent + "MHW CATEGORY ENTRY: Number: " + number + + " Description: " + description); + } + } +} + diff --git a/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/EPGCollectorSide/DVBServices/MediaHighway/MediaHighwayChanneInfoEntry.cs b/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/EPGCollectorSide/DVBServices/MediaHighway/MediaHighwayChanneInfoEntry.cs new file mode 100644 index 0000000..f5989bf --- /dev/null +++ b/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/EPGCollectorSide/DVBServices/MediaHighway/MediaHighwayChanneInfoEntry.cs @@ -0,0 +1,214 @@ +////////////////////////////////////////////////////////////////////////////////// +// // +// Copyright © 2005-2020 nzsjb // +// // +// This Program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation; either version 2, or (at your option) // +// any later version. // +// // +// This Program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with GNU Make; see the file COPYING. If not, write to // +// the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. // +// http://www.gnu.org/copyleft/gpl.html // +// // +////////////////////////////////////////////////////////////////////////////////// + +using System; + +using DomainObjects; + +namespace DVBServices +{ + /// + /// The class that describes a MediaHighway channel info entry. + /// + public class MediaHighwayChannelInfoEntry + { + /// + /// Get the original network ID for the channel. + /// + public int OriginalNetworkID { get { return (originalNetworkID); } } + /// + /// Get the transport stream ID for the channel. + /// + public int TransportStreamID { get { return (transportStreamID); } } + /// + /// Get the service ID for the channel. + /// + public int ServiceID { get { return (serviceID); } } + /// + /// Get the name of the channel. + /// + public string Name { get { return (name); } } + /// + /// Get the unknown data. + /// + public byte[] Unknown { get { return (unknown); } } + + /// + /// Get the index of the next byte in the section following this entry. + /// + /// + /// The entry has not been processed. + /// + public int Index + { + get + { + if (lastIndex == -1) + throw (new InvalidOperationException("MediaHighwayChannelEntry: Index requested before block processed")); + return (lastIndex); + } + } + + /// + /// Get the index of the next name byte in the section following this entry. + /// + /// + /// The entry has not been processed. + /// + public int NameIndex + { + get + { + if (nameIndex == -1) + throw (new InvalidOperationException("MediaHighwayChannelEntry: Name index requested before block processed")); + return (nameIndex); + } + } + + /// + /// Get the length of the entry. + /// + public int Length { get { return (length); } } + + private int originalNetworkID; + private int transportStreamID; + private int serviceID; + private string name; + private byte[] unknown; + + private int lastIndex = -1; + private int nameIndex; + private int length; + + /// + /// Initialize a new instance of the MediaHighwayChannelInfoEntry. + /// + public MediaHighwayChannelInfoEntry() { } + + /// + /// Parse the MHW1 descriptor. + /// + /// The MPEG2 section containing the entry. + /// Index of the first byte in the MPEG2 section of the entry. + internal void Process(byte[] byteData, int index) + { + lastIndex = index; + + try + { + originalNetworkID = Utils.Convert2BytesToInt(byteData, lastIndex); + lastIndex += 2; + + transportStreamID = Utils.Convert2BytesToInt(byteData, lastIndex); + lastIndex += 2; + + serviceID = Utils.Convert2BytesToInt(byteData, lastIndex); + lastIndex += 2; + + name = Utils.GetString(byteData, lastIndex, 16, ReplaceMode.TransferUnchanged).Trim(); + lastIndex += 16; + + length = lastIndex - index; + + Validate(); + } + catch (IndexOutOfRangeException) + { + throw (new ArgumentOutOfRangeException("The MediaHighway Channel Info Entry message is short")); + } + } + + /// + /// Parse the MHW2 descriptor. + /// + /// The MPEG2 section containing the entry. + /// Index of the first byte in the MPEG2 section of the entry. + /// Index of the first byte channel name in the MPEG2 section of the entry. + internal void Process(byte[] byteData, int index, int nameLengthIndex) + { + lastIndex = index; + nameIndex = nameLengthIndex; + + try + { + originalNetworkID = Utils.Convert2BytesToInt(byteData, lastIndex); + lastIndex += 2; + + transportStreamID = Utils.Convert2BytesToInt(byteData, lastIndex); + lastIndex += 2; + + serviceID = Utils.Convert2BytesToInt(byteData, lastIndex); + lastIndex += 2; + + unknown = Utils.GetBytes(byteData, lastIndex, 2); + lastIndex += unknown.Length; + + int nameLength = (int)byteData[nameIndex] & 0x3f; + nameIndex++; + + name = Utils.GetString(byteData, nameIndex, nameLength, ReplaceMode.TransferUnchanged).Trim(); + nameIndex += nameLength; + + length = lastIndex - index; + + Validate(); + } + catch (IndexOutOfRangeException) + { + throw (new ArgumentOutOfRangeException("The MediaHighway Channel Info Entry message is short")); + } + catch (NotImplementedException e) + { + throw e; + } + } + + /// + /// Validate the entry fields. + /// + /// + /// A descriptor field is not valid. + /// + internal void Validate() { } + + /// + /// Log the entry fields. + /// + internal void LogMessage() + { + if (Logger.ProtocolLogger == null) + return; + + string unknownString; + if (unknown == null) + unknownString = "n/a"; + else + unknownString = Utils.ConvertToHex(unknown); + + Logger.ProtocolLogger.Write(Logger.ProtocolIndent + "MHW CHANNEL INFO ENTRY: ONID: " + originalNetworkID + + " TSID: " + transportStreamID + + " SID: " + serviceID + + " Name: " + name + + " Unknown: " + unknownString); + } + } +} + diff --git a/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/EPGCollectorSide/DVBServices/Mpeg2BasicHeader.cs b/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/EPGCollectorSide/DVBServices/Mpeg2BasicHeader.cs new file mode 100644 index 0000000..bcd8431 --- /dev/null +++ b/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/EPGCollectorSide/DVBServices/Mpeg2BasicHeader.cs @@ -0,0 +1,134 @@ +////////////////////////////////////////////////////////////////////////////////// +// // +// Copyright © 2005-2020 nzsjb // +// // +// This Program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation; either version 2, or (at your option) // +// any later version. // +// // +// This Program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with GNU Make; see the file COPYING. If not, write to // +// the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. // +// http://www.gnu.org/copyleft/gpl.html // +// // +////////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace DVBServices +{ + /// + /// The class that describes a basic MPEG2 section header. + /// + public class Mpeg2BasicHeader + { + /// + /// Get the table identification. + /// + public int TableID { get { return (tableID); } } + /// + /// Get the section length. + /// + public int SectionLength { get { return (sectionLength); } } + /// + /// Return true if the section is private data; false otherwise. + /// + public bool PrivateIndicator { get { return (privateIndicator); } } + /// + /// Return true if the sysntax indicator is set; false otherwise. + /// + public bool SyntaxIndicator { get { return (syntaxIndicator); } } + + /// + /// Get the index of the next byte in the MPEG2 section following the header. + /// + /// + /// The header has not been processed. + /// + public virtual int Index + { + get + { + if (lastIndex == -1) + throw (new InvalidOperationException("Mpeg2BasicHeader: Index requested before block processed")); + return (lastIndex); + } + } + + private int tableID; + private int sectionLength; + private bool privateIndicator; + private bool syntaxIndicator; + + private int lastIndex = -1; + private int dataLength; + + // MPEG basic header layout is as follows + // + // tableID byte uimsbf + // + // section syntax ind 1 bit bslbf + // private indicator 1 bit bslbf + // reserved 2 bits bslbf + // section length 12 bits uimsbf + + /// + /// Initialize a new instance of the Mpeg2BasicHeader class. + /// + public Mpeg2BasicHeader() { } + + /// + /// Parse the header. + /// + /// The MPEG2 section containing the header. + public virtual void Process(byte[] byteData) + { + lastIndex = 0; + dataLength = byteData.Length; + + try + { + tableID = (int)byteData[lastIndex]; + lastIndex++; + + syntaxIndicator = (byteData[lastIndex] & 0x80) != 0; + privateIndicator = (byteData[lastIndex] & 0x40) != 0; + sectionLength = ((byteData[lastIndex] & 0x0f) * 256) + (int)byteData[lastIndex + 1]; + lastIndex += 2; + + Validate(); + } + catch (IndexOutOfRangeException) + { + throw (new ArgumentOutOfRangeException("MPEG2 basic header is short")); + } + } + + /// + /// Validate the header fields. + /// + /// + /// A header field is not valid. + /// + public virtual void Validate() + { + if (dataLength > 4096) + throw (new ArgumentOutOfRangeException("MPEG2 Section data length wrong")); + + if (sectionLength < 1 || sectionLength > dataLength - 3) + throw (new ArgumentOutOfRangeException("MPEG2 Section length wrong")); + } + + /// + /// Log the header fields. + /// + public virtual void LogMessage() { } + } +} + diff --git a/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/EPGCollectorSide/DVBServices/Utils.cs b/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/EPGCollectorSide/DVBServices/Utils.cs index 48ae93a..fb97cf3 100644 --- a/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/EPGCollectorSide/DVBServices/Utils.cs +++ b/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/EPGCollectorSide/DVBServices/Utils.cs @@ -1,8 +1,11 @@ -using System; +using DomainObjects; +using System; using System.Collections.Generic; using System.Linq; +using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; +using skyscraper5.Skyscraper.Text; namespace DVBServices { @@ -63,5 +66,77 @@ namespace DVBServices return ((char)('a' + (value - 10))); } + + /// + /// Get subset of bytes from an array. + /// + /// The array of bytes. + /// The index of the first byte of the subset. + /// The length of the subset. + /// The subset of bytes. + public static byte[] GetBytes(byte[] byteData, int offset, int length) + { + if (length < 1) + throw (new ArgumentOutOfRangeException("GetBytes length wrong")); + + try + { + byte[] outputBytes = new byte[length]; + + for (int index = 0; index < length; index++) + outputBytes[index] = byteData[offset + index]; + + return (outputBytes); + } + catch (OutOfMemoryException) + { + throw (new ArgumentOutOfRangeException("GetBytes length wrong")); + } + } + + /// + /// Convert 2 bytes to an integer with the most significant byte first. + /// + /// The byte array containing the byes to convert. + /// The index of the first byte in the array. + /// The converted value. + public static int Convert2BytesToInt(byte[] byteData, int index) + { + return (Convert2BytesToInt(byteData, index, 0xff)); + } + + /// + /// Convert 2 bytes to an integer with the most significant byte first and a mask. + /// + /// The byte array containing the byes to convert. + /// The index of the first byte in the array. + /// The mask for the most significant byte. + /// The converted value. + public static int Convert2BytesToInt(byte[] byteData, int index, byte mask) + { + return (((byteData[index] & mask) * 256) + (int)byteData[index + 1]); + } + + private static En300468AnnexATextDecoder _encodingProvider; + /// + /// Convert a subset of an array of text bytes to a string. + /// + /// The array of bytes. + /// The index of the first byte to be converted. + /// The number of bytes to be converted. + /// Action to be taken for non-Ascii bytes. + /// The converted string. + public static string GetString(byte[] byteData, int offset, int length, ReplaceMode replaceMode) + { + if (_encodingProvider == null) + { + _encodingProvider = En300468AnnexATextDecoder.GetInstance(); + } + + string temp = _encodingProvider.Decode(byteData, offset, length); + if (temp == null) + temp = ""; + return temp; + } } } diff --git a/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/EPGCollectorSide/DomainObjects/Logger.cs b/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/EPGCollectorSide/DomainObjects/Logger.cs index d646e97..78097bb 100644 --- a/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/EPGCollectorSide/DomainObjects/Logger.cs +++ b/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/EPGCollectorSide/DomainObjects/Logger.cs @@ -20,6 +20,14 @@ namespace DomainObjects } } + public static string ProtocolIndent + { + get + { + return new string(' ', indentation); + } + } + private class EPGCollector { @@ -47,5 +55,90 @@ namespace DomainObjects Write("============================ " + identity + " =============================="); Write(""); } + + private static int indentation; + public static void IncrementProtocolIndent() + { + indentation++; + } + + public static void DecrementProtocolIndent() + { + indentation--; + if (indentation <= -1) + { + indentation = 0; + } + } + + private class EPGCollectorProtocolLogger + { + + } + + private static Logger _protocolLogger; + public static Logger ProtocolLogger + { + get + { + if (_protocolLogger == null) + { + _protocolLogger = new Logger(); + _protocolLogger._realLogger = PluginLogManager.GetLogger(typeof(EPGCollectorProtocolLogger)); + } + + return _protocolLogger; + } + } + + public void Dump(string identity, byte[] buffer, int length) + { + Dump(identity, buffer, 0, length); + } + + public void Dump(string identity, byte[] buffer, int offset, int length) + { + Write("============================ " + identity + " starting =============================="); + + StringBuilder loggerLineHex = new StringBuilder("0000 "); + StringBuilder loggerLineChar = new StringBuilder(); + int lineIndex = 0; + + for (int index = 0; index < length; index++) + { + if (lineIndex == 16) + { + Write(loggerLineHex.ToString() + " " + loggerLineChar.ToString()); + loggerLineHex.Remove(0, loggerLineHex.Length); + loggerLineChar.Remove(0, loggerLineChar.Length); + loggerLineHex.Append(index.ToString("0000 ")); + lineIndex = 0; + } + + loggerLineHex.Append(getHex(buffer[index + offset] >> 4)); + loggerLineHex.Append(getHex(buffer[index + offset] & 0x0f)); + loggerLineHex.Append(' '); + + if (buffer[index + offset] > ' ' - 1 && buffer[index + offset] < 0x7f) + loggerLineChar.Append((char)buffer[index + offset]); + else + loggerLineChar.Append('.'); + + lineIndex++; + } + + if (loggerLineHex.Length != 0) + Write(loggerLineHex.ToString() + " " + loggerLineChar.ToString()); + + Write("============================ " + identity + " ending =============================="); + } + + private char getHex(int dataChar) + { + if (dataChar < 10) + return ((char)('0' + dataChar)); + + return ((char)('a' + dataChar - 10)); + } } } diff --git a/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/FreesatBatSdtContestant.cs b/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/Freesat/FreesatBatSdtContestant.cs similarity index 88% rename from PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/FreesatBatSdtContestant.cs rename to PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/Freesat/FreesatBatSdtContestant.cs index 685e4d2..83ecbc8 100644 --- a/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/FreesatBatSdtContestant.cs +++ b/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/Freesat/FreesatBatSdtContestant.cs @@ -9,14 +9,16 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using skyscraper5.Skyscraper.Scraper.Utils; -namespace skyscraper8.EPGCollectorPort.SkyscraperSide +namespace skyscraper8.EPGCollectorPort.SkyscraperSide.Freesat { [SkyscraperPlugin] internal class FreesatBatSdtContestant : Contestant { public FreesatBatSdtContestant(int pid) : base("Freesat BAT/SDT", pid) { + PacketProcessor = new PacketDiscarder(); } public override void Dispose() @@ -29,7 +31,7 @@ namespace skyscraper8.EPGCollectorPort.SkyscraperSide if (freesatScraper == null) { freesatScraper = new FreesatTunnelScraper(); - freesatScraper.ConnectToStorage(skyscraperContext.DataStorage.GetPluginConnector()); + freesatScraper.ConnectToStorage(skyscraperContext.DataStorage.GetPluginConnector(), skyscraperContext.OnPluginEvent); skyscraperContext.PluginContext.Add(freesatScraper); } diff --git a/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/FreesatEpgContestant.cs b/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/Freesat/FreesatEpgContestant.cs similarity index 88% rename from PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/FreesatEpgContestant.cs rename to PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/Freesat/FreesatEpgContestant.cs index 1a7c3ff..8ab961c 100644 --- a/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/FreesatEpgContestant.cs +++ b/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/Freesat/FreesatEpgContestant.cs @@ -4,13 +4,14 @@ using skyscraper5.Mpeg2.Descriptors; using skyscraper5.Skyscraper.Plugins; using skyscraper5.Skyscraper.Scraper; using skyscraper5.Skyscraper.Scraper.StreamAutodetection; +using skyscraper5.Skyscraper.Scraper.Utils; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; -namespace skyscraper8.EPGCollectorPort.SkyscraperSide +namespace skyscraper8.EPGCollectorPort.SkyscraperSide.Freesat { [SkyscraperPlugin] internal class FreesatEpgContestant : Contestant @@ -18,6 +19,7 @@ namespace skyscraper8.EPGCollectorPort.SkyscraperSide public FreesatEpgContestant(int pid) : base("Freesat EPG", pid) { + PacketProcessor = new PacketDiscarder(); } public override void Dispose() @@ -30,7 +32,7 @@ namespace skyscraper8.EPGCollectorPort.SkyscraperSide if (freesatScraper == null) { freesatScraper = new FreesatTunnelScraper(); - freesatScraper.ConnectToStorage(skyscraperContext.DataStorage.GetPluginConnector()); + freesatScraper.ConnectToStorage(skyscraperContext.DataStorage.GetPluginConnector(), skyscraperContext.OnPluginEvent); skyscraperContext.PluginContext.Add(freesatScraper); } diff --git a/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/FreesatNitContestant.cs b/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/Freesat/FreesatNitContestant.cs similarity index 88% rename from PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/FreesatNitContestant.cs rename to PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/Freesat/FreesatNitContestant.cs index d73c3d7..2311c8c 100644 --- a/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/FreesatNitContestant.cs +++ b/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/Freesat/FreesatNitContestant.cs @@ -4,13 +4,14 @@ using skyscraper5.Mpeg2.Descriptors; using skyscraper5.Skyscraper.Plugins; using skyscraper5.Skyscraper.Scraper; using skyscraper5.Skyscraper.Scraper.StreamAutodetection; +using skyscraper5.Skyscraper.Scraper.Utils; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; -namespace skyscraper8.EPGCollectorPort.SkyscraperSide +namespace skyscraper8.EPGCollectorPort.SkyscraperSide.Freesat { [SkyscraperPlugin] internal class FreesatNitContestant : Contestant @@ -18,6 +19,7 @@ namespace skyscraper8.EPGCollectorPort.SkyscraperSide public FreesatNitContestant(int pid) : base("Freesat Network Information", pid) { + PacketProcessor = new PacketDiscarder(); } public override void Dispose() @@ -30,7 +32,7 @@ namespace skyscraper8.EPGCollectorPort.SkyscraperSide if (freesatScraper == null) { freesatScraper = new FreesatTunnelScraper(); - freesatScraper.ConnectToStorage(skyscraperContext.DataStorage.GetPluginConnector()); + freesatScraper.ConnectToStorage(skyscraperContext.DataStorage.GetPluginConnector(), skyscraperContext.OnPluginEvent); skyscraperContext.PluginContext.Add(freesatScraper); } diff --git a/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/FreesatTdtContestant.cs b/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/Freesat/FreesatTdtContestant.cs similarity index 85% rename from PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/FreesatTdtContestant.cs rename to PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/Freesat/FreesatTdtContestant.cs index 9b7d1e1..5af1ebc 100644 --- a/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/FreesatTdtContestant.cs +++ b/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/Freesat/FreesatTdtContestant.cs @@ -1,16 +1,17 @@ -using skyscraper5.Mpeg2.Descriptors; +using skyscraper5.Dvb.Psi; +using skyscraper5.Mpeg2; +using skyscraper5.Mpeg2.Descriptors; +using skyscraper5.Skyscraper.Plugins; using skyscraper5.Skyscraper.Scraper; using skyscraper5.Skyscraper.Scraper.StreamAutodetection; +using skyscraper5.Skyscraper.Scraper.Utils; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; -using skyscraper5.Dvb.Psi; -using skyscraper5.Mpeg2; -using skyscraper5.Skyscraper.Plugins; -namespace skyscraper8.EPGCollectorPort.SkyscraperSide +namespace skyscraper8.EPGCollectorPort.SkyscraperSide.Freesat { [SkyscraperPlugin] internal class FreesatTdtContestant : Contestant @@ -18,6 +19,7 @@ namespace skyscraper8.EPGCollectorPort.SkyscraperSide public FreesatTdtContestant(int pid) : base("Freesat Timestamp", pid) { + PacketProcessor = new PacketDiscarder(); } public override void Dispose() @@ -30,7 +32,7 @@ namespace skyscraper8.EPGCollectorPort.SkyscraperSide if (freesatScraper == null) { freesatScraper = new FreesatTunnelScraper(); - freesatScraper.ConnectToStorage(skyscraperContext.DataStorage.GetPluginConnector()); + freesatScraper.ConnectToStorage(skyscraperContext.DataStorage.GetPluginConnector(), skyscraperContext.OnPluginEvent); skyscraperContext.PluginContext.Add(freesatScraper); } diff --git a/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/FreesatTextDecoder.cs b/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/Freesat/FreesatTextDecoder.cs similarity index 93% rename from PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/FreesatTextDecoder.cs rename to PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/Freesat/FreesatTextDecoder.cs index b93e451..694083c 100644 --- a/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/FreesatTextDecoder.cs +++ b/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/Freesat/FreesatTextDecoder.cs @@ -8,7 +8,7 @@ using skyscraper5.Skyscraper.Plugins; using skyscraper8.EPGCollectorPort.Properties; using skyscraper8.Skyscraper.Text; -namespace skyscraper8.EPGCollectorPort.SkyscraperSide +namespace skyscraper8.EPGCollectorPort.SkyscraperSide.Freesat { [SkyscraperPlugin] [EncodingTypeId(0x01)] diff --git a/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/FreesatTunnelDataStorage.cs b/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/Freesat/FreesatTunnelDataStorage.cs similarity index 93% rename from PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/FreesatTunnelDataStorage.cs rename to PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/Freesat/FreesatTunnelDataStorage.cs index 9e91c92..ffee399 100644 --- a/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/FreesatTunnelDataStorage.cs +++ b/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/Freesat/FreesatTunnelDataStorage.cs @@ -3,11 +3,12 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using skyscraper5.Data.PostgreSql; using skyscraper5.Dvb.Psi.Model; using skyscraper5.Ietf.Rfc971; using skyscraper5.Skyscraper.Scraper.Storage.InMemory; -namespace skyscraper8.EPGCollectorPort.SkyscraperSide +namespace skyscraper8.EPGCollectorPort.SkyscraperSide.Freesat { internal interface IFreesatTunnelDataStorage { @@ -28,6 +29,9 @@ namespace skyscraper8.EPGCollectorPort.SkyscraperSide case InMemoryPluginToken t1: _storageEngine = new FreesatTunnelDataStorageInMemory(); break; + case PostgresqlToken t2: + _storageEngine = new FreesatTunnelDataStoragePostgresql(t2); + break; default: throw new NotImplementedException(o.GetType().FullName); } diff --git a/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/Freesat/FreesatTunnelDataStoragePostgresql.cs b/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/Freesat/FreesatTunnelDataStoragePostgresql.cs new file mode 100644 index 0000000..264130f --- /dev/null +++ b/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/Freesat/FreesatTunnelDataStoragePostgresql.cs @@ -0,0 +1,201 @@ +using Npgsql; +using NpgsqlTypes; +using skyscraper5.Data.PostgreSql; +using skyscraper5.Dvb.Psi.Model; +using skyscraper5.Skyscraper.Scraper.Storage.Utilities; +using System.Data.Common; +using System.Text; +using Newtonsoft.Json; + +namespace skyscraper8.EPGCollectorPort.SkyscraperSide.Freesat; + +class FreesatTunnelDataStoragePostgresql : IFreesatTunnelDataStorage +{ + private readonly PostgresqlToken token; + public FreesatTunnelDataStoragePostgresql(PostgresqlToken t2) + { + token = t2; + } + + private HashSet knownEitEvents; + + public bool StoreEitEvent(EitEvent eitEvent) + { + if (knownEitEvents == null) + knownEitEvents = new HashSet(); + DatabaseKeyEitEvent key = new DatabaseKeyEitEvent(eitEvent.ServiceId, eitEvent.TransportStreamId, eitEvent.OriginalNetworkId, eitEvent.EventId, eitEvent.StartTime); + if (knownEitEvents.Contains(key)) + return false; + if (TestForEitEvent(key)) + { + knownEitEvents.Add(key); + return false; + } + + token.EnqueueTask(x => WriteEitEvent(x, eitEvent)); + knownEitEvents.Add(key); + return true; + } + + private void WriteEitEvent(NpgsqlConnection connection, EitEvent eitEvent) + { + DatabaseKeyEitEvent key = new DatabaseKeyEitEvent(eitEvent.ServiceId, eitEvent.TransportStreamId, eitEvent.OriginalNetworkId, eitEvent.EventId, eitEvent.StartTime); + if (TestForEitEventEx(key, connection)) + { + return; + } + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = + "INSERT INTO freesat_eit " + + "(sid, tsid, onid, event_id, start_time, event) " + + "VALUES " + + "(@sid, @tsid, @onid, @event_id, @start_time, @event)"; + + command.Parameters.AddWithValue("@sid", NpgsqlDbType.Integer, (int)eitEvent.ServiceId); + command.Parameters.AddWithValue("@tsid", NpgsqlDbType.Integer, (int)eitEvent.TransportStreamId); + command.Parameters.AddWithValue("@onid", NpgsqlDbType.Integer, (int)eitEvent.OriginalNetworkId); + command.Parameters.AddWithValue("@event_id", NpgsqlDbType.Integer, (int)eitEvent.EventId); + command.Parameters.AddWithValue("@start_time", NpgsqlDbType.Timestamp, eitEvent.StartTime); + command.Parameters.AddWithValue("@event", NpgsqlDbType.Json, JsonConvert.SerializeObject(eitEvent)); + int executeNonQuery = command.ExecuteNonQuery(); + if (executeNonQuery != 1) + throw new NotImplementedException(string.Format("Wanted to insert 1 line, but it was {0}", executeNonQuery)); + } + + private bool TestForEitEvent(DatabaseKeyEitEvent eitEvent) + { + using (NpgsqlConnection conn = token.GetConnection()) + { + conn.Open(); + return TestForEitEventEx(eitEvent, conn); + } + } + + private static bool TestForEitEventEx(DatabaseKeyEitEvent eitEvent, NpgsqlConnection conn) + { + NpgsqlCommand command = conn.CreateCommand(); + command.CommandText = + "SELECT dateadded FROM freesat_eit WHERE sid = @sid AND tsid = @tsid AND onid = @onid AND event_id = @event_id AND start_time = @start_time"; + command.Parameters.AddWithValue("@sid", NpgsqlDbType.Integer, (int)eitEvent.ServiceId); + command.Parameters.AddWithValue("@tsid", NpgsqlDbType.Integer, (int)eitEvent.TransportStreamId); + command.Parameters.AddWithValue("@onid", NpgsqlDbType.Integer, (int)eitEvent.OriginalNetworkId); + command.Parameters.AddWithValue("@event_id", NpgsqlDbType.Integer, (int)eitEvent.EventId); + command.Parameters.AddWithValue("@start_time", NpgsqlDbType.Timestamp, eitEvent.StartTime); + NpgsqlDataReader dataReader = command.ExecuteReader(); + bool result = dataReader.Read(); + dataReader.Close(); + return result; + } + + private HashSet _knownUpdatedNitNetworks; + public void StoreNitNetwork(NitNetwork nitNetwork) + { + if (_knownUpdatedNitNetworks == null) + _knownUpdatedNitNetworks = new HashSet(); + + DatabaseKeyNitNetwork key = new DatabaseKeyNitNetwork(nitNetwork.NetworkId); + token.EnqueueTask(x => WriteNit(x, nitNetwork)); + knownNitNetworks.Add(key); + _knownUpdatedNitNetworks.Add(key); + } + + private void WriteNit(NpgsqlConnection connection, NitNetwork nitNetwork) + { + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = + "INSERT INTO freesat_nit (id, network) " + + "VALUES (@id, @network)"; + command.Parameters.AddWithValue("@id", NpgsqlDbType.Integer, (int)nitNetwork.NetworkId); + command.Parameters.AddWithValue("@network", NpgsqlDbType.Json, JsonConvert.SerializeObject(nitNetwork)); + command.ExecuteNonQuery(); + } + + private HashSet knownNitNetworks; + public bool TestNitNetwork(NitNetwork nitNetwork) + { + if (knownNitNetworks == null) + knownNitNetworks = new HashSet(); + + DatabaseKeyNitNetwork key = new DatabaseKeyNitNetwork(nitNetwork.NetworkId); + if (knownNitNetworks.Contains(key)) + return true; + + bool result = false; + using (NpgsqlConnection conn = token.GetConnection()) + { + conn.Open(); + NpgsqlCommand command = conn.CreateCommand(); + command.CommandText = "SELECT dateadded FROM freesat_nit WHERE id = @id"; + command.Parameters.AddWithValue("@id", NpgsqlDbType.Integer, (int)nitNetwork.NetworkId); + NpgsqlDataReader dataReader = command.ExecuteReader(); + result = dataReader.Read(); + dataReader.Close(); + dataReader.Dispose(); + command.Dispose(); + conn.Close(); + } + + if (result) + knownNitNetworks.Add(key); + return result; + } + + private HashSet _knownNitTs; + public bool TestForNitTransportStream(ushort networkId, NitTransportStream transportStream) + { + if (_knownNitTs == null) + _knownNitTs = new HashSet(); + + DatabaseKeyNitTs key = new DatabaseKeyNitTs(networkId, transportStream.TransportStreamId); + if (_knownNitTs.Contains(key)) + return true; + + bool result = false; + using (NpgsqlConnection conn = token.GetConnection()) + { + conn.Open(); + NpgsqlCommand command = conn.CreateCommand(); + command.CommandText = + "SELECT dateadded FROM freesat_nit_transport_stream WHERE nid = @nid AND tsid = @tsid"; + command.Parameters.AddWithValue("@nid", NpgsqlDbType.Integer, (int)networkId); + command.Parameters.AddWithValue("@tsid", NpgsqlDbType.Integer, (int)transportStream.TransportStreamId); + NpgsqlDataReader dataReader = command.ExecuteReader(); + result = dataReader.Read(); + dataReader.Close(); + command.Dispose(); + } + + if (result) + _knownNitTs.Add(key); + + return result; + } + + private HashSet _knownUpdatedNitTransportStream; + public void StoreNitTransportStream(ushort networkId, NitTransportStream transportStream) + { + token.EnqueueTask(x => WriteNitTransportStream(x, networkId, transportStream)); + DatabaseKeyNitTs ts = new DatabaseKeyNitTs(networkId, transportStream.TransportStreamId); + _knownNitTs.Add(ts); + if (_knownUpdatedNitTransportStream == null) + return; + _knownUpdatedNitTransportStream.Add(ts); + } + + private void WriteNitTransportStream(NpgsqlConnection conn, ushort networkId, NitTransportStream transportStream) + { + /*if (TestForNitTransportStream(networkId, transportStream)) + { + return; + }*/ + NpgsqlCommand command = conn.CreateCommand(); + command.CommandText = + "insert into freesat_nit_transport_stream (nid, tsid, transport_stream) " + + "values " + + "(@nid,@tsid,@transport_stream);"; + command.Parameters.AddWithValue("@nid", NpgsqlDbType.Integer, (int)networkId); + command.Parameters.AddWithValue("@tsid", NpgsqlDbType.Integer, (int)transportStream.TransportStreamId); + command.Parameters.AddWithValue("@transport_stream", NpgsqlDbType.Json, JsonConvert.SerializeObject(transportStream)); + command.ExecuteNonQuery(); + } +} \ No newline at end of file diff --git a/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/FreesatTunnelScraper.cs b/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/Freesat/FreesatTunnelScraper.cs similarity index 87% rename from PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/FreesatTunnelScraper.cs rename to PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/Freesat/FreesatTunnelScraper.cs index 145eccc..433e25b 100644 --- a/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/FreesatTunnelScraper.cs +++ b/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/Freesat/FreesatTunnelScraper.cs @@ -1,7 +1,6 @@ using skyscraper5.Dvb.Descriptors; using skyscraper5.Dvb.Psi; using skyscraper5.Dvb.Psi.Model; -using skyscraper8.EPGCollectorPort.SkyscraperSide; using skyscraper8.Skyscraper.Scraper.Storage; using System; using System.Collections.Generic; @@ -10,7 +9,7 @@ using System.Text; using System.Threading.Tasks; using skyscraper8.Skyscraper.Plugins; -namespace skyscraper5.Skyscraper.Scraper +namespace skyscraper8.EPGCollectorPort.SkyscraperSide.Freesat { internal class FreesatTunnelScraper : IBatEventHandler, ISdtEventHandler, ITdtEventHandler, ITotEventHandler, IEitEventHandler, INitEventHandler { @@ -72,7 +71,8 @@ namespace skyscraper5.Skyscraper.Scraper { _logger.Log(PluginLogLevel.Info, "NIT Network {0} Transport Stream {1}", networkId, transportStream.TransportStreamId); _dataStorage.StoreNitTransportStream(networkId, transportStream); - } + _hostNotification.Invoke(); + } } public void OnNitNetwork(NitNetwork nitNetwork) @@ -81,7 +81,8 @@ namespace skyscraper5.Skyscraper.Scraper { _logger.Log(PluginLogLevel.Info, "NIT Network {0}", nitNetwork.NetworkId); _dataStorage.StoreNitNetwork(nitNetwork); - } + _hostNotification.Invoke(); + } } private EitEvent[] runningEvents; @@ -90,26 +91,25 @@ namespace skyscraper5.Skyscraper.Scraper if (_dataStorage.StoreEitEvent(eitEvent)) { _logger.Log(PluginLogLevel.Info, "EIT Event: {0}", eitEvent.EventName); + _hostNotification.Invoke(); } if (eitEvent.RunningStatus == RunningStatus.Running) { if (runningEvents == null) - runningEvents = new EitEvent[UInt16.MaxValue]; + runningEvents = new EitEvent[ushort.MaxValue]; runningEvents[eitEvent.ServiceId] = eitEvent; } } private PluginLogger _logger; private IFreesatTunnelDataStorage _dataStorage; - public void ConnectToStorage(object[] getPluginConnector) + private Action _hostNotification; + public void ConnectToStorage(object[] getPluginConnector, Action onPluginEvent) { - if (_dataStorage == null) - { - _dataStorage = new FreesatTunnelDataStorage(getPluginConnector); - } - - _logger = PluginLogManager.GetLogger(this.GetType()); + _dataStorage = new FreesatTunnelDataStorage(getPluginConnector); + _logger = PluginLogManager.GetLogger(GetType()); + _hostNotification = onPluginEvent; } } } diff --git a/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/MediaHighway2/Mhw2Contestant.cs b/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/MediaHighway2/Mhw2Contestant.cs new file mode 100644 index 0000000..1fa741a --- /dev/null +++ b/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/MediaHighway2/Mhw2Contestant.cs @@ -0,0 +1,95 @@ +using DVBServices; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.Plugins; +using skyscraper5.Skyscraper.Scraper; +using skyscraper5.Skyscraper.Scraper.StreamAutodetection; +using skyscraper8.Skyscraper.Plugins; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper8.EPGCollectorPort.SkyscraperSide.MediaHighway2 +{ + //Look for PIDs 0x231, 0x234, and 0x236 + [SkyscraperPlugin] + [Experimental] + class Mhw2Contestant : Contestant, Mhw2EventHandler + { + + public Mhw2Contestant(int pid) : base("MediaHighway 2", pid) + { + PacketProcessor = new PsiDecoder(pid, new Mhw2Parser(this)); + if (_logger == null) + { + _logger = PluginLogManager.GetLogger(GetType()); + } + } + + public override void Dispose() + { + + } + + public override void DeclareWinner(SkyscraperContext skyscraperContext, int pid, ProgramContext programContext) + { + Mhw2Scraper mhw2Scraper = skyscraperContext.PluginContext.FirstOrDefault(x => x is Mhw2Scraper) as Mhw2Scraper; + if (mhw2Scraper == null) + { + mhw2Scraper = new Mhw2Scraper(skyscraperContext); + skyscraperContext.PluginContext.Add(mhw2Scraper); + } + + skyscraperContext.DvbContext.RegisterPacketProcessor(pid, new PsiDecoder(pid, new Mhw2Parser(mhw2Scraper))); + } + + public override void Introduce(ProgramContext programContext) + { + + } + + private byte[] deadSections; + private static PluginLogger _logger; + + public void OnNonMhw2Traffic(int sourcePid, int sectionTableId, byte mhwType) + { + if (deadSections == null) + deadSections = new byte[byte.MaxValue]; + deadSections[sectionTableId]++; + + if (deadSections[sectionTableId] == 1) + Score--; + } + + public void OnMhw2Channels(int sourcePid, MediaHighway2ChannelSection channelSection) + { + if (channelSection == null) + return; + + if (channelSection.Channels.Count > 0) + { + _logger.Log(PluginLogLevel.Info, "Found MHW2 Channel Section in PID {0}", sourcePid); + Score += channelSection.Channels.Count; + } + } + + public void OnMhw2Categories(int sourcePid, MediaHighway2CategorySection categorySection) + { + _logger.Log(PluginLogLevel.Info, "Found MHW2 Category Section in PID {0}", sourcePid); + Score += categorySection.Categories.Count; + } + + public void OnMhw2Titles(int sourcePid, MediaHighway2TitleSection titleSection) + { + _logger.Log(PluginLogLevel.Info, "Found MHW2 Title Section in PID {0}", sourcePid); + Score++; + } + + public void OnMhw2Summary(int sourcePid, MediaHighway2SummarySection summarySection) + { + _logger.Log(PluginLogLevel.Info, "Found MHW2 Summary Section in PID {0}", sourcePid); + Score++; + } + } +} diff --git a/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/MediaHighway2/Mhw2EventBinder.cs b/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/MediaHighway2/Mhw2EventBinder.cs new file mode 100644 index 0000000..925f30a --- /dev/null +++ b/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/MediaHighway2/Mhw2EventBinder.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper8.EPGCollectorPort.SkyscraperSide.MediaHighway2 +{ + internal class Mhw2EventBinder + { + public int EventId { get; } + + public Mhw2EventBinder(int eventId) + { + EventId = eventId; + } + + public string EventName { get; set; } + public int CategoryID { get; set; } + public int ChannelID { get; set; } + public TimeSpan Duration { get; set; } + public int MainCategory { get; set; } + public DateTime StartTime { get; set; } + public int SubCategory { get; set; } + + public string ShortDescription { get; set; } + public bool ShortDescriptionSeen { get; set; } + public bool TitleDataSeen { get; set; } + + + } +} diff --git a/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/MediaHighway2/Mhw2EventHandler.cs b/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/MediaHighway2/Mhw2EventHandler.cs new file mode 100644 index 0000000..2f0fdde --- /dev/null +++ b/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/MediaHighway2/Mhw2EventHandler.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using DVBServices; + +namespace skyscraper8.EPGCollectorPort.SkyscraperSide.MediaHighway2 +{ + internal interface Mhw2EventHandler + { + void OnNonMhw2Traffic(int sourcePid, int sectionTableId, byte trafficType); + void OnMhw2Channels(int sourcePid, MediaHighway2ChannelSection channelSection); + void OnMhw2Categories(int sourcePid, MediaHighway2CategorySection categorySection); + void OnMhw2Titles(int sourcePid, MediaHighway2TitleSection titleSection); + void OnMhw2Summary(int sourcePid, MediaHighway2SummarySection summarySection); + } +} diff --git a/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/MediaHighway2/Mhw2Parser.cs b/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/MediaHighway2/Mhw2Parser.cs new file mode 100644 index 0000000..ae9ea75 --- /dev/null +++ b/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/MediaHighway2/Mhw2Parser.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using DVBServices; +using skyscraper5.Mpeg2; + +namespace skyscraper8.EPGCollectorPort.SkyscraperSide.MediaHighway2 +{ + internal class Mhw2Parser : IPsiProcessor + { + private Mhw2EventHandler eventHandler; + + public Mhw2Parser(Mhw2EventHandler eventHandler) + { + this.eventHandler = eventHandler; + } + + public void GatherPsi(PsiSection section, int sourcePid) + { + if (section.TableId == 0xc8) + { + byte[] bytes = section.GetData(); + switch (bytes[3]) + { + case 0: + try + { + MediaHighway2ChannelSection channelSection = MediaHighway2ChannelSection.ProcessMediaHighwayChannelTable(bytes); + eventHandler.OnMhw2Channels(sourcePid, channelSection); + } + catch (NotImplementedException e) + { + Console.WriteLine(e); + throw; + } + return; + case 1: + try + { + MediaHighway2CategorySection categorySection = MediaHighway2CategorySection.ProcessMediaHighwayCategoryTable(bytes); + eventHandler.OnMhw2Categories(sourcePid, categorySection); + } + catch (NotImplementedException e) + { + Console.WriteLine(e); + throw; + } + return; + case 2: + //Unknown + break; + case 3: + //Unknown + break; + case 4: + //Channel Name String Table, uninteresting. + return; + default: + eventHandler.OnNonMhw2Traffic(sourcePid, section.TableId, bytes[3]); + return; + } + } + else if (section.TableId == 0xe6) + { + byte[] bytes = section.GetData(); + try + { + MediaHighway2TitleSection titleSection = MediaHighway2TitleSection.ProcessMediaHighwayTitleTable(bytes); + if (titleSection != null) + { + eventHandler.OnMhw2Titles(sourcePid, titleSection); + } + } + catch (Exception e) + { + } + + + return; + } + else if (section.TableId == 0x96) + { + byte[] bytes = section.GetData(); + MediaHighway2SummarySection summarySection = MediaHighway2SummarySection.ProcessMediaHighwaySummaryTable(bytes); + eventHandler.OnMhw2Summary(sourcePid,summarySection); + } + else + { + eventHandler.OnNonMhw2Traffic(sourcePid, section.TableId, section.GetData()[3]); + } + } + } +} diff --git a/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/MediaHighway2/Mhw2Scraper.cs b/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/MediaHighway2/Mhw2Scraper.cs new file mode 100644 index 0000000..71f6d11 --- /dev/null +++ b/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/MediaHighway2/Mhw2Scraper.cs @@ -0,0 +1,153 @@ +using DVBServices; +using skyscraper5.Skyscraper.Scraper; +using skyscraper8.Skyscraper.Plugins; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper8.EPGCollectorPort.SkyscraperSide.MediaHighway2 +{ + internal class Mhw2Scraper : Mhw2EventHandler + { + private readonly SkyscraperContext _skyscraperContext; + private readonly IMhw2Storage storage; + + public Mhw2Scraper(SkyscraperContext skyscraperContext) + { + _skyscraperContext = skyscraperContext; + storage = new Mhw2StorageImpl(skyscraperContext.DataStorage.GetPluginConnector()); + _logger = PluginLogManager.GetLogger(GetType()); + } + + public void OnNonMhw2Traffic(int sourcePid, int sectionTableId, byte mhwType) + { + } + + private bool hasChannels; + public void OnMhw2Channels(int sourcePid, MediaHighway2ChannelSection channelSection) + { + if (hasChannels) + return; + if (channelSection == null) + return; + if (!_skyscraperContext.CurrentNetworkId.HasValue) + return; + if (!_skyscraperContext.CurrentTransportStreamId.HasValue) + return; + + storage.InsertChannels(_skyscraperContext.CurrentNetworkId.Value, _skyscraperContext.CurrentTransportStreamId.Value, channelSection); + _logger.Log(PluginLogLevel.Info, "Found {0} channels", channelSection.Channels.Count); + hasChannels = true; + } + + private bool hasCategories; + public void OnMhw2Categories(int sourcePid, MediaHighway2CategorySection categorySection) + { + if (hasCategories) + return; + if (!_skyscraperContext.CurrentNetworkId.HasValue) + return; + if (!_skyscraperContext.CurrentTransportStreamId.HasValue) + return; + + storage.InsertCategories(_skyscraperContext.CurrentNetworkId.Value, _skyscraperContext.CurrentTransportStreamId.Value, categorySection); + _logger.Log(PluginLogLevel.Info, "Found {0} categories", categorySection.Categories.Count); + hasCategories = true; + } + + public void OnMhw2Titles(int sourcePid, MediaHighway2TitleSection titleSection) + { + if (_binders == null) + _binders = new Dictionary(); + + foreach (MediaHighway2TitleData titleData in titleSection.Titles) + { + Mhw2EventBinder currentBinder; + if (_binders.ContainsKey(titleData.EventID)) + { + currentBinder = _binders[titleData.EventID]; + } + else + { + currentBinder = new Mhw2EventBinder(titleData.EventID); + _binders.Add(titleData.EventID, currentBinder); + } + + if (!currentBinder.TitleDataSeen) + { + currentBinder.EventName = titleData.EventName; + currentBinder.CategoryID = titleData.CategoryID; + currentBinder.ChannelID = titleData.ChannelID; + currentBinder.Duration = titleData.Duration; + currentBinder.MainCategory = titleData.MainCategory; + currentBinder.StartTime = titleData.StartTime; + currentBinder.SubCategory = titleData.SubCategory; + currentBinder.TitleDataSeen = true; + } + + if (IsBinderComplete(currentBinder)) + { + StoreBinder(currentBinder); + _binders.Remove(titleData.EventID); + } + } + } + + public void OnMhw2Summary(int sourcePid, MediaHighway2SummarySection summarySection) + { + if (_binders == null) + _binders = new Dictionary(); + + Mhw2EventBinder currentBinder; + if (_binders.ContainsKey(summarySection.SummaryData.EventID)) + { + currentBinder = _binders[summarySection.SummaryData.EventID]; + } + else + { + currentBinder = new Mhw2EventBinder(summarySection.SummaryData.EventID); + _binders.Add(summarySection.SummaryData.EventID, currentBinder); + } + + currentBinder.ShortDescription = summarySection.SummaryData.ShortDescription; + currentBinder.ShortDescriptionSeen = true; + if (IsBinderComplete(currentBinder)) + { + StoreBinder(currentBinder); + _binders.Remove(summarySection.SummaryData.EventID); + } + } + + private Dictionary _binders; + private readonly PluginLogger _logger; + + + private bool IsBinderComplete(Mhw2EventBinder binder) + { + if (!_skyscraperContext.CurrentNetworkId.HasValue) + return false; + if (!_skyscraperContext.CurrentTransportStreamId.HasValue) + return false; + if (!hasCategories) + return false; + if (!hasChannels) + return false; + if (!binder.ShortDescriptionSeen) + return false; + if (!binder.TitleDataSeen) + return false; + return true; + } + + private void StoreBinder(Mhw2EventBinder binder) + { + if (!storage.TestForEvent(_skyscraperContext.CurrentNetworkId.Value, _skyscraperContext.CurrentTransportStreamId.Value, binder)) + { + _logger.Log(PluginLogLevel.Info, "Event: {0}", binder.EventName); + storage.InsertEvent(_skyscraperContext.CurrentNetworkId.Value, _skyscraperContext.CurrentTransportStreamId.Value, binder); + } + } + } +} diff --git a/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/MediaHighway2/Mhw2Storage.cs b/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/MediaHighway2/Mhw2Storage.cs new file mode 100644 index 0000000..0a0789a --- /dev/null +++ b/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/SkyscraperSide/MediaHighway2/Mhw2Storage.cs @@ -0,0 +1,297 @@ +using DVBServices; +using skyscraper5.Data.PostgreSql; +using skyscraper5.Skyscraper.Scraper.Storage.InMemory; +using skyscraper8.EPGCollectorPort.SkyscraperSide.Freesat; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Npgsql; +using NpgsqlTypes; + +namespace skyscraper8.EPGCollectorPort.SkyscraperSide.MediaHighway2 +{ + internal interface IMhw2Storage + { + void InsertCategories(int networkId, int transportStreamId, MediaHighway2CategorySection categorySection); + void InsertChannels(int networkId, int transportStreamId, MediaHighway2ChannelSection channelSection); + bool TestForEvent(int networkId, int transportStreamId, Mhw2EventBinder binder); + void InsertEvent(int networkId, int transportStreamId, Mhw2EventBinder binder); + } + internal class Mhw2StorageImpl : IMhw2Storage + { + private readonly IMhw2Storage _storageEngine; + + public Mhw2StorageImpl(object[] getPluginConnector) + { + object o = getPluginConnector[0]; + switch (o) + { + case InMemoryPluginToken t1: + _storageEngine = new Mhw2StorageInMemory(); + break; + case PostgresqlToken t2: + _storageEngine = new Mhw2StoragePostgresql(t2); + break; + default: + throw new NotImplementedException(o.GetType().FullName); + } + } + + public void InsertCategories(int networkId, int transportStreamId, MediaHighway2CategorySection categorySection) + { + _storageEngine.InsertCategories(networkId, transportStreamId, categorySection); + } + + public void InsertChannels(int networkId, int transportStreamId, MediaHighway2ChannelSection channelSection) + { + _storageEngine.InsertChannels(networkId, transportStreamId, channelSection); + } + + public bool TestForEvent(int networkId, int transportStreamId, Mhw2EventBinder binder) + { + return _storageEngine.TestForEvent(networkId, transportStreamId, binder); + } + + public void InsertEvent(int networkId, int transportStreamId, Mhw2EventBinder binder) + { + _storageEngine.InsertEvent(networkId, transportStreamId, binder); + } + } + + internal class Mhw2StorageInMemory : IMhw2Storage + { + private Dictionary, MediaHighway2CategorySection> _categories; + public void InsertCategories(int networkId, int transportStreamId, MediaHighway2CategorySection categorySection) + { + if (_categories == null) + _categories = new Dictionary, MediaHighway2CategorySection>(); + + Tuple x = new Tuple(networkId, transportStreamId); + if (!_categories.ContainsKey(x)) + { + _categories.Add(x, categorySection); + } + } + + private Dictionary, MediaHighway2ChannelSection> _channels; + public void InsertChannels(int networkId, int transportStreamId, MediaHighway2ChannelSection channelSection) + { + if (_channels == null) + _channels = new Dictionary, MediaHighway2ChannelSection>(); + + Tuple x = new Tuple(networkId, transportStreamId); + if (!_categories.ContainsKey(x)) + { + _channels.Add(x, channelSection); + } + } + + + private Dictionary, Mhw2EventBinder> _events; + public bool TestForEvent(int networkId, int transportStreamId, Mhw2EventBinder binder) + { + if (_events == null) + return false; + + Tuple coordinates = new Tuple(networkId, transportStreamId, binder.EventId, binder.StartTime); + return _events.ContainsKey(coordinates); + } + + public void InsertEvent(int networkId, int transportStreamId, Mhw2EventBinder binder) + { + if (_events == null) + _events = new Dictionary, Mhw2EventBinder>(); + + + Tuple coordinates = new Tuple(networkId, transportStreamId, binder.EventId, binder.StartTime); + if (!_events.ContainsKey(coordinates)) + { + _events.Add(coordinates, binder); + } + } + + } + + internal class Mhw2StoragePostgresql : IMhw2Storage + { + private readonly PostgresqlToken _postgresqlToken; + + public Mhw2StoragePostgresql(PostgresqlToken postgresqlToken) + { + _postgresqlToken = postgresqlToken; + } + + private HashSet> _knownCategories; + public void InsertCategories(int networkId, int transportStreamId, MediaHighway2CategorySection categorySection) + { + if (_knownCategories == null) + _knownCategories = new HashSet>(); + + foreach (MediaHighwayCategoryEntry category in categorySection.Categories) + { + Tuple coordinate = new Tuple(networkId, transportStreamId, category.Number); + if (!_knownCategories.Contains(coordinate)) + { + _postgresqlToken.EnqueueTask(x => InsertCategory(x,networkId, transportStreamId, category)); + _knownCategories.Add(coordinate); + } + } + } + + private HashSet> _knownChannels; + public void InsertChannels(int networkId, int transportStreamId, MediaHighway2ChannelSection channelSection) + { + if (channelSection == null) + return; + + if (_knownChannels == null) + _knownChannels = new HashSet>(); + + foreach (MediaHighwayChannelInfoEntry channel in channelSection.Channels) + { + Tuple coordinate = new Tuple(networkId, transportStreamId, channel.OriginalNetworkID, channel.TransportStreamID, channel.ServiceID); + if (!_knownChannels.Contains(coordinate)) + { + _postgresqlToken.EnqueueTask(x => InsertChannel(x, networkId, transportStreamId, channel)); + _knownChannels.Add(coordinate); + } + } + } + + private HashSet> _knownEvents; + + public bool TestForEvent(int networkId, int transportStreamId, Mhw2EventBinder binder) + { + if (_knownEvents == null) + _knownEvents = new HashSet>(); + + Tuple coordinates = new Tuple(networkId, transportStreamId, binder.EventId, binder.StartTime); + if (_knownEvents.Contains(coordinates)) + return true; + + NpgsqlConnection connection = _postgresqlToken.GetConnection(); + connection.Open(); + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "SELECT dateadded FROM mediahighway2_events WHERE nid = @nid AND tsid = @tsid AND eid = @eid AND start_time = @stime"; + command.Parameters.AddWithValue("@nid", NpgsqlDbType.Integer, networkId); + command.Parameters.AddWithValue("@tsid", NpgsqlDbType.Integer, transportStreamId); + command.Parameters.AddWithValue("@eid", NpgsqlDbType.Integer, binder.EventId); + command.Parameters.AddWithValue("@stime", NpgsqlDbType.Timestamp, binder.StartTime); + NpgsqlDataReader dataReader = command.ExecuteReader(); + bool result = dataReader.Read(); + if (result) + { + _knownEvents.Add(coordinates); + } + dataReader.Close(); + command.Dispose(); + connection.Close(); + return result; + } + + public void InsertEvent(int networkId, int transportStreamId, Mhw2EventBinder binder) + { + _postgresqlToken.EnqueueTask(x => InsertEventEx(x, networkId, transportStreamId, binder)); + Tuple coordinates = new Tuple(networkId, transportStreamId, binder.EventId, binder.StartTime); + _knownEvents.Add(coordinates); + } + + private void InsertEventEx(NpgsqlConnection connection, int networkId, int transportStreamId, Mhw2EventBinder binder) + { + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = + "INSERT INTO mediahighway2_events VALUES (@nid,@tsid,@eid,@stime,DEFAULT,@ename,@category,@channel,@duration,@mcategory,@scategory,@description)"; + command.Parameters.AddWithValue("@nid", NpgsqlDbType.Integer, networkId); + command.Parameters.AddWithValue("@tsid", NpgsqlDbType.Integer, transportStreamId); + command.Parameters.AddWithValue("@eid", NpgsqlDbType.Integer, binder.EventId); + command.Parameters.AddWithValue("@stime", NpgsqlDbType.Timestamp, binder.StartTime); + command.Parameters.AddWithValue("@ename", NpgsqlDbType.Text, SanitizeString(binder.EventName)); + command.Parameters.AddWithValue("@category", NpgsqlDbType.Integer, binder.CategoryID); + command.Parameters.AddWithValue("@channel", NpgsqlDbType.Integer, binder.ChannelID); + command.Parameters.AddWithValue("@duration", NpgsqlDbType.Integer, Convert.ToInt32(binder.Duration.TotalSeconds)); + command.Parameters.AddWithValue("@mcategory", NpgsqlDbType.Integer, binder.MainCategory); + command.Parameters.AddWithValue("@scategory", NpgsqlDbType.Integer, binder.SubCategory); + command.Parameters.AddWithValue("@description", NpgsqlDbType.Text, binder.ShortDescription); + command.ExecuteNonQuery(); + } + + private string SanitizeString(string ename) + { + if (ename.Contains("\0")) + { + int indexOf = ename.IndexOf("\0"); + ename = ename.Substring(0, indexOf); + return ename; + } + + return ename; + } + + + private void InsertChannel(NpgsqlConnection connection, int networkId, int transportStreamId, MediaHighwayChannelInfoEntry channel) + { + if (TestChannel(connection, networkId, transportStreamId, channel)) + { + return; + } + + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "INSERT INTO mediahighway2_channels VALUES (@snid,@stsid,@tonid,@ttsid,@tsid,@name,DEFAULT)"; + command.Parameters.AddWithValue("@snid", NpgsqlDbType.Integer, networkId); + command.Parameters.AddWithValue("@stsid", NpgsqlDbType.Integer, transportStreamId); + command.Parameters.AddWithValue("@tonid", NpgsqlDbType.Integer, channel.OriginalNetworkID); + command.Parameters.AddWithValue("@ttsid", NpgsqlDbType.Integer, channel.TransportStreamID); + command.Parameters.AddWithValue("@tsid", NpgsqlDbType.Integer, channel.ServiceID); + command.Parameters.AddWithValue("@name", NpgsqlDbType.Text, channel.Name); + command.ExecuteNonQuery(); + } + + private bool TestChannel(NpgsqlConnection connection, int networkId, int transportStreamId, MediaHighwayChannelInfoEntry channel) + { + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "SELECT dateadded FROM mediahighway2_channels WHERE src_nid = @snid AND src_tsid = @stsid AND tgt_onid = @tonid AND tgt_tsid = @ttsid AND tgt_sid = @tsid"; + command.Parameters.AddWithValue("@snid", NpgsqlDbType.Integer, networkId); + command.Parameters.AddWithValue("@stsid", NpgsqlDbType.Integer, transportStreamId); + command.Parameters.AddWithValue("@tonid", NpgsqlDbType.Integer, channel.OriginalNetworkID); + command.Parameters.AddWithValue("@ttsid", NpgsqlDbType.Integer, channel.TransportStreamID); + command.Parameters.AddWithValue("@tsid", NpgsqlDbType.Integer, channel.ServiceID); + NpgsqlDataReader dataReader = command.ExecuteReader(); + bool result = dataReader.Read(); + dataReader.Close(); + command.Dispose(); + return result; + } + + private void InsertCategory(NpgsqlConnection connection, int networkId, int transportStreamId, MediaHighwayCategoryEntry category) + { + if (TestCategory(connection, networkId, transportStreamId, category)) + { + return; + } + + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "INSERT INTO mediahighway2_categories VALUES (@nid,@tsid,@number,@description,DEFAULT)"; + command.Parameters.AddWithValue("@nid", NpgsqlDbType.Integer, networkId); + command.Parameters.AddWithValue("@tsid", NpgsqlDbType.Integer, transportStreamId); + command.Parameters.AddWithValue("@number", NpgsqlDbType.Integer, category.Number); + command.Parameters.AddWithValue("@description", NpgsqlDbType.Text, category.Description); + command.ExecuteNonQuery(); + } + + private bool TestCategory(NpgsqlConnection connection, int networkId, int transportStreamId, MediaHighwayCategoryEntry category) + { + NpgsqlCommand command = connection.CreateCommand(); + command.CommandText = "SELECT dateadded FROM mediahighway2_categories WHERE nid = @nid AND tsid = @tsid AND number = @number"; + command.Parameters.AddWithValue("@nid", NpgsqlDbType.Integer, networkId); + command.Parameters.AddWithValue("@tsid", NpgsqlDbType.Integer, transportStreamId); + command.Parameters.AddWithValue("@number", NpgsqlDbType.Integer, category.Number); + NpgsqlDataReader dataReader = command.ExecuteReader(); + bool result = dataReader.Read(); + dataReader.Close(); + command.Dispose(); + return result; + } + } +} diff --git a/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/skyscraper8.EPGCollectorPort.csproj b/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/skyscraper8.EPGCollectorPort.csproj index 12072fa..2949469 100644 --- a/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/skyscraper8.EPGCollectorPort.csproj +++ b/PrivateDataSpecifiers/skyscraper8.EPGCollectorPort/skyscraper8.EPGCollectorPort.csproj @@ -7,6 +7,7 @@ + diff --git a/skyscraper8/Program.cs b/skyscraper8/Program.cs index cfcbc90..d661098 100644 --- a/skyscraper8/Program.cs +++ b/skyscraper8/Program.cs @@ -28,6 +28,7 @@ using skyscraper5.T2MI; using skyscraper5.src.Mpeg2.PacketFilter; using skyscraper8.Skyscraper.IO; using log4net; +using skyscraper5.Skyscraper.Scraper.StreamAutodetection; using skyscraper8.Skyscraper.Scraper.Storage; [assembly: log4net.Config.XmlConfigurator(ConfigFile = "log4net.config")] @@ -37,9 +38,15 @@ namespace skyscraper5 { private static readonly ILog logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name); + private static void IntegrationTest() + { + } + static void Main(string[] args) - { - logger.Info("Hello!"); + { + IntegrationTest(); + + logger.Info("Hello!"); ffmpegFrameGrabber.CanStart(); logger.DebugFormat("Found {0}-bit Operating system.", Environment.Is64BitOperatingSystem ? 64 : 32); logger.DebugFormat("I'm a {0}-bit Process.", Environment.Is64BitProcess ? 64 : 32); diff --git a/skyscraper8/Properties/launchSettings.json b/skyscraper8/Properties/launchSettings.json index 0124982..412f243 100644 --- a/skyscraper8/Properties/launchSettings.json +++ b/skyscraper8/Properties/launchSettings.json @@ -2,7 +2,7 @@ "profiles": { "skyscraper8": { "commandName": "Project", - "commandLineArgs": "\"Z:\\Freebies\\Datasets\\SkyscraperLibrarian\\DVB-S August 2024\\Astra28\\skyscraper_20240829_0651_0283E_10714_H_21999.ts\"", + "commandLineArgs": "cscan-live tcp://tetsuro:6969", "remoteDebugEnabled": false }, "Container (Dockerfile)": { diff --git a/skyscraper8/Skyscraper/Plugins/ExperimentalPlugin.cs b/skyscraper8/Skyscraper/Plugins/ExperimentalPlugin.cs new file mode 100644 index 0000000..f9a6baa --- /dev/null +++ b/skyscraper8/Skyscraper/Plugins/ExperimentalPlugin.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper8.Skyscraper.Plugins +{ + [AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = false)] + public sealed class ExperimentalAttribute : Attribute + { + public ExperimentalAttribute() + { + + } + } +} diff --git a/skyscraper8/Skyscraper/Plugins/PluginAppender.cs b/skyscraper8/Skyscraper/Plugins/PluginAppender.cs index b810317..2b5105e 100644 --- a/skyscraper8/Skyscraper/Plugins/PluginAppender.cs +++ b/skyscraper8/Skyscraper/Plugins/PluginAppender.cs @@ -16,10 +16,13 @@ namespace skyscraper8.Skyscraper.Plugins private PluginAppenderEx _finalOutput; protected override void Append(LoggingEvent loggingEvent) { + object? messageObject = loggingEvent.MessageObject; + if (messageObject == null) + messageObject = ""; PluginLogLevel mappedLevel = MapLevel(loggingEvent.Level); DateTime mappedTime = DateTime.Now; string mappedLoggerName = loggingEvent.LoggerName; - string mappedMessage = loggingEvent.MessageObject.ToString(); + string mappedMessage = messageObject.ToString(); _finalOutput.Log(mappedTime, mappedLevel, mappedLoggerName, mappedMessage); } diff --git a/skyscraper8/Skyscraper/Scraper/SkyscraperContext.cs b/skyscraper8/Skyscraper/Scraper/SkyscraperContext.cs index 9e51ef1..a2818cd 100644 --- a/skyscraper8/Skyscraper/Scraper/SkyscraperContext.cs +++ b/skyscraper8/Skyscraper/Scraper/SkyscraperContext.cs @@ -2698,5 +2698,10 @@ namespace skyscraper5.Skyscraper.Scraper return _pluginContexts; } } + + public void OnPluginEvent() + { + lastEventTimestamp = DateTime.Now; + } } } diff --git a/skyscraper8/Skyscraper/Scraper/Storage/NullObjectStorage.cs b/skyscraper8/Skyscraper/Scraper/Storage/NullObjectStorage.cs new file mode 100644 index 0000000..9cee83b --- /dev/null +++ b/skyscraper8/Skyscraper/Scraper/Storage/NullObjectStorage.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Dvb.DataBroadcasting.SkyscraperVfs; +using skyscraper8.DvbNip; +using skyscraper8.Ietf.FLUTE; + +namespace skyscraper8.Skyscraper.Scraper.Storage +{ + internal class NullObjectStorage : ObjectStorage + { + public bool ObjectCarouselFileArrival(VfsFile vfsFile, int transportStreamId, int networkId) + { + throw new NotImplementedException(); + } + + public void DataCarouselModuleArrival(int currentNetworkId, int currentTransportStreamId, int elementaryPid, + ushort moduleModuleId, byte moduleModuleVersion, Stream result) + { + throw new NotImplementedException(); + } + + public bool IsDsmCcModuleWanted(int currentNetworkId, int currentTransportStreamId, int elementaryPid, ushort moduleId, + byte moduleVersion) + { + return false; + } + + public bool TestForFramegrab(int currentNetworkId, int transportStreamId, ushort mappingProgramNumber, + int mappingStreamElementaryPid) + { + throw new NotImplementedException(); + } + + public void StoreFramegrab(int currentNetworkId, int transportStreamId, ushort mappingProgramNumber, ushort pid, + byte[] imageData) + { + throw new NotImplementedException(); + } + + public void WaitForCompletion() + { + throw new NotImplementedException(); + } + + public void UiSetVersion(int version) + { + throw new NotImplementedException(); + } + + public object[] GetPluginConnector() + { + throw new NotImplementedException(); + } + + public void Ping() + { + throw new NotImplementedException(); + } + + public bool DvbNipTestForFile(string announcedFileContentLocation) + { + throw new NotImplementedException(); + } + + public void DvbNipFileArrival(NipActualCarrierInformation carrier, FluteListener listener) + { + throw new NotImplementedException(); + } + } +} diff --git a/skyscraper8/Skyscraper/Scraper/StreamAutodetection/StreamTypeAutodetection.cs b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/StreamTypeAutodetection.cs index b4524fb..2020761 100644 --- a/skyscraper8/Skyscraper/Scraper/StreamAutodetection/StreamTypeAutodetection.cs +++ b/skyscraper8/Skyscraper/Scraper/StreamAutodetection/StreamTypeAutodetection.cs @@ -167,7 +167,7 @@ namespace skyscraper5.Skyscraper.Scraper.StreamAutodetection if (Scoreboard[i].Score <= -10) { //Console.WriteLine("PID 0x{0:X4} is probably not for {1}", Pid, Scoreboard[i].GetType().Name); - EventHandler.AutodetectionRuleOut(Pid, Scoreboard[i], ProgramContext); + //EventHandler.AutodetectionRuleOut(Pid, Scoreboard[i], ProgramContext); Scoreboard[i] = new DisqualifiedContestant(Scoreboard[i].Pid); } } diff --git a/skyscraper8/Skyscraper/Scraper/Utils/PacketDiscarder.cs b/skyscraper8/Skyscraper/Scraper/Utils/PacketDiscarder.cs index 4986636..240eb64 100644 --- a/skyscraper8/Skyscraper/Scraper/Utils/PacketDiscarder.cs +++ b/skyscraper8/Skyscraper/Scraper/Utils/PacketDiscarder.cs @@ -2,7 +2,7 @@ namespace skyscraper5.Skyscraper.Scraper.Utils { - class PacketDiscarder : ITsPacketProcessor + public class PacketDiscarder : ITsPacketProcessor { public void PushPacket(TsPacket packet) { diff --git a/skyscraper8/Skyscraper/Text/AsciiTable.cs b/skyscraper8/Skyscraper/Text/AsciiTable.cs index 3e4e104..cf81753 100644 --- a/skyscraper8/Skyscraper/Text/AsciiTable.cs +++ b/skyscraper8/Skyscraper/Text/AsciiTable.cs @@ -14,6 +14,7 @@ namespace skyscraper5.Skyscraper.Text { case 0x00: resultBuilder.Append('\0'); break; //Null case 0x01: break; //Start of Header? + case 0x02: break; //Start of text case 0x03: break; //End of text case 0x04: break; //End of transmission? Sure doesn't look like it. case 0x05: break; //Enquiry? @@ -25,6 +26,7 @@ namespace skyscraper5.Skyscraper.Text case 0x0b: resultBuilder.Append('\v'); break; //Vertical Tab case 0x0c: resultBuilder.Append('\f'); break; //Form Feed case 0x0d: resultBuilder.Append('\r'); break; //Carriage Return + case 0x0e: break; //Shift Out? case 0x0f: break; //Shift in? case 0x10: break; //Data Link Escape? case 0x11: break; // XON / XOFF diff --git a/skyscraper8/Skyscraper/Text/Encodings/dvb-big5.cs b/skyscraper8/Skyscraper/Text/Encodings/dvb-big5.cs new file mode 100644 index 0000000..d6326f9 --- /dev/null +++ b/skyscraper8/Skyscraper/Text/Encodings/dvb-big5.cs @@ -0,0 +1,33 @@ +using skyscraper5.Skyscraper.Text; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; + +namespace skyscraper8.Skyscraper.Text.Encodings +{ + [SkyscraperEncoding("dvb-big5")] + internal class dvb_big5 : SkyscraperBaseEncoding16 + { + protected override char[] Decrypt(byte[] preprocessed) + { + StringBuilder sb = new StringBuilder(); + MemoryStream ms = new MemoryStream(preprocessed, false); + while (ms.Position < ms.Length) + { + byte leadByte = ms.ReadUInt8(); + if (leadByte <= 0x7e) + { + AsciiTable.GetAsciiChar(leadByte, sb); + continue; + } + + throw new NotImplementedException(); + } + + return sb.ToString().ToCharArray(); + } + } +} diff --git a/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-10.cs b/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-10.cs index 07e820b..64f719f 100644 --- a/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-10.cs +++ b/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-10.cs @@ -6,8 +6,8 @@ using System.Threading.Tasks; namespace skyscraper5.Skyscraper.Text.Encodings { - [SkyscraperEncoding("dvb-iso-8859-10")] - class dvb_iso_8859_10 : SkyscraperBaseEncoding8 + [SkyscraperEncoding("dvb-iso-8859-11")] + class dvb_iso_8859_11 : SkyscraperBaseEncoding8 { protected override char[] Decrypt(byte[] preprocessed) { @@ -21,60 +21,7 @@ namespace skyscraper5.Skyscraper.Text.Encodings } switch (preprocessed[i]) { - case 0xa0: resultBuilder.Append('\u00a0'); break; - case 0xa9: resultBuilder.Append('Đ'); break; - case 0xaa: resultBuilder.Append('Š'); break; - case 0xac: resultBuilder.Append('Ž'); break; - case 0xae: resultBuilder.Append('Ū'); break; - case 0xb0: resultBuilder.Append('°'); break; - case 0xb1: resultBuilder.Append('ą'); break; - case 0xb9: resultBuilder.Append('đ'); break; - case 0xba: resultBuilder.Append('š'); break; - case 0xbc: resultBuilder.Append('ž'); break; - case 0xbe: resultBuilder.Append('ū'); break; - case 0xc1: resultBuilder.Append('Á'); break; - case 0xc2: resultBuilder.Append('Â'); break; - case 0xc4: resultBuilder.Append('Ä'); break; - case 0xc5: resultBuilder.Append('Å'); break; - case 0xc6: resultBuilder.Append('Æ'); break; - case 0xc8: resultBuilder.Append('Č'); break; - case 0xc9: resultBuilder.Append('É'); break; - case 0xcd: resultBuilder.Append('Í'); break; - case 0xcf: resultBuilder.Append('Ï'); break; - case 0xd0: resultBuilder.Append('Ð'); break; - case 0xd3: resultBuilder.Append('Ó'); break; - case 0xd6: resultBuilder.Append('Ö'); break; - case 0xda: resultBuilder.Append('Ú'); break; - case 0xdc: resultBuilder.Append('Ü'); break; - case 0xdd: resultBuilder.Append('Ý'); break; - case 0xdf: resultBuilder.Append('ß'); break; - case 0xe0: resultBuilder.Append('ā'); break; - case 0xe1: resultBuilder.Append('á'); break; - case 0xe2: resultBuilder.Append('â'); break; - case 0xe3: resultBuilder.Append('ã'); break; - case 0xe4: resultBuilder.Append('ä'); break; - case 0xe5: resultBuilder.Append('å'); break; - case 0xe6: resultBuilder.Append('æ'); break; - case 0xe8: resultBuilder.Append('č'); break; - case 0xe9: resultBuilder.Append('é'); break; - case 0xea: resultBuilder.Append('ę'); break; - case 0xeb: resultBuilder.Append('ë'); break; - case 0xed: resultBuilder.Append('í'); break; - case 0xee: resultBuilder.Append('î'); break; - case 0xef: resultBuilder.Append('ï'); break; - case 0xf0: resultBuilder.Append('ð'); break; - case 0xf1: resultBuilder.Append('ņ'); break; - case 0xf2: resultBuilder.Append('ō'); break; - case 0xf3: resultBuilder.Append('ó'); break; - case 0xf4: resultBuilder.Append('ô'); break; - case 0xf5: resultBuilder.Append('õ'); break; - case 0xf6: resultBuilder.Append('ö'); break; - case 0xf8: resultBuilder.Append('ø'); break; - case 0xfa: resultBuilder.Append('ú'); break; - case 0xfb: resultBuilder.Append('û'); break; - case 0xfd: resultBuilder.Append('ý'); break; - case 0xfc: resultBuilder.Append('ü'); break; - default: + default: throw new NotImplementedException(String.Format("0x{0:X2}", preprocessed[i])); } } diff --git a/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-11.cs b/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-11.cs new file mode 100644 index 0000000..5828aa5 --- /dev/null +++ b/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-11.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper5.Skyscraper.Text.Encodings +{ + [SkyscraperEncoding("dvb-iso-8859-10")] + class dvb_iso_8859_10 : SkyscraperBaseEncoding8 + { + protected override char[] Decrypt(byte[] preprocessed) + { + StringBuilder resultBuilder = new StringBuilder(); + for (int i = 0; i < preprocessed.Length; i++) + { + if (preprocessed[i] <= 0x7e) + { + AsciiTable.GetAsciiChar(preprocessed[i], resultBuilder); + continue; + } + switch (preprocessed[i]) + { + case 0x7f: break; + case 0xa0: resultBuilder.Append('\u00a0'); break; + case 0xa9: resultBuilder.Append('Đ'); break; + case 0xaa: resultBuilder.Append('Š'); break; + case 0xac: resultBuilder.Append('Ž'); break; + case 0xae: resultBuilder.Append('Ū'); break; + case 0xb0: resultBuilder.Append('°'); break; + case 0xb1: resultBuilder.Append('ą'); break; + case 0xb9: resultBuilder.Append('đ'); break; + case 0xba: resultBuilder.Append('š'); break; + case 0xbc: resultBuilder.Append('ž'); break; + case 0xbe: resultBuilder.Append('ū'); break; + case 0xc1: resultBuilder.Append('Á'); break; + case 0xc2: resultBuilder.Append('Â'); break; + case 0xc4: resultBuilder.Append('Ä'); break; + case 0xc5: resultBuilder.Append('Å'); break; + case 0xc6: resultBuilder.Append('Æ'); break; + case 0xc8: resultBuilder.Append('Č'); break; + case 0xc9: resultBuilder.Append('É'); break; + case 0xcd: resultBuilder.Append('Í'); break; + case 0xcf: resultBuilder.Append('Ï'); break; + case 0xd0: resultBuilder.Append('Ð'); break; + case 0xd3: resultBuilder.Append('Ó'); break; + case 0xd6: resultBuilder.Append('Ö'); break; + case 0xda: resultBuilder.Append('Ú'); break; + case 0xdc: resultBuilder.Append('Ü'); break; + case 0xdd: resultBuilder.Append('Ý'); break; + case 0xdf: resultBuilder.Append('ß'); break; + case 0xe0: resultBuilder.Append('ā'); break; + case 0xe1: resultBuilder.Append('á'); break; + case 0xe2: resultBuilder.Append('â'); break; + case 0xe3: resultBuilder.Append('ã'); break; + case 0xe4: resultBuilder.Append('ä'); break; + case 0xe5: resultBuilder.Append('å'); break; + case 0xe6: resultBuilder.Append('æ'); break; + case 0xe8: resultBuilder.Append('č'); break; + case 0xe9: resultBuilder.Append('é'); break; + case 0xea: resultBuilder.Append('ę'); break; + case 0xeb: resultBuilder.Append('ë'); break; + case 0xed: resultBuilder.Append('í'); break; + case 0xee: resultBuilder.Append('î'); break; + case 0xef: resultBuilder.Append('ï'); break; + case 0xf0: resultBuilder.Append('ð'); break; + case 0xf1: resultBuilder.Append('ņ'); break; + case 0xf2: resultBuilder.Append('ō'); break; + case 0xf3: resultBuilder.Append('ó'); break; + case 0xf4: resultBuilder.Append('ô'); break; + case 0xf5: resultBuilder.Append('õ'); break; + case 0xf6: resultBuilder.Append('ö'); break; + case 0xf8: resultBuilder.Append('ø'); break; + case 0xfa: resultBuilder.Append('ú'); break; + case 0xfb: resultBuilder.Append('û'); break; + case 0xfd: resultBuilder.Append('ý'); break; + case 0xfc: resultBuilder.Append('ü'); break; + default: + throw new NotImplementedException(String.Format("0x{0:X2}", preprocessed[i])); + } + } + + return resultBuilder.ToString().ToCharArray(); + } + } +} diff --git a/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-13.cs b/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-13.cs index b065f4e..2a40135 100644 --- a/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-13.cs +++ b/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-13.cs @@ -25,21 +25,29 @@ namespace skyscraper5.Skyscraper.Text.Encodings case 0xa1: resultBuilder.Append('”'); break; case 0xa3: resultBuilder.Append('£'); break; case 0xa5: resultBuilder.Append('„'); break; + case 0xa7: resultBuilder.Append('§'); break; case 0xa8: resultBuilder.Append('Ø'); break; case 0xab: resultBuilder.Append('«'); break; + case 0xac: resultBuilder.Append('¬'); break; case 0xad: resultBuilder.Append('\u00ad'); break; case 0xae: resultBuilder.Append('®'); break; case 0xb0: resultBuilder.Append('°'); break; case 0xb4: resultBuilder.Append('“'); break; case 0xb8: resultBuilder.Append('ø'); break; + case 0xb9: resultBuilder.Append('¹'); break; case 0xbb: resultBuilder.Append('»'); break; case 0xbf: resultBuilder.Append('æ'); break; case 0xc3: resultBuilder.Append('Ć'); break; case 0xc4: resultBuilder.Append('Ä'); break; case 0xc5: resultBuilder.Append('Å'); break; + case 0xc7: resultBuilder.Append('Ē'); break; case 0xc8: resultBuilder.Append('Č'); break; case 0xc9: resultBuilder.Append('É'); break; + case 0xca: resultBuilder.Append('Ź'); break; + case 0xce: resultBuilder.Append('Ī'); break; + case 0xcf: resultBuilder.Append('Ļ'); break; case 0xd0: resultBuilder.Append('Š'); break; + case 0xd1: resultBuilder.Append('Ń'); break; case 0xd3: resultBuilder.Append('Ó'); break; case 0xd6: resultBuilder.Append('Ö'); break; case 0xd9: resultBuilder.Append('Ł'); break; @@ -58,6 +66,7 @@ namespace skyscraper5.Skyscraper.Text.Encodings case 0xe9: resultBuilder.Append('é'); break; case 0xea: resultBuilder.Append('ź'); break; case 0xeb: resultBuilder.Append('ė'); break; + case 0xed: resultBuilder.Append('ķ'); break; case 0xee: resultBuilder.Append('ī'); break; case 0xf0: resultBuilder.Append('š'); break; case 0xf1: resultBuilder.Append('ń'); break; diff --git a/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-14.cs b/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-14.cs index 6397fc7..21707ed 100644 --- a/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-14.cs +++ b/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-14.cs @@ -22,20 +22,38 @@ namespace skyscraper5.Skyscraper.Text.Encodings switch (preprocessed[i]) { case 0xa0: resultBuilder.Append('\u00a0'); break; + case 0xa1: resultBuilder.Append('Ḃ'); break; + case 0xaf: resultBuilder.Append('Ÿ'); break; + case 0xa3: resultBuilder.Append('£'); break; + case 0xb4: resultBuilder.Append('Ṁ'); break; + case 0xb5: resultBuilder.Append('ṁ'); break; + case 0xb7: resultBuilder.Append('Ṗ'); break; + case 0xb8: resultBuilder.Append('ẁ'); break; + case 0xba: resultBuilder.Append('ẃ'); break; + case 0xbd: resultBuilder.Append('Ẅ'); break; case 0xc1: resultBuilder.Append('Á'); break; case 0xc2: resultBuilder.Append('Â'); break; case 0xc3: resultBuilder.Append('Ã'); break; case 0xc7: resultBuilder.Append('Ç'); break; case 0xc8: resultBuilder.Append('È'); break; + case 0xcb: resultBuilder.Append('Ë'); break; case 0xcf: resultBuilder.Append('Ï'); break; + case 0xd2: resultBuilder.Append('Ò'); break; case 0xd6: resultBuilder.Append('Ö'); break; + case 0xd8: resultBuilder.Append('Ø'); break; + case 0xd9: resultBuilder.Append('Ù'); break; + case 0xda: resultBuilder.Append('Ú'); break; + case 0xe0: resultBuilder.Append('à'); break; case 0xe1: resultBuilder.Append('á'); break; + case 0xe3: resultBuilder.Append('ã'); break; case 0xe4: resultBuilder.Append('ä'); break; case 0xe7: resultBuilder.Append('ç'); break; case 0xe8: resultBuilder.Append('è'); break; case 0xe9: resultBuilder.Append('é'); break; case 0xea: resultBuilder.Append('ê'); break; case 0xed: resultBuilder.Append('í'); break; + case 0xf0: resultBuilder.Append('ŵ'); break; + case 0xf2: resultBuilder.Append('ò'); break; case 0xf3: resultBuilder.Append('ó'); break; case 0xfa: resultBuilder.Append('ú'); break; case 0xfc: resultBuilder.Append('ü'); break; diff --git a/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-15.cs b/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-15.cs index 162eda4..11ea1ee 100644 --- a/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-15.cs +++ b/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-15.cs @@ -92,6 +92,7 @@ namespace skyscraper5.Skyscraper.Text.Encodings case 0xf4: resultBuilder.Append('ô'); break; case 0xf5: resultBuilder.Append('õ'); break; case 0xf6: resultBuilder.Append('ö'); break; + case 0xf7: resultBuilder.Append('÷'); break; case 0xf8: resultBuilder.Append('ø'); break; case 0xf9: resultBuilder.Append('ù'); break; case 0xfa: resultBuilder.Append('ú'); break; diff --git a/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-6.cs b/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-6.cs index 729eb59..6bd0821 100644 --- a/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-6.cs +++ b/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-6.cs @@ -20,6 +20,7 @@ namespace skyscraper5.Skyscraper.Text.Encodings case 0x00: break; //Null case 0x0a: resultBuilder.Append('\n'); break; case 0x10: break; //Data Link Escape + case 0x12: break; //Device Control 2 case 0x13: break; //Device Control 3 case 0x20: resultBuilder.Append(' '); break; case 0x21: resultBuilder.Append('!'); break; @@ -104,6 +105,7 @@ namespace skyscraper5.Skyscraper.Text.Encodings case 0x7a: resultBuilder.Append('z'); arabMode = false; break; case 0xa0: resultBuilder.Append('\u00a0'); break; case 0xac: resultBuilder.Append('،'); arabMode = true; break; + case 0xb2: break; case 0xbb: resultBuilder.Append('؛'); arabMode = true; break; case 0xbf: resultBuilder.Append('؟'); arabMode = true; break; case 0xc1: resultBuilder.Append('ء'); arabMode = true; break; @@ -132,6 +134,7 @@ namespace skyscraper5.Skyscraper.Text.Encodings case 0xd8: resultBuilder.Append('ظ'); arabMode = true; break; case 0xd9: resultBuilder.Append('ع'); arabMode = true; break; case 0xda: resultBuilder.Append('غ'); arabMode = true; break; + case 0xdb: resultBuilder.Append('؛'); arabMode = true; break; case 0xe0: resultBuilder.Append('ـ'); arabMode = true; break; case 0xe1: resultBuilder.Append('ف'); arabMode = true; break; case 0xe2: resultBuilder.Append('ق'); arabMode = true; break; diff --git a/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-8.cs b/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-8.cs index 285697c..3a03e8d 100644 --- a/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-8.cs +++ b/skyscraper8/Skyscraper/Text/Encodings/dvb-iso-8859-8.cs @@ -36,6 +36,9 @@ namespace skyscraper5.Skyscraper.Text.Encodings case 0xad: resultBuilder.Append('\u00ad'); break; case 0xae: resultBuilder.Append('®'); break; case 0xaf: resultBuilder.Append('¯'); break; + case 0xcd: break; + case 0xb4: resultBuilder.Append('´'); break; + case 0xd0: break; default: throw new NotImplementedException(String.Format("0x{0:X2}", preprocessed[i])); }