diff --git a/source/ChanSort.Loader.Hisense/ChanSort.Loader.Hisense.csproj b/source/ChanSort.Loader.Hisense/ChanSort.Loader.Hisense.csproj index c5cc766..cc3e471 100644 --- a/source/ChanSort.Loader.Hisense/ChanSort.Loader.Hisense.csproj +++ b/source/ChanSort.Loader.Hisense/ChanSort.Loader.Hisense.csproj @@ -59,4 +59,9 @@ + + + ..\..\..\..\..\Program Files\DevExpress 22.1\Components\Bin\Framework\DevExpress.PivotGrid.v22.1.Core.dll + + \ No newline at end of file diff --git a/source/ChanSort.Loader.Hisense/ChanSort.Loader.Hisense.ini b/source/ChanSort.Loader.Hisense/ChanSort.Loader.Hisense.ini index 8353049..27a0d89 100644 --- a/source/ChanSort.Loader.Hisense/ChanSort.Loader.Hisense.ini +++ b/source/ChanSort.Loader.Hisense/ChanSort.Loader.Hisense.ini @@ -1,4 +1,8 @@ -[Header] +; ======================================== +; HIS_SVL.BIN, HIS_TSL.BIN and HIS_FAV.BIN +; ======================================== + +[Header] RecordSize=40 ID=0 Name=2 @@ -86,3 +90,42 @@ DisplayNumber=4 DisplayNumberLength=10 ChannelName=15 ChannelNameLength=64 + + +; ======================================== +; HIS_DVB.BIN +; ======================================== +[HIS_DVB.BIN] +HeaderSize=16 +Version=0 +NumChannelsDvbT=4 +NumChannelsDvbC=8 +NumChannelsDvbS=12 + +[HIS_DVB.BIN_Record] +RecordSizeDvbT=498 +RecordSizeDvbC=498 +RecordSizeDvbS=562 +Skip=21 +Lock=22 +TvRadioData=29 +Fav=30 +ServiceType=35 +PcrPid=36 +VideoPid=38 +ProgNum=40 +PmtPid=46 +ServiceId=48 +;@62: languages[3] +AudioPid=68 +Name=286 +NameLength=50 +Provider=336 +ProviderLength=50 +Tsid=398 +Onid=400 +Frequency=408 +SymbolRate=420 +SatName=500 +SatNameLength=32 +SatOrbitalPos=548 \ No newline at end of file diff --git a/source/ChanSort.Loader.Hisense/HisBin/HisDvbBinSerializer.cs b/source/ChanSort.Loader.Hisense/HisBin/HisDvbBinSerializer.cs new file mode 100644 index 0000000..5bacd1b --- /dev/null +++ b/source/ChanSort.Loader.Hisense/HisBin/HisDvbBinSerializer.cs @@ -0,0 +1,321 @@ +using System.IO; +using System.Linq; +using System.Text; +using ChanSort.Api; + +namespace ChanSort.Loader.Hisense.HisBin; + +/* + * Loads Hisense HIS_DVB.BIN channel lists + * + * See also the his-dvb.h file in Information/FileStructures_for_HHD_Hex_Editor_Neo + * + * Some properties of these lists: + * - channel records are physically ordered by their program number + * - TV and radio are managed in separate lists, both starting at 1 + * - channel and provider names are raw DVB strings including control bytes + */ +public class HisDvbBinSerializer : SerializerBase +{ + private readonly ChannelList antTvChannels = new(SignalSource.DvbT | SignalSource.Tv, "Antenna TV"); + private readonly ChannelList antRadioChannels = new(SignalSource.DvbT | SignalSource.Radio, "Antenna Radio"); + private readonly ChannelList antDataChannels = new(SignalSource.DvbT | SignalSource.Data, "Antenna Data"); + private readonly ChannelList cabTvChannels = new(SignalSource.DvbC | SignalSource.Tv, "Cable TV"); + private readonly ChannelList cabRadioChannels = new(SignalSource.DvbC | SignalSource.Radio, "Cable Radio"); + private readonly ChannelList cabDataChannels = new(SignalSource.DvbC | SignalSource.Data, "Cable Data"); + private readonly ChannelList satTvChannels = new(SignalSource.DvbS | SignalSource.Tv, "Sat TV"); + private readonly ChannelList satRadioChannels = new(SignalSource.DvbS | SignalSource.Radio, "Sat Radio"); + private readonly ChannelList satDataChannels = new(SignalSource.DvbS | SignalSource.Data, "Sat Data"); + + private SubListInfo[] subListInfos; + + private byte[] fileContent; + private int headerRecordSize, antRecordSize, cabRecordSize, satRecordSize; + + private const string ERR_badFileFormat = "The content of the file doesn't match the expected format."; + + private IniFile ini; + private DataMapping headerMapping, dvbMapping; + private DvbStringDecoder dvbStringDecoder; + + #region class SubListInfo + private struct SubListInfo + { + public int Count; + public int Size; + public ChannelList TvList; + public ChannelList RadioList; + public ChannelList DataList; + public bool IsSat; + + public SubListInfo(int count, int size, ChannelList tvList, ChannelList radioList, ChannelList dataList, bool isSat) + { + Count = count; + Size = size; + TvList = tvList; + RadioList = radioList; + DataList = dataList; + IsSat = isSat; + } + } + #endregion + + #region ctor() + public HisDvbBinSerializer(string inputFile) : base(inputFile) + { + this.Features.ChannelNameEdit = ChannelNameEditMode.None; + this.Features.CanSkipChannels = true; + this.Features.CanLockChannels = true; + this.Features.CanHideChannels = false; + this.Features.FavoritesMode = FavoritesMode.Flags; + this.Features.MaxFavoriteLists = 1; + this.Features.DeleteMode = DeleteMode.Physically; + this.Features.CanHaveGaps = true; + this.Features.AllowGapsInFavNumbers = false; + this.ReadConfigurationFromIniFile(); + + this.DataRoot.AddChannelList(antTvChannels); + this.DataRoot.AddChannelList(antRadioChannels); + this.DataRoot.AddChannelList(antDataChannels); + this.DataRoot.AddChannelList(cabTvChannels); + this.DataRoot.AddChannelList(cabRadioChannels); + this.DataRoot.AddChannelList(cabDataChannels); + this.DataRoot.AddChannelList(satTvChannels); + this.DataRoot.AddChannelList(satRadioChannels); + this.DataRoot.AddChannelList(satDataChannels); + foreach (var list in this.DataRoot.ChannelLists) + { + list.VisibleColumnFieldNames.Remove(nameof(ChannelInfo.ChannelOrTransponder)); + list.VisibleColumnFieldNames.Remove(nameof(ChannelInfo.Encrypted)); + list.VisibleColumnFieldNames.Add(nameof(ChannelInfo.ServiceType)); + } + } + #endregion + + #region ReadConfigurationFromIniFile() + + private void ReadConfigurationFromIniFile() + { + string iniFile = this.GetType().Assembly.Location.ToLower().Replace(".dll", ".ini"); + this.ini = new IniFile(iniFile); + this.headerMapping = new DataMapping(ini.GetSection("HIS_DVB.BIN")); + this.headerRecordSize = headerMapping.Settings.GetInt("HeaderSize"); + + this.dvbMapping = new DataMapping(ini.GetSection("HIS_DVB.BIN_Record")); + this.antRecordSize = this.dvbMapping.Settings.GetInt("RecordSizeDvbT"); + this.cabRecordSize = this.dvbMapping.Settings.GetInt("RecordSizeDvbC"); + this.satRecordSize = this.dvbMapping.Settings.GetInt("RecordSizeDvbS"); + this.dvbMapping.DefaultEncoding = this.DefaultEncoding; + } + #endregion + + + #region Load() + + public override void Load() + { + this.fileContent = File.ReadAllBytes(this.FileName); + + this.headerMapping.SetDataPtr(this.fileContent, 0); + + var antChannelCount = this.headerMapping.GetWord("NumChannelsDvbT"); + var cabChannelCount = this.headerMapping.GetWord("NumChannelsDvbC"); + var satChannelCount = this.headerMapping.GetWord("NumChannelsDvbS"); + + var expectedSize = headerRecordSize + antChannelCount * antRecordSize + cabChannelCount * cabRecordSize + satChannelCount * satRecordSize; + if (this.fileContent.Length != expectedSize) + throw new FileLoadException(ERR_badFileFormat); + + this.dvbStringDecoder = new DvbStringDecoder(this.DefaultEncoding); + + var nameOffset = dvbMapping.Settings.GetInt("Name"); + var nameLength = dvbMapping.Settings.GetInt("NameLength"); + var providerOffset = dvbMapping.Settings.GetInt("Provider"); + var providerLength = dvbMapping.Settings.GetInt("ProviderLength"); + + this.subListInfos = new SubListInfo[] + { + new (antChannelCount, antRecordSize, antTvChannels, antRadioChannels, antDataChannels, false), + new (cabChannelCount, cabRecordSize, cabTvChannels, cabRadioChannels, cabDataChannels, false), + new (satChannelCount, satRecordSize, satTvChannels, satRadioChannels, satDataChannels, true) + }; + + var off = headerRecordSize; + foreach (var info in this.subListInfos) + { + for (int index = 0; index < info.Count; index++) + { + dvbMapping.SetDataPtr(fileContent, off); + var ci = ReadChannel(index, info.IsSat, nameOffset, nameLength, providerOffset, providerLength); + if (ci != null) + { + var src = ci.SignalSource & SignalSource.MaskTvRadioData; + var channels = src == SignalSource.Tv ? info.TvList : src == SignalSource.Radio ? info.RadioList : info.DataList; + this.DataRoot.AddChannel(channels, ci); + } + + off += info.Size; + } + } + } + #endregion + + #region ReadChannel() + private ChannelInfo ReadChannel(int index, bool isSat, int nameOffset, int nameLength, int providerOffset, int providerLength) + { + ChannelInfo ci = new ChannelInfo(0, index, 0, ""); + ci.RawDataOffset = dvbMapping.BaseOffset; + + var type = dvbMapping.GetByte("TvRadioData"); + if (type == 1) + ci.SignalSource |= SignalSource.Tv; + else if (type == 2) + ci.SignalSource |= SignalSource.Radio; + else + ci.SignalSource |= SignalSource.Data; + + ci.Favorites = dvbMapping.GetByte("Fav") != 0 ? Favorites.A : 0; + ci.Skip = dvbMapping.GetByte("Skip") != 0; + ci.Lock = dvbMapping.GetByte("Lock") != 0; + ci.ServiceType = dvbMapping.GetByte("ServiceType"); + ci.PcrPid = dvbMapping.GetWord("PcrPid"); + ci.VideoPid = dvbMapping.GetWord("VideoPid"); + ci.OldProgramNr = dvbMapping.GetWord("ProgNum"); + ci.ServiceId = dvbMapping.GetWord("ServiceId"); + ci.AudioPid = dvbMapping.GetWord("AudioPid"); + + this.dvbStringDecoder.GetChannelNames(fileContent, ci.RawDataOffset + nameOffset, nameLength, out var longName, out var shortName); + ci.Name = longName; + ci.ShortName = shortName; + + this.dvbStringDecoder.GetChannelNames(fileContent, ci.RawDataOffset + providerOffset, providerLength, out var provider, out _); + ci.Provider = provider; + + ci.TransportStreamId = dvbMapping.GetWord("Tsid"); + ci.OriginalNetworkId = dvbMapping.GetWord("Onid"); + ci.FreqInMhz = dvbMapping.GetDword("Frequency"); + if (ci.FreqInMhz > 20000) // DVB-C/T has value in kHZ, DVB-S in MHz + ci.FreqInMhz /= 1000; + ci.SymbolRate = dvbMapping.GetWord("SymbolRate"); + + if (isSat) + ci.Satellite = dvbMapping.GetString("SatName", dvbMapping.Settings.GetInt("SatNameLength")); + + return ci; + } + #endregion + + + // Saving ==================================== + + #region Save() + public override void Save() + { + using var mem = new MemoryStream(this.fileContent.Length); + using var writer = new BinaryWriter(mem); + writer.Write(this.fileContent, 0, this.headerRecordSize); + + foreach (var info in this.subListInfos) + { + int newIndex = 0; + foreach (var list in new[] { info.TvList, info.RadioList, info.DataList }) + { + var order = list.Channels.OrderBy(c => c, new DelegateComparer(OrderChannelsComparer)).ToList(); + + foreach (var channel in order) + { + if (channel.IsDeleted) + continue; + + // copy original data + var offset = writer.BaseStream.Position; + writer.Write(this.fileContent, channel.RawDataOffset, info.Size); + writer.Flush(); + + // prepare to overwrite with some new values + dvbMapping.SetDataPtr(mem.GetBuffer(), (int)offset); + dvbMapping.SetWord("ProgNum", channel.NewProgramNr); + dvbMapping.SetByte("Skip", channel.Skip ? 1 : 0); + dvbMapping.SetByte("Lock", channel.Lock ? 1 : 0); + dvbMapping.SetByte("Fav", channel.Favorites != 0 ? 1 : 0); + + channel.RecordIndex = newIndex++; + channel.RawDataOffset = (int)offset; + } + + // update number of channels in header + headerMapping.SetDataPtr(mem.GetBuffer(), 0); + headerMapping.SetDword("NumSatChannels", newIndex); + } + } + + // write to file + this.fileContent = new byte[mem.Length]; + Tools.MemCopy(mem.GetBuffer(), 0, this.fileContent, 0, (int)mem.Length); + File.WriteAllBytes(this.FileName, this.fileContent); + } + #endregion + + #region OrderChannelsComparer() + private int OrderChannelsComparer(ChannelInfo a, ChannelInfo b) + { + // deleted channels to the end + if (a.NewProgramNr < 0) + return b.NewProgramNr == 0 ? a.RecordIndex.CompareTo(b.RecordIndex) : +1; + if (b.NewProgramNr < 0) + return -1; + + return a.NewProgramNr.CompareTo(b.NewProgramNr); + } + #endregion + + + // Infrastructure ============================ + + #region DefaultEncoding + public override Encoding DefaultEncoding + { + get => base.DefaultEncoding; + set + { + if (value == this.DefaultEncoding) + return; + base.DefaultEncoding = value; + this.dvbMapping.DefaultEncoding = value; + + if (this.dvbStringDecoder != null) + { + this.dvbStringDecoder.DefaultEncoding = value; + this.ReparseNames(); + } + } + } + #endregion + + #region ReparseNames() + private void ReparseNames() + { + var nameOffset = dvbMapping.Settings.GetInt("Name"); + var nameLength = dvbMapping.Settings.GetInt("NameLength"); + var providerOffset = dvbMapping.Settings.GetInt("Provider"); + var providerLength = dvbMapping.Settings.GetInt("ProviderLength"); + + foreach (var list in this.DataRoot.ChannelLists) + { + if (list.IsMixedSourceFavoritesList) + continue; + foreach (var chan in list.Channels) + { + dvbMapping.BaseOffset = chan.RawDataOffset; + + this.dvbStringDecoder.GetChannelNames(this.fileContent, chan.RawDataOffset + nameOffset, nameLength, out var longName, out var shortName); + chan.Name = longName; + chan.ShortName = shortName; + + this.dvbStringDecoder.GetChannelNames(this.fileContent, chan.RawDataOffset + providerOffset, providerLength, out var provider, out _); + chan.Provider = provider; + } + } + } + #endregion +} diff --git a/source/ChanSort.Loader.Hisense/HisBin/HisBinSerializer.cs b/source/ChanSort.Loader.Hisense/HisBin/HisSvlBinSerializer.cs similarity index 99% rename from source/ChanSort.Loader.Hisense/HisBin/HisBinSerializer.cs rename to source/ChanSort.Loader.Hisense/HisBin/HisSvlBinSerializer.cs index 8d3ce06..c972841 100644 --- a/source/ChanSort.Loader.Hisense/HisBin/HisBinSerializer.cs +++ b/source/ChanSort.Loader.Hisense/HisBin/HisSvlBinSerializer.cs @@ -22,7 +22,7 @@ namespace ChanSort.Loader.Hisense.HisBin; * - favorite lists allow mixing channels from different inputs and also radio and TV * - character encoding is implicit and can be UTF8 or latin-1 */ -public class HisBinSerializer : SerializerBase +public class HisSvlBinSerializer : SerializerBase { private readonly ChannelList dvbtChannels = new (SignalSource.DvbT | SignalSource.Tv | SignalSource.Radio, "DVB-T"); private readonly ChannelList dvbcChannels = new (SignalSource.DvbC | SignalSource.Tv | SignalSource.Radio, "DVB-C"); @@ -45,7 +45,7 @@ public class HisBinSerializer : SerializerBase private readonly Dictionary transponder = new (); #region ctor() - public HisBinSerializer(string inputFile) : base(inputFile) + public HisSvlBinSerializer(string inputFile) : base(inputFile) { this.Features.ChannelNameEdit = ChannelNameEditMode.All; this.Features.CanSkipChannels = true; diff --git a/source/ChanSort.Loader.Hisense/HisensePlugin.cs b/source/ChanSort.Loader.Hisense/HisensePlugin.cs index 7b4d9e7..0d7dde8 100644 --- a/source/ChanSort.Loader.Hisense/HisensePlugin.cs +++ b/source/ChanSort.Loader.Hisense/HisensePlugin.cs @@ -19,8 +19,11 @@ namespace ChanSort.Loader.Hisense if (name.Contains("servicelist")) // models 2017 and later return new ServicelistDb.ServicelistDbSerializer(inputFile); - if (name.StartsWith("his_") && name.EndsWith(".bin")) - return new HisBin.HisBinSerializer(inputFile); + if (name.StartsWith("his_dvb") && name.EndsWith(".bin")) // HIS_DVB.BIN + return new HisBin.HisDvbBinSerializer(inputFile); + + if (name.StartsWith("his_") && name.EndsWith(".bin")) // HIS_SVL.BIN, HIS_TSL.BIN, HIS_FAV.BIN + return new HisBin.HisSvlBinSerializer(inputFile); return null; } } diff --git a/source/ChanSort.sln b/source/ChanSort.sln index 07ae1a0..6533f50 100644 --- a/source/ChanSort.sln +++ b/source/ChanSort.sln @@ -115,6 +115,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "HDD Hex Edit Neo", "HDD Hex Information\FileStructures_for_HHD_Hex_Editor_Neo\dtv_cmdb_1-bin.h = Information\FileStructures_for_HHD_Hex_Editor_Neo\dtv_cmdb_1-bin.h Information\FileStructures_for_HHD_Hex_Editor_Neo\dtv_cmdb_2-bin.h = Information\FileStructures_for_HHD_Hex_Editor_Neo\dtv_cmdb_2-bin.h Information\FileStructures_for_HHD_Hex_Editor_Neo\get_doc_size.js = Information\FileStructures_for_HHD_Hex_Editor_Neo\get_doc_size.js + Information\FileStructures_for_HHD_Hex_Editor_Neo\his-dvb.h = Information\FileStructures_for_HHD_Hex_Editor_Neo\his-dvb.h Information\FileStructures_for_HHD_Hex_Editor_Neo\his-svl.h = Information\FileStructures_for_HHD_Hex_Editor_Neo\his-svl.h Information\FileStructures_for_HHD_Hex_Editor_Neo\LaSat_lst.h = Information\FileStructures_for_HHD_Hex_Editor_Neo\LaSat_lst.h Information\FileStructures_for_HHD_Hex_Editor_Neo\panasonic_idtvChannelBin.h = Information\FileStructures_for_HHD_Hex_Editor_Neo\panasonic_idtvChannelBin.h diff --git a/source/Information/FileStructures_for_HHD_Hex_Editor_Neo/his-dvb.h b/source/Information/FileStructures_for_HHD_Hex_Editor_Neo/his-dvb.h new file mode 100644 index 0000000..bcb4d09 --- /dev/null +++ b/source/Information/FileStructures_for_HHD_Hex_Editor_Neo/his-dvb.h @@ -0,0 +1,75 @@ +#include "chansort.h" + +[display(format("{0}", trim(lang)))] +struct LanguageInfo +{ + byte lang[4]; + byte u2[2]; + word audioPid; + byte u2[6]; +}; + +enum SubList : byte +{ + TV=1, Radio=2, Data=3 +}; + +[display(format("{0} - {1}", progNr, trim(name)))] +struct DvbChannel +{ + byte u1[21]; + byte skip; + byte lock; + byte u2[6]; + SubList list; + byte isFav; + byte u3[4]; + ServiceType serviceType; + word pcrPid; + word videoPid; + word progNr; + byte u4[4]; + word pmtPid; + word serviceId; + byte u5[12]; + LanguageInfo languages[3]; + byte u6[182]; + char name[50]; + char provider[50]; + byte u7[10]; + word u8; + word tsid; + word onid1; + word onid2; + byte u9[4]; + word freq; + byte u10[10]; + dword symbolRate; + byte u11[74]; +}; + +[display(format("{0}.{1} - {2}", dvb.list, dvb.progNr, trim(dvb.name)))] +struct SatChannel +{ + DvbChannel dvb; + word u1; + char satName[32]; + byte u11a[2]; + word lowFreq; + word highFreq; + byte u11b[10]; + byte orbitalPos; + byte u12[13]; +}; + +public struct HIS_DVB_BIN +{ + dword versionMaybe; + dword numAntennaChannels; + dword numCableChannels; + dword numSatChannels; + + DvbChannel antennaChannels[numAntennaChannels]; + DvbChannel cableChannels[numCableChannels]; + SatChannel satChannels[numSatChannels]; +}; diff --git a/source/changelog.md b/source/changelog.md index d952dac..9acc95f 100644 --- a/source/changelog.md +++ b/source/changelog.md @@ -1,6 +1,12 @@ ChanSort Change Log =================== +2023-01-23 +- added support for Hisense HIS_DVB.BIN channel lists + +2023-01-18 +- added support for Hisense HIS_SVL.BIN / HIS_TSL.BIN / HIS_FAV.BIN channel lists + 2023-01-15 - added support for Vision EDGE 4K set-top-box (DVB-S only) - TCL: separate lists for DVB-C/T/S, each starting at 1