mirror of
https://github.com/PredatH0r/ChanSort.git
synced 2026-02-20 05:16:45 +01:00
- implemented saving for "e" and normal formats
- correct handling for "e" program numbers - code cleanup
This commit is contained in:
10
readme.md
10
readme.md
@@ -9,9 +9,9 @@ Links
|
||||
|
||||
About ChanSort
|
||||
--------------
|
||||
ChanSort is a PC/Windows application that allows you to reorder your TV's channel list.
|
||||
ChanSort is a Windows application that allows you to reorder your TV's channel list.
|
||||
Most modern TVs can transfer channel lists via USB stick, which you can plug into your PC.
|
||||
ChanSort supports various models from Hisense, Samsung, LG, Panasonic, Toshiba and the Linux VDR project.
|
||||
ChanSort supports various file formats from **Sony** (new), **Silva-Schneider** (new), Hisense, Samsung, LG, Panasonic, Toshiba and the Linux VDR project.
|
||||
|
||||

|
||||
|
||||
@@ -85,10 +85,16 @@ the channel list from/to USB can be found in the Wiki.
|
||||
**Panasonic**
|
||||
Viera models with an svl.bin or svl.db channel list (most models since 2011)
|
||||
|
||||
**Sony**
|
||||
Android-TVs "sdb.xml" files using formats "FormateVer" 1.1.0 and "FormatVer" 1.0.0, 1.1.0 and 1.2.0
|
||||
|
||||
**Toshiba**
|
||||
Models that export a .zip file containing chmgt.db, dvbSysData.db and dvbMainData.db files.
|
||||
(e.g. RL, SL, TL, UL, VL, WL, XL, YL models of series 8xx/9xx)
|
||||
|
||||
**Silva-Schneider**
|
||||
.sdx files (currently only satellite lists are supported)
|
||||
|
||||
**VDR (Linux Video Disk Recorder)**
|
||||
Supports the channels.conf file format.
|
||||
Implementation for this was provided by TCr82 from the VDR project.
|
||||
|
||||
10
readme_de.md
10
readme_de.md
@@ -9,9 +9,9 @@ Links
|
||||
|
||||
<EFBFBD>ber ChanSort
|
||||
--------------
|
||||
ChanSort ist eine PC/Windows-Anwendung, die das Sortieren von Fernsehsenderlisten erlaubt.
|
||||
ChanSort ist eine Windows-Anwendung, die das Sortieren von Fernsehsenderlisten erlaubt.
|
||||
Die meisten modernen Fernseher k<>nnen Senderlisten auf einen USB-Stick <20>bertragen, den man danach am PC anschlie<69>t.
|
||||
ChanSort unterst<73>tzt diverse Modelle von **Hisense** (NEU), LG, Panasonic, Samsung, Toshiba und das Linux VDR Projekt.
|
||||
ChanSort unterst<73>tzt diverse Dateiformate von **Sony** (NEU), **Silva-Schneider** (NEU), Hisense, LG, Panasonic, Samsung, Toshiba und das Linux VDR Projekt.
|
||||
|
||||

