mirror of
https://github.com/PredatH0r/ChanSort.git
synced 2026-07-01 10:49:16 +02:00
- 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:
@@ -1032,3 +1032,7 @@
|
||||
[FirmwareData:39680]
|
||||
; LB580V
|
||||
offSize = 0
|
||||
|
||||
|
||||
[webOS 5]
|
||||
set_userEditChNumber=true
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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:<length>[ key="value" ...],<TrackName>
|
||||
/// </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
|
||||
|
||||
@@ -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("&");
|
||||
else if (c == '<' && inQuotes)
|
||||
sb.Append("<");
|
||||
else if (c == '>' && inQuotes)
|
||||
sb.Append(">");
|
||||
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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user