Skip to content

Commit

Permalink
- fixed loading .txt reference lists
Browse files Browse the repository at this point in the history
- added support for HB\_DATABASE\_\*.DBM channel lists with file size 74303  (e.g. Renkforce 1510 C HD, Telestar digiHD TC 7)
- added support for dtv_cmdb_2.bin files with file size 2731173  (e.g. Dijitsu Android TV with LD-M538 board)
- improved experimental support for amdb\*.db Android STB channel lists  (now grouped by TV and radio for each satellite)
- combined HHD Hex Editor Neo structure definition files for HB_DATABASE.DBM file formats
  • Loading branch information
PredatH0r committed Oct 22, 2023
1 parent 5667674 commit 8ac45d5
Show file tree
Hide file tree
Showing 17 changed files with 558 additions and 87 deletions.
6 changes: 4 additions & 2 deletions source/ChanSort.Api/Controller/TxtRefListSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

namespace ChanSort.Api
{
/// <summary>
/// This class reads .txt files where each line has the format: progNr;name;onid-tsid-sid
/// </summary>
public class TxtRefListSerializer : SerializerBase
{
private static readonly char[] Separators = { ';' };
Expand Down Expand Up @@ -68,7 +71,7 @@ private void ReadChannels()
if (!int.TryParse(parts[0], out progNr))
continue;

var channel = new ChannelInfo(SignalSource.All, lineNr, progNr, parts[1]);
var channel = new ChannelInfo(SignalSource.Any, lineNr, progNr, parts[1]);
if (parts.Length >= 3)
{
var subParts = parts[2].Split('-');
Expand All @@ -84,7 +87,6 @@ private void ReadChannels()
}
}
this.DataRoot.AddChannel(this.allChannels, channel);
lineNr++;
}
}

Expand Down
88 changes: 88 additions & 0 deletions source/ChanSort.Api/Utils/ListDictionary.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;

namespace ChanSort.Api.Utils;

public class ListDictionary<K,V> : IDictionary<K,V>
{
private readonly ListDictionary dict = new();

public IEnumerator<KeyValuePair<K, V>> GetEnumerator() => (IEnumerator<KeyValuePair<K, V>>)dict.GetEnumerator();

IEnumerator IEnumerable.GetEnumerator() => dict.GetEnumerator();
public void Add(KeyValuePair<K, V> item) => dict.Add(item.Key, item.Value);
public void Clear() => dict.Clear();

public bool Contains(KeyValuePair<K, V> item)
{
var val = dict[item.Key];
return val != null && Equals(val, item.Value);
}

public void CopyTo(KeyValuePair<K, V>[] array, int arrayIndex)
{
var i = arrayIndex;
foreach (DictionaryEntry entry in dict)
array[i++] = new KeyValuePair<K, V>((K)entry.Key, (V)entry.Value);
}

public bool Remove(KeyValuePair<K, V> item)
{
var val = dict[item.Key];
if (val != null && Equals(val, item.Value))
{
dict.Remove(item.Key);
return true;
}

return false;
}

public int Count => dict.Count;
public bool IsReadOnly => dict.IsReadOnly;
public bool ContainsKey(K key) => dict.Contains(key);

public void Add(K key, V value) => dict.Add(key, value);

public bool Remove(K key)
{
if (!dict.Contains(key))
return false;

dict.Remove(key);
return true;
}

public bool TryGetValue(K key, out V value)
{
var obj = dict[key];
if (obj == null)
{
value = default;
return false;
}

value = (V)obj;
return true;
}

public V this[K key]
{
get => (V)dict[key];
set => dict[key] = value;
}

public ICollection<K> Keys => (ICollection<K>)dict.Keys;

public ICollection<V> Values
{
get
{
var list = new List<V>();
foreach(DictionaryEntry e in dict)
list.Add((V)e.Value);
return list;
}
}
}
168 changes: 113 additions & 55 deletions source/ChanSort.Loader.Amdb/AmdbSerializer.cs
Original file line number Diff line number Diff line change
@@ -1,20 +1,29 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Data.Sqlite;
using ChanSort.Api;
using ChanSort.Api.Utils;

