- re-added and reworked Hisense SVL_*.BIN loader (read-only atm)

This commit is contained in:
Horst Beham
2023-01-18 15:27:59 +01:00
parent 4556d94077
commit c17a3eb020
9 changed files with 839 additions and 28 deletions

View File

@@ -50,4 +50,13 @@
<PackageReference Include="System.Numerics.Vectors" Version="4.5.0" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
</ItemGroup>
<ItemGroup>
<Content Remove="C:\Users\horst\.nuget\packages\sqlitepclraw.lib.e_sqlite3\2.1.2\buildTransitive\net461\..\..\runtimes\win-x86\native\e_sqlite3.dll" />
</ItemGroup>
<ItemGroup>
<Content Remove="C:\Users\horst\.nuget\packages\sqlitepclraw.lib.e_sqlite3\2.1.2\buildTransitive\net461\..\..\runtimes\win-x64\native\e_sqlite3.dll" />
</ItemGroup>
<ItemGroup>
<Content Remove="C:\Users\horst\.nuget\packages\sqlitepclraw.lib.e_sqlite3\2.1.2\buildTransitive\net461\..\..\runtimes\win-arm\native\e_sqlite3.dll" />
</ItemGroup>
</Project>

View File

@@ -9,10 +9,14 @@ RecordSize=328
ID=0
BroadcastType=2
ServiceType=3
Nid=4
Onid=6
Tsid=8
Frequency=16
SymbolRate=28
SymbolRate=20
DvbsSymbolRate=28
Name=216
NameLength=96
NameLength=32
[SVL_Record]
@@ -21,33 +25,32 @@ RecordSize=304
RecordId=0
ChannelId=6
HashcodeFieldMask=8
HashcodeFieldMask_Name=0x01
HashcodeFieldMask_ChannelId=0x02
HashcodeFieldMask_BroadcastType=0x04
HashcodeFieldMask_TslRecId=0x08
HashcodeFieldMask_PrgNum=0x10
HashcodeFieldMask_ShortName=0x20
HashcodeFieldMask_Radio = 0x0400
HashcodeFieldMask_Encrypted = 0x0800
HashcodeFieldMask_Tv = 0x2000
Hashcode=8
Hashcode_Name=0x01
Hashcode_ServiceId=0x02
Hashcode_BroadcastType=0x04
;Hashcode_TslRecId=0x08
;Hashcode_PrgNum=0x10
;Hashcode_ShortName=0x20
NwMask=12
NwMask=8
NwMask_Skip = 0x08
NwMask_Fav1 = 0x10
NwMask_Fav2 = 0x20
NwMask_Fav3 = 0x40
NwMask_Fav4 = 0x80
NwMask_Lock = 0x80
NwMask_Hide = 0x00
NwMask_Lock = 0x100
NwMask_Radio = 0x0400
NwMask_Encrypted = 0x0800
NwMask_Tv = 0x2000
OptionMask=16
OptionMask=12
MaskRename = 0x08
MaskMoved = 0x400
OptionMask2=20
OptionMask2=16
ProgramId=24
ServiceId=24
TslTableId=26
TslRecordId=28
NwlTableId=30
@@ -63,16 +66,22 @@ BroadcastSystemData=136
[DVB_Data]
ShortName=4
ShortName_Size=16
ShortNameLength=16
LinkageMask=28
LinkageMask_Ts=0x04
Onid=44
Tsid=46
Ssid=48
Encrypted=64
Sid=48
DvbcTsid=118
DvbcOnid=120
ServiceType=129
[Columns]
DVB_T=Position,OldProgramNr,Name,ShortName,Favorites,Lock,Skip,Encrypted,ChannelOrTransponder,FreqInMhz,ServiceTypeName
DVB_C=Position,OldProgramNr,Name,ShortName,Favorites,Lock,Skip,Encrypted,ChannelOrTransponder,FreqInMhz,ServiceTypeName,SymbolRate
DVB_S=Position,OldProgramNr,Name,ShortName,Favorites,Lock,Skip,Encrypted,FreqInMhz,ServiceTypeName,SymbolRate
[FAV_Record]
RecordSize=80
SvlTableId=0
SvlRecordId=2
DisplayNumber=4
DisplayNumberLength=10
ChannelName=15
ChannelNameLength=64

