- LG webOS 5/6: setting userEditChNumber and userSelCHNo (just like the TV does) to prevent DVB-C lists (and maybe others too) for changing channel numbers after import

- m3u: support for extended header and a couple more columns
- fixed exception when the Favorites column is added manually for lists that don't support favorites
This commit is contained in:
Horst Beham
2021-10-24 22:35:32 +02:00
parent c9bbf884b6
commit 1b90ebcc78
9 changed files with 203 additions and 33 deletions

View File

@@ -1032,3 +1032,7 @@
[FirmwareData:39680]
; LB580V
offSize = 0
[webOS 5]
set_userEditChNumber=true

View File

@@ -16,9 +16,10 @@ namespace ChanSort.Loader.GlobalClone
internal class GcJsonSerializer : SerializerBase
{
private readonly string content;
string xmlPrefix;
string xmlSuffix;
private string xmlPrefix;
private string xmlSuffix;
private JObject doc;
private readonly IniFile.Section ini;
public GcJsonSerializer(string filename, string content) : base(filename)
{
@@ -42,6 +43,9 @@ namespace ChanSort.Loader.GlobalClone
this.DataRoot.AddChannelList(new ChannelList(SignalSource.DvbC | SignalSource.Radio, "DVB-C Radio"));
this.DataRoot.AddChannelList(new ChannelList(SignalSource.DvbS | SignalSource.Tv | SignalSource.Data, "DVB-S TV"));
this.DataRoot.AddChannelList(new ChannelList(SignalSource.DvbS | SignalSource.Radio, "DVB-S Radio"));
string iniFile = this.GetType().Assembly.Location.ToLowerInvariant().Replace(".dll", ".ini");
this.ini = new IniFile(iniFile).GetSection("webOS 5", false);
}
#region Load()
@@ -315,6 +319,7 @@ namespace ChanSort.Loader.GlobalClone
foreach (var list in this.DataRoot.ChannelLists)
{
int radioMask = (list.SignalSource & SignalSource.Radio) != 0 ? 0x4000 : 0;
var updateUserEditChNumber = ini.GetBool("set_userEditChNumber");
foreach (var chan in list.Channels)
{
if (!(chan is GcChannel<JToken> ch))
@@ -326,7 +331,8 @@ namespace ChanSort.Loader.GlobalClone
node["chNameBase64"] = Convert.ToBase64String(Encoding.UTF8.GetBytes(ch.Name));
}
node["deleted"] = ch.IsDeleted;
//node["deleted"] = ch.IsDeleted; // we don't support deleting in this serializer
var nr = Math.Max(ch.NewProgramNr, 0) | radioMask; // radio channels have 0x4000 added to the majorNumber
node["majorNumber"] = nr;
node["skipped"] = ch.Skip;
@@ -337,11 +343,11 @@ namespace ChanSort.Loader.GlobalClone
// the only successfully imported file was one where these flags were NOT set by ChanSort
// these flags do get set when changing numbers through the TV's menu, but then prevent further modifications, e.g. through an import
//if (ch.NewProgramNr != Math.Max(ch.OldProgramNr, 0))
//{
// node["userEditChNumber"] = false;
// node["userSelCHNo"] = false;
//}
if (updateUserEditChNumber && ch.NewProgramNr != Math.Max(ch.OldProgramNr, 0))
{
node["userEditChNumber"] = true;
node["userSelCHNo"] = true;
}
//node["disableUpdate"] = true; // No-Go! This blocked the whole list and required a factory reset. Regardless of the setting, the TV showed wrong numbers.

View File

@@ -39,7 +39,7 @@ namespace ChanSort.Loader.M3u
base.DefaultEncoding = new UTF8Encoding(false);
this.allChannels.VisibleColumnFieldNames = new List<string>()
{
"+OldPosition", "+Position", "+Name", "+FreqInMhz", "+Polarity", "+SymbolRate", "+VideoPid", "+AudioPid", "+Satellite", "+Provider"
"+OldPosition", "+Position", "+Name", "+SatPosition", "+Source", "+FreqInMhz", "+Polarity", "+SymbolRate", "+Satellite", "+Provider", "+Debug"
};
}
#endregion
@@ -60,7 +60,7 @@ namespace ChanSort.Loader.M3u
var rdr = new StreamReader(new MemoryStream(content), overrideEncoding ?? this.DefaultEncoding);
string line = rdr.ReadLine()?.TrimEnd();
if (line == null || line != "#EXTM3U")
if (line == null || !(line == "#EXTM3U" || line.StartsWith("#EXTM3U ")))
throw new FileLoadException("Unsupported .m3u file: " + this.FileName);
this.headerLines.Add(line);
@@ -122,15 +122,14 @@ namespace ChanSort.Loader.M3u
{
int progNr = 0;
string name = "";
int extInfTrackNameIndex = -1;
int extInfTrackNameIndex = -1;
if (extInfLine != null)
{
bool extInfContainsProgNr = false;
extInfTrackNameIndex = FindExtInfTrackName(extInfLine);
if (extInfTrackNameIndex >= 0)
ParseExtInf(extInfLine, out name, out extInfTrackNameIndex, out var param);
if (name != "")
{
name = extInfLine.Substring(extInfTrackNameIndex);
var match = ExtInfTrackName.Match(name);
if (!string.IsNullOrEmpty(match.Groups[1].Value))
{
@@ -139,6 +138,10 @@ namespace ChanSort.Loader.M3u
extInfContainsProgNr = true;
}
}
if (string.IsNullOrEmpty(group))
param.TryGetValue("group-title", out group);
this.allChannelsPrefixedWithProgNr &= extInfContainsProgNr;
}
@@ -156,11 +159,15 @@ namespace ChanSort.Loader.M3u
var uri = new Uri(uriLine);
chan.Satellite = uri.GetLeftPart(UriPartial.Path);
var parms = HttpUtility.ParseQueryString(uri.Query);
chan.AddDebug(uri.Query);
foreach (var key in parms.AllKeys)
{
var val = parms.Get(key);
switch (key)
{
case "src":
chan.SatPosition = "src=" + val;
break;
case "freq":
chan.FreqInMhz = this.ParseInt(val);
break;
@@ -168,18 +175,12 @@ namespace ChanSort.Loader.M3u
if (val.Length == 1)
chan.Polarity = Char.ToUpperInvariant(val[0]);
break;
case "msys":
chan.Source = val;
break;
case "sr":
chan.SymbolRate = this.ParseInt(val);
break;
case "pids":
var pids = val.Split(',');
//if (pids.Length > 3)
// chan.PcrPid = this.ParseInt(pids[3]);
if (pids.Length > 4)
chan.VideoPid = this.ParseInt(pids[4]);
if (pids.Length > 5)
chan.AudioPid = this.ParseInt(pids[5]);
break;
}
}
@@ -196,24 +197,78 @@ namespace ChanSort.Loader.M3u
}
#endregion
#region FindExtInfTrackName()
#region ParseExtInf()
enum ExtInfParsePhase
{
Header,
Length,
Key,
Value,
Name
}
/// <summary>
/// parse track name from lines that may look like:
/// #EXTINF:&lt;length&gt;[ key="value" ...],&lt;TrackName&gt;
/// </summary>
private int FindExtInfTrackName(string extInfLine)
private void ParseExtInf(string extInfLine, out string name, out int nameIndex, out Dictionary<string,string> param)
{
name = "";
nameIndex = -1;
param = new Dictionary<string, string>();
bool inQuote = false;
var key = "";
var value = "";
ExtInfParsePhase phase = ExtInfParsePhase.Header;
for (int i = 0, c = extInfLine.Length; i < c; i++)
{
var ch = extInfLine[i];
if (ch == ',' && !inQuote)
return i + 1;
if (ch == '"')
inQuote = !inQuote;
}
switch (phase)
{
case ExtInfParsePhase.Header:
if (ch == ':')
phase = ExtInfParsePhase.Length;
break;
case ExtInfParsePhase.Length:
if (ch == ' ')
phase = ExtInfParsePhase.Key;
else if (ch == ',')
{
phase = ExtInfParsePhase.Name;
nameIndex = i + 1;
}
break;
case ExtInfParsePhase.Key:
if (ch == '=')
phase = ExtInfParsePhase.Value;
else
key += ch;
break;
case ExtInfParsePhase.Value:
if (ch == '"')
inQuote = !inQuote;
else if (ch == ' ' && !inQuote)
{
param[key] = value;
key = "";
value = "";
return -1;
}
else if (ch == ',' && !inQuote)
{
phase = ExtInfParsePhase.Name;
param[key] = value;
}
else
value += ch;
break;
case ExtInfParsePhase.Name:
if (ch != ' ' || name.Length > 0)
name += ch;
break;
}
}
}
#endregion

