- added partial support for Philips .xml channel lists

(There are MANY different file formats, only a few are currently supported)
- fixed "most-recently-used" getting reversed every time the program was started
- added "UTF-8 (Unicode)" character set to menu
- fixed disappearing columns when loading different channel lists without restarting the application
This commit is contained in:
hbeham
2019-08-05 23:50:53 +02:00
parent 55a7a1a048
commit 25b2df7734
17 changed files with 815 additions and 21 deletions

View File

@@ -11,7 +11,7 @@ About ChanSort
--------------
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 file formats from **Sony** (new), **ITT, Medion, Nabo, ok., PEAQ, Schaub-Lorenz, Silva-Schneider, Telefunken** (new),
ChanSort supports various file formats from **Philips** (new), **Sony** (new), **ITT, Medion, Nabo, ok., PEAQ, Schaub-Lorenz, Silva-Schneider, Telefunken** (new),
Hisense, Samsung, LG, Panasonic, Toshiba and the Linux VDR project.
![screenshot](http://beham.biz/chansort/ChanSort-en.png)
@@ -96,6 +96,10 @@ Models that export a .zip file containing chmgt.db, dvbSysData.db and dvbMainDat
**ITT, Medion, Nabo, ok., PEAQ, Schaub-Lorenz, Silva-Schneider, Telefunken**
These brands use .sdx files (currently only satellite lists are supported)
**Philips**
Philips uses countless incompatible file formats for various TV models.
ChanSort currently supports 2 different versions of .xml files, other formats are not supported.
**VDR (Linux Video Disk Recorder)**
Supports the channels.conf file format.
Implementation for this was provided by TCr82 from the VDR project.

View File

@@ -11,7 +11,7 @@ Links
--------------
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 Dateiformate von **Sony** (new), **ITT, Medion, Nabo, ok., PEAQ, Schaub-Lorenz, Silva-Schneider, Telefunken** (new),
ChanSort unterst<73>tzt diverse Dateiformate von **Philips** (neu), **Sony** (new), **ITT, Medion, Nabo, ok., PEAQ, Schaub-Lorenz, Silva-Schneider, Telefunken** (new),
Hisense, Samsung, LG, Panasonic, Toshiba und dem Linux VDR Projekt.
![screenshot](http://beham.biz/chansort/ChanSort-de.png)
@@ -91,6 +91,10 @@ Die Marken nutzen .sdx Dateien (derzeit wird nur Satellitenempfang unterst
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)
**Philips**
Philips verwendet unz<6E>hlige unterschiedliche Dateiformate f<>r diverse TV-Modelle.
ChanSort unterst<73>tzt derzeit 2 Varianten von .xml-Dateien. Andere Formate werden nicht unterst<73>tzt.
**VDR (Linux Video Disk Recorder)**
Unterst<EFBFBD>tzung des channels.conf Dateiformats.
Die Implementation hierf<72>r wurde vom Mitglied "TCr82" des VDR Projekts beigesteuert.

View File

@@ -20,7 +20,7 @@ namespace ChanSort.Api
this.SignalSource = source;
this.ShortCaption = caption;
this.FirstProgramNumber = (source & SignalSource.Digital) != 0 ? 1 : 0;
this.VisibleColumnFieldNames = DefaultVisibleColumns;
this.VisibleColumnFieldNames = DefaultVisibleColumns.ToList(); // create copy of default list, so it can be modified
}
public string ShortCaption { get; }

View File

@@ -24,6 +24,8 @@ namespace ChanSort.Api
Sat = 0x0040,
IP = 0x0080,
MaskAdInput = MaskAnalogDigital | MaskAntennaCableSat,
// bit 9+10: TV/Radio
MaskTvRadio = 0x0300,
Tv = 0x0100,

View File

@@ -0,0 +1,74 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{D7BAFD55-50F5-46C3-A76B-2193BED5358F}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>ChanSort.Loader.PhilipsXml</RootNamespace>
<AssemblyName>ChanSort.Loader.PhilipsXml</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x86\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
<OutputPath>bin\x86\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Channel.cs" />
<Compile Include="CustomXmlWriter.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Serializer.cs" />
<Compile Include="SerializerPlugin.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ChanSort.Api\ChanSort.Api.csproj">
<Project>{dccffa08-472b-4d17-bb90-8f513fc01392}</Project>
<Name>ChanSort.Api</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View File

@@ -0,0 +1,20 @@
using System.Xml;
using ChanSort.Api;
namespace ChanSort.Loader.PhilipsXml
{
internal class Channel : ChannelInfo
{
public readonly XmlNode SetupNode;
public string RawName;
public string RawSatellite;
internal Channel(SignalSource source, int order, int rowId, XmlNode setupNode)
{
this.SignalSource = source;
this.RecordOrder = order;
this.RecordIndex = rowId;
this.SetupNode = setupNode;
}
}
}

View File

@@ -0,0 +1,170 @@
using System;
using System.IO;
using System.Xml;
namespace ChanSort.Loader.PhilipsXml
{
/// <summary>
/// This XmlWriter replaces some characters with Char- or Entity- references the same way
/// they are escaped in the original Sony XML files
/// </summary>
class CustomXmlWriter : XmlWriter
{
private static readonly char[] CharsToEscape = { '\'', '\"', '&', '<', '>' };
private static readonly string[] CharEntites = { "apos", "quot", "amp", "lt", "gt" };
private XmlWriter w;
private readonly bool escapeAsEntityRef; // if true, use &amp; otherwise &#34;
public CustomXmlWriter(TextWriter tw, XmlWriterSettings settings, bool useEntityRef)
{
this.w = XmlWriter.Create(tw, settings);
this.escapeAsEntityRef = useEntityRef;
}
public override void WriteString(string text)
{
int i = 0, j;
while ((j = text.IndexOfAny(CharsToEscape, i)) >= 0)
{
this.w.WriteString(text.Substring(i, j - i));
if (this.escapeAsEntityRef)
{
// => &amp;
int k = Array.IndexOf(CharsToEscape, text[j]);
this.w.WriteEntityRef(CharEntites[k]);
}
else
{
// => &#38;
//this.w.WriteCharEntity(text[j]);
this.w.WriteRaw("&#" + (int)text[j] + ";");
}
i = j + 1;
}
this.w.WriteString(text.Substring(i));
}
#region 1:1 delegation
public override void WriteStartDocument()
{
this.w.WriteStartDocument();
}
public override void WriteStartDocument(bool standalone)
{
this.w.WriteStartDocument(standalone);
}
public override void WriteEndDocument()
{
this.w.WriteEndDocument();
}
public override void WriteDocType(string name, string pubid, string sysid, string subset)
{
this.w.WriteDocType(name, pubid, sysid, subset);
}
public override void WriteStartElement(string prefix, string localName, string ns)
{
this.w.WriteStartElement(prefix, localName, ns);
}
public override void WriteEndElement()
{
this.w.WriteEndElement();
}
public override void WriteFullEndElement()
{
this.w.WriteFullEndElement();
}
public override void WriteStartAttribute(string prefix, string localName, string ns)
{
this.w.WriteStartAttribute(prefix, localName, ns);
}
public override void WriteEndAttribute()
{
this.w.WriteEndAttribute();
}
public override void WriteCData(string text)
{
this.w.WriteCData(text);
}
public override void WriteComment(string text)
{
this.w.WriteComment(text);
}
public override void WriteProcessingInstruction(string name, string text)
{
this.w.WriteProcessingInstruction(name, text);
}
public override void WriteEntityRef(string name)
{
this.w.WriteEntityRef(name);
}
public override void WriteCharEntity(char ch)
{
this.w.WriteCharEntity(ch);
}
public override void WriteWhitespace(string ws)
{
this.w.WriteWhitespace(ws);
}
public override void WriteSurrogateCharEntity(char lowChar, char highChar)
{
this.w.WriteSurrogateCharEntity(lowChar, highChar);
}
public override void WriteChars(char[] buffer, int index, int count)
{
this.w.WriteChars(buffer, index, count);
}
public override void WriteRaw(char[] buffer, int index, int count)
{
this.w.WriteRaw(buffer, index, count);
}
public override void WriteRaw(string data)
{
this.w.WriteRaw(data);
}
public override void WriteBase64(byte[] buffer, int index, int count)
{
this.w.WriteBase64(buffer, index, count);
}
public override void Close()
{
this.w.Close();
}
public override void Flush()
{
this.w.Flush();
}
public override string LookupPrefix(string ns)
{
return this.w.LookupPrefix(ns);
}
public override WriteState WriteState => this.w.WriteState;
#endregion
}
}

View File

@@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("ChanSort.Loader.PhilipsXml")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("ChanSort.Loader.PhilipsXml")]
[assembly: AssemblyCopyright("Copyright © 2019")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("d7bafd55-50f5-46c3-a76b-2193bed5358f")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@@ -0,0 +1,408 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Schema;
using ChanSort.Api;
namespace ChanSort.Loader.PhilipsXml
{
/*
This loader supports 2 different kinds of XML files from Philips.
I had a test file "DVBS.xml" with satellite channels and entries like:
<Channel>
<Setup SatelliteName="0x54 0x00 0x55 0x00 0x52 0x00 0x4B 0x00 0x53 0x00 0x41 0x00 0x54 0x00 0x20 0x00 0x34 0x00 0x32 0x00 0x45 0x00 " ChannelNumber="1" ChannelName="0x54 0x00 0xC4 0x00 0xB0 0x00 0x56 0x00 0xC4 0x00 0xB0 0x00 0x42 0x00 0x55 0x00 0x20 0x00 0x53 0x00 0x50 0x00 0x4F 0x00 0x52 0x00 " ChannelLock="0" UserModifiedName="0" LogoID="0" UserModifiedLogo="0" LogoLock="0" UserHidden="0" FavoriteNumber="0" />
<Broadcast ChannelType="3" Onid="1070" Tsid="43203" Sid="16001" Frequency="11794" Modulation="0" ServiceType="1" SymbolRate="27507" LNBNumber="38" Polarization="0" SystemHidden="0" />
</Channel>
The other file was "CM_TPM1013E_LA_CK.xml" with entries like:
<Channel>
<Setup oldpresetnumber="1" presetnumber="1" name="Das Erste" ></Setup>
<Broadcast medium="dvbc" frequency="410000" system="west" serviceID="1" ONID="41985" TSID="1101" modulation="256" symbolrate="6901000" bandwidth="Unknown"></Broadcast>
</Channel>
*/
class Serializer : SerializerBase
{
private readonly ChannelList terrChannels = new ChannelList(SignalSource.DvbT | 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");
private ChannelList curList;
private XmlDocument doc;
private byte[] content;
private string textContent;
private string newline;
private int formatVersion;
#region ctor()
public Serializer(string inputFile) : base(inputFile)
{
this.Features.ChannelNameEdit = ChannelNameEditMode.All;
this.Features.CanDeleteChannels = true;
this.DataRoot.AddChannelList(this.terrChannels);
this.DataRoot.AddChannelList(this.cableChannels);
this.DataRoot.AddChannelList(this.satChannels);
foreach (var list in this.DataRoot.ChannelLists)
{
list.VisibleColumnFieldNames.Remove("PcrPid");
list.VisibleColumnFieldNames.Remove("VideoPid");
list.VisibleColumnFieldNames.Remove("AudioPid");
list.VisibleColumnFieldNames.Remove("Skip");
list.VisibleColumnFieldNames.Remove("ShortName");
list.VisibleColumnFieldNames.Remove("Provider");
}
}
#endregion
#region DisplayName
public override string DisplayName => "Philips *.xml loader";
#endregion
#region Load()
public override void Load()
{
bool fail = false;
try
{
this.doc = new XmlDocument();
this.content = File.ReadAllBytes(this.FileName);
this.textContent = Encoding.UTF8.GetString(this.content);
this.newline = this.textContent.Contains("\r\n") ? "\r\n" : "\n";
var settings = new XmlReaderSettings
{
CheckCharacters = false,
IgnoreProcessingInstructions = true,
ValidationFlags = XmlSchemaValidationFlags.None,
DtdProcessing = DtdProcessing.Ignore
};
using (var reader = XmlReader.Create(new StringReader(textContent), settings))
{
doc.Load(reader);
}
}
catch
{
fail = true;
}
var root = doc.FirstChild;
if (root is XmlDeclaration)
root = root.NextSibling;
if (fail || root == null || root.LocalName != "ChannelMap")
throw new FileLoadException("\"" + this.FileName + "\" is not a supported Philips XML file");
int rowId = 0;
foreach (XmlNode child in root.ChildNodes)
{
switch (child.LocalName)
{
case "Channel":
if (rowId == 0)
this.DetectFormatAndFeatures(child);
if (this.curList != null)
this.ReadChannel(child, rowId++);
break;
}
}
}
#endregion
#region DetectFormatAndFeatures()
private void DetectFormatAndFeatures(XmlNode node)
{
var setupNode = node["Setup"] ?? throw new FileLoadException("Missing Setup XML element");
var bcastNode = node["Broadcast"] ?? throw new FileLoadException("Missing Broadcast XML element");
var fname = Path.GetFileNameWithoutExtension(this.FileName).ToLower();
var medium = bcastNode.GetAttribute("medium");
if (medium == "" && fname.Length == 4 && fname.StartsWith("dvb"))
medium = fname;
bool hasEncrypt = false;
if (setupNode.HasAttribute("ChannelName"))
{
this.formatVersion = 1;
this.DataRoot.SupportedFavorites = Favorites.A;
this.DataRoot.SortedFavorites = true;
var dtype = bcastNode.GetAttribute("DecoderType");
if (dtype == "1")
medium = "dvbt";
else if (dtype == "2")
medium = "dvbc";
else if (dtype == "3")
medium = "dvbs";
hasEncrypt = setupNode.HasAttribute("Scrambled");
}
else if (setupNode.HasAttribute("name"))
{
this.formatVersion = 2;
this.DataRoot.SupportedFavorites = 0;
this.DataRoot.SortedFavorites = false;
foreach (var list in this.DataRoot.ChannelLists)
{
list.VisibleColumnFieldNames.Remove("Favorites");
list.VisibleColumnFieldNames.Remove("Lock");
list.VisibleColumnFieldNames.Remove("Hidden");
list.VisibleColumnFieldNames.Remove("ServiceType");
list.VisibleColumnFieldNames.Remove("ServiceTypeName");
list.VisibleColumnFieldNames.Remove("Encrypted");
}
}
else
throw new FileLoadException("Unknown data format");
switch (medium)
{
case "dvbt":
this.curList = this.terrChannels;
break;
case "dvbc":
this.curList = this.cableChannels;
break;
case "dvbs":
this.curList = this.satChannels;
break;
default:
this.curList = null;
break;
}
if (!hasEncrypt)
this.curList?.VisibleColumnFieldNames.Remove("Encrypted");
}
#endregion
#region ReadChannel()
private void ReadChannel(XmlNode node, int rowId)
{
var setupNode = node["Setup"] ?? throw new FileLoadException("Missing Setup XML element");
var bcastNode = node["Broadcast"] ?? throw new FileLoadException("Missing Broadcast XML element");
var data = new Dictionary<string,string>(StringComparer.InvariantCultureIgnoreCase);
foreach (var n in new[] {setupNode, bcastNode})
{
foreach(XmlAttribute attr in n.Attributes)
data.Add(attr.LocalName, attr.Value);
}
var chan = new Channel(this.curList.SignalSource, rowId, rowId, setupNode);
chan.OldProgramNr = -1;
chan.IsDeleted = false;
if (this.formatVersion == 1)
this.ParseChannelFormat1(data, chan);
else if (this.formatVersion == 2)
this.ParseChannelFormat2(data, chan);
if ((chan.SignalSource & SignalSource.MaskAdInput) == SignalSource.DvbT)
chan.ChannelOrTransponder = LookupData.Instance.GetDvbtTransponder(chan.FreqInMhz).ToString();
else if ((chan.SignalSource & SignalSource.MaskAdInput) == SignalSource.DvbC)
chan.ChannelOrTransponder = LookupData.Instance.GetDvbcChannelName(chan.FreqInMhz);
DataRoot.AddChannel(this.curList, chan);
}
#endregion
#region ParseChannelFormat1
private void ParseChannelFormat1(Dictionary<string,string> data, Channel chan)
{
chan.RawSatellite = data.TryGet("SatelliteName");
chan.Satellite = DecodeName(chan.RawSatellite);
chan.OldProgramNr = ParseInt(data.TryGet("ChannelNumber"));
chan.RawName = data.TryGet("ChannelName");
chan.Name = DecodeName(chan.RawName);
chan.Lock = data.TryGet("ChannelLock") == "1";
chan.Hidden = data.TryGet("UserHidden") == "1";
var fav = ParseInt(data.TryGet("FavoriteNumber"));
chan.FavIndex[0] = fav == 0 ? -1 : fav;
chan.OriginalNetworkId = ParseInt(data.TryGet("Onid"));
chan.TransportStreamId = ParseInt(data.TryGet("Tsid"));
chan.ServiceId = ParseInt(data.TryGet("Sid"));
var freq = ParseInt(data.TryGet("Frequency"));
chan.FreqInMhz = freq;
chan.ServiceType = ParseInt(data.TryGet("ServiceType"));
chan.SignalSource |= LookupData.Instance.IsRadioOrTv(chan.ServiceType);
chan.SymbolRate = ParseInt(data.TryGet("SymbolRate"));
if (data.TryGetValue("Polarization", out var pol))
chan.Polarity = pol == "0" ? 'H' : 'V';
chan.Hidden |= data.TryGet("SystemHidden") == "1";
chan.Encrypted = data.TryGet("Scrambled") == "1"; // doesn't exist in all format versions
}
#endregion
#region ParseChannelFormat2
private void ParseChannelFormat2(Dictionary<string, string> data, Channel chan)
{
chan.OldProgramNr = ParseInt(data.TryGet("presetnumber"));
chan.Name = data.TryGet("name");
chan.RawName = chan.Name;
chan.FreqInMhz = ParseInt(data.TryGet("frequency"));
if (chan.FreqInMhz > 100000)
chan.FreqInMhz /= 1000;
chan.ServiceId = ParseInt(data.TryGet("serviceID"));
chan.OriginalNetworkId = ParseInt(data.TryGet("ONID"));
chan.TransportStreamId = ParseInt(data.TryGet("TSID"));
chan.ServiceType = ParseInt(data.TryGet("serviceType"));
chan.SymbolRate = ParseInt(data.TryGet("symbolrate")) / 1000;
}
#endregion
#region ParseInt()
private int ParseInt(string input)
{
if (string.IsNullOrWhiteSpace(input))
return 0;
if (input.Length > 2 && input[0] == '0' && char.ToLower(input[1]) == 'x')
return int.Parse(input.Substring(2), NumberStyles.HexNumber);
if (int.TryParse(input, out var value))
return value;
return 0;
}
#endregion
#region DecodeName()
private string DecodeName(string input)
{
if (input == null || !input.StartsWith("0x")) // fallback for unknown input
return input;
var hexParts = input.Split(' ');
var buffer = new MemoryStream();
foreach (var part in hexParts)
{
if (part == "" || part == "0x00")
continue;
buffer.WriteByte((byte)ParseInt(part));
}
return this.DefaultEncoding.GetString(buffer.GetBuffer(), 0, (int)buffer.Length);
}
#endregion
#region DefaultEncoding
public override Encoding DefaultEncoding
{
get => base.DefaultEncoding;
set
{
if (value == this.DefaultEncoding)
return;
base.DefaultEncoding = value;
this.ChangeEncoding();
}
}
#endregion
#region ChangeEncoding
private void ChangeEncoding()
{
foreach (var list in this.DataRoot.ChannelLists)
{
foreach (var channel in list.Channels)
{
if (!(channel is Channel ch))
continue;
ch.Name = this.DecodeName(ch.RawName);
ch.Satellite = this.DecodeName(ch.RawSatellite);
}
}
}
#endregion
#region Save()
public override void Save(string tvOutputFile)
{
foreach (var list in this.DataRoot.ChannelLists)
this.UpdateChannelList(list);
// 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;
xmlSettings.CheckCharacters = false;
xmlSettings.Indent = true;
xmlSettings.IndentChars = "";
xmlSettings.NewLineHandling = NewLineHandling.None;
xmlSettings.NewLineChars = this.newline;
xmlSettings.OmitXmlDeclaration = false;
string xml;
using (var sw = new StringWriter())
using (var w = new CustomXmlWriter(sw, xmlSettings, false))
{
this.doc.WriteTo(w);
w.Flush();
xml = sw.ToString();
}
var enc = new UTF8Encoding(false, false);
File.WriteAllText(tvOutputFile, xml, enc);
}
#endregion
#region UpdateChannelList()
private void UpdateChannelList(ChannelList list)
{
foreach (var channel in list.Channels)
{
var ch = channel as Channel;
if (ch == null)
continue; // might be a proxy channel from a reference list
if (ch.IsDeleted || ch.NewProgramNr < 0)
{
ch.SetupNode.ParentNode.ParentNode.RemoveChild(ch.SetupNode.ParentNode);
continue;
}
if (this.formatVersion == 1)
this.UpdateChannelFormat1(ch);
else if (this.formatVersion == 2)
this.UpdateChannelFormat2(ch);
}
}
#endregion
#region UpdateChannelFormat1 and 2
private void UpdateChannelFormat1(Channel ch)
{
ch.SetupNode.Attributes["ChannelNumber"].Value = ch.NewProgramNr.ToString();
if (ch.IsNameModified)
ch.SetupNode.Attributes["ChannelName"].Value = EncodeName(ch.Name);
ch.SetupNode.Attributes["FavoriteNumber"].Value = Math.Max(ch.FavIndex[0], 0).ToString();
}
private void UpdateChannelFormat2(Channel ch)
{
ch.SetupNode.Attributes["presetnumber"].Value = ch.NewProgramNr.ToString();
if (ch.IsNameModified)
ch.SetupNode.Attributes["name"].Value = ch.Name;
}
#endregion
#region EncodeName
private string EncodeName(string name)
{
var bytes = this.DefaultEncoding.GetBytes(name);
var sb = new StringBuilder();
foreach (var b in bytes)
sb.Append($"0x{b:X2} 0x00 ");
return sb.ToString();
}
#endregion
}
}

View File

@@ -0,0 +1,16 @@
using ChanSort.Api;
namespace ChanSort.Loader.PhilipsXml
{
public class SerializerPlugin : ISerializerPlugin
{
public string DllName { get; set; }
public string PluginName => "Philips .xml";
public string FileFilter => "*.xml";
public SerializerBase CreateSerializer(string inputFile)
{
return new Serializer(inputFile);
}
}
}

View File

@@ -23,7 +23,7 @@ namespace ChanSort.Loader.Sony
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 terrChannels = new ChannelList(SignalSource.DvbT | 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");
private readonly ChannelList satChannelsP = new ChannelList(SignalSource.DvbS | SignalSource.Tv | SignalSource.Radio, "DVB-S Preset");

View File

@@ -52,6 +52,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChanSort.Loader.SilvaSchnei
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChanSort.Loader.Sony", "ChanSort.Loader.Sony\ChanSort.Loader.Sony.csproj", "{70E29C6B-B926-4859-9548-23375BF1E1B5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChanSort.Loader.PhilipsXml", "ChanSort.Loader.PhilipsXml\ChanSort.Loader.PhilipsXml.csproj", "{D7BAFD55-50F5-46C3-A76B-2193BED5358F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -254,6 +256,18 @@ Global
{70E29C6B-B926-4859-9548-23375BF1E1B5}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{70E29C6B-B926-4859-9548-23375BF1E1B5}.Release|x86.ActiveCfg = Release|x86
{70E29C6B-B926-4859-9548-23375BF1E1B5}.Release|x86.Build.0 = Release|x86
{D7BAFD55-50F5-46C3-A76B-2193BED5358F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D7BAFD55-50F5-46C3-A76B-2193BED5358F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D7BAFD55-50F5-46C3-A76B-2193BED5358F}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{D7BAFD55-50F5-46C3-A76B-2193BED5358F}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{D7BAFD55-50F5-46C3-A76B-2193BED5358F}.Debug|x86.ActiveCfg = Debug|x86
{D7BAFD55-50F5-46C3-A76B-2193BED5358F}.Debug|x86.Build.0 = Debug|x86
{D7BAFD55-50F5-46C3-A76B-2193BED5358F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D7BAFD55-50F5-46C3-A76B-2193BED5358F}.Release|Any CPU.Build.0 = Release|Any CPU
{D7BAFD55-50F5-46C3-A76B-2193BED5358F}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{D7BAFD55-50F5-46C3-A76B-2193BED5358F}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{D7BAFD55-50F5-46C3-A76B-2193BED5358F}.Release|x86.ActiveCfg = Release|Any CPU
{D7BAFD55-50F5-46C3-A76B-2193BED5358F}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@@ -386,6 +386,10 @@
<Project>{68da8072-3a29-4076-9f64-d66f38349585}</Project>
<Name>ChanSort.Loader.Panasonic</Name>
</ProjectReference>
<ProjectReference Include="..\ChanSort.Loader.PhilipsXml\ChanSort.Loader.PhilipsXml.csproj">
<Project>{d7bafd55-50f5-46c3-a76b-2193bed5358f}</Project>
<Name>ChanSort.Loader.PhilipsXml</Name>
</ProjectReference>
<ProjectReference Include="..\ChanSort.Loader.SamsungJ\ChanSort.Loader.SamsungJ.csproj">
<Project>{33897002-0537-49a4-b963-a18d17311b3d}</Project>
<Name>ChanSort.Loader.SamsungJ</Name>

View File

@@ -161,6 +161,7 @@
this.miShowWarningsAfterLoad = new DevExpress.XtraBars.BarCheckItem();
this.miAllowEditPredefinedLists = new DevExpress.XtraBars.BarButtonItem();
this.miExplorerIntegration = new DevExpress.XtraBars.BarButtonItem();
this.miCheckUpdates = new DevExpress.XtraBars.BarButtonItem();
this.mnuAccessibility = new DevExpress.XtraBars.BarSubItem();
this.mnuGotoChannelList = new DevExpress.XtraBars.BarSubItem();
this.mnuInputSource = new DevExpress.XtraBars.BarLinkContainerItem();
@@ -206,7 +207,7 @@
this.pageProgNr = new DevExpress.XtraTab.XtraTabPage();
this.popupInputSource = new DevExpress.XtraBars.PopupMenu(this.components);
this.popupFavList = new DevExpress.XtraBars.PopupMenu(this.components);
this.miCheckUpdates = new DevExpress.XtraBars.BarButtonItem();
this.miUtf8Charset = new DevExpress.XtraBars.BarButtonItem();
((System.ComponentModel.ISupportInitialize)(this.splitContainerControl1)).BeginInit();
this.splitContainerControl1.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.grpOutputList)).BeginInit();
@@ -1026,9 +1027,10 @@
this.miCzech,
this.miRomanian,
this.miExplorerIntegration,
this.miCheckUpdates});
this.miCheckUpdates,
this.miUtf8Charset});
this.barManager1.MainMenu = this.bar1;
this.barManager1.MaxItemId = 99;
this.barManager1.MaxItemId = 100;
this.barManager1.ShowFullMenus = true;
//
// bar1
@@ -1608,7 +1610,8 @@
this.mnuCharset.ImageOptions.ImageIndex = ((int)(resources.GetObject("mnuCharset.ImageOptions.ImageIndex")));
this.mnuCharset.LinksPersistInfo.AddRange(new DevExpress.XtraBars.LinkPersistInfo[] {
new DevExpress.XtraBars.LinkPersistInfo(this.miCharsetForm),
new DevExpress.XtraBars.LinkPersistInfo(this.miIsoCharSets, true)});
new DevExpress.XtraBars.LinkPersistInfo(this.miUtf8Charset, true),
new DevExpress.XtraBars.LinkPersistInfo(this.miIsoCharSets)});
this.mnuCharset.Name = "mnuCharset";
this.mnuCharset.PaintStyle = DevExpress.XtraBars.BarItemPaintStyle.CaptionInMenu;
//
@@ -1653,6 +1656,14 @@
this.miExplorerIntegration.Name = "miExplorerIntegration";
this.miExplorerIntegration.ItemClick += new DevExpress.XtraBars.ItemClickEventHandler(this.miExplorerIntegration_ItemClick);
//
// miCheckUpdates
//
this.miCheckUpdates.ButtonStyle = DevExpress.XtraBars.BarButtonStyle.Check;
resources.ApplyResources(this.miCheckUpdates, "miCheckUpdates");
this.miCheckUpdates.Id = 98;
this.miCheckUpdates.Name = "miCheckUpdates";
this.miCheckUpdates.ItemClick += new DevExpress.XtraBars.ItemClickEventHandler(this.miCheckUpdates_ItemClick);
//
// mnuAccessibility
//
resources.ApplyResources(this.mnuAccessibility, "mnuAccessibility");
@@ -2055,13 +2066,12 @@
this.popupFavList.Name = "popupFavList";
this.popupFavList.ShowCaption = true;
//
// miCheckUpdates
// miUtf8Charset
//
this.miCheckUpdates.ButtonStyle = DevExpress.XtraBars.BarButtonStyle.Check;
resources.ApplyResources(this.miCheckUpdates, "miCheckUpdates");
this.miCheckUpdates.Id = 98;
this.miCheckUpdates.Name = "miCheckUpdates";
this.miCheckUpdates.ItemClick += new DevExpress.XtraBars.ItemClickEventHandler(this.miCheckUpdates_ItemClick);
resources.ApplyResources(this.miUtf8Charset, "miUtf8Charset");
this.miUtf8Charset.Id = 99;
this.miUtf8Charset.Name = "miUtf8Charset";
this.miUtf8Charset.ItemClick += new DevExpress.XtraBars.ItemClickEventHandler(this.MiUtf8Charset_ItemClick);
//
// MainForm
//
@@ -2304,6 +2314,7 @@
private DevExpress.XtraGrid.Columns.GridColumn colPcrPid;
private DevExpress.XtraBars.BarButtonItem miExplorerIntegration;
private DevExpress.XtraBars.BarButtonItem miCheckUpdates;
private DevExpress.XtraBars.BarButtonItem miUtf8Charset;
}
}

