Files
ChanSort/source/Spike.LgWebOs5/Program.cs
Horst Beham e7bb88c554 2020-12-26
- LG WebOS 5: added warning that support is only experimental.
- Panasonic: Channel name editing is now supported for svl.bin files (unless there is no indicator what encoding to use)
- Hungarian translation: added missing files to .zip
2020-12-26 16:51:33 +01:00

372 lines
11 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Xml;
using ChanSort.Api;
using Newtonsoft.Json;
namespace Spike.LgWebOs5
{
class Program
{
private static string basedir;
static void Main(string[] args)
{
basedir = args.Length > 0 ? args[0] : @"d:\sources\chansort\testfiles_lg";
using var stream = new FileStream(@"d:\sources\chansort\testfiles_lg\__webOS5\lg.csv", FileMode.Create);
using var csv = new StreamWriter(stream, Encoding.UTF8);
var sources = new[] { "All", "Analog", "DVB-T", "DVB-C", "DVB-S", "Others" };
csv.Write("\t\t\t\t\t\t\t\t");
bool detailed = true;
foreach (var src in sources)
{
csv.Write("\t" + src + Dup("\t", (detailed ? ChanListStats.ColumnHeadersLong.Length : ChanListStats.ColumnHeadersShort.Length) - 1));
detailed = false;
}
csv.WriteLine();
csv.Write("Path\tStatus\tModelName\tVersion\tDTVInfo\tBroadcastCountrySettings\tcountry\tFW info\t#channels");
detailed = true;
foreach (var source in sources)
{
var fields = detailed ? ChanListStats.ColumnHeadersLong : ChanListStats.ColumnHeadersShort;
foreach (var field in fields)
csv.Write("\t" + field);
detailed = false;
}
csv.WriteLine();
ProcessWebOs5Files(basedir, csv);
}
private static void ProcessWebOs5Files(string dir, StreamWriter csv)
{
var files = Directory.GetFiles(dir, "GlobalClone*.tll");
foreach (var tll in files)
{
var bak = tll + ".bak";
var file = File.Exists(bak) ? bak : tll;
var line = file.Substring(basedir.Length + 1) + "\t" + ProcessFile(file);
csv.WriteLine(line);
}
foreach (var subdir in Directory.GetDirectories(dir))
ProcessWebOs5Files(subdir, csv);
}
#region ProcessFile()
private static string ProcessFile(string file)
{
var data = File.ReadAllBytes(file);
if (data.Length < 3)
return "too small";
if (data[0] != '<' || data[1] != '?')
return "not XML";
var xml = Encoding.UTF8.GetString(data);
var ending = "";
for (var i = xml.Length - 1; i >= 0; i--)
{
var ch = xml[i];
if (ch == '\n')
ending = "NL" + ending;
else if (ch == '\r')
ending = "CR" + ending;
else if (ch == ' ')
ending = "SP " + ending;
else
break;
}
var sb = new StringBuilder();
var doc = new XmlDocument();
try
{
doc.LoadXml(xml);
var mi = doc["TLLDATA"]["ModelInfo"];
sb.Append("\t" + mi["ModelName"]?.InnerText);
var cv = mi["CloneVersion"];
sb.Append("\t" + cv.Attributes["type"]?.InnerText + "." + cv["MajorVersion"]?.InnerText + "." + cv["MinorVersion"]?.InnerText + " / " + cv["SatelliteDBVersion"]?.InnerText);
sb.Append("\t" + mi["DTVInfo"]?.InnerText);
sb.Append("\t" + mi["BroadcastCountrySetting"]?.InnerText);
sb.Append("\t" + mi["country"]?.InnerText);
var legacy = doc["TLLDATA"]["CHANNEL"]["legacybroadcast"];
if (legacy == null)
sb.Append("\tnot webOS 5");
else
{
sb.Append("\t" + GetFirmwareVersionFromInfoFile(Path.Combine(Path.GetDirectoryName(file), @"LG Smart TV\TN\INFO")));
sb.Append(ProcessWebOs5JsonData(legacy.InnerText));
}
}
catch (Exception ex)
{
return ex.Message + "\t" + sb;
}
return "ok ("+ending+")" + sb;
}
private static string GetFirmwareVersionFromInfoFile(string file)
{
// this info file contains an escaped JSON string as the value of the INFO json property
if (!File.Exists(file))
return "";
var content = File.ReadAllText(file);
content = content.Replace("\\\"", "\"");
var key = "core_os_release\": \"";
var start = content.IndexOf(key);
if (start < 0)
return "";
var end = content.IndexOf("\"", start + key.Length + 1);
start += key.Length;
var value = content.Substring(start, end - start);
return value;
}
#endregion
#region ProcessWebOs5JsonData()
private static string ProcessWebOs5JsonData(string json)
{
dynamic doc = JsonConvert.DeserializeObject(json);
var sb = new StringBuilder();
var channels = (IList) doc.channelList;
sb.Append("\t").Append(channels?.Count);
var tvAndRadioPerSignalSource = new Dictionary<string, ChanListStats>();
foreach (dynamic ch in channels)
{
var src = (string) ch.sourceIndex;
if (!tvAndRadioPerSignalSource.TryGetValue(src, out var stats))
{
stats = new ChanListStats();
tvAndRadioPerSignalSource[src] = stats;
}
stats.Add(ch);
}
var a = new ChanListStats();
var t = new ChanListStats();
var c = new ChanListStats();
var s = new ChanListStats();
var o = new ChanListStats();
var all = new ChanListStats();
foreach (var entry in tvAndRadioPerSignalSource)
{
switch (entry.Key)
{
case "ANTENNA ANALOG":
case "CABLE ANALOG":
case "SATELLITE ANALOG":
a += entry.Value;
break;
case "ANTENNA DIGITAL":
t += entry.Value;
break;
case "CABLE DIGITAL":
c += entry.Value;
break;
case "SATELLITE DIGITAL":
s += entry.Value;
break;
default:
o += entry.Value;
break;
}
all += entry.Value;
}
sb.Append(all.ToString(true) + a + t + c + s + o);
return sb.ToString();
}
#endregion
private static string Dup(string str, int count)
{
var sb = new StringBuilder(str.Length * count);
for (int i = 0; i < count; i++)
sb.Append(str);
return sb.ToString();
}
}
#region class ChanListStats
class ChanListStats
{
public int Tv;
public int Radio;
public int Radio0;
public int Radio4k;
public int RadioMaskServiceTypeMismatch;
public int maxMajorTv;
public int maxMajorRadio;
public bool inMajorOrder = true;
public bool hasGap = false;
public bool deletedMajor0 = false;
public bool deletedMajorNon0 = false;
public int UserEditChNumber;
public int UserSelChNo;
public int FactoryDefault;
public int Disabled;
public int Skipped;
public int Invisible;
public int Locked;
public int Deleted;
public int Discarded;
public int UserCustomize;
public int NumUnSel;
public int Lcn;
public static readonly string[] ColumnHeadersLong = {
"TV", "Radio",
// "Rad 0/4K", "BadSvcType",
"InOrder/Gaps",
"Del0/!0",
"UserEdit",
"FactDef",
"LCN",
"DelDisbDisc",
"SLHU"
};
public static readonly string[] ColumnHeadersShort = {
"TV", "Radio",
// "Rad 0/4K", "BadSvcType",
"InOrder/Gaps",
"Del0/!0"
};
public override string ToString() => ToString(false);
public string ToString(bool full)
{
var part1 =
"\t" + Tv + "\t" + Radio
// + "\t" + Radio0 + "/" + Radio4k + "\t" + RadioMaskServiceTypeMismatch
+ "\t" + (inMajorOrder ? "J" : "N") + "/" + (hasGap ? "J" : "N")
+ "\t" + (deletedMajor0 ? "J" : "N") + "/" + (deletedMajorNon0 ? "J" : "N");
if (!full)
return part1;
return
part1
+ "\t" + UserEditChNumber + "/" + UserSelChNo
+ "\t" + FactoryDefault
+ "\t" + Lcn
+ "\t" + Deleted + "/" + Disabled + "/" + Discarded
+ "\t" + Skipped + "/" + Locked + "/" + Invisible + "/" + NumUnSel;
}
public void Add(dynamic ch)
{
var major = (int)ch.majorNumber;
var nr = major & 0x3FFF;
if ((major & 0x4000) != 0)
{
++Radio;
if (inMajorOrder && nr != 0 && nr <= maxMajorRadio)
inMajorOrder = false;
else
maxMajorRadio = nr;
hasGap |= nr != 0 && nr != maxMajorRadio;
}
else
{
++Tv;
if (inMajorOrder && major != 0 && major <= maxMajorTv)
inMajorOrder = false;
else
maxMajorTv = major;
hasGap |= nr != 0 && nr != maxMajorTv;
}
if (major == 0x4000)
++Radio4k;
if (ch.serviceType != null)
{
var serviceIsRadio = LookupData.Instance.IsRadioTvOrData((int)ch.serviceType) == SignalSource.Radio;
if (major == 0 && serviceIsRadio)
++Radio0;
if (((major & 0x4000) != 0) != serviceIsRadio)
++RadioMaskServiceTypeMismatch;
}
if (ch.deleted != null && (bool)ch.deleted)
{
++Deleted;
if (nr == 0)
deletedMajor0 = true;
else
deletedMajorNon0 = true;
}
if (ch.diabled != null && (bool)ch.disabled)
++Disabled;
if (ch.discarded != null && (bool)ch.discarded)
++Discarded;
if (ch.userEditChNumber != null && (bool) ch.userEditChNumber)
++UserEditChNumber;
if (ch.userSelCHNo != null && (bool) ch.userSelCHNo)
++UserSelChNo;
if (ch.factoryDefault != null && (bool) ch.factoryDefault)
++FactoryDefault;
if (ch.skipped != null && (bool) ch.skipped)
++Skipped;
if (ch.locked != null && (bool) ch.locked)
++Locked;
if (ch.Invisible != null && (bool) ch.Invisible)
++Invisible;
if (ch.NumUnSel != null && (bool) ch.NumUnSel)
++NumUnSel;
if (ch.validLCN != null && (bool) ch.validLCN)
++Lcn;
}
public static ChanListStats operator +(ChanListStats a, ChanListStats b)
{
var stats = new ChanListStats();
stats.Tv = a.Tv + b.Tv;
stats.Radio = a.Radio + b.Radio;
stats.RadioMaskServiceTypeMismatch = a.RadioMaskServiceTypeMismatch + b.RadioMaskServiceTypeMismatch;
stats.Radio0 = a.Radio0 + b.Radio0;
stats.Radio4k = a.Radio4k + b.Radio4k;
stats.inMajorOrder = a.inMajorOrder && b.inMajorOrder;
stats.hasGap = a.hasGap || b.hasGap;
stats.deletedMajor0 = a.deletedMajor0 || b.deletedMajor0;
stats.deletedMajorNon0 = a.deletedMajorNon0 || b.deletedMajorNon0;
stats.UserEditChNumber = a.UserEditChNumber + b.UserEditChNumber;
stats.UserSelChNo = a.UserSelChNo + b.UserSelChNo;
stats.FactoryDefault = a.FactoryDefault + b.FactoryDefault;
stats.Disabled = a.Disabled + b.Disabled;
stats.Skipped = a.Skipped + b.Skipped;
stats.Invisible = a.Invisible + b.Invisible;
stats.Deleted = a.Deleted + b.Deleted;
stats.Discarded = a.Discarded + b.Discarded;
stats.UserCustomize = a.UserCustomize + b.UserCustomize;
stats.NumUnSel = a.NumUnSel + b.NumUnSel;
stats.Lcn = a.Lcn + b.Lcn;
return stats;
}
}
#endregion
}