View File

@@ -4,6 +4,7 @@ using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
using System.Xml.Schema;
using ChanSort.Api;
@@ -70,6 +71,7 @@ namespace ChanSort.Loader.Panasonic
doc = new XmlDocument();
var content = File.ReadAllBytes(this.FileName);
var textContent = Encoding.UTF8.GetString(content);
textContent = FixUnescapedXmlChars(textContent);
var settings = new XmlReaderSettings
{
@@ -104,6 +106,30 @@ namespace ChanSort.Loader.Panasonic
}
#endregion
#region FixUnescapedXmlChars()
private string FixUnescapedXmlChars(string textContent)
{
var sb = new StringBuilder((int)(textContent.Length * 1.1));
var inQuotes = false;
foreach (var c in textContent)
{
if (c == '\"')
inQuotes = !inQuotes;
if (c == '&' && inQuotes)
sb.Append("&amp;");
else if (c == '<' && inQuotes)
sb.Append("&lt;");
else if (c == '>' && inQuotes)
sb.Append("&gt;");
else
sb.Append(c);
}
return sb.ToString();
}
#endregion
#region ReadChannel()
private void ReadChannel(XmlNode node)
{
@@ -152,13 +178,75 @@ namespace ChanSort.Loader.Panasonic
xmlSettings.NewLineHandling = NewLineHandling.None;
xmlSettings.NewLineChars = "\n";
xmlSettings.OmitXmlDeclaration = true;
using var w = XmlWriter.Create(tvOutputFile, xmlSettings);
// write to a string so that we can patch the result to be binary identical to the original file (if there are no changes)
using var stringWriter = new StringWriter();
using var w = XmlWriter.Create(stringWriter, xmlSettings);
doc.WriteTo(w);
w.Flush();
stringWriter.Write('\n'); // original file has a trailing \x0A
var xml = stringWriter.ToString();
xml = UnescapeXmlChars(xml); // create same broken XML as the original export with unescaped entities
xml = xml.Replace(" />", "/>"); // original file has no space before the element end
File.WriteAllText(tvOutputFile, xml, xmlSettings.Encoding);
this.FileName = tvOutputFile;
}
#endregion
#region UnescapeXmlChars()
/// <summary>
/// Generate the same broken XML with unescaped XML-entities as the original Panasonic XML export does (i.e. literal '&' character in channel names)
/// </summary>
private string UnescapeXmlChars(string xml)
{
bool inQuotes = false;
bool inEntity = false;
string entity = "";
var sb = new StringBuilder(xml.Length);
foreach (var c in xml)
{
if (inEntity)
{
if (c == ';')
{
switch (entity)
{
case "lt":
sb.Append("<");
break;
case "gt":
sb.Append(">");
break;
case "amp":
sb.Append("&");
break;
}
inEntity = false;
}
else
entity += c;
continue;
}
if (c == '"')
inQuotes = !inQuotes;
if (c == '&' && inQuotes)
{
inEntity = true;
entity = "";
continue;
}
sb.Append(c);
}
return sb.ToString();
}
#endregion
#region class XmlChannel

