From e27087e6e424ca72c2bf8cb5a687dc0ca8e96636 Mon Sep 17 00:00:00 2001 From: Horst Beham Date: Wed, 4 Jan 2023 13:31:46 +0100 Subject: [PATCH] - TCL/Thomson: improved file detection (.tar file or directory containing DtvData.db, satellite.db, cloneCRC.bin) - m3u: #EXTINF tag data is displayed in "Short Name" column and can be edited - m3u: fixed saving #EXTINF lines containing tag data - m3u: readded "File / Save as" menu item (but not for other types of lists) --- .../ChanSort.Api/Controller/SerializerBase.cs | 3 + source/ChanSort.Loader.M3u/Channel.cs | 1 + source/ChanSort.Loader.M3u/Serializer.cs | 53 ++++-- .../ChanSort.Loader.TCL/DtvDataSerializer.cs | 167 ++++++++++++------ source/ChanSort.Loader.TCL/TclPlugin.cs | 26 ++- source/ChanSort/MainForm.Designer.cs | 17 +- source/ChanSort/MainForm.cs | 14 +- source/ChanSort/MainForm.resx | 24 ++- source/Test.Loader.M3u/M3uTest.cs | 29 ++- source/Test.Loader.M3u/Test.Loader.M3u.csproj | 4 + .../Test.Loader.M3u/TestFiles/extinftags.m3u | 14 ++ source/changelog.md | 7 + source/fileformats.md | 4 + source/fileformats_de.md | 4 + 14 files changed, 283 insertions(+), 84 deletions(-) create mode 100644 source/Test.Loader.M3u/TestFiles/extinftags.m3u diff --git a/source/ChanSort.Api/Controller/SerializerBase.cs b/source/ChanSort.Api/Controller/SerializerBase.cs index 7ea5819..b5291af 100644 --- a/source/ChanSort.Api/Controller/SerializerBase.cs +++ b/source/ChanSort.Api/Controller/SerializerBase.cs @@ -30,6 +30,7 @@ namespace ChanSort.Api public ChannelNameEditMode ChannelNameEdit { get; set; } public bool CleanUpChannelData { get; set; } public bool DeviceSettings { get; set; } + public bool CanSaveAs { get; set; } public bool CanSkipChannels { get; set; } = true; public bool CanLockChannels { get; set; } = true; public bool CanHideChannels { get; set; } = true; @@ -67,12 +68,14 @@ namespace ChanSort.Api public bool CanEditFavListNames { get; set; } public bool CanEditAudioPid { get; set; } + public bool AllowShortNameEdit { get; set; } } #endregion private Encoding defaultEncoding; public string FileName { get; protected set; } + public string SaveAsFileName { get; set; } public DataRoot DataRoot { get; protected set; } public SupportedFeatures Features { get; } = new SupportedFeatures(); diff --git a/source/ChanSort.Loader.M3u/Channel.cs b/source/ChanSort.Loader.M3u/Channel.cs index d53af55..9587ad6 100644 --- a/source/ChanSort.Loader.M3u/Channel.cs +++ b/source/ChanSort.Loader.M3u/Channel.cs @@ -7,6 +7,7 @@ namespace ChanSort.Loader.M3u { public List Lines { get; } public int ExtInfTrackNameIndex { get; set; } + public int ExtInfParamIndex { get; set; } public Channel(int index, int progNr, string name, List lines) : base(SignalSource.IP, index, progNr, name) { diff --git a/source/ChanSort.Loader.M3u/Serializer.cs b/source/ChanSort.Loader.M3u/Serializer.cs index 4297f2d..54e9b1f 100644 --- a/source/ChanSort.Loader.M3u/Serializer.cs +++ b/source/ChanSort.Loader.M3u/Serializer.cs @@ -30,16 +30,18 @@ namespace ChanSort.Loader.M3u this.Features.ChannelNameEdit = ChannelNameEditMode.All; this.Features.DeleteMode = DeleteMode.Physically; this.Features.FavoritesMode = FavoritesMode.None; + this.Features.CanSaveAs = true; this.Features.CanLockChannels = false; this.Features.CanSkipChannels = false; this.Features.CanHideChannels = false; + this.Features.AllowShortNameEdit = true; this.DataRoot.AddChannelList(this.allChannels); base.DefaultEncoding = new UTF8Encoding(false); this.allChannels.VisibleColumnFieldNames = new List() { - "+OldPosition", "+Position", "+Name", "+SatPosition", "+Source", "+FreqInMhz", "+Polarity", "+SymbolRate", "+Satellite", "+Provider", "+Debug" + "+OldPosition", "+Position", "+Name", "+SatPosition", "+Source", "+FreqInMhz", "+Polarity", "+SymbolRate", "+Satellite", "+Provider", "+Debug", "+ShortName" }; } #endregion @@ -122,12 +124,14 @@ namespace ChanSort.Loader.M3u { int progNr = 0; string name = ""; + string paramStr = null; + int extInfParamIndex = -1; int extInfTrackNameIndex = -1; if (extInfLine != null) { bool extInfContainsProgNr = false; - ParseExtInf(extInfLine, out name, out extInfTrackNameIndex, out var param); + ParseExtInf(extInfLine, out name, out extInfTrackNameIndex, out paramStr, out extInfParamIndex, out var param); if (name != "") { var match = ExtInfTrackName.Match(name); @@ -153,6 +157,8 @@ namespace ChanSort.Loader.M3u chan.Uid = uriLine; chan.ExtInfTrackNameIndex = extInfTrackNameIndex; chan.Provider = group; + chan.ShortName = paramStr; + chan.ExtInfParamIndex = extInfParamIndex; try { @@ -212,10 +218,12 @@ namespace ChanSort.Loader.M3u /// parse track name from lines that may look like: /// #EXTINF:<length>[ key="value" ...],<TrackName> /// - private void ParseExtInf(string extInfLine, out string name, out int nameIndex, out Dictionary param) + private void ParseExtInf(string extInfLine, out string name, out int nameIndex, out string paramString, out int paramIndex, out Dictionary param) { name = ""; nameIndex = -1; + paramString = ""; + paramIndex = -1; param = new Dictionary(); bool inQuote = false; var key = ""; @@ -232,7 +240,10 @@ namespace ChanSort.Loader.M3u break; case ExtInfParsePhase.Length: if (ch == ' ') + { phase = ExtInfParsePhase.Key; + paramIndex = i; + } else if (ch == ',') { phase = ExtInfParsePhase.Name; @@ -253,12 +264,12 @@ namespace ChanSort.Loader.M3u param[key] = value; key = ""; value = ""; - } else if (ch == ',' && !inQuote) { - phase = ExtInfParsePhase.Name; param[key] = value; + phase = ExtInfParsePhase.Name; + nameIndex = i + 1; } else value += ch; @@ -269,6 +280,9 @@ namespace ChanSort.Loader.M3u break; } } + + if (paramIndex >= 0 && nameIndex >= 0) + paramString = extInfLine.Substring(paramIndex + 1, nameIndex - paramIndex - 2); } #endregion @@ -276,6 +290,9 @@ namespace ChanSort.Loader.M3u #region Save() public override void Save() { + if (!string.IsNullOrEmpty(this.SaveAsFileName)) + this.FileName = this.SaveAsFileName; + using var file = new StreamWriter(new FileStream(this.FileName, FileMode.Create), this.overrideEncoding ?? this.DefaultEncoding); file.NewLine = this.newLine; @@ -285,19 +302,25 @@ namespace ChanSort.Loader.M3u foreach (ChannelInfo channel in this.allChannels.GetChannelsByNewOrder()) { // when a reference list was applied, the list may contain proxy entries for deleted channels, which must be ignored - if (channel is Channel chan && !channel.IsDeleted) + if (channel is not Channel chan || channel.IsDeleted) + continue; + + foreach (var line in chan.Lines) { - foreach (var line in chan.Lines) + if (line.StartsWith("#EXTINF:")) { - if (line.StartsWith("#EXTINF:")) - { - var progNrPrefix = this.allChannelsPrefixedWithProgNr ? chan.NewProgramNr + ". " : ""; - var linePrefix = chan.ExtInfTrackNameIndex >= 0 ? line.Substring(0, chan.ExtInfTrackNameIndex) : ""; - file.WriteLine($"{linePrefix}{progNrPrefix}{chan.Name}"); - } - else - file.WriteLine(line); + var progNrPrefix = this.allChannelsPrefixedWithProgNr ? chan.NewProgramNr + ". " : ""; + string linePrefix; + if (chan.ExtInfParamIndex >= 0) + linePrefix = line.Substring(0, chan.ExtInfParamIndex) + " " + chan.ShortName + ","; + else if (chan.ExtInfTrackNameIndex >= 0) + linePrefix = line.Substring(0, chan.ExtInfTrackNameIndex); + else + linePrefix = "#EXTINF:-1,"; + file.WriteLine($"{linePrefix}{progNrPrefix}{chan.Name}"); } + else + file.WriteLine(line); } } diff --git a/source/ChanSort.Loader.TCL/DtvDataSerializer.cs b/source/ChanSort.Loader.TCL/DtvDataSerializer.cs index f0e0a60..6ff32ee 100644 --- a/source/ChanSort.Loader.TCL/DtvDataSerializer.cs +++ b/source/ChanSort.Loader.TCL/DtvDataSerializer.cs @@ -10,14 +10,16 @@ using SharpCompress.Writers.Tar; namespace ChanSort.Loader.TCL { /* - * This class loads TCL / Thomson .tar files containing DtvData.db and satellite.db SQLite databases. + * This class loads TCL / Thomson channel lists from a directory or a .tar file containing cloneCRC.bin, DtvData.db and satellite.db. * * None of the sample files contained more than a single input source (DVB-C/T/S), so for the time being this loader puts everything into a single list */ class DtvDataSerializer : SerializerBase { private readonly ChannelList channels = new (SignalSource.All, "All"); + private string dbDir; private string dtvFile; + private string satFile; private string crcFile; private readonly HashSet tableNames = new(); @@ -67,58 +69,66 @@ namespace ChanSort.Loader.TCL #region Load() public override void Load() + { + PrepareWorkingDirectory(); + ValidateCrc(); + ReadSattelliteDb(); + ReadDtvDataDb(); + } + #endregion + + #region PrepareWorkingDirectory() + /// + /// this.FileName might be + /// - a .tar file containing database/cloneCRC.bin, database/userdata/DtvData.db, database/userdata/satellite.db + /// - a .db file in a folder with DtvData.db and satellite.db and a cloneCRC.bin in either the same dir or the parent dir + /// Other situations have already been handled in the + /// + private void PrepareWorkingDirectory() + { + var ext = Path.GetExtension(this.FileName).ToLowerInvariant(); + if (ext == ".tar") + { + UntarToTempDir(); + this.crcFile = Path.Combine(this.TempPath, "database", "cloneCRC.bin"); + this.dbDir = Path.Combine(this.TempPath, "database", "userdata"); + } + else if (ext == ".db") + { + this.dbDir = Path.GetDirectoryName(this.FileName); + this.crcFile = Path.Combine(this.dbDir, "cloneCRC.bin"); + if (!File.Exists(crcFile)) + this.crcFile = Path.Combine(Path.GetDirectoryName(this.dbDir), "cloneCRC.bin"); + } + else + throw LoaderException.TryNext("unrecognized TCL/Thomson directory structure"); + + this.dtvFile = Path.Combine(dbDir, "DtvData.db"); + if (!File.Exists(dtvFile)) + throw LoaderException.TryNext("Missing DtvData.db file"); + + this.satFile = Path.Combine(dbDir, "satellite.db"); + if (!File.Exists(satFile)) + satFile = null; + + if (!File.Exists(crcFile)) + crcFile = null; + } + #endregion + + #region UntarToTempDir() + private void UntarToTempDir() { using var tar = TarArchive.Open(this.FileName); var rdr = tar.ExtractAllEntries(); this.TempPath = Path.Combine(Path.GetTempPath(), "ChanSort_" + DateTime.Now.ToString("yyyyMMdd-HHmmss")); Directory.CreateDirectory(this.TempPath); - rdr.WriteAllToDirectory(this.TempPath, new ExtractionOptions { ExtractFullPath=true }); - - this.crcFile = Path.Combine(this.TempPath, "database", "cloneCRC.bin"); - var dbDir = Path.Combine(this.TempPath, "database", "userdata"); - this.dtvFile = Path.Combine(dbDir, "DtvData.db"); - var satFile = Path.Combine(dbDir, "satellite.db"); - - if (!File.Exists(dtvFile) || !File.Exists(satFile)) - throw LoaderException.TryNext("DtvData.db or satellite.db missing"); - - ValidateCrc(satFile); - - string satConnString = $"Data Source={satFile};Pooling=False"; - using (var conn = new SqliteConnection(satConnString)) - { - conn.Open(); - using var cmd = conn.CreateCommand(); - this.RepairCorruptedDatabaseImage(cmd); - - cmd.CommandText = "SELECT name FROM sqlite_master WHERE type = 'table'"; - using (var r = cmd.ExecuteReader()) - { - while (r.Read()) - this.tableNames.Add(r.GetString(0).ToLowerInvariant()); - } - - if (!this.tableNames.Contains("sateliteinfotbl") || !this.tableNames.Contains("transponderinfotbl")) - throw LoaderException.TryNext("File doesn't contain the expected tables"); - - this.ReadSatellites(cmd); - } - - string dtvConnString = $"Data Source={dtvFile};Pooling=False"; - using (var conn = new SqliteConnection(dtvConnString)) - { - conn.Open(); - using var cmd = conn.CreateCommand(); - this.RepairCorruptedDatabaseImage(cmd); - - this.ReadTransponders(cmd); - this.ReadChannels(cmd); - } + rdr.WriteAllToDirectory(this.TempPath, new ExtractionOptions { ExtractFullPath = true }); } #endregion #region ValidateCrc() - private void ValidateCrc(string satFile) + private void ValidateCrc() { if (!File.Exists(crcFile)) return; @@ -136,19 +146,62 @@ namespace ChanSort.Loader.TCL //throw LoaderException.Fail(msg); } - data = File.ReadAllBytes(satFile); - actual = crc.Calc(data); - expected = BitConverter.ToUInt16(crcData, 4); - if (actual != expected) + if (satFile != null) { - var msg = $"Invalid CRC16-CCITT check sum for {satFile}. Expected {expected:X4} but calculated {actual:X4}"; - protocol.AppendLine(msg); - //throw LoaderException.Fail(msg); + data = File.ReadAllBytes(satFile); + actual = crc.Calc(data); + expected = BitConverter.ToUInt16(crcData, 4); + if (actual != expected) + { + var msg = $"Invalid CRC16-CCITT check sum for {satFile}. Expected {expected:X4} but calculated {actual:X4}"; + protocol.AppendLine(msg); + //throw LoaderException.Fail(msg); + } } } #endregion - + + #region ReadSattelliteDb() + private void ReadSattelliteDb() + { + if (this.satFile == null) + return; + string satConnString = $"Data Source={satFile};Pooling=False"; + using var conn = new SqliteConnection(satConnString); + conn.Open(); + using var cmd = conn.CreateCommand(); + this.RepairCorruptedDatabaseImage(cmd); + + cmd.CommandText = "SELECT name FROM sqlite_master WHERE type = 'table'"; + using (var r = cmd.ExecuteReader()) + { + while (r.Read()) + this.tableNames.Add(r.GetString(0).ToLowerInvariant()); + } + + if (!this.tableNames.Contains("sateliteinfotbl") || !this.tableNames.Contains("transponderinfotbl")) + throw LoaderException.TryNext("File doesn't contain the expected tables"); + + this.ReadSatellites(cmd); + } + + #endregion + + #region ReadDtvDataDb() + private void ReadDtvDataDb() + { + string dtvConnString = $"Data Source={dtvFile};Pooling=False"; + using var conn = new SqliteConnection(dtvConnString); + conn.Open(); + using var cmd = conn.CreateCommand(); + this.RepairCorruptedDatabaseImage(cmd); + + this.ReadTransponders(cmd); + this.ReadChannels(cmd); + } + #endregion + #region RepairCorruptedDatabaseImage() private void RepairCorruptedDatabaseImage(SqliteCommand cmd) { @@ -285,7 +338,8 @@ left outer join CurCIOPSerType c on c.u8DtvRoute=p.u8DtvRoute UpdateCrc(); - WriteToTar(); + if (Path.GetExtension(this.FileName).ToLowerInvariant() == ".tar") + WriteToTar(); } #endregion @@ -329,9 +383,14 @@ left outer join CurCIOPSerType c on c.u8DtvRoute=p.u8DtvRoute #endregion #region UpdateCrc + /// + /// update CRC in cloneCRC.bin + /// private void UpdateCrc() { - // update cloneCRC.bin in temp folder + if (this.crcFile == null) + return; + var dtvData = File.ReadAllBytes(dtvFile); var crc = Crc16.CCITT.Calc(dtvData); var crcData = File.ReadAllBytes(this.crcFile); diff --git a/source/ChanSort.Loader.TCL/TclPlugin.cs b/source/ChanSort.Loader.TCL/TclPlugin.cs index 7753c2f..049e879 100644 --- a/source/ChanSort.Loader.TCL/TclPlugin.cs +++ b/source/ChanSort.Loader.TCL/TclPlugin.cs @@ -6,11 +6,33 @@ namespace ChanSort.Loader.TCL { public string DllName { get; set; } public string PluginName => "TCL"; - public string FileFilter => "*.tar"; + public string FileFilter => "*.tar;*.bin;*.db"; public SerializerBase CreateSerializer(string inputFile) { - return new DtvDataSerializer(inputFile); + var ext = Path.GetExtension(inputFile).ToLowerInvariant(); + if (ext == ".tar") + return new DtvDataSerializer(inputFile); + + var name = Path.GetFileName(inputFile).ToLowerInvariant(); + var dir = Path.GetDirectoryName(inputFile); + + if (name == "dtvdata.db" || name == "satellite.db") + return new DtvDataSerializer(Path.Combine(dir, "DtvData.db")); + + if (name == "clonecrc.bin") + { + // cloneCRC.bin normally is in the parent folder of userdata/DtvData.db, but might also be in the same folder + var file1 = Path.Combine(dir, "userdata", "DtvData.db"); + var file2 = Path.Combine(dir, "DtvData.db"); + foreach (var file in new[] { file1, file2 }) + { + if (File.Exists(file)) + return new DtvDataSerializer(file); + } + } + + throw LoaderException.TryNext("No DtvData.db file found"); } } } diff --git a/source/ChanSort/MainForm.Designer.cs b/source/ChanSort/MainForm.Designer.cs index 0bf9ccc..b9702c9 100644 --- a/source/ChanSort/MainForm.Designer.cs +++ b/source/ChanSort/MainForm.Designer.cs @@ -117,6 +117,7 @@ this.miRestoreOriginal = new DevExpress.XtraBars.BarButtonItem(); this.miDeleteBackup = new DevExpress.XtraBars.BarButtonItem(); this.miSave = new DevExpress.XtraBars.BarButtonItem(); + this.miSaveAs = new DevExpress.XtraBars.BarButtonItem(); this.miSaveReferenceFile = new DevExpress.XtraBars.BarButtonItem(); this.miConvert = new DevExpress.XtraBars.BarButtonItem(); this.miExcelExport = new DevExpress.XtraBars.BarButtonItem(); @@ -1133,9 +1134,10 @@ this.miDeleteBackup, this.miMarkForSwapping, this.miSwapWithMarked, - this.miConvert}); + this.miConvert, + this.miSaveAs}); this.barManager1.MainMenu = this.bar1; - this.barManager1.MaxItemId = 121; + this.barManager1.MaxItemId = 122; this.barManager1.ShowFullMenus = true; this.barManager1.ShortcutItemClick += new DevExpress.XtraBars.ShortcutItemClickEventHandler(this.barManager1_ShortcutItemClick); // @@ -1188,6 +1190,7 @@ new DevExpress.XtraBars.LinkPersistInfo(this.miRestoreOriginal), new DevExpress.XtraBars.LinkPersistInfo(this.miDeleteBackup), new DevExpress.XtraBars.LinkPersistInfo(this.miSave, true), + new DevExpress.XtraBars.LinkPersistInfo(this.miSaveAs), new DevExpress.XtraBars.LinkPersistInfo(this.miSaveReferenceFile), new DevExpress.XtraBars.LinkPersistInfo(this.miConvert), new DevExpress.XtraBars.LinkPersistInfo(this.miExcelExport), @@ -1249,6 +1252,13 @@ this.miSave.Name = "miSave"; this.miSave.ItemClick += new DevExpress.XtraBars.ItemClickEventHandler(this.miSave_ItemClick); // + // miSaveAs + // + resources.ApplyResources(this.miSaveAs, "miSaveAs"); + this.miSaveAs.Id = 121; + this.miSaveAs.Name = "miSaveAs"; + this.miSaveAs.ItemClick += new DevExpress.XtraBars.ItemClickEventHandler(this.miSaveAs_ItemClick); + // // miSaveReferenceFile // resources.ApplyResources(this.miSaveReferenceFile, "miSaveReferenceFile"); @@ -2516,6 +2526,7 @@ private DevExpress.XtraBars.BarButtonItem miMarkForSwapping; private DevExpress.XtraBars.BarButtonItem miSwapWithMarked; private DevExpress.XtraBars.BarButtonItem miConvert; - } + private DevExpress.XtraBars.BarButtonItem miSaveAs; + } } diff --git a/source/ChanSort/MainForm.cs b/source/ChanSort/MainForm.cs index 0ea960d..c74f410 100644 --- a/source/ChanSort/MainForm.cs +++ b/source/ChanSort/MainForm.cs @@ -308,6 +308,10 @@ namespace ChanSort.Ui this.currentRefFile = Path.Combine(Path.GetDirectoryName(this.currentTvFile) ?? "", Path.GetFileNameWithoutExtension(this.currentTvFile) + ".txt"); } + + if (this.currentTvSerializer != null) + this.currentTvSerializer.SaveAsFileName = tvDataFile; + this.Text = this.title + " - " + this.currentTvFile; } @@ -358,6 +362,7 @@ namespace ChanSort.Ui this.UpdateFavoritesEditor(this.DataRoot.SupportedFavorites); this.colEncrypted.OptionsColumn.AllowEdit = this.currentTvSerializer.Features.EncryptedFlagEdit; this.colAudioPid.OptionsColumn.AllowEdit = this.currentTvSerializer.Features.CanEditAudioPid; + this.colShortName.OptionsColumn.AllowEdit = this.currentTvSerializer.Features.AllowShortNameEdit; this.UpdateMenu(true); if (this.DataRoot.Warnings.Length > 0 && this.miShowWarningsAfterLoad.Checked) @@ -1796,6 +1801,8 @@ namespace ChanSort.Ui this.mnuGotoChannelList.Enabled = fileLoaded; this.mnuGotoFavList.Enabled = fileLoaded; this.miGotoLeftList.Enabled = this.miGotoRightList.Enabled = fileLoaded; + this.miSaveAs.Enabled = fileLoaded && this.currentTvSerializer.Features.CanSaveAs; + this.miSaveAs.Visibility = miSaveAs.Enabled ? BarItemVisibility.Always : BarItemVisibility.Never; } this.miAddChannel.Enabled = mayEdit; // && isRight; @@ -3331,6 +3338,11 @@ namespace ChanSort.Ui TryExecute(this.SaveFiles); } + private void miSaveAs_ItemClick(object sender, ItemClickEventArgs e) + { + TryExecute(this.ShowSaveFileDialog); + } + private void miConvert_ItemClick(object sender, ItemClickEventArgs e) { XtraMessageBox.Show(this, Resources.MainForm_miConvert_MessageBody, Resources.MainForm_miConvert_MessageHeader); @@ -3936,6 +3948,6 @@ namespace ChanSort.Ui } } - #endregion + #endregion } } \ No newline at end of file diff --git a/source/ChanSort/MainForm.resx b/source/ChanSort/MainForm.resx index 860ae38..fbd9209 100644 --- a/source/ChanSort/MainForm.resx +++ b/source/ChanSort/MainForm.resx @@ -276,7 +276,7 @@ gridLeft - ChanSort.XGridControl, ChanSort, Version=1.0.8403.33212, Culture=neutral, PublicKeyToken=null + ChanSort.XGridControl, ChanSort, Version=1.0.8404.23947, Culture=neutral, PublicKeyToken=null grpOutputList @@ -419,6 +419,12 @@ 4 + + Save as... + + + Save channel list under a different file name + Save reference list... @@ -1234,7 +1240,7 @@ gviewLeft - ChanSort.XGridView, ChanSort, Version=1.0.8403.33212, Culture=neutral, PublicKeyToken=null + ChanSort.XGridView, ChanSort, Version=1.0.8404.23947, Culture=neutral, PublicKeyToken=null colIndex1 @@ -1318,13 +1324,13 @@ globalImageCollection1 - ChanSort.Ui.GlobalImageCollection, ChanSort, Version=1.0.8403.33212, Culture=neutral, PublicKeyToken=null + ChanSort.Ui.GlobalImageCollection, ChanSort, Version=1.0.8404.23947, Culture=neutral, PublicKeyToken=null gviewRight - ChanSort.XGridView, ChanSort, Version=1.0.8403.33212, Culture=neutral, PublicKeyToken=null + ChanSort.XGridView, ChanSort, Version=1.0.8404.23947, Culture=neutral, PublicKeyToken=null colIndex @@ -1578,6 +1584,12 @@ DevExpress.XtraBars.BarButtonItem, DevExpress.XtraBars.v22.1, Version=22.1.6.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a + + miSaveAs + + + DevExpress.XtraBars.BarButtonItem, DevExpress.XtraBars.v22.1, Version=22.1.6.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a + miSaveReferenceFile @@ -2113,7 +2125,7 @@ DevExpress.XtraEditors.XtraForm, DevExpress.Utils.v22.1, Version=22.1.6.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a - 01/03/2023 18:28:18 + 01/04/2023 13:23:37 16, 16 @@ -2996,7 +3008,7 @@ gridRight - ChanSort.XGridControl, ChanSort, Version=1.0.8403.33212, Culture=neutral, PublicKeyToken=null + ChanSort.XGridControl, ChanSort, Version=1.0.8404.23947, Culture=neutral, PublicKeyToken=null grpInputList diff --git a/source/Test.Loader.M3u/M3uTest.cs b/source/Test.Loader.M3u/M3uTest.cs index a55d4e9..48e6a99 100644 --- a/source/Test.Loader.M3u/M3uTest.cs +++ b/source/Test.Loader.M3u/M3uTest.cs @@ -1,4 +1,5 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.IO; +using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Linq; using ChanSort.Api; using ChanSort.Loader.M3u; @@ -8,8 +9,9 @@ namespace Test.Loader.M3u [TestClass] public class M3uTest { + #region TestReading() [TestMethod] - public void TestMethod1() + public void TestReading() { var m3uFile = TestUtils.DeploymentItem("Test.Loader.M3u\\TestFiles\\example.m3u"); var refFile = TestUtils.DeploymentItem("Test.Loader.M3u\\TestFiles\\example-ref.txt"); @@ -49,6 +51,28 @@ namespace Test.Loader.M3u Assert.AreEqual(1, chans[5].NewProgramNr); Assert.AreEqual(2, chans[4].NewProgramNr); } + #endregion + + #region TestSavingKeepsExtinfTags() + [TestMethod] + public void TestSavingKeepsExtinfTags() + { + var m3uFile = TestUtils.DeploymentItem("Test.Loader.M3u\\TestFiles\\extinftags.m3u"); + + var orig = File.ReadAllText(m3uFile); + + var loader = new M3uPlugin(); + var ser = loader.CreateSerializer(m3uFile); + ser.Load(); + ser.Save(); + + var text = File.ReadAllText(m3uFile); + + orig = orig.Replace("\r", "").TrimEnd(); + text = text.Replace("\r", "").TrimEnd(); + NUnit.Framework.Assert.AreEqual(orig, text); + } + #endregion #region TestChannelAndFavListEditing [TestMethod] @@ -58,6 +82,5 @@ namespace Test.Loader.M3u RoundtripTest.TestChannelAndFavListEditing(tempFile, new M3uPlugin()); } #endregion - } } diff --git a/source/Test.Loader.M3u/Test.Loader.M3u.csproj b/source/Test.Loader.M3u/Test.Loader.M3u.csproj index cd61984..f0ca594 100644 --- a/source/Test.Loader.M3u/Test.Loader.M3u.csproj +++ b/source/Test.Loader.M3u/Test.Loader.M3u.csproj @@ -15,6 +15,9 @@ + + PreserveNewest + PreserveNewest @@ -25,6 +28,7 @@ + \ No newline at end of file diff --git a/source/Test.Loader.M3u/TestFiles/extinftags.m3u b/source/Test.Loader.M3u/TestFiles/extinftags.m3u new file mode 100644 index 0000000..8f10a4b --- /dev/null +++ b/source/Test.Loader.M3u/TestFiles/extinftags.m3u @@ -0,0 +1,14 @@ +#EXTM3U url-tvg="http://satip-server.local/epg/myepg.xml" +# +#EXTINF:-1 tvg-id="3sat" tvg-logo="https://raw.githubusercontent.com/jnk22/kodinerds-iptv/master/logos/tv/3sat.png",3sat HD +#EXTALBUMARTURL:https://raw.githubusercontent.com/jnk22/kodinerds-iptv/master/logos/tv/3sat.png +http://satip-server.local/?src=1&freq=11347&pol=v&msys=dvbs2&mtype=8psk&sr=22000&pids=0,16,17,18,20,21,6500,6510,6520,6521,6522,6523,6530,6531,6570 +# +#EXTINF:-1 tvg-id="kika" tvg-logo="https://raw.githubusercontent.com/jnk22/kodinerds-iptv/master/logos/tv/kika.png",KiKA HD +#EXTALBUMARTURL:https://raw.githubusercontent.com/jnk22/kodinerds-iptv/master/logos/tv/kika.png +http://satip-server.local/?src=1&freq=11347&pol=v&msys=dvbs2&mtype=8psk&sr=22000&pids=0,16,17,18,20,21,6600,6610,6620,6621,6622,6630,6631,6670 + +#EXTINF:0 group-title="Streams",Russia Today +https://rt-news-gd.secure2.footprint.net/1103.m3u8 +#EXTINF:0,RBK +http://e3.online.video.rbc.ru/online2/rbctv_576p/index.m3u8 diff --git a/source/changelog.md b/source/changelog.md index fcbdb68..5230be6 100644 --- a/source/changelog.md +++ b/source/changelog.md @@ -1,6 +1,13 @@ ChanSort Change Log =================== +2023-01-04 +- TCL/Thomson: improved file detection (.tar file or directory containing DtvData.db, + satellite.db, cloneCRC.bin) +- m3u: #EXTINF tag data is displayed in "Short Name" column and can be edited +- m3u: fixed saving #EXTINF lines containing tag data +- m3u: readded "File / Save as" menu item (but not for other types of lists) + 2023-01-03 - added support for TCL / Thomson \*.tar channel lists (containing DtvData.db and satellite.db) - updated hotbird reference list for Italy diff --git a/source/fileformats.md b/source/fileformats.md index 9c084db..07248d6 100644 --- a/source/fileformats.md +++ b/source/fileformats.md @@ -94,6 +94,10 @@ Do not make any changes in the service menu, as this could damage your TV. Only - Models that export files named dvb\*_config.xml. - Models that export a cvt_database.dat file, e.g. 24 GHB 5944: see [Sharp](#Sharp) +TCL, Thomson +--- +- Models that export a .tar file containing DtvData.db and satellite.db + SatcoDX (supplier for ITT, Medion, Nabo, ok., PEAQ, Schaub-Lorenz, Silva-Schneider, Telefunken) --- Various brands use the same hardware for DVB-S, which exports .sdx files diff --git a/source/fileformats_de.md b/source/fileformats_de.md index b114cc4..d35bb31 100644 --- a/source/fileformats_de.md +++ b/source/fileformats_de.md @@ -94,6 +94,10 @@ Servicemenu: MENU 1147 / MENU 11471147 / SOURCE 2580 - Modelle die Dateien mit Namen dvb\*_config.xml exportieren. - Modelle die eine cvt_database.dat Datei exportieren, z.B. 24 GHB 5944: siehe "Sharp" +TCL, Thomson +--- +- Modelle die eine .tar Datei exportieren, in der DtvData.db und satellite.db enthalten sind + SatcoDX (Lieferant für ITT, Medion, Nabo, ok., PEAQ, Schaub-Lorenz, Silva-Schneider, Telefunken) --- Mehrere Marken nutzen die gleiche Hardware für DVB-S und exportieren .sdx Dateien