- hopefully fixed corrupted Sony lists when channels were deleted

- upgrade to DX 19.2.6
- detect file corruption when all bytes have same value instead of just 0x00 (as there are broken files with 0xFF too)
This commit is contained in:
Horst Beham
2020-02-02 23:31:44 +01:00
parent 860fe2bab4
commit a901a7fa65
23 changed files with 447 additions and 382 deletions

View File

@@ -54,6 +54,16 @@ namespace ChanSort.Loader.Sony
NotDeletedByUserOption = 0x0200,
Radio = 0x0400,
Encrypted = 0x0800,
MaskWhenDeleted = 0x0206
}
[Flags]
private enum NwOptionMask : uint
{
NameEdited = 1 << 3, // guess based on values from Hisense
ChNumEdited = 1 << 10, // used by Sony Channel Editor 1.2.0, SetEdit 1.21 and Hisense
DeletedByUser = 1 << 13 // used by Sony Channel Editor 1.2.0 and Hisense
}
#endregion
@@ -325,7 +335,8 @@ namespace ChanSort.Loader.Sony
{
var recId = int.Parse(svcData["ui2_svl_rec_id"][i]);
var chan = new Channel(signalSource, i, recId);
chan.OldProgramNr = (ParseInt(svcData["No"][i]) >> 18) & 0x3FFFF;
var no = ParseInt(svcData["No"][i]);
chan.OldProgramNr = (int)((uint)no >> 18);
var nwMask = (NwMask)uint.Parse(svcData["ui4_nw_mask"][i]);
chan.AddDebug("NW=");
chan.AddDebug((uint)nwMask);
@@ -338,7 +349,7 @@ namespace ChanSort.Loader.Sony
chan.Encrypted |= dvbData["t_free_ca_mode"][i] == "1";
chan.Favorites = (Favorites) ((uint)(nwMask & NwMask.FavMask) >> 4);
chan.ServiceId = int.Parse(svcData["ui2_prog_id"][i]);
chan.Name = svcData["Name"][i];
chan.Name = svcData["Name"][i].Replace("&amp;", "&");
var favNumbers = svcData["aui1_custom_data"][i]?.Split(' ');
if (favNumbers != null)
{
@@ -375,10 +386,12 @@ namespace ChanSort.Loader.Sony
}
chan.ServiceType = int.Parse(dvbData["ui1_sdt_service_type"][i]);
if ((nwMask & NwMask.Radio) != 0)
if ((no & 0x07) == 1)
chan.SignalSource |= SignalSource.Tv;
else if ((no & 0x07) == 2)
chan.SignalSource |= SignalSource.Radio;
else
chan.SignalSource |= LookupData.Instance.IsRadioTvOrData(chan.ServiceType);
chan.SignalSource |= SignalSource.Data;
CopyDataValues(serviceNode, svcData, i, chan.ServiceData);
@@ -466,7 +479,10 @@ namespace ChanSort.Loader.Sony
{
if (node.Attributes?["loop"] == null)
continue;
var lines = node.InnerText.Trim('\n').Split('\n');
var inner = node.InnerText;
if (inner.Length >= 2)
inner = inner.Substring(1, inner.Length - 2); // remove new-lines that follow/lead the XML tag
var lines = inner.Split('\n');
dict[node.LocalName] = lines.Length == 1 && lines[0] == "" ? new string[0] : lines;
}
@@ -566,28 +582,29 @@ namespace ChanSort.Loader.Sony
#region Save()
public override void Save(string tvOutputFile)
{
// sdbT
if (this.channeListNodes.TryGetValue(SignalSource.DvbT, out var nodes))
{
var dvbt = this.DataRoot.GetChannelList(SignalSource.DvbT | SignalSource.Tv).Channels
.Concat(this.DataRoot.GetChannelList(SignalSource.DvbT | SignalSource.Radio).Channels)
.Concat(this.DataRoot.GetChannelList(SignalSource.DvbT | SignalSource.Data).Channels)
.ToList();
this.UpdateChannelList(dvbt, nodes);
this.UpdateChannelListNode(nodes,
this.DataRoot.GetChannelList(SignalSource.DvbT | SignalSource.Tv),
this.DataRoot.GetChannelList(SignalSource.DvbT | SignalSource.Radio),
this.DataRoot.GetChannelList(SignalSource.DvbT | SignalSource.Data));
}
// sdbC
if (this.channeListNodes.TryGetValue(SignalSource.DvbC, out nodes))
{
var dvbc = this.DataRoot.GetChannelList(SignalSource.DvbC | SignalSource.Tv).Channels
.Concat(this.DataRoot.GetChannelList(SignalSource.DvbC | SignalSource.Radio).Channels)
.Concat(this.DataRoot.GetChannelList(SignalSource.DvbC | SignalSource.Data).Channels)
.ToList();
this.UpdateChannelList(dvbc, nodes);
this.UpdateChannelListNode(nodes,
this.DataRoot.GetChannelList(SignalSource.DvbC | SignalSource.Tv),
this.DataRoot.GetChannelList(SignalSource.DvbC | SignalSource.Radio),
this.DataRoot.GetChannelList(SignalSource.DvbC | SignalSource.Data));
}
// sdbGs, sdbPs, sdbCis
foreach (var list in this.DataRoot.ChannelLists)
{
if ((list.SignalSource & SignalSource.DvbS) == SignalSource.DvbS && this.channeListNodes.TryGetValue(list.SignalSource & ~SignalSource.MaskTvRadioData, out nodes))
this.UpdateChannelList(list.Channels, nodes);
this.UpdateChannelListNode(nodes, list);
}
// by default .NET reformats the whole XML. These settings produce almost same format as the TV xml files use
@@ -632,26 +649,24 @@ namespace ChanSort.Loader.Sony
}
#endregion
#region UpdateChannelList()
private void UpdateChannelList(IList<ChannelInfo> channels, ChannelListNodes nodes)
#region UpdateChannelListNode()
private void UpdateChannelListNode(ChannelListNodes nodes, params ChannelList[] channelLists)
{
if (this.isEFormat || channels.Any(ch => ch.IsNameModified))
this.UpdateDataInChildNodes(nodes.Service, channels.OrderBy(c => c.RecordOrder), ch => true, ch => ch.ServiceData, this.GetNewValueForServiceNode);
if (!this.isEFormat)
this.UpdateDataInChildNodes(nodes.Programme, channels.OrderBy(c => c.NewProgramNr), ch => !(ch.IsDeleted || ch.NewProgramNr < 0), ch => ch.ProgrammeData, this.GetNewValueForProgrammeNode);
int serviceCount = 0, programmeCount = 0;
var sbService = this.CreateStringBuilderDict(nodes.Service);
var sbProgramme = this.CreateStringBuilderDict(nodes.Programme);
foreach(var list in channelLists)
this.UpdateChannelList(sbService, sbProgramme, ref serviceCount, ref programmeCount, list.Channels);
this.ApplyStringBuilderDictToXmlNodes(nodes.Service, sbService, serviceCount);
this.ApplyStringBuilderDictToXmlNodes(nodes.Programme, sbProgramme, programmeCount);
}
#endregion
#region UpdateDataInChildNodes()
void UpdateDataInChildNodes(
XmlNode parentNode,
IEnumerable<ChannelInfo> channels,
Predicate<ChannelInfo> accept,
Func<Channel,Dictionary<string,string>> getChannelData,
Func<Channel, string, string, string> getNewValue)
#region CreateStringBuilderDict()
private Dictionary<string, StringBuilder> CreateStringBuilderDict(XmlNode parentNode)
{
var count = 0;
if (parentNode == null)
return null;
var sbDict = new Dictionary<string, StringBuilder>();
foreach (XmlNode node in parentNode.ChildNodes)
{
@@ -659,6 +674,42 @@ namespace ChanSort.Loader.Sony
sbDict[node.LocalName] = new StringBuilder(this.newline);
}
return sbDict;
}
#endregion
#region UpdateChannelList()
private void UpdateChannelList(Dictionary<string, StringBuilder> sbDictService, Dictionary<string, StringBuilder> sbDictProgramme,
ref int serviceCount, ref int programmeCount, IList<ChannelInfo> channels)
{
if (this.isEFormat)
{
// keep original record order in the <Service> element so that we don't need to reorder data in <Service><dvb_info> and its
// <t_svc_replmnt_info>, <t_ca_replmnt_info>, <t_cmplt_eit_replmnt_info>, <t_hd_simulcat_info>, <t_orig_simulcat_info> child nodes
// (Sony Channel Editor 1.2.0 does it the same way, but that tool is questionable since it generates an invalid checksum)
// however, as some sample files suggest, when the TV re-exports a modified list, it re-orders the channels by "ServiceFilter"+"No"
this.AddDataToStringBuilders(sbDictService, ref serviceCount, channels.OrderBy(c => c.RecordOrder), ch => true, ch => ch.ServiceData, this.GetNewValueForServiceNode);
}
else
{
if (channels.Any(ch => ch.IsNameModified))
this.AddDataToStringBuilders(sbDictService, ref serviceCount, channels.OrderBy(c => c.RecordOrder), ch => true, ch => ch.ServiceData, this.GetNewValueForServiceNode);
this.AddDataToStringBuilders(sbDictProgramme, ref programmeCount, channels.OrderBy(c => c.NewProgramNr), ch => !(ch.IsDeleted || ch.NewProgramNr < 0), ch => ch.ProgrammeData,
this.GetNewValueForProgrammeNode);
}
}
#endregion
#region AddDataToStringBuilders()
void AddDataToStringBuilders(
Dictionary<string, StringBuilder> sbDict,
ref int count,
IEnumerable<ChannelInfo> channels,
Predicate<ChannelInfo> accept,
Func<Channel,Dictionary<string,string>> getChannelData,
Func<Channel, string, string, string> getNewValue)
{
foreach (var channel in channels)
{
var ch = channel as Channel;
@@ -676,14 +727,6 @@ namespace ChanSort.Loader.Sony
}
++count;
}
foreach (XmlNode node in parentNode.ChildNodes)
{
if (sbDict.TryGetValue(node.LocalName, out var sb))
{
node.InnerText = sb.ToString();
node.Attributes["loop"].InnerText = count.ToString();
}
}
}
#endregion
@@ -691,16 +734,23 @@ namespace ChanSort.Loader.Sony
private string GetNewValueForServiceNode(Channel ch, string field, string value)
{
if (field == "Name")
return ch.IsNameModified ? ch.Name : value;
return ch.IsNameModified ? ch.Name.Replace("&", "&amp;") : value; // TV has the XML element double-escaped like &amp;amp;
if (this.isEFormat)
{
if (field == "b_deleted_by_user")
return ch.IsDeleted ? "0" : "1"; // file seems to contain reverse logic (1 = not deleted)
if (field == "No")
return ((ch.NewProgramNr << 18) | (int.Parse(value) & 0x3FFFF)).ToString();
return ((ch.NewProgramNr << 18) | (int.Parse(value) & 0x3FFFF)).ToString(); // Sony Channel Editor 1.2.0 exports 9999 as new No for all deleted channels, we use unique numbers
if (field == "ui4_nw_mask")
return (((uint)ch.Favorites << 4) | (ch.Hidden ? 0u : (uint)NwMask.Visible) | (uint.Parse(value) & ~(uint)(NwMask.FavMask|NwMask.Visible))).ToString();
{
var mask = ((uint) ch.Favorites << 4) | (ch.Hidden ? 0u : (uint) NwMask.Visible) | (uint.Parse(value) & ~(uint) (NwMask.FavMask | NwMask.Visible));
if (ch.IsDeleted)
mask &= ~(uint)NwMask.MaskWhenDeleted;
return mask.ToString();
}
if (field == "ui4_nw_option_mask")
return (uint.Parse(value) | (uint)(NwOptionMask.ChNumEdited | (ch.IsNameModified ? NwOptionMask.NameEdited : 0) | (ch.IsDeleted ? NwOptionMask.DeletedByUser : 0))).ToString();
if (field == "aui1_custom_data") // mixed favorite list position
{
var vals = value.Split(' ');
@@ -723,6 +773,23 @@ namespace ChanSort.Loader.Sony
return value;
}
#endregion
#region ApplyStringBuilderDictToXmlNodes()
private void ApplyStringBuilderDictToXmlNodes(XmlNode parentNode, Dictionary<string, StringBuilder> sbDict, int count)
{
if (parentNode == null)
return;
foreach (XmlNode node in parentNode.ChildNodes)
{
if (sbDict.TryGetValue(node.LocalName, out var sb))
{
node.InnerText = sb.ToString();
node.Attributes["loop"].InnerText = count.ToString();
}
}
}
#endregion
}
class ChannelListNodes