View File

@@ -502,9 +502,13 @@ namespace ChanSort.Ui
regex += c;
}
regex += "]*";
if (favorites == 0)
regex = "";
else
regex += "]*";
this.repositoryItemCheckedComboBoxEdit1.Mask.EditMask = regex;
this.repositoryItemCheckedComboBoxEdit2.Mask.EditMask = regex;
this.repositoryItemCheckedComboBoxEdit1.ReadOnly = this.repositoryItemCheckedComboBoxEdit2.ReadOnly = favorites == 0;
this.tabSubList.BeginUpdate();
while (this.tabSubList.TabPages.Count > favCount + 1)

View File

@@ -126,4 +126,7 @@
<data name="btnOk.Text" xml:space="preserve">
<value>Ok</value>
</data>
<data name="btnReset.Text" xml:space="preserve">
<value>Standard-Skin verwenden</value>
</data>
</root>

Binary file not shown.

Binary file not shown.

View File

@@ -1,6 +1,16 @@
ChanSort Change Log
===================
2021-10-24
- LG webOS 5 and 6: Improved support for DVB-C lists which changed channel numbers after import
(Now setting the "userEditChNumber" and "userSelCHNo" flags when channel numbers are changed.
This can be disabled in ChanSort.Loader.LG.ini, section \[webOS 5\], setting set_userEditChNumber=false)
- Sat>IP/.m3u: Support for files with extra information after the #EXTM3U header
Also capturing the group-title from #EXTINF, showing the msys value in the Source column (dvbs/dvbt/...),
and showing all URL-parameters in the Debug column
- Panasonic .xml: files with "&" characters in channel names can now be loaded
(Panasonic does not escape special XML characters and produces invalid XML syntax)
2021-09-23_1945
- Philips: disabled deleting of channels for ChannelMap\_100 - 115, except for version 100 without any .bin files.
(Lists with .bin files require that the .xml file contains all the same channels to override all channel numbers)