View File

@@ -412,6 +412,9 @@ namespace ChanSort.Ui
private void UpdateFavoritesEditor(Favorites favorites)
{
var miSet = new[] {this.miFavASet, this.miFavBSet, this.miFavCSet, this.miFavDSet, this.miFavESet};
var miUnset = new[] { this.miFavAUnset, this.miFavBUnset, this.miFavCUnset, this.miFavDUnset, this.miFavEUnset };
this.repositoryItemCheckedComboBoxEdit1.Items.Clear();
this.repositoryItemCheckedComboBoxEdit2.Items.Clear();
byte mask = 0x01;
@@ -424,9 +427,16 @@ namespace ChanSort.Ui
var c = (char) ('A' + bit);
this.repositoryItemCheckedComboBoxEdit1.Items.Add(c);
this.repositoryItemCheckedComboBoxEdit2.Items.Add(c);
miSet[bit].Visibility = BarItemVisibility.Always;
miUnset[bit].Visibility = BarItemVisibility.Always;
regex += c;
++favCount;
}
else
{
miSet[bit].Visibility = BarItemVisibility.Never;
miUnset[bit].Visibility = BarItemVisibility.Never;
}
}
regex += "]*";
this.repositoryItemCheckedComboBoxEdit1.Mask.EditMask = regex;
@@ -1257,8 +1267,8 @@ namespace ChanSort.Ui
this.cbCloseGap.Checked = Config.Default.CloseGaps;
this.ClearLeftFilter();
this.ClearRightFilter();
foreach(var path in Config.Default.MruFiles)
this.AddFileToMruList(path);
this.mruFiles.Clear();
this.mruFiles.AddRange(Config.Default.MruFiles);
this.UpdateMruMenu();
this.miExplorerIntegration.Down = Config.Default.ExplorerIntegration;
@@ -2872,6 +2882,11 @@ namespace ChanSort.Ui
#region Character set menu
private void MiUtf8Charset_ItemClick(object sender, ItemClickEventArgs e)
{
TryExecute(() => this.SetDefaultEncoding(Encoding.UTF8));
}
private void miIsoCharSets_ListItemClick(object sender, ListItemClickEventArgs e)
{
TryExecute(() => this.SetDefaultEncoding(Encoding.GetEncoding(this.isoEncodings[e.Index])));

View File

@@ -647,6 +647,9 @@
<data name="miCharsetForm.ImageOptions.ImageIndex" type="System.Int32, mscorlib">
<value>9</value>
</data>
<data name="miUtf8Charset.Caption" xml:space="preserve">
<value>UTF-8 (Unicode)</value>
</data>
<data name="miIsoCharSets.Caption" xml:space="preserve">
<value>ISO character sets</value>
</data>
@@ -1255,7 +1258,7 @@
<value>globalImageCollection1</value>
</data>
<data name="&gt;&gt;globalImageCollection1.Type" xml:space="preserve">
<value>ChanSort.Ui.GlobalImageCollection, ChanSort, Version=1.0.7134.23381, Culture=neutral, PublicKeyToken=null</value>
<value>ChanSort.Ui.GlobalImageCollection, ChanSort, Version=1.0.7156.40088, Culture=neutral, PublicKeyToken=null</value>
</data>
<data name="&gt;&gt;gviewRight.Name" xml:space="preserve">
<value>gviewRight</value>
@@ -1809,6 +1812,12 @@
<data name="&gt;&gt;miExplorerIntegration.Type" xml:space="preserve">
<value>DevExpress.XtraBars.BarButtonItem, DevExpress.XtraBars.v19.1, Version=19.1.4.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a</value>
</data>
<data name="&gt;&gt;miCheckUpdates.Name" xml:space="preserve">
<value>miCheckUpdates</value>
</data>
<data name="&gt;&gt;miCheckUpdates.Type" xml:space="preserve">
<value>DevExpress.XtraBars.BarButtonItem, DevExpress.XtraBars.v19.1, Version=19.1.4.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a</value>
</data>
<data name="&gt;&gt;mnuAccessibility.Name" xml:space="preserve">
<value>mnuAccessibility</value>
</data>
@@ -1971,10 +1980,10 @@
<data name="&gt;&gt;popupFavList.Type" xml:space="preserve">
<value>DevExpress.XtraBars.PopupMenu, DevExpress.XtraBars.v19.1, Version=19.1.4.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a</value>
</data>
<data name="&gt;&gt;miCheckUpdates.Name" xml:space="preserve">
<value>miCheckUpdates</value>
<data name="&gt;&gt;miUtf8Charset.Name" xml:space="preserve">
<value>miUtf8Charset</value>
</data>
<data name="&gt;&gt;miCheckUpdates.Type" xml:space="preserve">
<data name="&gt;&gt;miUtf8Charset.Type" xml:space="preserve">
<value>DevExpress.XtraBars.BarButtonItem, DevExpress.XtraBars.v19.1, Version=19.1.4.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a</value>
</data>
<data name="&gt;&gt;$this.Name" xml:space="preserve">
@@ -1984,7 +1993,7 @@
<value>DevExpress.XtraEditors.XtraForm, DevExpress.Utils.v19.1, Version=19.1.4.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a</value>
</data>
<data name="SharedImageCollection.Timestamp" type="System.DateTime, mscorlib">
<value>07/14/2019 13:16:32</value>
<value>08/05/2019 22:23:17</value>
</data>
<data name="SharedImageCollection.ImageSize" type="System.Drawing.Size, System.Drawing">
<value>16, 16</value>

View File

@@ -1,6 +1,13 @@
ChanSort Change Log
===================
2019-08-05
- added partial support for Philips .xml channel lists
(There are MANY different file formats, only a few are currently supported)
- fixed "most-recently-used" getting reversed every time the program was started
- added "UTF-8 (Unicode)" character set to menu
- fixed disappearing columns when loading different channel lists without restarting the application
2019-07-25
- fix: Application failed to save config and didn't exit when the folder %LOCALAPPDATA%\ChanSort doesn't exist