|
||||
|
||||
@@ -80,6 +80,12 @@ Eine Anleitung zum Aufruf des geheimen Service-Men
|
||||
**Panasonic**
|
||||
Viera-Modelle mit svl.bin oder svl.db Dateien (die meisten Modelle seit 2011)
|
||||
|
||||
**Sony**
|
||||
Android-TV "sdb.xml" Dateien mit Versionen "FormateVer" 1.1.0 and "FormatVer" 1.0.0, 1.1.0 and 1.2.0
|
||||
|
||||
**Silva-Schneider**
|
||||
.sdx Dateien (derzeit wird nur Satellitenempfang unterst<73>tzt)
|
||||
|
||||
**Toshiba**
|
||||
Modelle, die eine .zip-Datei mit folgendem Inhalt: chmgt.db, dvbSysData.db und dvbMainData.db.
|
||||
(z.B. RL, SL, TL, UL, VL, WL, XL, YL models of series 8xx/9xx)
|
||||
|
||||
@@ -21,6 +21,8 @@ namespace ChanSort.Loader.Sony
|
||||
* NOTE: Even within the same version, there are some files using CRLF and some using LF for newlines.
|
||||
*/
|
||||
|
||||
private const string SupportedFormatVersions = " e1.1.0 1.0.0 1.1.0 1.2.0 ";
|
||||
|
||||
private readonly ChannelList terrChannels = new ChannelList(SignalSource.DvbC | SignalSource.Tv | SignalSource.Radio, "DVB-T");
|
||||
private readonly ChannelList cableChannels = new ChannelList(SignalSource.DvbC | SignalSource.Tv | SignalSource.Radio, "DVB-C");
|
||||
private readonly ChannelList satChannels = new ChannelList(SignalSource.DvbS | SignalSource.Tv | SignalSource.Radio, "DVB-S");
|
||||
@@ -31,6 +33,7 @@ namespace ChanSort.Loader.Sony
|
||||
private byte[] content;
|
||||
private string textContent;
|
||||
private string format;
|
||||
private bool isEFormat;
|
||||
private string newline;
|
||||
|
||||
private readonly Dictionary<ChannelList, ChannelListNodes> channeListNodes = new Dictionary<ChannelList, ChannelListNodes>();
|
||||
@@ -136,7 +139,7 @@ namespace ChanSort.Loader.Sony
|
||||
}
|
||||
}
|
||||
|
||||
if (this.format.StartsWith("e"))
|
||||
if (!this.isEFormat)
|
||||
{
|
||||
satChannels.VisibleColumnFieldNames.Remove("Hidden");
|
||||
satChannels.VisibleColumnFieldNames.Remove("Satellite");
|
||||
@@ -148,13 +151,17 @@ namespace ChanSort.Loader.Sony
|
||||
private void ReadSdbXml(XmlNode node)
|
||||
{
|
||||
this.format = "";
|
||||
this.isEFormat = false;
|
||||
var formatNode = node["FormatVer"];
|
||||
if (formatNode != null)
|
||||
this.format = formatNode.InnerText;
|
||||
else if ((formatNode = node["FormateVer"]) != null)
|
||||
{
|
||||
this.format = "e" + formatNode.InnerText;
|
||||
this.isEFormat = true;
|
||||
}
|
||||
|
||||
if (" e1.1.0 1.0.0 1.1.0 1.2.0 ".IndexOf(" " + this.format + " ", StringComparison.Ordinal) < 0)
|
||||
if (SupportedFormatVersions.IndexOf(" " + this.format + " ", StringComparison.Ordinal) < 0)
|
||||
throw new FileLoadException("Unsupported file format version: " + this.format);
|
||||
|
||||
foreach(XmlNode child in node.ChildNodes)
|
||||
@@ -183,7 +190,7 @@ namespace ChanSort.Loader.Sony
|
||||
this.ReadSatellites(node, idAdjustment);
|
||||
this.ReadTransponder(node, idAdjustment, dvbSystem);
|
||||
|
||||
if (this.format.StartsWith("e"))
|
||||
if (this.isEFormat)
|
||||
this.ReadServicesE110(node, list, idAdjustment);
|
||||
else
|
||||
this.ReadServices(node, list, idAdjustment);
|
||||
@@ -215,7 +222,6 @@ namespace ChanSort.Loader.Sony
|
||||
var mux = node["Multiplex"] ?? throw new FileLoadException("Missing Multiplex XML element");
|
||||
|
||||
var muxData = SplitLines(mux);
|
||||
var isEFormat = this.format.StartsWith("e");
|
||||
var muxIds = isEFormat ? muxData["MuxID"] : muxData["MuxRowId"];
|
||||
var rfParmData = isEFormat ? null : SplitLines(mux["RfParam"]);
|
||||
var dvbsData = isEFormat ? null : SplitLines(mux["RfParam"]?[dvbSystem]);
|
||||
@@ -256,11 +262,15 @@ namespace ChanSort.Loader.Sony
|
||||
var svcData = SplitLines(serviceNode);
|
||||
var dvbData = SplitLines(serviceNode["dvb_info"]);
|
||||
|
||||
// remember the nodes that need to be updated when saving
|
||||
var nodes = this.channeListNodes[list];
|
||||
nodes.Service = serviceNode;
|
||||
|
||||
for (int i = 0, c = svcData["ui2_svl_rec_id"].Length; i < c; i++)
|
||||
{
|
||||
var recId = int.Parse(svcData["ui2_svl_rec_id"][i]);
|
||||
var chan = new Channel(list.SignalSource, i, recId);
|
||||
chan.OldProgramNr = recId;
|
||||
chan.OldProgramNr = (int)((uint)ParseInt(svcData["No"][i]) >> 18);
|
||||
chan.IsDeleted = svcData["b_deleted_by_user"][i] != "1";
|
||||
var nwMask = int.Parse(svcData["ui4_nw_mask"][i]);
|
||||
chan.Hidden = (nwMask & 8) == 0;
|
||||
@@ -293,6 +303,8 @@ namespace ChanSort.Loader.Sony
|
||||
chan.ServiceType = int.Parse(dvbData["ui1_sdt_service_type"][i]);
|
||||
chan.SignalSource |= LookupData.Instance.IsRadioOrTv(chan.ServiceType);
|
||||
|
||||
CopyDataValues(serviceNode, svcData, i, chan.ServiceData);
|
||||
|
||||
this.DataRoot.AddChannel(list, chan);
|
||||
}
|
||||
}
|
||||
@@ -309,8 +321,8 @@ namespace ChanSort.Loader.Sony
|
||||
|
||||
// remember the nodes that need to be updated when saving
|
||||
var nodes = this.channeListNodes[list];
|
||||
nodes.Service = node["Service"];
|
||||
nodes.Programme = node["Programme"];
|
||||
nodes.Service = serviceNode;
|
||||
nodes.Programme = progNode;
|
||||
|
||||
var map = new Dictionary<int, Channel>();
|
||||
for (int i = 0, c = svcData["ServiceRowId"].Length; i < c; i++)
|
||||
@@ -342,11 +354,10 @@ namespace ChanSort.Loader.Sony
|
||||
chan.SignalSource |= LookupData.Instance.IsRadioOrTv(chan.ServiceType);
|
||||
var att = this.ParseInt(svcData["Attribute"][i]);
|
||||
chan.Encrypted = (att & 8) != 0;
|
||||
this.DataRoot.AddChannel(list, chan);
|
||||
|
||||
// keep a copy of all data values (including unknowns) so they can be saved again correctly
|
||||
foreach (XmlNode child in serviceNode.ChildNodes)
|
||||
chan.ServiceData[child.LocalName] = svcData[child.LocalName][i];
|
||||
CopyDataValues(serviceNode, svcData, i, chan.ServiceData);
|
||||
|
||||
this.DataRoot.AddChannel(list, chan);
|
||||
}
|
||||
|
||||
for (int i = 0, c = progData["ServiceRowId"].Length; i < c; i++)
|
||||
@@ -360,9 +371,7 @@ namespace ChanSort.Loader.Sony
|
||||
var flag = int.Parse(progData["Flag"][i]);
|
||||
chan.Favorites = (Favorites)(flag & 0x0F);
|
||||
|
||||
// keep a copy of all data values (including unknowns) so they can be saved again correctly
|
||||
foreach (XmlNode child in progNode.ChildNodes)
|
||||
chan.ProgrammeData[child.LocalName] = progData[child.LocalName][i];
|
||||
CopyDataValues(progNode, progData, i, chan.ProgrammeData);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
@@ -376,46 +385,67 @@ namespace ChanSort.Loader.Sony
|
||||
if (node.Attributes?["loop"] == null)
|
||||
continue;
|
||||
var lines = node.InnerText.Trim('\n').Split('\n');
|
||||
dict[node.LocalName] = lines;
|
||||
dict[node.LocalName] = lines.Length == 1 && lines[0] == "" ? new string[0] : lines;
|
||||
}
|
||||
|
||||
return dict;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region CopyDataValues()
|
||||
private void CopyDataValues(XmlNode parentNode, Dictionary<string, string[]> svcData, int i, Dictionary<string, string> target)
|
||||
{
|
||||
// copy of data values from all child nodes into the channel.
|
||||
// this inverts the [field,channel] data presentation from the file to [channel,field] and is later used for saving channels
|
||||
foreach (XmlNode child in parentNode.ChildNodes)
|
||||
{
|
||||
var field = child.LocalName;
|
||||
if (svcData.ContainsKey(field))
|
||||
target[field] = svcData[field][i];
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region ReadChecksum()
|
||||
|
||||
private void ReadChecksum(XmlNode node)
|
||||
{
|
||||
byte[] data;
|
||||
// skip "0x" prefix ("e"-format doesn't have it)
|
||||
uint expectedCrc = uint.Parse(this.isEFormat ? node.InnerText : node.InnerText.Substring(2), NumberStyles.HexNumber);
|
||||
|
||||
uint crc = CalcChecksum(this.content, this.textContent);
|
||||
|
||||
if (crc != expectedCrc)
|
||||
throw new FileLoadException($"Invalid checksum: expected 0x{expectedCrc:x8}, calculated 0x{crc:x8}");
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region CalcChecksum()
|
||||
private uint CalcChecksum(byte[] data, string dataAsText)
|
||||
{
|
||||
int start;
|
||||
int end;
|
||||
uint expectedCrc;
|
||||
|
||||
if (this.format.StartsWith("e"))
|
||||
if (this.isEFormat)
|
||||
{
|
||||
// files with the typo-element "<FormateVer>1.1.0</FormateVer>" differ from other formats:
|
||||
// files with the typo-element "<FormateVer>1.1.0</FormateVer>" differ from the other formats:
|
||||
// - "\n" after the closing <SdbXml> Tag is included in the checksum,
|
||||
// - the value in the <CheckSum> element has no "0x" prefix
|
||||
// - the file's bytes are used as-is for the calculation, without any prior XML cleanup
|
||||
data = this.content;
|
||||
start = this.FindMarker(this.content, "<SdbXml>");
|
||||
end = this.FindMarker(this.content, "</SdbXml>") + 10; // including the \n at the end
|
||||
expectedCrc = uint.Parse(node.InnerText, NumberStyles.HexNumber);
|
||||
// - the file's bytes are used as-is for the calculation, without CRLF conversion
|
||||
start = FindMarker(data, "<SdbXml>");
|
||||
end = FindMarker(data, "</SdbXml>") + 10; // including the \n at the end
|
||||
}
|
||||
else
|
||||
{
|
||||
start = this.textContent.IndexOf("<SdbXml>", StringComparison.Ordinal);
|
||||
end = this.textContent.IndexOf("</SdbXml>", StringComparison.Ordinal) + 9;
|
||||
start = dataAsText.IndexOf("<SdbXml>", StringComparison.Ordinal);
|
||||
end = dataAsText.IndexOf("</SdbXml>", StringComparison.Ordinal) + 9;
|
||||
// the TV calculates the checksum with just LF as newline character, so we need to replace CRLF first
|
||||
var text = this.textContent.Substring(start, end - start);
|
||||
var text = dataAsText.Substring(start, end - start);
|
||||
if (this.newline == "\r\n")
|
||||
text = text.Replace("\r\n", "\n");
|
||||
|
||||
data = Encoding.UTF8.GetBytes(text);
|
||||
start = 0;
|
||||
end = data.Length;
|
||||
expectedCrc = uint.Parse(node.InnerText.Substring(2), NumberStyles.HexNumber);
|
||||
}
|
||||
|
||||
uint crc = 0xFFFFFFFF;
|
||||
@@ -424,10 +454,7 @@ namespace ChanSort.Loader.Sony
|
||||
var b = data[i];
|
||||
crc = (crc << 8) ^ Crc32Table[b ^ (crc >> 24)];
|
||||
}
|
||||
crc = ~crc;
|
||||
|
||||
if (crc != expectedCrc)
|
||||
throw new FileLoadException($"Invalid checksum: expected 0x{expectedCrc:x8}, calculated 0x{crc:x8}");
|
||||
return ~crc;
|
||||
}
|
||||
#endregion
|
||||
|
||||
@@ -446,7 +473,7 @@ namespace ChanSort.Loader.Sony
|
||||
int j;
|
||||
for (j = 1; j < len; j++)
|
||||
{
|
||||
if (this.content[i + j] != bytes[j])
|
||||
if (data[i + j] != bytes[j])
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -480,9 +507,6 @@ namespace ChanSort.Loader.Sony
|
||||
foreach (var list in this.DataRoot.ChannelLists)
|
||||
this.UpdateChannelList(list);
|
||||
|
||||
|
||||
bool isEFormat = this.format.StartsWith("e");
|
||||
|
||||
// by default .NET reformats the whole XML. These settings produce almost same format as the TV xml files use
|
||||
var xmlSettings = new XmlWriterSettings();
|
||||
xmlSettings.Encoding = this.DefaultEncoding;
|
||||
@@ -502,7 +526,7 @@ namespace ChanSort.Loader.Sony
|
||||
xml = sw.ToString();
|
||||
}
|
||||
|
||||
// elements with a 'loop="0"' attribute must contain a newline instead of <...></...> (or <../>)
|
||||
// elements with a 'loop="0"' attribute must contain a newline instead of <...></...>
|
||||
var emptyTagsWithNewline = new[] { "loop=\"0\">", "loop=\"0\" notation=\"DEC\">", "loop=\"0\" notation=\"HEX\">" };
|
||||
foreach (var tag in emptyTagsWithNewline)
|
||||
xml = xml.Replace(tag + "</", tag + this.newline + "</");
|
||||
@@ -512,7 +536,13 @@ namespace ChanSort.Loader.Sony
|
||||
|
||||
xml += this.newline;
|
||||
|
||||
// TODO update checksum
|
||||
// put new checksum in place
|
||||
var newContent = Encoding.UTF8.GetBytes(xml);
|
||||
var crc = this.CalcChecksum(newContent, xml);
|
||||
var i1 = xml.LastIndexOf("</CheckSum>", StringComparison.Ordinal);
|
||||
var i0 = xml.LastIndexOf(">", i1, StringComparison.Ordinal);
|
||||
var hexCrc = this.isEFormat ? crc.ToString("x") : "0x" + crc.ToString("X");
|
||||
xml = xml.Substring(0, i0 + 1) + hexCrc + xml.Substring(i1);
|
||||
|
||||
var enc = new UTF8Encoding(false, false);
|
||||
File.WriteAllText(tvOutputFile, xml, enc);
|
||||
@@ -526,26 +556,11 @@ namespace ChanSort.Loader.Sony
|
||||
if (nodes == null) // this list wasn't present in the file
|
||||
return;
|
||||
|
||||
var nameModified = list.Channels.Any(ch => ch.IsNameModified);
|
||||
if (this.isEFormat || list.Channels.Any(ch => ch.IsNameModified))
|
||||
this.UpdateDataInChildNodes(nodes.Service, list.Channels.OrderBy(c => c.RecordOrder), ch => true, ch => ch.ServiceData, this.GetNewValueForServiceNode);
|
||||
|
||||
if (nameModified)
|
||||
{
|
||||
this.UpdateDataInChildNodes(nodes.Service, list.Channels.OrderBy(c => c.RecordOrder), ch => true, ch => ch.ServiceData, (ch, field, value) =>
|
||||
{
|
||||
if (field == "Name")
|
||||
return ch.Name;
|
||||
return value;
|
||||
});
|
||||
}
|
||||
|
||||
this.UpdateDataInChildNodes(nodes.Programme, list.Channels.OrderBy(c => c.NewProgramNr), ch => !(ch.IsDeleted || ch.NewProgramNr < 0), ch => ch.ProgrammeData, (ch, field, value) =>
|
||||
{
|
||||
if (field == "No")
|
||||
return ch.NewProgramNr.ToString();
|
||||
if (field == "Flag")
|
||||
return ((int)ch.Favorites & 0x0F).ToString();
|
||||
return value;
|
||||
});
|
||||
if (!this.isEFormat)
|
||||
this.UpdateDataInChildNodes(nodes.Programme, list.Channels.OrderBy(c => c.NewProgramNr), ch => !(ch.IsDeleted || ch.NewProgramNr < 0), ch => ch.ProgrammeData, this.GetNewValueForProgrammeNode);
|
||||
}
|
||||
#endregion
|
||||
|
||||
@@ -592,6 +607,36 @@ namespace ChanSort.Loader.Sony
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region GetNewValueForServiceNode()
|
||||
private string GetNewValueForServiceNode(Channel ch, string field, string value)
|
||||
{
|
||||
if (field == "Name")
|
||||
return ch.IsNameModified ? ch.Name : value;
|
||||
|
||||
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();
|
||||
if (field == "ui4_nw_mask")
|
||||
return (((int)ch.Favorites << 4) | (ch.Hidden ? 0 : 8) | (int.Parse(value) & 0x07)).ToString();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region GetNewValueForProgrammeNode()
|
||||
private string GetNewValueForProgrammeNode(Channel ch, string field, string value)
|
||||
{
|
||||
if (field == "No")
|
||||
return ch.NewProgramNr.ToString();
|
||||
if (field == "Flag")
|
||||
return ((int)ch.Favorites & 0x0F).ToString();
|
||||
return value;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
class ChannelListNodes
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
ChanSort Change Log
|
||||
===================
|
||||
|
||||
2019-07-xx
|
||||
- added partial support for Sony sdb.xml channel list format (DVB-S only)
|
||||
2019-07-16
|
||||
- added support for various Sony sdb.xml channel list formats
|
||||
- added option to disable check for program updates
|
||||
- fixed 200 Mhz offset for DVB-C frequencies (Samsung SCM)
|
||||
|
||||
2019-07-14
|
||||
- added support for Silva-Schneider .sdx channel lists
|
||||
|
||||
Reference in New Issue
Block a user