├── Properties
└── launchSettings.json
├── TVHeadEnd
├── Model
│ ├── Service.cs
│ ├── Network.cs
│ ├── ObjectBase.cs
│ └── Mux.cs
└── TVHClient.cs
├── TVHeadEndM3USync.csproj
├── M3U
├── Model
│ └── Entry.cs
├── Cleaner.cs
└── Parser.cs
├── TVHeadEndM3USync.sln
├── .gitattributes
├── README.md
├── Program.cs
└── .gitignore
/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "TVHeadEndM3USync": {
4 | "commandName": "Project",
5 | "commandLineArgs": "http://192.168.200.71:9981/ \"c:\\temp\\t.m3u\" testy m3u m3u"
6 | }
7 | }
8 | }
--------------------------------------------------------------------------------
/TVHeadEnd/Model/Service.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace TVHeadEndM3USync.TVHeadEnd.Model
6 | {
7 | class Service : ModelBase
8 | {
9 |
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/TVHeadEnd/Model/Network.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace TVHeadEndM3USync.TVHeadEnd.Model
6 | {
7 | class Network : ModelBase
8 | {
9 |
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/TVHeadEndM3USync.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net6.0
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/TVHeadEnd/Model/ObjectBase.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace TVHeadEndM3USync.TVHeadEnd.Model
6 | {
7 | class ModelBase
8 | {
9 | public string UUID { get; set; }
10 | public string Name { get; set; }
11 | public bool Enabled { get; set; }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/TVHeadEnd/Model/Mux.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace TVHeadEndM3USync.TVHeadEnd.Model
6 | {
7 | class Mux : ModelBase
8 | {
9 | public string NetworkName { get; set; }
10 | public string NetworkUUID { get; set; }
11 | public string Url { get; set; }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/M3U/Model/Entry.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace TVHeadEndM3USync.M3U.Model
6 | {
7 | class Entry
8 | {
9 | public string Url { get; set; }
10 | public string XTINF { get; set; }
11 | public string Name { get; set; }
12 | public string TVH_UUID { get; set;}
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/TVHeadEndM3USync.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.30225.117
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TVHeadEndM3USync", "TVHeadEndM3USync.csproj", "{A094F73C-1317-4F91-AD94-5358DEC731A8}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {A094F73C-1317-4F91-AD94-5358DEC731A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {A094F73C-1317-4F91-AD94-5358DEC731A8}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {A094F73C-1317-4F91-AD94-5358DEC731A8}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {A094F73C-1317-4F91-AD94-5358DEC731A8}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ExtensibilityGlobals) = postSolution
23 | SolutionGuid = {A0CFFE68-4790-4ED7-AF9A-9C387AE35946}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/M3U/Cleaner.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Text;
5 | using TVHeadEndM3USync.M3U.Model;
6 | using System.Linq;
7 | namespace TVHeadEndM3USync.M3U
8 | {
9 | static class M3UCleaner
10 | {
11 | public static void CleanupM3UFile(string filename)
12 | {
13 | if (!File.Exists(filename))
14 | {
15 | Console.WriteLine($"M3U cleanup - File {filename} not found");
16 | return;
17 | }
18 |
19 | var requireSave = false;
20 | var entires = Parser.GetEntries(filename);
21 | foreach (var entry in entires)
22 | {
23 | var madeChanges = CleanupEntry(entry);
24 | if (madeChanges)
25 | requireSave = true;
26 | }
27 | if (requireSave)
28 | {
29 | var backupFilename = filename;
30 |
31 | backupFilename = Path.Combine(Path.GetDirectoryName(filename), "cleanupBackup" + DateTime.Now.ToString("yyyyMMddhhmmss") + "_" + Path.GetFileName(filename));
32 | Console.WriteLine("Cleanup needed , saving backup file to {0}", backupFilename);
33 | File.Copy(filename, backupFilename);
34 | Parser.WriteFile(filename, entires);
35 | Console.WriteLine("File cleaned up.");
36 | }
37 | else
38 | Console.WriteLine("No cleanup needed.");
39 | }
40 |
41 | private static bool CleanupEntry(Entry entry)
42 | {
43 | var tags = entry.XTINF.Substring(entry.XTINF.IndexOf(" ") + 1);
44 | tags = tags.Substring(0, tags.IndexOf(","));
45 | var tagList = Parser.ParseTags(tags);
46 | if (tagList.Any(x => x.Item1 != Parser.TVH_UUID_KEY))
47 | {
48 | tagList = tagList.Where(x => x.Item1 == Parser.TVH_UUID_KEY).ToList();
49 | var prefix = entry.XTINF.Substring(0, entry.XTINF.IndexOf(" ") + 1);
50 | var suffix = entry.XTINF.Substring(entry.XTINF.IndexOf(","));
51 | entry.XTINF = prefix + GetTags(tagList) + suffix;
52 | return true;
53 | }
54 | return false;
55 | }
56 |
57 | private static string GetTags(List> tagList)
58 | {
59 | var sb = new StringBuilder();
60 | foreach (var t in tagList)
61 | sb.Append($"{t.Item1}=\"{t.Item2}\" ");
62 | return sb.ToString();
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # TVHeadEndM3USync
2 |
3 | This is a small utility (.Net core based) for keeping your M3U IPTV services up to date with your Tvheadend installation.
4 |
5 | Tvheadend has built in support for reading .m3u files and create muxes from them.
6 |
7 | The built in feature works just fine if your mux urls does not change.
8 |
9 | For example ,if your iptv channel url is :
10 |
11 | http://my.site/xxx/yyy/12323
12 |
13 | And your iptv provider changes it to :
14 |
15 | http://my.site/xxx/zzz/12323
16 |
17 | Tvheadend will delete the old mux and create new mux with the new url , which means you'll have to map the new service to the channel or the channel will not work.
18 |
19 | What this tool does is altering your local m3u file (the tool work only with local m3u file) and add Tvheadend's uuid tag to the m3u file so it can keep it in sync.
20 |
21 | For example, lets say i have the following m3u file :
22 |
23 | ```
24 | #EXTM3U
25 | #EXTINF:-1 ,ch1
26 | http://my.site/xxx/yyy/12323
27 | #EXTINF:-1 ,ch2
28 | http://my.site/xxx/yyy/12325
29 | ```
30 | After first run of this utility, the tool will see this is new m3u links and will create network/mux for them and then will save the changes to the m3u file (will keep a backup file) :
31 |
32 | ```
33 | #EXTM3U
34 | #EXTINF:-1 TVH-UUID="d034dfd4bacb08183ce5a3487d46bd82",ch1
35 | http://my.site/xxx/yyy/12323
36 | #EXTINF:-1 TVH-UUID="8dc02b130c9c4e53dbbb746a43eff255",ch2
37 | http://my.site/xxx/yyy/12325
38 | ```
39 | As you can see the tool added TVH-UUID tag to each url, this UUID is generated by Tvheadend as mux id.
40 |
41 | Now , lets say our iptv provider changes the url for ch1 from 12323 to 98333 we just change our local m3u file to :
42 |
43 | ```
44 | #EXTM3U
45 | #EXTINF:-1 TVH-UUID="d034dfd4bacb08183ce5a3487d46bd82",ch1
46 | http://my.site/xxx/yyy/98333
47 | #EXTINF:-1 TVH-UUID="8dc02b130c9c4e53dbbb746a43eff255",ch2
48 | http://my.site/xxx/yyy/12325
49 | ```
50 |
51 | Save the changes and rerun the tool , it will compare the uuid to the mux on Tvheadend and detect that there is new url and will update the same mux url to the new one (without deleting so we dont need to remap the channel).
52 |
53 | if you want to change the mux name from ch1 , we can do it also :
54 |
55 | ```
56 | #EXTM3U
57 | #EXTINF:-1 TVH-UUID="d034dfd4bacb08183ce5a3487d46bd82",ch1 HD
58 | http://my.site/xxx/yyy/98333
59 | #EXTINF:-1 TVH-UUID="8dc02b130c9c4e53dbbb746a43eff255",ch2
60 | http://my.site/xxx/yyy/12325
61 | ```
62 |
63 | After running the tool the mux name will change to "ch1 HD".
64 |
65 | If you want to delete a mux , just remove the line from the m3u and re-run the tool , the mux will be deleted from Tvheadend.
66 |
67 | If from some reason the TVH-UUID is gone missing or you delete the mux via Tvheadend GUI and re-run the tool it will re-add the mux and assign the m3u file with the new uuid value.
68 |
69 | To keep things simple , manage your IPTV urls only by editing your local m3u file and sync them using this tool.
70 |
71 | The tool requires 3 parameters :
72 |
73 | 1.Tvheadend url - the url to your web gui with the correct port , should be something like http://tvheadendip:9981/.
74 |
75 | 2.path to your local m3u file (do remember it will be written with uuid for each url so keep it available, its **not** one time file)
76 |
77 | 3.network name , supply the name of the network to sync/manage on Tvheadend , i would advise using new network for each m3u file.
78 |
79 | 4.username if needed by your Tvheadend setup. (tested only with "both plain and digest" setting on Tvheadend)
80 |
81 | 5.password if needed by your Tvheadend setup.
82 |
83 | Run it in the following way :
84 |
85 | ```
86 | dotnet TVHeadEndM3USync.dll
87 | ```
88 |
89 | Have fun.
90 |
--------------------------------------------------------------------------------
/M3U/Parser.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Text;
5 | using TVHeadEndM3USync.M3U.Model;
6 |
7 | namespace TVHeadEndM3USync.M3U
8 | {
9 | class Parser
10 | {
11 | const string HEADER = "#EXTM3U";
12 | public const string TVH_UUID_KEY = "TVH-UUID";
13 | public const string TVH_UUID = TVH_UUID_KEY + "=\"";
14 |
15 | public static List GetEntries(string filePath)
16 | {
17 | using (var sr = new StreamReader(new FileStream(filePath, FileMode.Open, FileAccess.Read)))
18 | {
19 | var header = sr.ReadLine();
20 | if (header != HEADER)
21 | throw new ApplicationException("expected #EXTM3U as file header");
22 | var lst = new List();
23 |
24 | string lastXTINF = null;
25 | while (!sr.EndOfStream)
26 | {
27 | var line = sr.ReadLine();
28 | if (line.StartsWith("#EXTINF"))
29 | lastXTINF = line;
30 | else
31 | if (lastXTINF != null)
32 | {
33 | var e = new Entry();
34 | e.Url = line.Trim();
35 | e.XTINF = lastXTINF;
36 | var idx = e.XTINF.LastIndexOf(",");
37 | if (idx > 0)
38 | {
39 | e.Name = e.XTINF.Substring(idx + 1).Trim();
40 | e.TVH_UUID = GetTVH_UUID(e.XTINF);
41 | lst.Add(e);
42 | }
43 | lastXTINF = null;
44 | }
45 | }
46 | return lst;
47 | }
48 | }
49 |
50 | private static string GetTVH_UUID(string xTINF)
51 | {
52 | var idx = xTINF.IndexOf(TVH_UUID);
53 | if (idx > 0)
54 | {
55 | var uuid = xTINF.Substring(idx + TVH_UUID.Length).Split("\"")[0];
56 | return uuid;
57 | }
58 | return null;
59 | }
60 |
61 | internal static List> ParseTags(string tags)
62 | {
63 | var lst = new List>();
64 | while (tags.Contains("="))
65 | {
66 | var key = tags.Substring(0, tags.IndexOf("="));
67 | tags = tags.Remove(0, key.Length + 2); // extra one for "
68 | var value = tags.Substring(0, tags.IndexOf("\""));
69 | tags = tags.Remove(0, value.Length + 1).Trim();
70 | lst.Add(new Tuple(key, value));
71 | }
72 |
73 | return lst;
74 | }
75 |
76 | internal static void WriteFile(string m3uFile, List entries)
77 | {
78 | File.Copy(m3uFile, m3uFile + ".backup" + DateTime.Now.ToString("yyyyMMddHHmmss"));
79 | File.Delete(m3uFile);
80 | using (var sw = new StreamWriter(File.Create(m3uFile)))
81 | {
82 | sw.WriteLine(HEADER);
83 | foreach (var e in entries)
84 | {
85 | var idx = e.XTINF.LastIndexOf(",");
86 | var x = e.XTINF;
87 | if (idx > 0)
88 | x = x.Substring(0, idx);
89 |
90 | if (!string.IsNullOrEmpty(e.TVH_UUID))
91 | {
92 | var currentUUID = GetTVH_UUID(e.XTINF);
93 | if (currentUUID == null)
94 | {
95 | x = x + " " + TVH_UUID + e.TVH_UUID + "\"";
96 | }
97 | else
98 | {
99 | x = x.Replace(currentUUID, e.TVH_UUID);
100 | }
101 | }
102 |
103 | x = x + "," + e.Name;
104 | sw.WriteLine(x);
105 | sw.WriteLine(e.Url);
106 | }
107 | }
108 | }
109 | }
110 | }
--------------------------------------------------------------------------------
/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Linq;
4 | using TVHeadEndM3USync.TVHeadEnd;
5 |
6 | namespace TVHeadEndM3USync
7 | {
8 | class Program
9 | {
10 | static void Main(string[] args)
11 | {
12 | if (args.Length == 1)
13 | {
14 | M3U.M3UCleaner.CleanupM3UFile(args[0]);
15 | return;
16 | }
17 |
18 | if (args.Length < 3)
19 | {
20 | Console.WriteLine("Missing Parameters");
21 | Console.WriteLine("1. TVHeadEnd Url");
22 | Console.WriteLine("2. M3U File Path");
23 | Console.WriteLine("3. TVHeadEnd Network Name");
24 | Console.WriteLine("4. Username (Optional , depends on if set on TVH)");
25 | Console.WriteLine("5. Password (Optional , depends on if set on TVH)");
26 | Environment.Exit(1);
27 | }
28 | var url = args[0];
29 | var m3uFile = args[1];
30 | var networkNameToSync = args[2];
31 | var username = args.Length > 3 ? args[3] : string.Empty;
32 | var password = args.Length > 4 ? args[4] : string.Empty;
33 |
34 | if (!File.Exists(m3uFile))
35 | {
36 | Console.WriteLine($"M3U File ({m3uFile}) not found");
37 | Environment.Exit(1);
38 | }
39 | try
40 | {
41 | var uri = new Uri(url);
42 | }
43 | catch
44 | {
45 | Console.WriteLine($"Invalid Url ({url})");
46 | Environment.Exit(1);
47 | }
48 |
49 | var m3uEntries = M3U.Parser.GetEntries(m3uFile);
50 |
51 | var cli = new TVHClient(url) { Username = username, Password = password };
52 |
53 | var currentNetwork = GetNetwork(networkNameToSync, cli);
54 |
55 |
56 | // scan existings muxes and update them according to m3u file info
57 |
58 | var muxes = cli.GetMuxes();
59 | var networkMuxes = muxes.Where(x => x.NetworkUUID == currentNetwork.UUID);
60 | foreach (var mux in networkMuxes)
61 | {
62 | var match = m3uEntries.FirstOrDefault(x => x.TVH_UUID == mux.UUID);
63 | if (match != null)
64 | {
65 | var needsUpdate = false;
66 | if (mux.Url != match.Url)
67 | {
68 | Console.WriteLine("mux {0} url changed from {1} to {2}", mux.Name, mux.Url, match.Url);
69 | mux.Url = match.Url;
70 | needsUpdate = true;
71 | }
72 | if (mux.Name != match.Name)
73 | {
74 | Console.WriteLine("mux name changed from {0} to {1}", mux.Name, match.Name);
75 | mux.Name = match.Name;
76 | needsUpdate = true;
77 | }
78 | if (needsUpdate)
79 | cli.UpdateMux(mux);
80 | }
81 | }
82 |
83 | // m3u update if needed , only update mux uuid tag on the correct enter for future sync , normally should happen only on first run for the entry
84 |
85 | var updateM3UFile = false;
86 |
87 | foreach (var e in m3uEntries)
88 | {
89 | var currentMux = networkMuxes.FirstOrDefault(x => x.Url == e.Url);
90 | if (currentMux == null)
91 | {
92 | Console.WriteLine("Creating new mux with url {0} , name {1}", e.Url, e.Name);
93 | var uuid = cli.AddMux(currentNetwork, e);
94 | e.TVH_UUID = uuid;
95 | updateM3UFile = true;
96 | }
97 | else
98 | {
99 | if (e.Name != currentMux.Name)
100 | {
101 | e.TVH_UUID = currentMux.UUID;
102 | Console.WriteLine("mux name changed from {0} to {1}, updateing m3u to mux uuid = {2}", currentMux.Name, e.Name, e.TVH_UUID);
103 | currentMux.Name = e.Name;
104 | cli.UpdateMux(currentMux);
105 | updateM3UFile = true;
106 | }
107 | }
108 | }
109 | Console.WriteLine("Finished analyzing {0} M3U entries.", m3uEntries.Count);
110 | if (updateM3UFile)
111 | {
112 | Console.Write("Updating M3U file... ");
113 | M3U.Parser.WriteFile(m3uFile, m3uEntries);
114 | Console.WriteLine("Done.");
115 | }
116 | else
117 | Console.WriteLine("No M3U file update needed.");
118 |
119 | // find muxes for the network which have urls not found on m3u file , maybe add parameter for this , not all users will want this , if user manually add mux it will beremoved , user should add only from m3u file
120 | // should only happen if user removed entry from m3u
121 |
122 | muxes = cli.GetMuxes().Where(x => x.NetworkUUID == currentNetwork.UUID).ToList();
123 | if (muxes.Count > 0)
124 | {
125 | int counter = 0;
126 | foreach (var mux in muxes)
127 | {
128 | if (m3uEntries.All(x => x.Url != mux.Url))
129 | {
130 | if (counter == 0)
131 | Console.WriteLine("Deleting old muxes from network {0} ...", currentNetwork.Name);
132 | Console.WriteLine("Deleting old mux {0} , url {1}", mux.Name, mux.Url);
133 | cli.DeleteMux(mux);
134 | counter++;
135 | }
136 | }
137 | if (counter == 0)
138 | Console.WriteLine("No muxes for deletion.");
139 | else
140 | Console.WriteLine("Finished deleting old muxes.");
141 | }
142 | }
143 |
144 | private static TVHeadEnd.Model.Network GetNetwork(string networkNameToSync, TVHClient cli)
145 | {
146 | var networks = cli.GetNetworks();
147 | var workNetwork = networks.FirstOrDefault(x => x.Name == networkNameToSync);
148 | if (workNetwork == null)
149 | {
150 | cli.CreateNewIPTVNework(networkNameToSync);
151 | networks = cli.GetNetworks();
152 | workNetwork = networks.First(x => x.Name == networkNameToSync);
153 | }
154 |
155 | return workNetwork;
156 | }
157 | }
158 | }
--------------------------------------------------------------------------------
/TVHeadEnd/TVHClient.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Net;
6 | using System.Text;
7 | using TVHeadEndM3USync.M3U.Model;
8 | using TVHeadEndM3USync.TVHeadEnd.Model;
9 |
10 | namespace TVHeadEndM3USync.TVHeadEnd
11 | {
12 | enum AuthenticationType
13 | {
14 | Plain,
15 | }
16 |
17 | class GridRequestParameters
18 | {
19 | public int? Start { get; set; }
20 | public int? Limit { get; set; }
21 | }
22 |
23 | class TVHClient
24 | {
25 | public string Username { get; set; }
26 | public string Password { get; set; }
27 | public string _baseUrl { get; }
28 |
29 | public AuthenticationType AuthenticationType { get; set; }
30 |
31 | public TVHClient(string baseUrl)
32 | {
33 | _baseUrl = baseUrl;
34 | }
35 |
36 | HttpWebRequest NewWebRequest(string urlPath)
37 | {
38 | var wr = (HttpWebRequest)WebRequest.Create(Path.Combine(_baseUrl, urlPath));
39 | switch (AuthenticationType)
40 | {
41 | case AuthenticationType.Plain:
42 | wr.Headers["Authorization"] = "Basic " + Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(Username + ":" + Password)); ;
43 | break;
44 | default:
45 | throw new NotImplementedException();
46 | }
47 | return wr;
48 | }
49 |
50 | HttpWebRequest CreateRequest(string urlPath, GridRequestParameters p = null)
51 | {
52 | if (p == null)
53 | p = new GridRequestParameters();
54 | var wr = NewWebRequest(urlPath);
55 | wr.Method = "POST";
56 | var param = new List();
57 | if (p.Start.HasValue)
58 | param.Add("start=" + p.Start.Value);
59 | if (p.Limit.HasValue)
60 | param.Add("limit=" + p.Limit.Value);
61 | var byteArray = Encoding.UTF8.GetBytes(string.Join("&",param));
62 |
63 | SetRequestStream(wr, byteArray);
64 |
65 | return wr;
66 | }
67 |
68 | public void CreateNewIPTVNework(string name)
69 | {
70 | var wr = NewWebRequest("api/mpegts/network/create");
71 | wr.Method = "POST";
72 | var param = "class=iptv_network&conf=";
73 | var obj = new Newtonsoft.Json.Linq.JObject();
74 | obj["enabled"] = false;
75 | obj["max_timeout"] = 10;
76 | obj["networkname"] = name;
77 | obj["pnetworkname"] = name;
78 | obj["scan_create"] = 0;
79 | obj["max_streams"] = 1;
80 | var byteArray = Encoding.UTF8.GetBytes(param + System.Web.HttpUtility.UrlEncode(obj.ToString()));
81 |
82 | SetRequestStream(wr, byteArray);
83 |
84 | wr.GetResponse();
85 | }
86 |
87 | private static void SetRequestStream(HttpWebRequest wr, byte[] byteArray)
88 | {
89 | wr.ContentType = "application/x-www-form-urlencoded";
90 | wr.ContentLength = byteArray.Length;
91 | var postStream = wr.GetRequestStream();
92 | postStream.Write(byteArray, 0, byteArray.Length);
93 | postStream.Close();
94 | }
95 |
96 | internal string AddMux(Network workNetwork, Entry e)
97 | {
98 | var wr = NewWebRequest("api/mpegts/network/mux_create");
99 | wr.Method = "POST";
100 | var param = "uuid=" + workNetwork.UUID + "&conf=";
101 | var obj = new Newtonsoft.Json.Linq.JObject();
102 | obj["Enabled"] = true;
103 | obj["epg"] = false;
104 | obj["iptv_url"] = e.Url;
105 | obj["iptv_muxname"] = e.Name;
106 | obj["scan_state"] = 0;
107 | var byteArray = Encoding.UTF8.GetBytes(param + System.Web.HttpUtility.UrlEncode(obj.ToString()));
108 |
109 | SetRequestStream(wr, byteArray);
110 |
111 | var res = GetJSON(wr);
112 | return res.Value("uuid");
113 | }
114 |
115 | internal void UpdateMux(Mux mux)
116 | {
117 | var wr = NewWebRequest("api/idnode/save");
118 | wr.Method = "POST";
119 | var param = "node=";
120 | var obj = new Newtonsoft.Json.Linq.JObject();
121 | obj["uuid"] = mux.UUID;
122 | obj["iptv_muxname"] = mux.Name;
123 | obj["iptv_url"] = mux.Url;
124 | var byteArray = Encoding.UTF8.GetBytes(param + System.Web.HttpUtility.UrlEncode(obj.ToString()));
125 |
126 | SetRequestStream(wr, byteArray);
127 |
128 | wr.GetResponse();
129 | }
130 |
131 | internal void DeleteMux(Mux mux)
132 | {
133 | var wr = NewWebRequest("api/idnode/delete");
134 | wr.Method = "POST";
135 | var param = "uuid=";
136 | var obj = new Newtonsoft.Json.Linq.JArray();
137 | obj.Add(mux.UUID);
138 | var byteArray = Encoding.UTF8.GetBytes(param + System.Web.HttpUtility.UrlEncode(obj.ToString()));
139 |
140 | SetRequestStream(wr, byteArray);
141 |
142 | wr.GetResponse();
143 | }
144 |
145 | Newtonsoft.Json.Linq.JObject GetJSON(HttpWebRequest wr)
146 | {
147 | var res = (HttpWebResponse)wr.GetResponse();
148 | using (var sr = new StreamReader(res.GetResponseStream(), Encoding.UTF8))
149 | {
150 | var obj = Newtonsoft.Json.Linq.JObject.Parse(sr.ReadToEnd());
151 | return obj;
152 | }
153 | }
154 |
155 | public List GetNetworks()
156 | {
157 | var lst = new List();
158 | var wr = CreateRequest("api/mpegts/network/grid");
159 | var obj = GetJSON(wr);
160 | foreach (var ent in obj["entries"])
161 | {
162 | var n = new Network();
163 | n.Name = ent.Value("networkname");
164 | n.Enabled = ent.Value("enabled");
165 | n.UUID = ent.Value("uuid");
166 | lst.Add(n);
167 | }
168 |
169 | return lst;
170 | }
171 |
172 | public List GetMuxes()
173 | {
174 | var lst = new List();
175 | var wr = CreateRequest("api/mpegts/mux/grid", new GridRequestParameters { Start = 0, Limit = int.MaxValue });
176 | var obj = GetJSON(wr);
177 | foreach (var ent in obj["entries"])
178 | {
179 | var m = new Mux();
180 | m.Enabled = ent.Value("enabled");
181 | m.UUID = ent.Value("uuid");
182 | m.NetworkName = ent.Value("networkname");
183 | m.NetworkUUID = ent.Value("network_uuid");
184 | m.Name = ent.Value("name");
185 | m.Url = ent.Value("iptv_url");
186 | lst.Add(m);
187 | }
188 | return lst;
189 | }
190 | }
191 | }
192 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Build results
17 | [Dd]ebug/
18 | [Dd]ebugPublic/
19 | [Rr]elease/
20 | [Rr]eleases/
21 | x64/
22 | x86/
23 | [Aa][Rr][Mm]/
24 | [Aa][Rr][Mm]64/
25 | bld/
26 | [Bb]in/
27 | [Oo]bj/
28 | [Ll]og/
29 |
30 | # Visual Studio 2015/2017 cache/options directory
31 | .vs/
32 | # Uncomment if you have tasks that create the project's static files in wwwroot
33 | #wwwroot/
34 |
35 | # Visual Studio 2017 auto generated files
36 | Generated\ Files/
37 |
38 | # MSTest test Results
39 | [Tt]est[Rr]esult*/
40 | [Bb]uild[Ll]og.*
41 |
42 | # NUNIT
43 | *.VisualState.xml
44 | TestResult.xml
45 |
46 | # Build Results of an ATL Project
47 | [Dd]ebugPS/
48 | [Rr]eleasePS/
49 | dlldata.c
50 |
51 | # Benchmark Results
52 | BenchmarkDotNet.Artifacts/
53 |
54 | # .NET Core
55 | project.lock.json
56 | project.fragment.lock.json
57 | artifacts/
58 |
59 | # StyleCop
60 | StyleCopReport.xml
61 |
62 | # Files built by Visual Studio
63 | *_i.c
64 | *_p.c
65 | *_h.h
66 | *.ilk
67 | *.meta
68 | *.obj
69 | *.iobj
70 | *.pch
71 | *.pdb
72 | *.ipdb
73 | *.pgc
74 | *.pgd
75 | *.rsp
76 | *.sbr
77 | *.tlb
78 | *.tli
79 | *.tlh
80 | *.tmp
81 | *.tmp_proj
82 | *_wpftmp.csproj
83 | *.log
84 | *.vspscc
85 | *.vssscc
86 | .builds
87 | *.pidb
88 | *.svclog
89 | *.scc
90 |
91 | # Chutzpah Test files
92 | _Chutzpah*
93 |
94 | # Visual C++ cache files
95 | ipch/
96 | *.aps
97 | *.ncb
98 | *.opendb
99 | *.opensdf
100 | *.sdf
101 | *.cachefile
102 | *.VC.db
103 | *.VC.VC.opendb
104 |
105 | # Visual Studio profiler
106 | *.psess
107 | *.vsp
108 | *.vspx
109 | *.sap
110 |
111 | # Visual Studio Trace Files
112 | *.e2e
113 |
114 | # TFS 2012 Local Workspace
115 | $tf/
116 |
117 | # Guidance Automation Toolkit
118 | *.gpState
119 |
120 | # ReSharper is a .NET coding add-in
121 | _ReSharper*/
122 | *.[Rr]e[Ss]harper
123 | *.DotSettings.user
124 |
125 | # JustCode is a .NET coding add-in
126 | .JustCode
127 |
128 | # TeamCity is a build add-in
129 | _TeamCity*
130 |
131 | # DotCover is a Code Coverage Tool
132 | *.dotCover
133 |
134 | # AxoCover is a Code Coverage Tool
135 | .axoCover/*
136 | !.axoCover/settings.json
137 |
138 | # Visual Studio code coverage results
139 | *.coverage
140 | *.coveragexml
141 |
142 | # NCrunch
143 | _NCrunch_*
144 | .*crunch*.local.xml
145 | nCrunchTemp_*
146 |
147 | # MightyMoose
148 | *.mm.*
149 | AutoTest.Net/
150 |
151 | # Web workbench (sass)
152 | .sass-cache/
153 |
154 | # Installshield output folder
155 | [Ee]xpress/
156 |
157 | # DocProject is a documentation generator add-in
158 | DocProject/buildhelp/
159 | DocProject/Help/*.HxT
160 | DocProject/Help/*.HxC
161 | DocProject/Help/*.hhc
162 | DocProject/Help/*.hhk
163 | DocProject/Help/*.hhp
164 | DocProject/Help/Html2
165 | DocProject/Help/html
166 |
167 | # Click-Once directory
168 | publish/
169 |
170 | # Publish Web Output
171 | *.[Pp]ublish.xml
172 | *.azurePubxml
173 | # Note: Comment the next line if you want to checkin your web deploy settings,
174 | # but database connection strings (with potential passwords) will be unencrypted
175 | *.pubxml
176 | *.publishproj
177 |
178 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
179 | # checkin your Azure Web App publish settings, but sensitive information contained
180 | # in these scripts will be unencrypted
181 | PublishScripts/
182 |
183 | # NuGet Packages
184 | *.nupkg
185 | # The packages folder can be ignored because of Package Restore
186 | **/[Pp]ackages/*
187 | # except build/, which is used as an MSBuild target.
188 | !**/[Pp]ackages/build/
189 | # Uncomment if necessary however generally it will be regenerated when needed
190 | #!**/[Pp]ackages/repositories.config
191 | # NuGet v3's project.json files produces more ignorable files
192 | *.nuget.props
193 | *.nuget.targets
194 |
195 | # Microsoft Azure Build Output
196 | csx/
197 | *.build.csdef
198 |
199 | # Microsoft Azure Emulator
200 | ecf/
201 | rcf/
202 |
203 | # Windows Store app package directories and files
204 | AppPackages/
205 | BundleArtifacts/
206 | Package.StoreAssociation.xml
207 | _pkginfo.txt
208 | *.appx
209 |
210 | # Visual Studio cache files
211 | # files ending in .cache can be ignored
212 | *.[Cc]ache
213 | # but keep track of directories ending in .cache
214 | !?*.[Cc]ache/
215 |
216 | # Others
217 | ClientBin/
218 | ~$*
219 | *~
220 | *.dbmdl
221 | *.dbproj.schemaview
222 | *.jfm
223 | *.pfx
224 | *.publishsettings
225 | orleans.codegen.cs
226 |
227 | # Including strong name files can present a security risk
228 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
229 | #*.snk
230 |
231 | # Since there are multiple workflows, uncomment next line to ignore bower_components
232 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
233 | #bower_components/
234 |
235 | # RIA/Silverlight projects
236 | Generated_Code/
237 |
238 | # Backup & report files from converting an old project file
239 | # to a newer Visual Studio version. Backup files are not needed,
240 | # because we have git ;-)
241 | _UpgradeReport_Files/
242 | Backup*/
243 | UpgradeLog*.XML
244 | UpgradeLog*.htm
245 | ServiceFabricBackup/
246 | *.rptproj.bak
247 |
248 | # SQL Server files
249 | *.mdf
250 | *.ldf
251 | *.ndf
252 |
253 | # Business Intelligence projects
254 | *.rdl.data
255 | *.bim.layout
256 | *.bim_*.settings
257 | *.rptproj.rsuser
258 | *- Backup*.rdl
259 |
260 | # Microsoft Fakes
261 | FakesAssemblies/
262 |
263 | # GhostDoc plugin setting file
264 | *.GhostDoc.xml
265 |
266 | # Node.js Tools for Visual Studio
267 | .ntvs_analysis.dat
268 | node_modules/
269 |
270 | # Visual Studio 6 build log
271 | *.plg
272 |
273 | # Visual Studio 6 workspace options file
274 | *.opt
275 |
276 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
277 | *.vbw
278 |
279 | # Visual Studio LightSwitch build output
280 | **/*.HTMLClient/GeneratedArtifacts
281 | **/*.DesktopClient/GeneratedArtifacts
282 | **/*.DesktopClient/ModelManifest.xml
283 | **/*.Server/GeneratedArtifacts
284 | **/*.Server/ModelManifest.xml
285 | _Pvt_Extensions
286 |
287 | # Paket dependency manager
288 | .paket/paket.exe
289 | paket-files/
290 |
291 | # FAKE - F# Make
292 | .fake/
293 |
294 | # JetBrains Rider
295 | .idea/
296 | *.sln.iml
297 |
298 | # CodeRush personal settings
299 | .cr/personal
300 |
301 | # Python Tools for Visual Studio (PTVS)
302 | __pycache__/
303 | *.pyc
304 |
305 | # Cake - Uncomment if you are using it
306 | # tools/**
307 | # !tools/packages.config
308 |
309 | # Tabs Studio
310 | *.tss
311 |
312 | # Telerik's JustMock configuration file
313 | *.jmconfig
314 |
315 | # BizTalk build output
316 | *.btp.cs
317 | *.btm.cs
318 | *.odx.cs
319 | *.xsd.cs
320 |
321 | # OpenCover UI analysis results
322 | OpenCover/
323 |
324 | # Azure Stream Analytics local run output
325 | ASALocalRun/
326 |
327 | # MSBuild Binary and Structured Log
328 | *.binlog
329 |
330 | # NVidia Nsight GPU debugger configuration file
331 | *.nvuser
332 |
333 | # MFractors (Xamarin productivity tool) working folder
334 | .mfractor/
335 |
336 | # Local History for Visual Studio
337 | .localhistory/
338 |
339 | # BeatPulse healthcheck temp database
340 | healthchecksdb
--------------------------------------------------------------------------------