namespace ChanSort.Loader.Amdb
{
/*
* This class loads amdb*.db files from unknown Android set-top-boxes.
* The srv_table contains two columns which somehow represent an order, but neither is the actual program number as shown in the menu.
* chan_num: seems to be grouped by service type (TV, Radio, Other) across satellites, each group starting at 1
* chan_order: is probably the order in which the channels where found during the scan. mostly ordered by satellite, but not strictly
* major_chan_num: always 0 (for DVB-S)
* Regardless of these columns, the receiver displays each combination of TV/Radio and satellite as a separate list starting at 1
*/
class AmdbSerializer : SerializerBase
{
private readonly HashSet<string> tableNames = new();

private readonly ChannelList tv = new (SignalSource.Tv, "TV");
private readonly ChannelList radio = new(SignalSource.Radio, "Radio");
private readonly ChannelList data = new(SignalSource.Data, "Data");
private readonly IDictionary<Tuple<SignalSource, int>, ChannelList> listBySatellite = new ListDictionary<Tuple<SignalSource, int>, ChannelList>();
private static readonly SignalSource[] SignalSources = { SignalSource.Tv, SignalSource.Radio, SignalSource.Data };
private static readonly string[] SignalSourceNames = { "TV", "Radio", "Other" };

private readonly Dictionary<int, int> onidByNetId = new Dictionary<int, int>();

#region ctor()
public AmdbSerializer(string inputFile) : base(inputFile)
Expand All @@ -27,10 +36,6 @@ public AmdbSerializer(string inputFile) : base(inputFile)
this.Features.CanHaveGaps = false;
this.Features.AllowGapsInFavNumbers = false;
this.Features.FavoritesMode = FavoritesMode.None;

this.DataRoot.AddChannelList(tv);
this.DataRoot.AddChannelList(radio);
this.DataRoot.AddChannelList(data);
}
#endregion

Expand All @@ -56,6 +61,7 @@ public override void Load()
throw LoaderException.TryNext("File doesn't contain the expected tables");

this.ReadSatellites(cmd);
this.ReadNetworks(cmd);
this.ReadTransponders(cmd);
this.ReadChannels(cmd);
this.AdjustColumns();
Expand Down Expand Up @@ -91,31 +97,61 @@ private void ReadSatellites(SqliteCommand cmd)
pos = 360 - pos;
eastWest = "W";
}

sat.OrbitalPosition = $"{pos / 10}.{pos % 10}{eastWest}";
sat.Name = r.GetString(1);
this.DataRoot.AddSatellite(sat);

int i = 0;
foreach (var ss in SignalSources)
{
var list = new ChannelList(SignalSource.Sat | ss, SignalSourceNames[i++] + " " + sat.Name);
this.listBySatellite[Tuple.Create(ss, sat.Id)] = list;
}
}

foreach (var entry in this.listBySatellite.OrderBy(e => e.Key.Item1))
{
if ((entry.Key.Item1 & SignalSource.Data) != 0)
continue;
this.DataRoot.AddChannelList(entry.Value);
}
}
#endregion

#region ReadNetworks()
private void ReadNetworks(SqliteCommand cmd)
{
cmd.CommandText = "select db_id, network_id from net_table";
using var r = cmd.ExecuteReader();
while (r.Read())
onidByNetId[r.GetInt32(0)] = r.GetInt32(1);
}
#endregion

