StephaneLenclud@2
|
1 |
using System;
|
StephaneLenclud@2
|
2 |
using System.Collections.Generic;
|
StephaneLenclud@2
|
3 |
using System.Diagnostics;
|
StephaneLenclud@2
|
4 |
using System.Linq;
|
StephaneLenclud@2
|
5 |
using System.Text;
|
StephaneLenclud@2
|
6 |
using System.Threading.Tasks;
|
StephaneLenclud@2
|
7 |
using System.Xml;
|
StephaneLenclud@5
|
8 |
using System.IO;
|
StephaneLenclud@2
|
9 |
|
StephaneLenclud@2
|
10 |
namespace SatChanGen
|
StephaneLenclud@2
|
11 |
{
|
StephaneLenclud@2
|
12 |
class MediaPortal
|
StephaneLenclud@2
|
13 |
{
|
StephaneLenclud@2
|
14 |
|
StephaneLenclud@2
|
15 |
public static string Polarisation(Channel aChannel)
|
StephaneLenclud@2
|
16 |
{
|
StephaneLenclud@2
|
17 |
switch (aChannel.Polarisation)
|
StephaneLenclud@2
|
18 |
{
|
StephaneLenclud@2
|
19 |
case "H":
|
StephaneLenclud@2
|
20 |
return "1";
|
StephaneLenclud@2
|
21 |
case "V":
|
StephaneLenclud@2
|
22 |
return "2";
|
StephaneLenclud@2
|
23 |
case "L": //Check that on KingOfSat
|
StephaneLenclud@2
|
24 |
return "3";
|
StephaneLenclud@2
|
25 |
case "R": //Check that on KingOfSat
|
StephaneLenclud@2
|
26 |
return "4";
|
StephaneLenclud@2
|
27 |
|
StephaneLenclud@2
|
28 |
default:
|
StephaneLenclud@2
|
29 |
{
|
StephaneLenclud@2
|
30 |
Debug.Write("WARNING: Unknown Polarisation " + aChannel.Polarisation + " for " + aChannel.Name + "\n");
|
StephaneLenclud@2
|
31 |
return "-1";
|
StephaneLenclud@2
|
32 |
}
|
StephaneLenclud@2
|
33 |
}
|
StephaneLenclud@2
|
34 |
|
StephaneLenclud@2
|
35 |
/*
|
StephaneLenclud@2
|
36 |
"Not Set",
|
StephaneLenclud@2
|
37 |
"Not Defined",
|
StephaneLenclud@2
|
38 |
"Horizontal",
|
StephaneLenclud@2
|
39 |
"Vertical",
|
StephaneLenclud@2
|
40 |
"Circular Left",
|
StephaneLenclud@2
|
41 |
"Circular Right"
|
StephaneLenclud@2
|
42 |
*/
|
StephaneLenclud@2
|
43 |
}
|
StephaneLenclud@2
|
44 |
|
StephaneLenclud@2
|
45 |
public static string Modulation(Channel aChannel)
|
StephaneLenclud@2
|
46 |
{
|
StephaneLenclud@2
|
47 |
//Those can be worked out from TvLibrary FormDVBSTuningDetail.Designer.cs
|
StephaneLenclud@2
|
48 |
|
StephaneLenclud@2
|
49 |
switch (aChannel.Modulation)
|
StephaneLenclud@2
|
50 |
{
|
StephaneLenclud@2
|
51 |
case "QPSK":
|
StephaneLenclud@2
|
52 |
return "20";
|
StephaneLenclud@2
|
53 |
case "8PSK":
|
StephaneLenclud@2
|
54 |
return "27";
|
StephaneLenclud@2
|
55 |
default:
|
StephaneLenclud@2
|
56 |
{
|
StephaneLenclud@2
|
57 |
Debug.Write("WARNING: Unknown modulation " + aChannel.Modulation + " for " + aChannel.Name + "\n");
|
StephaneLenclud@2
|
58 |
return "-1";
|
StephaneLenclud@2
|
59 |
}
|
StephaneLenclud@2
|
60 |
}
|
StephaneLenclud@2
|
61 |
|
StephaneLenclud@2
|
62 |
|
StephaneLenclud@2
|
63 |
/*
|
StephaneLenclud@2
|
64 |
"Not Set",
|
StephaneLenclud@2
|
65 |
"Not Defined",
|
StephaneLenclud@2
|
66 |
"16 QAM",
|
StephaneLenclud@2
|
67 |
"32 QAM",
|
StephaneLenclud@2
|
68 |
"64 QAM",
|
StephaneLenclud@2
|
69 |
"80 QAM",
|
StephaneLenclud@2
|
70 |
"96 QAM",
|
StephaneLenclud@2
|
71 |
"112 QAM",
|
StephaneLenclud@2
|
72 |
"128 QAM",
|
StephaneLenclud@2
|
73 |
"160 QAM",
|
StephaneLenclud@2
|
74 |
"192 QAM",
|
StephaneLenclud@2
|
75 |
"224 QAM",
|
StephaneLenclud@2
|
76 |
"256 QAM",
|
StephaneLenclud@2
|
77 |
"320 QAM",
|
StephaneLenclud@2
|
78 |
"384 QAM",
|
StephaneLenclud@2
|
79 |
"448 QAM",
|
StephaneLenclud@2
|
80 |
"512 QAM",
|
StephaneLenclud@2
|
81 |
"640 QAM",
|
StephaneLenclud@2
|
82 |
"768 QAM",
|
StephaneLenclud@2
|
83 |
"896 QAM",
|
StephaneLenclud@2
|
84 |
"1024 QAM",
|
StephaneLenclud@2
|
85 |
"QPSK",
|
StephaneLenclud@2
|
86 |
"BPSK",
|
StephaneLenclud@2
|
87 |
"OQPSK",
|
StephaneLenclud@2
|
88 |
"8 VSB",
|
StephaneLenclud@2
|
89 |
"16 VSB",
|
StephaneLenclud@2
|
90 |
"Analog Amplitude",
|
StephaneLenclud@2
|
91 |
"Analog Frequency",
|
StephaneLenclud@2
|
92 |
"8 PSK",
|
StephaneLenclud@2
|
93 |
"RF",
|
StephaneLenclud@2
|
94 |
"16 APSK",
|
StephaneLenclud@2
|
95 |
"32 APSK",
|
StephaneLenclud@2
|
96 |
"QPSK2 (DVB-S2)",
|
StephaneLenclud@2
|
97 |
"8 PSK2 (DVB-S2)",
|
StephaneLenclud@2
|
98 |
"DirectTV"
|
StephaneLenclud@2
|
99 |
*/
|
StephaneLenclud@2
|
100 |
}
|
StephaneLenclud@2
|
101 |
|
StephaneLenclud@2
|
102 |
|
StephaneLenclud@2
|
103 |
public static string FEC(Channel aChannel)
|
StephaneLenclud@2
|
104 |
{
|
StephaneLenclud@2
|
105 |
//Those can be worked out from TvLibrary FormDVBSTuningDetail.Designer.cs
|
StephaneLenclud@2
|
106 |
|
StephaneLenclud@2
|
107 |
switch (aChannel.FEC)
|
StephaneLenclud@2
|
108 |
{
|
StephaneLenclud@2
|
109 |
case "1/2":
|
StephaneLenclud@2
|
110 |
return "1";
|
StephaneLenclud@2
|
111 |
case "2/3":
|
StephaneLenclud@2
|
112 |
return "2";
|
StephaneLenclud@2
|
113 |
case "3/4":
|
StephaneLenclud@2
|
114 |
return "3";
|
StephaneLenclud@2
|
115 |
case "3/5":
|
StephaneLenclud@2
|
116 |
return "4";
|
StephaneLenclud@2
|
117 |
case "4/5":
|
StephaneLenclud@2
|
118 |
return "5";
|
StephaneLenclud@2
|
119 |
case "5/6":
|
StephaneLenclud@2
|
120 |
return "6";
|
StephaneLenclud@2
|
121 |
case "5/11":
|
StephaneLenclud@2
|
122 |
return "7";
|
StephaneLenclud@2
|
123 |
case "7/8":
|
StephaneLenclud@2
|
124 |
return "8";
|
StephaneLenclud@2
|
125 |
case "1/4":
|
StephaneLenclud@2
|
126 |
return "9";
|
StephaneLenclud@2
|
127 |
case "1/3":
|
StephaneLenclud@2
|
128 |
return "10";
|
StephaneLenclud@2
|
129 |
case "2/5":
|
StephaneLenclud@2
|
130 |
return "11";
|
StephaneLenclud@2
|
131 |
case "6/7":
|
StephaneLenclud@2
|
132 |
return "12";
|
StephaneLenclud@2
|
133 |
case "8/9":
|
StephaneLenclud@2
|
134 |
return "13";
|
StephaneLenclud@2
|
135 |
case "9/10":
|
StephaneLenclud@2
|
136 |
return "14";
|
StephaneLenclud@2
|
137 |
|
StephaneLenclud@2
|
138 |
default:
|
StephaneLenclud@2
|
139 |
{
|
StephaneLenclud@2
|
140 |
Debug.Write("WARNING: Unknown FEC " + aChannel.FEC + " for " + aChannel.Name + "\n");
|
StephaneLenclud@2
|
141 |
return "-1";
|
StephaneLenclud@2
|
142 |
}
|
StephaneLenclud@2
|
143 |
}
|
StephaneLenclud@2
|
144 |
|
StephaneLenclud@2
|
145 |
|
StephaneLenclud@2
|
146 |
}
|
StephaneLenclud@2
|
147 |
|
StephaneLenclud@5
|
148 |
//
|
StephaneLenclud@5
|
149 |
//
|
StephaneLenclud@5
|
150 |
//
|
StephaneLenclud@4
|
151 |
public static void Export(List<Channel> aChannels, List<string> aTunerCards, string aFileName, bool aAddMapping)
|
StephaneLenclud@2
|
152 |
{
|
StephaneLenclud@5
|
153 |
//Create a dictionary for our groups
|
StephaneLenclud@5
|
154 |
Dictionary<string, List<Channel>> groups = new Dictionary<string, List<Channel>>();
|
StephaneLenclud@5
|
155 |
|
StephaneLenclud@2
|
156 |
XmlDocument xmlDoc = new XmlDocument();
|
StephaneLenclud@5
|
157 |
XmlDeclaration xmlDeclaration = xmlDoc.CreateXmlDeclaration("1.0", "UTF-8", null);
|
StephaneLenclud@5
|
158 |
xmlDoc.AppendChild(xmlDeclaration);
|
StephaneLenclud@5
|
159 |
|
StephaneLenclud@2
|
160 |
XmlNode rootElement = xmlDoc.CreateElement("tvserver");
|
StephaneLenclud@2
|
161 |
AddAttribute(rootElement, "version", "1.0");
|
StephaneLenclud@2
|
162 |
xmlDoc.AppendChild(rootElement);
|
StephaneLenclud@2
|
163 |
//Create our channel element
|
StephaneLenclud@2
|
164 |
XmlNode nodechannels = xmlDoc.CreateElement("channels");
|
StephaneLenclud@2
|
165 |
rootElement.AppendChild(nodechannels);
|
StephaneLenclud@2
|
166 |
|
StephaneLenclud@2
|
167 |
int channelId = 0;
|
StephaneLenclud@5
|
168 |
int channelMapId = 0;
|
StephaneLenclud@2
|
169 |
foreach (Channel channel in aChannels)
|
StephaneLenclud@2
|
170 |
{
|
StephaneLenclud@5
|
171 |
//Create group if needed
|
StephaneLenclud@5
|
172 |
if (!groups.ContainsKey(channel.Category))
|
StephaneLenclud@5
|
173 |
{
|
StephaneLenclud@5
|
174 |
//KeyValuePair<string, List<Channel>> entry=new KeyValuePair<string, List<Channel>>();
|
StephaneLenclud@5
|
175 |
groups.Add(channel.Category, new List<Channel>());
|
StephaneLenclud@5
|
176 |
}
|
StephaneLenclud@5
|
177 |
|
StephaneLenclud@5
|
178 |
//Add that channel to its group
|
StephaneLenclud@5
|
179 |
groups[channel.Category].Add(channel);
|
StephaneLenclud@5
|
180 |
|
StephaneLenclud@5
|
181 |
//Create and populate channel element
|
StephaneLenclud@2
|
182 |
channelId++;
|
StephaneLenclud@2
|
183 |
XmlNode nodechannel = xmlDoc.CreateElement("channel");
|
StephaneLenclud@2
|
184 |
AddAttribute(nodechannel, "GrabEpg", "True");
|
StephaneLenclud@2
|
185 |
AddAttribute(nodechannel, "IdChannel", channelId);
|
StephaneLenclud@2
|
186 |
AddAttribute(nodechannel, "IsRadio", "False");
|
StephaneLenclud@2
|
187 |
AddAttribute(nodechannel, "IsTv", "True");
|
StephaneLenclud@5
|
188 |
AddAttribute(nodechannel, "LastGrabTime", "2000-1-1 0:0:0");
|
StephaneLenclud@2
|
189 |
//AddAttribute(nodechannel, "SortOrder", channel.SortOrder);
|
StephaneLenclud@5
|
190 |
//AddAttribute(nodechannel, "TimesWatched", "0");
|
StephaneLenclud@5
|
191 |
AddAttribute(nodechannel, "TotalTimeWatched", "2000-1-1 0:0:0");
|
StephaneLenclud@7
|
192 |
AddAttribute(nodechannel, "VisibleInGuide", !channel.Name.StartsWith("."));
|
StephaneLenclud@2
|
193 |
AddAttribute(nodechannel, "DisplayName", channel.Name);
|
StephaneLenclud@2
|
194 |
AddAttribute(nodechannel, "ChannelNumber", channelId);
|
StephaneLenclud@2
|
195 |
|
StephaneLenclud@5
|
196 |
if (aAddMapping)
|
StephaneLenclud@5
|
197 |
{
|
StephaneLenclud@5
|
198 |
//I don't think we can get mapping to work without having corresponding server and card definition elements
|
StephaneLenclud@5
|
199 |
XmlNode nodeMaps = xmlDoc.CreateElement("mappings");
|
StephaneLenclud@5
|
200 |
foreach (string tuner in aTunerCards)
|
StephaneLenclud@5
|
201 |
{
|
StephaneLenclud@5
|
202 |
channelMapId++;
|
StephaneLenclud@5
|
203 |
XmlNode nodeMap = xmlDoc.CreateElement("map");
|
StephaneLenclud@5
|
204 |
AddAttribute(nodeMap, "IdCard", tuner);
|
StephaneLenclud@5
|
205 |
AddAttribute(nodeMap, "IdChannel", channelId);
|
StephaneLenclud@5
|
206 |
AddAttribute(nodeMap, "IdChannelMap", channelMapId);
|
StephaneLenclud@5
|
207 |
nodeMaps.AppendChild(nodeMap);
|
StephaneLenclud@5
|
208 |
}
|
StephaneLenclud@5
|
209 |
nodechannel.AppendChild(nodeMaps);
|
StephaneLenclud@5
|
210 |
}
|
StephaneLenclud@2
|
211 |
|
StephaneLenclud@2
|
212 |
XmlNode nodeTuningDetails = xmlDoc.CreateElement("TuningDetails");
|
StephaneLenclud@2
|
213 |
XmlNode nodeTune = xmlDoc.CreateElement("tune");
|
StephaneLenclud@2
|
214 |
AddAttribute(nodeTune, "IdChannel", channelId);
|
StephaneLenclud@2
|
215 |
AddAttribute(nodeTune, "IdTuning", channelId);
|
StephaneLenclud@2
|
216 |
AddAttribute(nodeTune, "Bandwidth", "8"); //Weird
|
StephaneLenclud@2
|
217 |
AddAttribute(nodeTune, "ChannelNumber", "10000");
|
StephaneLenclud@2
|
218 |
AddAttribute(nodeTune, "ChannelType", "3"); //Bad hard coding, does it means DVB-S
|
StephaneLenclud@2
|
219 |
AddAttribute(nodeTune, "CountryId", "31"); //Is that Germany?
|
StephaneLenclud@2
|
220 |
AddAttribute(nodeTune, "Diseqc", "0"); //Disabled
|
StephaneLenclud@2
|
221 |
AddAttribute(nodeTune, "FreeToAir", "False"); //Can we get that from KingOfSat? Does it matter?
|
StephaneLenclud@2
|
222 |
AddAttribute(nodeTune, "Frequency", ((Int32)(Convert.ToDouble(channel.Frequency)*1000)).ToString());
|
StephaneLenclud@2
|
223 |
AddAttribute(nodeTune, "MajorChannel", "-1");
|
StephaneLenclud@2
|
224 |
AddAttribute(nodeTune, "MinorChannel", "-1");
|
StephaneLenclud@2
|
225 |
AddAttribute(nodeTune, "Modulation", Modulation(channel));
|
StephaneLenclud@2
|
226 |
AddAttribute(nodeTune, "Name", channel.Name);
|
StephaneLenclud@2
|
227 |
AddAttribute(nodeTune, "NetworkId", channel.NetworkID);
|
StephaneLenclud@2
|
228 |
AddAttribute(nodeTune, "PmtPid", channel.PMT);
|
StephaneLenclud@2
|
229 |
AddAttribute(nodeTune, "Polarisation", Polarisation(channel));
|
StephaneLenclud@2
|
230 |
AddAttribute(nodeTune, "Provider", channel.Provider); //Does that matter? Should really be SKY or CSAT
|
StephaneLenclud@2
|
231 |
AddAttribute(nodeTune, "ServiceId", channel.SID);
|
StephaneLenclud@2
|
232 |
AddAttribute(nodeTune, "SwitchingFrequency", "11700000"); //Does it matter?
|
StephaneLenclud@2
|
233 |
AddAttribute(nodeTune, "Symbolrate", channel.SymbolRate);
|
StephaneLenclud@2
|
234 |
AddAttribute(nodeTune, "TransportId", channel.TransponderID);
|
StephaneLenclud@2
|
235 |
AddAttribute(nodeTune, "TuningSource", "0"); //Is that needed?
|
StephaneLenclud@2
|
236 |
AddAttribute(nodeTune, "VideoSource", "0"); //Is that needed?
|
StephaneLenclud@2
|
237 |
AddAttribute(nodeTune, "AudioSource", "0"); //Is that needed?
|
StephaneLenclud@2
|
238 |
AddAttribute(nodeTune, "IsVCRSignal", "False");
|
StephaneLenclud@2
|
239 |
AddAttribute(nodeTune, "SatIndex", "-1");
|
StephaneLenclud@2
|
240 |
AddAttribute(nodeTune, "InnerFecRate", FEC(channel));
|
StephaneLenclud@2
|
241 |
AddAttribute(nodeTune, "Band", "0"); //Needed?
|
StephaneLenclud@2
|
242 |
AddAttribute(nodeTune, "Pilot", "-1");
|
StephaneLenclud@2
|
243 |
AddAttribute(nodeTune, "RollOff", "-1");
|
StephaneLenclud@2
|
244 |
AddAttribute(nodeTune, "Url", "");
|
StephaneLenclud@2
|
245 |
AddAttribute(nodeTune, "Bitrate", "0"); //Should we bother putting it in there?
|
StephaneLenclud@2
|
246 |
nodeTuningDetails.AppendChild(nodeTune);
|
StephaneLenclud@2
|
247 |
|
StephaneLenclud@2
|
248 |
nodechannel.AppendChild(nodeTuningDetails);
|
StephaneLenclud@2
|
249 |
|
StephaneLenclud@2
|
250 |
nodechannels.AppendChild(nodechannel);
|
StephaneLenclud@2
|
251 |
}
|
StephaneLenclud@2
|
252 |
|
StephaneLenclud@5
|
253 |
//Create groups element
|
StephaneLenclud@5
|
254 |
XmlNode nodeChannelGroups = xmlDoc.CreateElement("channelgroups");
|
StephaneLenclud@5
|
255 |
rootElement.AppendChild(nodeChannelGroups);
|
StephaneLenclud@2
|
256 |
|
StephaneLenclud@5
|
257 |
int groupSortOrder = 0;
|
StephaneLenclud@5
|
258 |
foreach (KeyValuePair<string, List<Channel>> group in groups)
|
StephaneLenclud@5
|
259 |
{
|
StephaneLenclud@5
|
260 |
//Create group element
|
StephaneLenclud@5
|
261 |
XmlNode nodeChannelGroup = xmlDoc.CreateElement("channelgroup");
|
StephaneLenclud@5
|
262 |
AddAttribute(nodeChannelGroup, "GroupName", group.Key);
|
StephaneLenclud@5
|
263 |
AddAttribute(nodeChannelGroup, "SortOrder", groupSortOrder.ToString());
|
StephaneLenclud@5
|
264 |
XmlNode nodeGroupMap = xmlDoc.CreateElement("mappings");
|
StephaneLenclud@5
|
265 |
nodeChannelGroup.AppendChild(nodeGroupMap);
|
StephaneLenclud@5
|
266 |
nodeChannelGroups.AppendChild(nodeChannelGroup);
|
StephaneLenclud@2
|
267 |
|
StephaneLenclud@5
|
268 |
//Sort by name
|
StephaneLenclud@5
|
269 |
List<Channel> sortedChannels = group.Value.OrderBy(o => o.Name).ToList();
|
StephaneLenclud@5
|
270 |
|
StephaneLenclud@5
|
271 |
//Add each channel to its group
|
StephaneLenclud@5
|
272 |
int channelSortOrder = 0;
|
StephaneLenclud@5
|
273 |
foreach (Channel channel in sortedChannels)
|
StephaneLenclud@5
|
274 |
{
|
StephaneLenclud@5
|
275 |
XmlNode nodeMap = xmlDoc.CreateElement("map");
|
StephaneLenclud@5
|
276 |
AddAttribute(nodeMap, "ChannelName", channel.Name);
|
StephaneLenclud@5
|
277 |
AddAttribute(nodeMap, "SortOrder", channelSortOrder.ToString());
|
StephaneLenclud@5
|
278 |
nodeGroupMap.AppendChild(nodeMap);
|
StephaneLenclud@5
|
279 |
channelSortOrder++;
|
StephaneLenclud@5
|
280 |
}
|
StephaneLenclud@5
|
281 |
|
StephaneLenclud@5
|
282 |
groupSortOrder++;
|
StephaneLenclud@5
|
283 |
}
|
StephaneLenclud@5
|
284 |
|
StephaneLenclud@5
|
285 |
using (TextWriter sw = new StreamWriter(aFileName, false, Encoding.UTF8)) //Set encoding
|
StephaneLenclud@5
|
286 |
{
|
StephaneLenclud@5
|
287 |
xmlDoc.Save(sw);
|
StephaneLenclud@5
|
288 |
}
|
StephaneLenclud@5
|
289 |
//xmlDoc.Save(aFileName);
|
StephaneLenclud@2
|
290 |
}
|
StephaneLenclud@2
|
291 |
|
StephaneLenclud@2
|
292 |
private static void AddAttribute(XmlNode node, string tagName, string tagValue)
|
StephaneLenclud@2
|
293 |
{
|
StephaneLenclud@2
|
294 |
XmlAttribute attr = node.OwnerDocument.CreateAttribute(tagName);
|
StephaneLenclud@2
|
295 |
attr.InnerText = tagValue;
|
StephaneLenclud@2
|
296 |
node.Attributes.Append(attr);
|
StephaneLenclud@2
|
297 |
}
|
StephaneLenclud@2
|
298 |
|
StephaneLenclud@2
|
299 |
private static void AddAttribute(XmlNode node, string tagName, int tagValue)
|
StephaneLenclud@2
|
300 |
{
|
StephaneLenclud@2
|
301 |
AddAttribute(node, tagName, tagValue.ToString());
|
StephaneLenclud@2
|
302 |
}
|
StephaneLenclud@2
|
303 |
|
StephaneLenclud@2
|
304 |
// store DateTime Values as strings. Improves readability
|
StephaneLenclud@2
|
305 |
private static void AddAttribute(XmlNode node, string tagName, DateTime tagValue)
|
StephaneLenclud@2
|
306 |
{
|
StephaneLenclud@2
|
307 |
AddAttribute(node, tagName,
|
StephaneLenclud@2
|
308 |
String.Format("{0}-{1}-{2} {3}:{4}:{5}", tagValue.Year, tagValue.Month, tagValue.Day, tagValue.Hour,
|
StephaneLenclud@2
|
309 |
tagValue.Minute, tagValue.Second));
|
StephaneLenclud@2
|
310 |
}
|
StephaneLenclud@2
|
311 |
|
StephaneLenclud@2
|
312 |
private static void AddAttribute(XmlNode node, string tagName, bool tagValue)
|
StephaneLenclud@2
|
313 |
{
|
StephaneLenclud@2
|
314 |
AddAttribute(node, tagName, tagValue.ToString());
|
StephaneLenclud@2
|
315 |
}
|
StephaneLenclud@2
|
316 |
}
|
StephaneLenclud@2
|
317 |
}
|