View File

@@ -0,0 +1,432 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using ChanSort.Api;
namespace ChanSort.Loader.Hisense.HisBin;
/*
* Loads Hisense HIS_SVL.BIN channel lists
*
* This binary format is based on a customized MediaTek format, which means that there may be many incompatible
* variants that can't be identified and distinguished easily.
* This loader only supports the known Hisense variant with 304 bytes per channel in HIS_SVL.BIN.
*
* See also the his-svl.h file in Information/FileStructures_for_HHD_Hex_Editor_Neo
*
* Some properties of these lists:
* - channel records are physically ordered by their program number. All TV channels first, then radio, then data.
* - favorite lists allow mixing channels from different inputs and also radio and TV
* - character encoding seen as both implicit UTF8 or latin-1
*/
public class HisBinSerializer : 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");
private readonly ChannelList dvbsChannels = new (SignalSource.DvbS | SignalSource.Tv | SignalSource.Radio, "DVB-S");
private readonly ChannelList favChannels = new(SignalSource.All, "Fav") { IsMixedSourceFavoritesList = true };
private byte[] svlFileContent;
private byte[] tslFileContent;
private const int MaxFileSize = 4 << 20; // 4 MB
private int tSize, cSize, sSize;
private const string ERR_fileTooBig = "The file size {0} is larger than the allowed maximum of {1}.";
private const string ERR_badFileFormat = "The content of the file doesn't match the expected format.";
private DataMapping svlMapping, tslMapping, dvbMapping, favMapping;
private readonly Dictionary<int, Transponder> transponder = new ();
#region ctor()
public HisBinSerializer(string inputFile) : base(inputFile)
{
this.Features.ChannelNameEdit = ChannelNameEditMode.All;
this.Features.CanSkipChannels = true;
this.Features.CanLockChannels = true;
this.Features.CanHideChannels = false;
this.Features.FavoritesMode = FavoritesMode.MixedSource;
this.Features.MaxFavoriteLists = 4;
this.ReadConfigurationFromIniFile();
this.DataRoot.AddChannelList(dvbcChannels);
this.DataRoot.AddChannelList(dvbtChannels);
this.DataRoot.AddChannelList(dvbsChannels);
this.DataRoot.AddChannelList(favChannels);
foreach (var list in this.DataRoot.ChannelLists)
{
list.VisibleColumnFieldNames.Remove(nameof(ChannelInfo.PcrPid));
list.VisibleColumnFieldNames.Remove(nameof(ChannelInfo.VideoPid));
list.VisibleColumnFieldNames.Remove(nameof(ChannelInfo.AudioPid));
list.VisibleColumnFieldNames.Remove(nameof(ChannelInfo.Satellite));
list.VisibleColumnFieldNames.Add(nameof(ChannelInfo.ServiceType));
}
}
#endregion
#region ReadConfigurationFromIniFile()
private void ReadConfigurationFromIniFile()
{
string iniFile = this.GetType().Assembly.Location.ToLower().Replace(".dll", ".ini");
IniFile ini = new IniFile(iniFile);
this.svlMapping = new DataMapping(ini.GetSection("SVL_Record"));
this.svlMapping.DefaultEncoding = this.DefaultEncoding;
this.tslMapping = new DataMapping(ini.GetSection("TSL_Record"));
this.tslMapping.DefaultEncoding = this.DefaultEncoding;
this.dvbMapping = new DataMapping(ini.GetSection("DVB_Data"));
this.dvbMapping.DefaultEncoding = this.DefaultEncoding;
this.favMapping = new DataMapping(ini.GetSection("FAV_Record"));
}
#endregion
#region Load()
public override void Load()
{
var dir = Path.GetDirectoryName(this.FileName);
var name = Path.GetFileNameWithoutExtension(this.FileName);
var i = name.LastIndexOf('_');
var basename = i < 0 ? name : name.Substring(0, i);
this.FileName = Path.Combine(dir, basename + "_SVL.BIN");
this.LoadTslFile(Path.Combine(dir, basename + "_TSL.BIN"));
this.LoadSvlFile(this.FileName);
this.LoadFavFile(Path.Combine(dir, basename + "_FAV.BIN"));
}
#endregion
#region LoadTslFile()
private void LoadTslFile(string fileName)
{
long fileSize = new FileInfo(fileName).Length;
if (fileSize > MaxFileSize)
throw new FileLoadException(string.Format(ERR_fileTooBig, fileSize, MaxFileSize));
this.tslFileContent = File.ReadAllBytes(fileName);
int off = 0;
tSize = this.ReadHeader(tslFileContent, ref off);
cSize = this.ReadHeader(tslFileContent, ref off);
sSize = this.ReadHeader(tslFileContent, ref off);
this.ReadTransponder(ref off, tSize, 1, 1000000);
this.ReadTransponder(ref off, cSize, 2, 1000000);
this.ReadTransponder(ref off, sSize, 3, 1);
}
#endregion
#region ReadTransponder()
private void ReadTransponder(ref int off, int size, int table, int freqFactor)
{
int recordSize = tslMapping.Settings.GetInt("RecordSize");
if (size % recordSize != 0)
throw new FileLoadException(ERR_badFileFormat);
int count = size / recordSize;
if (count == 0)
return;
for (int i = 0; i < count; i++)
{
tslMapping.SetDataPtr(tslFileContent, off);
var id = (table << 16) + tslMapping.GetWord("ID");
var trans = new Transponder(id);
trans.FrequencyInMhz = (decimal)tslMapping.GetDword("Frequency") / freqFactor;
var sym = tslMapping.GetDword("SymbolRate");
if (sym == 0)
sym = tslMapping.GetDword("DvbsSymbolRate");
trans.SymbolRate = (int)(sym > 1000000 ? sym / 1000 : sym);
trans.OriginalNetworkId = tslMapping.GetWord("Onid");
if (trans.OriginalNetworkId == 0) // some files have Onid=0 but provide a Nid, which seems to be the Onid
trans.OriginalNetworkId = tslMapping.GetWord("Nid");
trans.TransportStreamId = tslMapping.GetWord("Tsid");
trans.Name = tslMapping.GetString("Name", tslMapping.Settings.GetInt("NameLength"));
var z = trans.Name.IndexOf('\0');
if (z >= 0)
trans.Name = trans.Name.Substring(0, z);
this.transponder.Add(id, trans);
off += recordSize;
}
}
#endregion
#region LoadSvlFile()
private void LoadSvlFile(string fileName)
{
long fileSize = new FileInfo(fileName).Length;
if (fileSize > MaxFileSize)
throw new FileLoadException(string.Format(ERR_fileTooBig, fileSize, MaxFileSize));
this.svlFileContent = File.ReadAllBytes(this.FileName);
int off = 0;
tSize = this.ReadHeader(svlFileContent, ref off);
cSize = this.ReadHeader(svlFileContent, ref off);
sSize = this.ReadHeader(svlFileContent, ref off);
this.ReadChannelList(ref off, tSize, 1, dvbtChannels);
this.ReadChannelList(ref off, cSize, 2, dvbcChannels);
this.ReadChannelList(ref off, sSize, 3, dvbsChannels);
}
#endregion
#region ReadHeader()
private int ReadHeader(byte[] data, ref int off)
{
if (off + 40 > data.Length)
throw new FileLoadException(ERR_badFileFormat);
var blockSize = BitConverter.ToInt32(data, off + 36);
if (off + blockSize > data.Length)
throw new FileLoadException(ERR_badFileFormat);
off += 40;
return blockSize;
}
#endregion
#region ReadChannelList()
private void ReadChannelList(ref int off, int size, int table, ChannelList channels)
{
int recordSize = svlMapping.Settings.GetInt("RecordSize");
if (size % recordSize != 0)
throw new FileLoadException(ERR_badFileFormat);
int channelCount = size / recordSize;
if (channelCount == 0)
return;
var broadcastDataOffset = svlMapping.Settings.GetInt("BroadcastSystemData");
var nameLength = svlMapping.Settings.GetInt("NameLength");
var source = channels.SignalSource & (SignalSource.MaskAnalogDigital | SignalSource.MaskAntennaCableSat);
for (int i = 0; i < channelCount; i++)
{
svlMapping.SetDataPtr(svlFileContent, off);
dvbMapping.SetDataPtr(svlFileContent, off + broadcastDataOffset);
var ci = ReadChannel(source, i, nameLength);
if (ci != null)
{
this.DataRoot.AddChannel(channels, ci);
this.favChannels.AddChannel(ci);
}
off += recordSize;
}
}
#endregion
#region ReadChannel()
private ChannelInfo ReadChannel(SignalSource source, int index, int nameLength)
{
var id = svlMapping.GetWord("RecordId");
ChannelInfo ci = new ChannelInfo(source, id, 0, "");
ci.RecordOrder = index;
ci.OldProgramNr = svlMapping.GetWord("ChannelId") >> 2;
ci.RawDataOffset = svlMapping.BaseOffset;
var nwMask = svlMapping.GetDword("NwMask");
ci.Skip = (nwMask & svlMapping.Settings.GetInt("NwMask_Skip")) == 0; // reverse logic!
ci.Lock = (nwMask & svlMapping.Settings.GetInt("NwMask_Lock")) != 0;
for (int i = 1; i <= 4; i++)
{
bool isFav = (nwMask & svlMapping.Settings.GetInt("NwMask_Fav" + i)) != 0;
if (isFav)
ci.Favorites |= (Favorites)(1 << (i-1));
}
ci.Encrypted = (nwMask & svlMapping.Settings.GetInt("NwMask_Encrypted")) != 0;
ci.Name = ReadString(svlMapping, "Name", nameLength);
var serviceType = svlMapping.GetByte("ServiceType");
if (serviceType == 1)
{
ci.SignalSource |= SignalSource.Tv;
ci.ServiceTypeName = "TV";
}
else if (serviceType == 2)
{
ci.SignalSource |= SignalSource.Radio;
ci.ServiceTypeName = "Radio";
}
else
{
ci.ServiceTypeName = "Data";
}
ci.ServiceId = svlMapping.GetWord("ServiceId");
int transpTableId = svlMapping.GetWord("TslTableId");
int transpRecordId = svlMapping.GetWord("TslRecordId");
var transpId = (transpTableId << 16) + transpRecordId;
var transp = this.transponder.TryGet(transpId);
if (transp != null)
{
ci.Transponder = transp;
ci.FreqInMhz = transp.FrequencyInMhz;
ci.SymbolRate = transp.SymbolRate;
switch (ci.SymbolRate) // can be either an enum for bandwidth 1=6 MHz, 2=7 MHz, 3=8 MHz - or the symbol rate
{
case 1: ci.SymbolRate = 6000; break;
case 2: ci.SymbolRate = 7000; break;
case 3: ci.SymbolRate = 8000; break;
}
ci.OriginalNetworkId = transp.OriginalNetworkId;
ci.TransportStreamId = transp.TransportStreamId;
ci.Provider = transp.Name;
}
var bcastType = svlMapping.GetByte("BroadcastType");
if (bcastType == 1)
ReadAnalogData(ci);
else if (bcastType == 2)
ReadDvbData(ci);
return ci;
}
#endregion
#region ReadAnalogData()
private void ReadAnalogData(ChannelInfo ci)
{
}
#endregion
#region ReadDvbData()
private void ReadDvbData(ChannelInfo ci)
{
var mask = dvbMapping.GetDword("LinkageMask");
var tsFlag = dvbMapping.Settings.GetInt("LinkageMask_Ts");
if ((mask & tsFlag) != 0)
{
ci.OriginalNetworkId = dvbMapping.GetWord("Onid");
ci.TransportStreamId = dvbMapping.GetWord("Tsid");
ci.ServiceId = dvbMapping.GetWord("Sid");
}
if ((ci.SignalSource & (SignalSource.MaskAnalogDigital | SignalSource.MaskAntennaCableSat)) == SignalSource.DvbC)
{
ci.OriginalNetworkId = dvbMapping.GetWord("DvbcOnid");
ci.TransportStreamId = dvbMapping.GetWord("DvbcTsid");
}
if ((ci.SignalSource & SignalSource.DvbT) == SignalSource.DvbT)
ci.ChannelOrTransponder = LookupData.Instance.GetDvbtTransponder(ci.FreqInMhz).ToString();
else if ((ci.SignalSource & SignalSource.DvbC) == SignalSource.DvbC)
ci.ChannelOrTransponder = LookupData.Instance.GetDvbcChannelName(ci.FreqInMhz).ToString();
ci.ServiceType = dvbMapping.GetByte("ServiceType");
if (ci.ServiceType != 0)
ci.ServiceTypeName = LookupData.Instance.GetServiceTypeDescription(ci.ServiceType);
ci.ShortName = dvbMapping.GetString("ShortName", dvbMapping.Settings.GetInt("ShortNameLength"));
}
#endregion
#region ReadString()
private string ReadString(DataMapping mapping, string name, int nameLength)
{
var str = mapping.GetString(name, nameLength);
int term = str.IndexOf('\0');
if (term >= 0)
str = str.Substring(0, term);
return str;
}
#endregion
#region LoadFavFile()
private void LoadFavFile(string filename)
{
if (!File.Exists(filename))
return;
var content = File.ReadAllBytes(filename);
var favListSizes = new int[4];
for (int i = 0; i < 4; i++)
favListSizes[i] = BitConverter.ToInt32(content, i * 4);
var recSize = favMapping.Settings.GetInt("RecordSize");
var dispNumLen = favMapping.Settings.GetInt("DisplayNumberLength");
favMapping.SetDataPtr(content, 16 - recSize);
for (int i = 0; i < 4; i++)
{
for (int j = 0, c = favListSizes[i] / recSize; j < c; j++)
{
favMapping.BaseOffset += recSize;
//if (favMapping.BaseOffset + recSize >= content.Length) //
// break;
var tblId = favMapping.GetWord("SvlTableId");
var recId = favMapping.GetWord("SvlRecordId");
var list = tblId == 1 ? dvbtChannels : tblId == 2 ? dvbcChannels : tblId == 3 ? dvbsChannels : null;
if (list == null) // should never happen
continue;
var chan = list.GetChannelById(recId);
if (chan == null) // should never happen
continue;
var dispNr = favMapping.GetString("DisplayNumber", dispNumLen);
var nr = int.Parse(dispNr);
chan.SetOldPosition(1 + i, nr);
}
}
}
#endregion
// Saving ====================================
#region Save()
public override void Save()
{
throw new NotImplementedException();
// TODO
// HIS_SVL
// - update size in header
// - write channels in new physical order
// HIS_FAV
}
#endregion
// Infrastructure ============================
#region DefaultEncoding
public override Encoding DefaultEncoding
{
get => base.DefaultEncoding;
set
{
if (value == this.DefaultEncoding)
return;
base.DefaultEncoding = value;
this.svlMapping.DefaultEncoding = value;
this.tslMapping.DefaultEncoding = value;
this.dvbMapping.DefaultEncoding = value;
this.ReparseNames();
}
}
#endregion
#region ReparseNames()
private void ReparseNames()
{
var nameLength = svlMapping.Settings.GetInt("NameLength");
var shortNameLength = dvbMapping.Settings.GetInt("ShortNameLength");
var dvbOffset = svlMapping.Settings.GetInt("BroadcastSystemData");
foreach (var list in this.DataRoot.ChannelLists)
{
if (list.IsMixedSourceFavoritesList)
continue;
foreach (var chan in list.Channels)
{
svlMapping.BaseOffset = chan.RawDataOffset;
chan.Name = ReadString(svlMapping, "Name", nameLength);
dvbMapping.BaseOffset = chan.RawDataOffset + dvbOffset;
chan.ShortName = ReadString(dvbMapping, "ShortName", shortNameLength);
}
}
}
#endregion
}

View File

@@ -7,7 +7,7 @@ namespace ChanSort.Loader.Hisense
{
public string DllName { get; set; }
public string PluginName => "Hisense (channel.db, servicelist.db)";
public string FileFilter => "*.db";
public string FileFilter => "*.db;*.bin";
public SerializerBase CreateSerializer(string inputFile)
{
@@ -19,6 +19,8 @@ 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);
return null;
}
}