#region ReadTransponders()
private void ReadTransponders(SqliteCommand cmd)
{
cmd.CommandText = "select db_id, db_sat_para_id, freq, polar, symb from ts_table";
cmd.CommandText = "select db_id, db_sat_para_id, db_net_id, ts_id, freq, polar, symb from ts_table";
using var r = cmd.ExecuteReader();
while (r.Read())
{
int id = r.GetInt32(0);
int satId = r.GetInt32(1);
int freq = r.GetInt32(2);
var netId = r.GetInt32(2);
var tsid = r.GetInt32(3);
int freq = r.GetInt32(4);

if (this.DataRoot.Transponder.TryGet(id) != null)
continue;
Transponder tp = new Transponder(id);
tp.TransportStreamId = tsid;
tp.FrequencyInMhz = (int)(freq/1000);
tp.Polarity = r.GetInt32(3) == 0 ? 'H' : 'V';
tp.Polarity = r.GetInt32(5) == 0 ? 'H' : 'V';
tp.Satellite = this.DataRoot.Satellites.TryGet(satId);
tp.SymbolRate = r.GetInt32(4) / 1000;
this.onidByNetId.TryGetValue(netId, out var onid);
tp.OriginalNetworkId = onid;
tp.SymbolRate = r.GetInt32(6) / 1000;
this.DataRoot.AddTransponder(tp.Satellite, tp);
}
}
Expand All @@ -125,15 +161,12 @@ private void ReadTransponders(SqliteCommand cmd)
private void ReadChannels(SqliteCommand cmd)
{
int ixP = 0;
int ixST = ixP + 12;

cmd.CommandText = @"
select
p.db_id, p.chan_num, p.name, p.service_id, p.vid_pid, p.pcr_pid, p.service_type, p.vid_fmt, p.free_ca_mode, p.lock, p.skip, p.hidden,
t.db_sat_para_id, 0, t.ts_id, t.freq, t.polar, t.symb
p.db_id, p.chan_num, p.name, p.service_id, p.vid_pid, p.pcr_pid, p.service_type, p.vid_fmt, p.free_ca_mode, p.lock, p.skip, p.hidden, p.db_net_id, p.db_ts_id, p.db_sat_para_id
from srv_table p
left outer join ts_table t on t.db_id=p.db_ts_id
order by p.chan_num";
order by t.db_sat_para_id, case p.service_type when 0 then 3 when 1 then 0 when 2 then 1 else p.service_type end, p.chan_num";

using var r = cmd.ExecuteReader();
while (r.Read())
Expand All @@ -142,8 +175,10 @@ from srv_table p
var oldProgNr = r.GetInt32(ixP + 1);
var name = r.GetString(ixP + 2);
if (name.StartsWith("xxx"))
name = name.Substring(3);
ChannelInfo channel = new ChannelInfo(0, handle, oldProgNr, name);
name = name.Substring(3).Trim();

ChannelInfo channel = new ChannelInfo(0, handle, 0, name);
channel.RecordOrder = oldProgNr;
channel.ServiceId = r.GetInt32(ixP + 3) & 0x7FFF;
channel.VideoPid = r.GetInt32(ixP + 4);
channel.PcrPid = r.IsDBNull(ixP + 5) ? 0 : r.GetInt32(ixP + 5);
Expand All @@ -170,25 +205,32 @@ from srv_table p
channel.Skip = r.GetBoolean(ixP + 10);
channel.Hidden = r.GetBoolean(ixP + 11);

// DVB-S
if (!r.IsDBNull(ixST + 0))
var netDbId = r.GetInt32(ixP + 12);
this.onidByNetId.TryGetValue(netDbId, out var onid);
channel.OriginalNetworkId = onid;

var tsDbId = r.GetInt32(ixP + 13);
DataRoot.Transponder.TryGetValue(tsDbId, out var transponder);
if (transponder != null)
{
var satId = r.GetInt32(ixST + 0);
var sat = this.DataRoot.Satellites.TryGet(satId);
channel.Satellite = sat?.Name;
channel.SatPosition = sat?.OrbitalPosition;
channel.OriginalNetworkId = r.GetInt32(ixST + 1) & 0x7FFF;
channel.TransportStreamId = r.GetInt32(ixST + 2) & 0x7FFF;
channel.FreqInMhz = r.GetInt32(ixST + 3);
channel.Transponder = transponder;
channel.TransportStreamId = transponder.TransportStreamId;
channel.Satellite = transponder.Satellite?.Name;
channel.SatPosition = transponder.Satellite?.OrbitalPosition;
channel.FreqInMhz = transponder.FrequencyInMhz;
if (channel.FreqInMhz > 20000) // DVB-S is in MHz already, DVB-C/T in kHz
channel.FreqInMhz /= 1000;
channel.Polarity = r.GetInt32(ixST + 4) == 0 ? 'H' : 'V';
channel.SymbolRate = r.GetInt32(ixST + 5)/1000;
channel.Polarity = transponder.Polarity;
channel.SymbolRate = transponder.SymbolRate;
}

var list = this.DataRoot.GetChannelList(channel.SignalSource);
if (list != null)
var satId = r.GetInt32(ixP + 14);
var key = Tuple.Create(channel.SignalSource & SignalSource.MaskTvRadioData, satId);
if (this.listBySatellite.TryGetValue(key, out var list))
{
channel.OldProgramNr = list.Count + 1;
this.DataRoot.AddChannel(list, channel);
}
}
}
#endregion
Expand Down Expand Up @@ -229,9 +271,10 @@ public override void Save()
#region WriteChannels()
private void WriteChannels(SqliteCommand cmd, SqliteCommand cmdDelete)
{
cmd.CommandText = "update srv_table set chan_num=@nr, chan_order=@nr, name=@name, skip=@skip, lock=@lock, hidden=@hide where db_id=@handle";
cmd.CommandText = "update srv_table set chan_num=@num, chan_order=@order, name=@name, skip=@skip, lock=@lock, hidden=@hide where db_id=@handle";
cmd.Parameters.Add("@handle", SqliteType.Integer);
cmd.Parameters.Add("@nr", SqliteType.Integer);
cmd.Parameters.Add("@num", SqliteType.Integer);
cmd.Parameters.Add("@order", SqliteType.Integer);
cmd.Parameters.Add("@name", SqliteType.Text);
cmd.Parameters.Add("@skip", SqliteType.Integer);
cmd.Parameters.Add("@lock", SqliteType.Integer);
Expand All @@ -242,31 +285,46 @@ private void WriteChannels(SqliteCommand cmd, SqliteCommand cmdDelete)
cmdDelete.Parameters.Add("@handle", SqliteType.Integer);
cmdDelete.Prepare();

foreach (var list in this.DataRoot.ChannelLists)
// combine all lists (including "others") into a single one
IEnumerable<ChannelInfo> union = new List<ChannelInfo>();
foreach (var list in this.listBySatellite.Values)
union = union.Concat(list.Channels);

var chanOrder = 1;
var allChannels = union.OrderBy(SignalSourceOrder).ThenBy(ch => ch.NewProgramNr).ToList();
foreach (ChannelInfo channel in allChannels)
{
foreach (ChannelInfo channel in list.Channels.OrderBy(ch => (ch.SignalSource & SignalSource.Tv) != 0 ? 0 : 1).ThenBy(ch => ch.NewProgramNr))
if (channel.IsProxy) // ignore reference list proxy channels
continue;

if (channel.IsDeleted)
{
cmdDelete.Parameters["@handle"].Value = channel.RecordIndex;
cmdDelete.ExecuteNonQuery();
}
else
{
if (channel.IsProxy) // ignore reference list proxy channels
continue;

if (channel.IsDeleted)
{
cmdDelete.Parameters["@handle"].Value = channel.RecordIndex;
cmdDelete.ExecuteNonQuery();
}
else
{
channel.UpdateRawData();
cmd.Parameters["@handle"].Value = channel.RecordIndex;
cmd.Parameters["@nr"].Value = channel.NewProgramNr;
cmd.Parameters["@name"].Value = "xxx" + channel.Name;
cmd.Parameters["@skip"].Value = channel.Skip ? 1 : 0;
cmd.Parameters["@lock"].Value = channel.Lock ? 1 : 0;
cmd.Parameters["@hide"].Value = channel.Hidden ? 1 : 0;
cmd.ExecuteNonQuery();
}
channel.UpdateRawData();
cmd.Parameters["@handle"].Value = channel.RecordIndex;
cmd.Parameters["@num"].Value = channel.NewProgramNr;
cmd.Parameters["@order"].Value = chanOrder++;
cmd.Parameters["@name"].Value = "xxx" + channel.Name;
cmd.Parameters["@skip"].Value = channel.Skip ? 1 : 0;
cmd.Parameters["@lock"].Value = channel.Lock ? 1 : 0;
cmd.Parameters["@hide"].Value = channel.Hidden ? 1 : 0;
cmd.ExecuteNonQuery();
}
}

int SignalSourceOrder(ChannelInfo ch)
{
var ss = ch.SignalSource;
if ((ss & SignalSource.Tv) != 0)
return 0;
if ((ss & SignalSource.Radio) != 0)
return 1;
return 2;
}
}
#endregion
}
Expand Down
Loading

0 comments on commit 8ac45d5

Please sign in to comment.