epg-grabber 0.36.1 → 0.37.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +176 -22
- package/package.json +1 -1
- package/src/Channel.js +5 -2
- package/src/Program.js +101 -12
- package/src/xmltv.js +87 -18
- package/tests/Channel.test.js +5 -2
- package/tests/Program.test.js +6 -6
- package/tests/index.test.js +17 -2
- package/tests/parser.test.js +6 -6
- package/tests/xmltv.test.js +104 -11
- package/tests/__data__/output/guide.xml +0 -6
package/README.md
CHANGED
|
@@ -172,27 +172,9 @@ module.exports = {
|
|
|
172
172
|
|
|
173
173
|
return [
|
|
174
174
|
{
|
|
175
|
-
title,
|
|
176
|
-
start,
|
|
177
|
-
stop
|
|
178
|
-
sub_title, // program sub-title (optional)
|
|
179
|
-
description, // description of the program (optional)
|
|
180
|
-
category, // type of program (optional)
|
|
181
|
-
season, // season number (optional)
|
|
182
|
-
episode, // episode number (optional)
|
|
183
|
-
date, // the date the programme or film was finished (optional)
|
|
184
|
-
icon, // image associated with the program (optional)
|
|
185
|
-
rating, // program rating (optional)
|
|
186
|
-
director, // the name of director (optional)
|
|
187
|
-
actor, // the name of actor (optional)
|
|
188
|
-
writer, // the name of writer (optional)
|
|
189
|
-
adapter, // the name of adapter (optional)
|
|
190
|
-
producer, // the name of producer (optional)
|
|
191
|
-
composer, // the name of composer (optional)
|
|
192
|
-
editor, // the name of editor (optional)
|
|
193
|
-
presenter, // the name of presenter (optional)
|
|
194
|
-
commentator, // the name of commentator (optional)
|
|
195
|
-
guest // the name of guest (optional)
|
|
175
|
+
title,
|
|
176
|
+
start,
|
|
177
|
+
stop
|
|
196
178
|
},
|
|
197
179
|
...
|
|
198
180
|
]
|
|
@@ -212,6 +194,176 @@ From each function in `config.js` you can access a `context` object containing t
|
|
|
212
194
|
- `request`: The request config
|
|
213
195
|
- `cached`: A boolean to check whether this request was cached or not
|
|
214
196
|
|
|
197
|
+
## Program Object
|
|
198
|
+
|
|
199
|
+
| Property | Aliases | Type | Required |
|
|
200
|
+
| --------------- | -------------------------------- | ------------------------------------------------ | -------- |
|
|
201
|
+
| start | | `String` or `Number` or `Date()` | true |
|
|
202
|
+
| stop | | `String` or `Number` or `Date()` | true |
|
|
203
|
+
| title | titles | `String` or `Object` or `String[]` or `Object[]` | true |
|
|
204
|
+
| subTitle | subTitles, sub_title, sub_titles | `String` or `Object` or `String[]` or `Object[]` | false |
|
|
205
|
+
| description | desc, descriptions | `String` or `Object` or `String[]` or `Object[]` | false |
|
|
206
|
+
| date | | `String` or `Number` or `Date()` | false |
|
|
207
|
+
| category | categories | `String` or `Object` or `String[]` or `Object[]` | false |
|
|
208
|
+
| keyword | keywords | `String` or `Object` or `String[]` or `Object[]` | false |
|
|
209
|
+
| language | languages | `String` or `Object` or `String[]` or `Object[]` | false |
|
|
210
|
+
| origLanguage | origLanguages | `String` or `Object` or `String[]` or `Object[]` | false |
|
|
211
|
+
| length | | `String` or `Object` or `String[]` or `Object[]` | false |
|
|
212
|
+
| url | urls | `String` or `Object` or `String[]` or `Object[]` | false |
|
|
213
|
+
| country | countries | `String` or `Object` or `String[]` or `Object[]` | false |
|
|
214
|
+
| video | | `Object` | false |
|
|
215
|
+
| audio | | `Object` | false |
|
|
216
|
+
| season | | `String` or `Number` | false |
|
|
217
|
+
| episode | | `String` or `Number` | false |
|
|
218
|
+
| episodeNumber | episodeNum, episodeNumbers | `Object` | false |
|
|
219
|
+
| previouslyShown | | `String` or `Object` or `String[]` or `Object[]` | false |
|
|
220
|
+
| premiere | | `String` or `Object` or `String[]` or `Object[]` | false |
|
|
221
|
+
| lastChance | | `String` or `Object` or `String[]` or `Object[]` | false |
|
|
222
|
+
| new | | `Boolean` | false |
|
|
223
|
+
| subtitles | | `Object` or `Object[]` | false |
|
|
224
|
+
| rating | ratings | `String` or `Object` or `String[]` or `Object[]` | false |
|
|
225
|
+
| starRating | starRatings | `String` or `Object` or `String[]` or `Object[]` | false |
|
|
226
|
+
| review | reviews | `String` or `Object` or `String[]` or `Object[]` | false |
|
|
227
|
+
| director | directors | `String` or `Object` or `String[]` or `Object[]` | false |
|
|
228
|
+
| actor | actors | `String` or `Object` or `String[]` or `Object[]` | false |
|
|
229
|
+
| writer | writers | `String` or `Object` or `String[]` or `Object[]` | false |
|
|
230
|
+
| adapter | adapters | `String` or `Object` or `String[]` or `Object[]` | false |
|
|
231
|
+
| producer | producers | `String` or `Object` or `String[]` or `Object[]` | false |
|
|
232
|
+
| presenter | presenters | `String` or `Object` or `String[]` or `Object[]` | false |
|
|
233
|
+
| composer | composers | `String` or `Object` or `String[]` or `Object[]` | false |
|
|
234
|
+
| editor | editors | `String` or `Object` or `String[]` or `Object[]` | false |
|
|
235
|
+
| commentator | commentators | `String` or `Object` or `String[]` or `Object[]` | false |
|
|
236
|
+
| guest | guests | `String` or `Object` or `String[]` or `Object[]` | false |
|
|
237
|
+
| image | images | `String` or `Object` or `String[]` or `Object[]` | false |
|
|
238
|
+
| icon | icons | `String` or `Object` or `String[]` or `Object[]` | false |
|
|
239
|
+
|
|
240
|
+
Example:
|
|
241
|
+
|
|
242
|
+
```js
|
|
243
|
+
{
|
|
244
|
+
start: '2021-03-19T06:00:00.000Z',
|
|
245
|
+
stop: '2021-03-19T06:30:00.000Z',
|
|
246
|
+
title: 'Program 1',
|
|
247
|
+
subTitle: 'Sub-title & 1',
|
|
248
|
+
description: 'Description for Program 1',
|
|
249
|
+
date: '2022-05-06',
|
|
250
|
+
categories: ['Comedy', 'Drama'],
|
|
251
|
+
keywords: [
|
|
252
|
+
{ lang: 'en', value: 'physical-comedy' },
|
|
253
|
+
{ lang: 'en', value: 'romantic' }
|
|
254
|
+
],
|
|
255
|
+
language: 'English',
|
|
256
|
+
origLanguage: { lang: 'en', value: 'French' },
|
|
257
|
+
length: { units: 'minutes', value: '60' },
|
|
258
|
+
url: 'http://example.com/title.html',
|
|
259
|
+
country: 'US',
|
|
260
|
+
video: {
|
|
261
|
+
present: 'yes',
|
|
262
|
+
colour: 'no',
|
|
263
|
+
aspect: '16:9',
|
|
264
|
+
quality: 'HDTV'
|
|
265
|
+
},
|
|
266
|
+
audio: {
|
|
267
|
+
present: 'yes',
|
|
268
|
+
stereo: 'Dolby Digital'
|
|
269
|
+
},
|
|
270
|
+
season: 9,
|
|
271
|
+
episode: 239,
|
|
272
|
+
previouslyShown: [{ start: '20080711000000', channel: 'channel-two.tv' }],
|
|
273
|
+
premiere: 'First time on British TV',
|
|
274
|
+
lastChance: [{ lang: 'en', value: 'Last time on this channel' }],
|
|
275
|
+
new: true,
|
|
276
|
+
subtitles: [
|
|
277
|
+
{ type: 'teletext', language: 'English' },
|
|
278
|
+
{ type: 'onscreen', language: [{ lang: 'en', value: 'Spanish' }] }
|
|
279
|
+
],
|
|
280
|
+
rating: {
|
|
281
|
+
system: 'MPAA',
|
|
282
|
+
value: 'P&G',
|
|
283
|
+
icon: 'http://example.com/pg_symbol.png'
|
|
284
|
+
},
|
|
285
|
+
starRatings: [
|
|
286
|
+
{
|
|
287
|
+
system: 'TV Guide',
|
|
288
|
+
value: '4/5',
|
|
289
|
+
icon: [{ src: 'stars.png', width: 100, height: 100 }]
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
system: 'IMDB',
|
|
293
|
+
value: '8/10'
|
|
294
|
+
}
|
|
295
|
+
],
|
|
296
|
+
reviews: [
|
|
297
|
+
{
|
|
298
|
+
type: 'text',
|
|
299
|
+
source: 'Rotten Tomatoes',
|
|
300
|
+
reviewer: 'Joe Bloggs',
|
|
301
|
+
lang: 'en',
|
|
302
|
+
value: 'This is a fantastic show!'
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
type: 'text',
|
|
306
|
+
source: 'IDMB',
|
|
307
|
+
reviewer: 'Jane Doe',
|
|
308
|
+
lang: 'en',
|
|
309
|
+
value: 'I love this show!'
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
type: 'url',
|
|
313
|
+
source: 'Rotten Tomatoes',
|
|
314
|
+
reviewer: 'Joe Bloggs',
|
|
315
|
+
lang: 'en',
|
|
316
|
+
value: 'https://example.com/programme_one_review'
|
|
317
|
+
}
|
|
318
|
+
],
|
|
319
|
+
directors: [
|
|
320
|
+
{
|
|
321
|
+
value: 'Director 1',
|
|
322
|
+
url: { value: 'http://example.com/director1.html', system: 'TestSystem' },
|
|
323
|
+
image: [
|
|
324
|
+
'https://example.com/image1.jpg',
|
|
325
|
+
{
|
|
326
|
+
value: 'https://example.com/image2.jpg',
|
|
327
|
+
type: 'person',
|
|
328
|
+
size: '2',
|
|
329
|
+
system: 'TestSystem',
|
|
330
|
+
orient: 'P'
|
|
331
|
+
}
|
|
332
|
+
]
|
|
333
|
+
},
|
|
334
|
+
'Director 2'
|
|
335
|
+
],
|
|
336
|
+
actors: ['Actor 1', 'Actor 2'],
|
|
337
|
+
writer: 'Writer 1',
|
|
338
|
+
producers: 'Roger Dobkowitz',
|
|
339
|
+
presenters: 'Drew Carey',
|
|
340
|
+
images: [
|
|
341
|
+
{
|
|
342
|
+
type: 'poster',
|
|
343
|
+
size: '1',
|
|
344
|
+
orient: 'P',
|
|
345
|
+
system: 'tvdb',
|
|
346
|
+
value: 'https://tvdb.com/programme_one_poster_1.jpg'
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
type: 'poster',
|
|
350
|
+
size: '2',
|
|
351
|
+
orient: 'P',
|
|
352
|
+
system: 'tmdb',
|
|
353
|
+
value: 'https://tmdb.com/programme_one_poster_2.jpg'
|
|
354
|
+
},
|
|
355
|
+
{
|
|
356
|
+
type: 'backdrop',
|
|
357
|
+
size: '3',
|
|
358
|
+
orient: 'L',
|
|
359
|
+
system: 'tvdb',
|
|
360
|
+
value: 'https://tvdb.com/programme_one_backdrop_3.jpg'
|
|
361
|
+
}
|
|
362
|
+
],
|
|
363
|
+
icon: 'https://example.com/images/Program1.png?x=шеллы&sid=777'
|
|
364
|
+
}
|
|
365
|
+
```
|
|
366
|
+
|
|
215
367
|
## Channels List
|
|
216
368
|
|
|
217
369
|
```xml
|
|
@@ -222,7 +374,7 @@ From each function in `config.js` you can access a `context` object containing t
|
|
|
222
374
|
</channels>
|
|
223
375
|
```
|
|
224
376
|
|
|
225
|
-
You can also specify the language, site
|
|
377
|
+
You can also specify the language, site, url, logo and LCN (Logical Channel Number) for each channel individually, like so:
|
|
226
378
|
|
|
227
379
|
```xml
|
|
228
380
|
<channel
|
|
@@ -231,6 +383,8 @@ You can also specify the language, site and logo for each channel individually,
|
|
|
231
383
|
xmltv_id="France24.fr"
|
|
232
384
|
lang="fr"
|
|
233
385
|
logo="https://example.com/france24.png"
|
|
386
|
+
url="https://example.com/"
|
|
387
|
+
lcn="36"
|
|
234
388
|
>France 24</channel>
|
|
235
389
|
```
|
|
236
390
|
|
package/package.json
CHANGED
package/src/Channel.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const { toArray } = require('./utils')
|
|
2
|
+
|
|
1
3
|
class Channel {
|
|
2
4
|
constructor(c) {
|
|
3
5
|
const data = {
|
|
@@ -6,8 +8,9 @@ class Channel {
|
|
|
6
8
|
site: c.site || '',
|
|
7
9
|
site_id: c.site_id,
|
|
8
10
|
lang: c.lang || '',
|
|
9
|
-
|
|
10
|
-
url: c.url || toURL(c.site)
|
|
11
|
+
icon: c.icon || c.logo || '',
|
|
12
|
+
url: c.url || toURL(c.site),
|
|
13
|
+
lcn: c.lcn
|
|
11
14
|
}
|
|
12
15
|
|
|
13
16
|
for (let key in data) {
|
package/src/Program.js
CHANGED
|
@@ -9,21 +9,43 @@ class Program {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
const data = {
|
|
12
|
-
|
|
12
|
+
start: p.start ? toUnix(p.start) : null,
|
|
13
|
+
stop: p.stop ? toUnix(p.stop) : null,
|
|
13
14
|
channel: c.xmltv_id || '',
|
|
14
15
|
titles: toArray(p.titles || p.title).map(text => toTextObject(text, c.lang)),
|
|
15
|
-
|
|
16
|
+
subTitles: toArray(p.subTitles || p.subTitle || p.sub_titles || p.sub_title).map(text =>
|
|
17
|
+
toTextObject(text, c.lang)
|
|
18
|
+
),
|
|
16
19
|
descriptions: toArray(p.descriptions || p.description || p.desc).map(text =>
|
|
17
20
|
toTextObject(text, c.lang)
|
|
18
21
|
),
|
|
19
|
-
icon: toIconObject(p.icon),
|
|
20
|
-
episodeNumbers: p.episodeNum || p.episodeNumbers || getEpisodeNumbers(p.season, p.episode),
|
|
21
22
|
date: p.date ? toUnix(p.date) : null,
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
categories: toArray(p.categories || p.category).map(text => toTextObject(text, c.lang)),
|
|
24
|
+
keywords: toArray(p.keywords || p.keyword).map(text => toTextObject(text, c.lang)),
|
|
25
|
+
languages: toArray(p.languages || p.language).map(text => toTextObject(text, c.lang)),
|
|
26
|
+
origLanguages: toArray(p.origLanguages || p.origLanguage).map(text =>
|
|
27
|
+
toTextObject(text, c.lang)
|
|
28
|
+
),
|
|
29
|
+
length: toArray(p.length).map(toLengthObject),
|
|
24
30
|
urls: toArray(p.urls || p.url).map(toUrlObject),
|
|
31
|
+
countries: toArray(p.countries || p.country).map(text => toTextObject(text, c.lang)),
|
|
32
|
+
site: c.site || '',
|
|
33
|
+
episodeNumbers: toArray(
|
|
34
|
+
p.episodeNum ||
|
|
35
|
+
p.episodeNumber ||
|
|
36
|
+
p.episodeNumbers ||
|
|
37
|
+
makeEpisodeNumberObjects(p.season, p.episode)
|
|
38
|
+
).map(toEpisodeNumberObject),
|
|
39
|
+
video: toVideoObject(p.video),
|
|
40
|
+
audio: toAudioObject(p.audio),
|
|
41
|
+
previouslyShown: toArray(p.previouslyShown).map(toPreviouslyShownObject),
|
|
42
|
+
premiere: toArray(p.premiere).map(toTextObject),
|
|
43
|
+
lastChance: toArray(p.lastChance).map(toTextObject),
|
|
44
|
+
new: p.new,
|
|
45
|
+
subtitles: toArray(p.subtitles).map(toSubtitlesObject),
|
|
25
46
|
ratings: toArray(p.ratings || p.rating).map(toRatingObject),
|
|
26
|
-
|
|
47
|
+
starRatings: toArray(p.starRatings || p.starRating).map(toRatingObject),
|
|
48
|
+
reviews: toArray(p.reviews || p.review).map(toReviewObject),
|
|
27
49
|
directors: toArray(p.directors || p.director).map(toPersonObject),
|
|
28
50
|
actors: toArray(p.actors || p.actor).map(toPersonObject),
|
|
29
51
|
writers: toArray(p.writers || p.writer).map(toPersonObject),
|
|
@@ -33,7 +55,9 @@ class Program {
|
|
|
33
55
|
editors: toArray(p.editors || p.editor).map(toPersonObject),
|
|
34
56
|
presenters: toArray(p.presenters || p.presenter).map(toPersonObject),
|
|
35
57
|
commentators: toArray(p.commentators || p.commentator).map(toPersonObject),
|
|
36
|
-
guests: toArray(p.guests || p.guest).map(toPersonObject)
|
|
58
|
+
guests: toArray(p.guests || p.guest).map(toPersonObject),
|
|
59
|
+
images: toArray(p.images || p.image).map(toImageObject),
|
|
60
|
+
icons: toArray(p.icons || p.icon).map(toIconObject)
|
|
37
61
|
}
|
|
38
62
|
|
|
39
63
|
for (let key in data) {
|
|
@@ -71,6 +95,10 @@ function toPersonObject(person) {
|
|
|
71
95
|
}
|
|
72
96
|
}
|
|
73
97
|
|
|
98
|
+
function toSubtitlesObject(subtitles) {
|
|
99
|
+
return { type: subtitles.type || '', language: toArray(subtitles.language).map(toTextObject) }
|
|
100
|
+
}
|
|
101
|
+
|
|
74
102
|
function toImageObject(image) {
|
|
75
103
|
if (typeof image === 'string') return { type: '', size: '', orient: '', system: '', value: image }
|
|
76
104
|
|
|
@@ -84,15 +112,24 @@ function toImageObject(image) {
|
|
|
84
112
|
}
|
|
85
113
|
|
|
86
114
|
function toRatingObject(rating) {
|
|
87
|
-
if (typeof rating === 'string') return { system: '', icon:
|
|
115
|
+
if (typeof rating === 'string') return { system: '', icon: [], value: rating }
|
|
88
116
|
|
|
89
117
|
return {
|
|
90
118
|
system: rating.system || '',
|
|
91
|
-
icon: rating.icon
|
|
119
|
+
icon: toArray(rating.icon).map(toIconObject),
|
|
92
120
|
value: rating.value || ''
|
|
93
121
|
}
|
|
94
122
|
}
|
|
95
123
|
|
|
124
|
+
function toLengthObject(length) {
|
|
125
|
+
if (typeof length === 'string') return { units: '', value: length }
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
units: length.units || '',
|
|
129
|
+
value: length.value || ''
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
96
133
|
function toUrlObject(url) {
|
|
97
134
|
if (typeof url === 'string') return { system: '', value: url }
|
|
98
135
|
|
|
@@ -102,22 +139,74 @@ function toUrlObject(url) {
|
|
|
102
139
|
}
|
|
103
140
|
}
|
|
104
141
|
|
|
142
|
+
function toVideoObject(video) {
|
|
143
|
+
if (!video) return {}
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
present: video.present || '',
|
|
147
|
+
colour: video.colour || '',
|
|
148
|
+
aspect: video.aspect || '',
|
|
149
|
+
quality: video.quality || ''
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function toAudioObject(audio) {
|
|
154
|
+
if (!audio) return {}
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
present: audio.present || '',
|
|
158
|
+
stereo: audio.stereo || ''
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function toReviewObject(review) {
|
|
163
|
+
if (!review) return {}
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
type: review.type || '',
|
|
167
|
+
source: review.source || '',
|
|
168
|
+
reviewer: review.reviewer || '',
|
|
169
|
+
lang: review.lang || '',
|
|
170
|
+
value: review.value || ''
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function toPreviouslyShownObject(previouslyShown) {
|
|
175
|
+
if (!previouslyShown) return {}
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
start: previouslyShown.start || '',
|
|
179
|
+
channel: previouslyShown.channel || ''
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
105
183
|
function toIconObject(icon) {
|
|
106
184
|
if (!icon) return { src: '' }
|
|
107
185
|
if (typeof icon === 'string') return { src: icon }
|
|
108
186
|
|
|
109
187
|
return {
|
|
110
|
-
src: icon.src
|
|
188
|
+
src: icon.src,
|
|
189
|
+
width: icon.width,
|
|
190
|
+
height: icon.height
|
|
111
191
|
}
|
|
112
192
|
}
|
|
113
193
|
|
|
114
|
-
function
|
|
194
|
+
function makeEpisodeNumberObjects(s, e) {
|
|
115
195
|
s = parseNumber(s)
|
|
116
196
|
e = parseNumber(e)
|
|
117
197
|
|
|
118
198
|
return [createXMLTVNS(s, e), createOnScreen(s, e)].filter(Boolean)
|
|
119
199
|
}
|
|
120
200
|
|
|
201
|
+
function toEpisodeNumberObject(episode) {
|
|
202
|
+
if (!episode) return {}
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
system: episode.system || '',
|
|
206
|
+
value: episode.value || ''
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
121
210
|
function createXMLTVNS(s, e) {
|
|
122
211
|
if (!e) return null
|
|
123
212
|
s = s || 1
|
package/src/xmltv.js
CHANGED
|
@@ -31,8 +31,9 @@ function createElements(channels, programs, date) {
|
|
|
31
31
|
'\r\n' +
|
|
32
32
|
el('channel', { id: channel.xmltv_id }, [
|
|
33
33
|
el('display-name', {}, [escapeString(channel.name)]),
|
|
34
|
-
el('icon', { src: channel.
|
|
35
|
-
el('url', {}, [channel.url])
|
|
34
|
+
el('icon', { src: channel.icon }),
|
|
35
|
+
el('url', {}, [escapeString(channel.url)]),
|
|
36
|
+
el('lcn', {}, [escapeString(channel.lcn)])
|
|
36
37
|
])
|
|
37
38
|
)
|
|
38
39
|
}),
|
|
@@ -50,8 +51,8 @@ function createElements(channels, programs, date) {
|
|
|
50
51
|
...program.titles.map(title =>
|
|
51
52
|
el('title', { lang: title.lang }, [escapeString(title.value)])
|
|
52
53
|
),
|
|
53
|
-
...program.
|
|
54
|
-
el('sub-title', { lang:
|
|
54
|
+
...program.subTitles.map(subTitle =>
|
|
55
|
+
el('sub-title', { lang: subTitle.lang }, [escapeString(subTitle.value)])
|
|
55
56
|
),
|
|
56
57
|
...program.descriptions.map(desc =>
|
|
57
58
|
el('desc', { lang: desc.lang }, [escapeString(desc.value)])
|
|
@@ -72,17 +73,83 @@ function createElements(channels, programs, date) {
|
|
|
72
73
|
...program.categories.map(category =>
|
|
73
74
|
el('category', { lang: category.lang }, [escapeString(category.value)])
|
|
74
75
|
),
|
|
75
|
-
|
|
76
|
-
|
|
76
|
+
...program.keywords.map(keyword =>
|
|
77
|
+
el('keyword', { lang: keyword.lang }, [escapeString(keyword.value)])
|
|
78
|
+
),
|
|
79
|
+
...program.languages.map(language =>
|
|
80
|
+
el('language', { lang: language.lang }, [escapeString(language.value)])
|
|
81
|
+
),
|
|
82
|
+
...program.origLanguages.map(origLanguage =>
|
|
83
|
+
el('orig-language', { lang: origLanguage.lang }, [escapeString(origLanguage.value)])
|
|
84
|
+
),
|
|
85
|
+
...program.length.map(length =>
|
|
86
|
+
el('length', { units: length.units }, [escapeString(length.value)])
|
|
87
|
+
),
|
|
88
|
+
...program.countries.map(country =>
|
|
89
|
+
el('country', { lang: country.lang }, [escapeString(country.value)])
|
|
90
|
+
),
|
|
91
|
+
...program.urls.map(createURLElement),
|
|
77
92
|
...program.episodeNumbers.map(episode =>
|
|
78
93
|
el('episode-num', { system: episode.system }, [episode.value])
|
|
79
94
|
),
|
|
95
|
+
el('video', {}, [
|
|
96
|
+
el('present', {}, [program.video.present]),
|
|
97
|
+
el('colour', {}, [program.video.colour]),
|
|
98
|
+
el('aspect', {}, [program.video.aspect]),
|
|
99
|
+
el('quality', {}, [program.video.quality])
|
|
100
|
+
]),
|
|
101
|
+
el('audio', {}, [
|
|
102
|
+
el('present', {}, [program.audio.present]),
|
|
103
|
+
el('stereo', {}, [program.audio.stereo])
|
|
104
|
+
]),
|
|
105
|
+
...program.previouslyShown.map(previouslyShown =>
|
|
106
|
+
el('previously-shown', {
|
|
107
|
+
start: previouslyShown.start,
|
|
108
|
+
channel: previouslyShown.channel
|
|
109
|
+
})
|
|
110
|
+
),
|
|
111
|
+
...program.premiere.map(premiere =>
|
|
112
|
+
el('premiere', { lang: premiere.lang }, [escapeString(premiere.value)])
|
|
113
|
+
),
|
|
114
|
+
...program.lastChance.map(lastChance =>
|
|
115
|
+
el('last-chance', { lang: lastChance.lang }, [escapeString(lastChance.value)])
|
|
116
|
+
),
|
|
117
|
+
program.new ? el('new') : '',
|
|
118
|
+
...program.subtitles.map(subtitles =>
|
|
119
|
+
el(
|
|
120
|
+
'subtitles',
|
|
121
|
+
{ type: subtitles.type },
|
|
122
|
+
subtitles.language.map(language =>
|
|
123
|
+
el('language', { lang: language.lang }, [escapeString(language.value)])
|
|
124
|
+
)
|
|
125
|
+
)
|
|
126
|
+
),
|
|
80
127
|
...program.ratings.map(rating =>
|
|
81
128
|
el('rating', { system: rating.system }, [
|
|
82
129
|
el('value', {}, [escapeString(rating.value)]),
|
|
83
|
-
el('icon',
|
|
130
|
+
...rating.icon.map(icon => el('icon', icon))
|
|
131
|
+
])
|
|
132
|
+
),
|
|
133
|
+
...program.starRatings.map(rating =>
|
|
134
|
+
el('star-rating', { system: rating.system }, [
|
|
135
|
+
el('value', {}, [escapeString(rating.value)]),
|
|
136
|
+
...rating.icon.map(icon => el('icon', icon))
|
|
84
137
|
])
|
|
85
|
-
)
|
|
138
|
+
),
|
|
139
|
+
...program.reviews.map(review =>
|
|
140
|
+
el(
|
|
141
|
+
'review',
|
|
142
|
+
{
|
|
143
|
+
type: review.type,
|
|
144
|
+
source: review.source,
|
|
145
|
+
reviewer: review.reviewer,
|
|
146
|
+
lang: review.lang
|
|
147
|
+
},
|
|
148
|
+
[escapeString(review.value)]
|
|
149
|
+
)
|
|
150
|
+
),
|
|
151
|
+
...program.images.map(createImageElement),
|
|
152
|
+
...program.icons.map(icon => el('icon', icon))
|
|
86
153
|
]
|
|
87
154
|
)
|
|
88
155
|
)
|
|
@@ -94,12 +161,12 @@ function createElements(channels, programs, date) {
|
|
|
94
161
|
function createCastMember(position, data) {
|
|
95
162
|
return el(position, {}, [
|
|
96
163
|
escapeString(data.value),
|
|
97
|
-
...data.url.map(
|
|
98
|
-
...data.image.map(
|
|
164
|
+
...data.url.map(createURLElement),
|
|
165
|
+
...data.image.map(createImageElement)
|
|
99
166
|
])
|
|
100
167
|
}
|
|
101
168
|
|
|
102
|
-
function
|
|
169
|
+
function createImageElement(image) {
|
|
103
170
|
return el(
|
|
104
171
|
'image',
|
|
105
172
|
{
|
|
@@ -112,16 +179,17 @@ function createImage(image) {
|
|
|
112
179
|
)
|
|
113
180
|
}
|
|
114
181
|
|
|
115
|
-
function
|
|
182
|
+
function createURLElement(url) {
|
|
116
183
|
return el('url', { system: url.system }, [url.value])
|
|
117
184
|
}
|
|
118
185
|
|
|
119
|
-
function createElement(name, attrs
|
|
186
|
+
function createElement(name, attrs, children) {
|
|
120
187
|
return toString({ name, attrs, children })
|
|
121
188
|
}
|
|
122
189
|
|
|
123
190
|
function toString(elem) {
|
|
124
191
|
if (typeof elem === 'string' || typeof elem === 'number') return elem
|
|
192
|
+
if (!elem.attrs && !elem.children) return `<${elem.name}/>`
|
|
125
193
|
|
|
126
194
|
let attrs = ''
|
|
127
195
|
for (let key in elem.attrs) {
|
|
@@ -131,17 +199,18 @@ function toString(elem) {
|
|
|
131
199
|
}
|
|
132
200
|
}
|
|
133
201
|
|
|
134
|
-
|
|
135
|
-
|
|
202
|
+
let children = elem.children || []
|
|
203
|
+
if (children.filter(Boolean).length) {
|
|
204
|
+
let _children = ''
|
|
136
205
|
elem.children.forEach(childElem => {
|
|
137
|
-
|
|
206
|
+
_children += toString(childElem)
|
|
138
207
|
})
|
|
139
208
|
|
|
140
|
-
return `<${elem.name}${attrs}>${
|
|
209
|
+
return `<${elem.name}${attrs}>${_children}</${elem.name}>`
|
|
141
210
|
}
|
|
142
211
|
|
|
143
212
|
if (!attrs) return ''
|
|
144
|
-
if (!['icon'].includes(elem.name)) return ''
|
|
213
|
+
if (!['icon', 'previously-shown'].includes(elem.name)) return ''
|
|
145
214
|
|
|
146
215
|
return `<${elem.name}${attrs}/>`
|
|
147
216
|
}
|
package/tests/Channel.test.js
CHANGED
|
@@ -7,7 +7,9 @@ it('can create new Channel', () => {
|
|
|
7
7
|
site_id: '1',
|
|
8
8
|
site: 'example.com',
|
|
9
9
|
lang: 'fr',
|
|
10
|
-
|
|
10
|
+
url: 'https://example.com',
|
|
11
|
+
logo: 'https://example.com/logos/1TV.png',
|
|
12
|
+
lcn: 36
|
|
11
13
|
})
|
|
12
14
|
|
|
13
15
|
expect(channel).toMatchObject({
|
|
@@ -17,6 +19,7 @@ it('can create new Channel', () => {
|
|
|
17
19
|
site: 'example.com',
|
|
18
20
|
url: 'https://example.com',
|
|
19
21
|
lang: 'fr',
|
|
20
|
-
|
|
22
|
+
icon: 'https://example.com/logos/1TV.png',
|
|
23
|
+
lcn: 36
|
|
21
24
|
})
|
|
22
25
|
})
|
package/tests/Program.test.js
CHANGED
|
@@ -53,14 +53,14 @@ it('can create new Program', () => {
|
|
|
53
53
|
site: 'example.com',
|
|
54
54
|
channel: '1tv',
|
|
55
55
|
titles: [{ value: 'Title', lang: 'fr' }],
|
|
56
|
-
|
|
56
|
+
subTitles: [{ value: 'Subtitle', lang: 'fr' }],
|
|
57
57
|
descriptions: [{ value: 'Description', lang: 'fr' }],
|
|
58
58
|
urls: [{ system: '', value: 'http://example.com/title.html' }],
|
|
59
59
|
categories: [
|
|
60
60
|
{ value: 'Category1', lang: 'fr' },
|
|
61
61
|
{ value: 'Category2', lang: 'fr' }
|
|
62
62
|
],
|
|
63
|
-
|
|
63
|
+
icons: [{ src: 'https://example.com/image.jpg' }],
|
|
64
64
|
episodeNumbers: [
|
|
65
65
|
{ system: 'xmltv_ns', value: '8.237.0/1' },
|
|
66
66
|
{ system: 'onscreen', value: 'S09E238' }
|
|
@@ -72,7 +72,7 @@ it('can create new Program', () => {
|
|
|
72
72
|
{
|
|
73
73
|
system: 'MPAA',
|
|
74
74
|
value: 'PG',
|
|
75
|
-
icon: 'http://example.com/pg_symbol.png'
|
|
75
|
+
icon: [{ src: 'http://example.com/pg_symbol.png' }]
|
|
76
76
|
}
|
|
77
77
|
],
|
|
78
78
|
directors: [{ value: 'Director1', url: [], image: [] }],
|
|
@@ -154,11 +154,11 @@ it('can create program from exist object', () => {
|
|
|
154
154
|
expect(program).toMatchObject({
|
|
155
155
|
channel: '1tv',
|
|
156
156
|
titles: [{ value: 'Program 1', lang: 'de' }],
|
|
157
|
-
|
|
157
|
+
subTitles: [],
|
|
158
158
|
descriptions: [],
|
|
159
159
|
urls: [],
|
|
160
160
|
categories: [],
|
|
161
|
-
|
|
161
|
+
icons: [],
|
|
162
162
|
episodeNumbers: [],
|
|
163
163
|
date: null,
|
|
164
164
|
start: 1616133600000,
|
|
@@ -167,7 +167,7 @@ it('can create program from exist object', () => {
|
|
|
167
167
|
{
|
|
168
168
|
system: 'MPAA',
|
|
169
169
|
value: 'PG',
|
|
170
|
-
icon: 'http://example.com/pg_symbol.png'
|
|
170
|
+
icon: [{ src: 'http://example.com/pg_symbol.png' }]
|
|
171
171
|
}
|
|
172
172
|
],
|
|
173
173
|
directors: [],
|
package/tests/index.test.js
CHANGED
|
@@ -138,9 +138,9 @@ it('can mock epg grabber', done => {
|
|
|
138
138
|
site: 'example.com',
|
|
139
139
|
channel: '1TV.fr',
|
|
140
140
|
titles: [{ value: 'Test (1TV)', lang: 'fr' }],
|
|
141
|
-
|
|
141
|
+
subTitles: [],
|
|
142
142
|
descriptions: [],
|
|
143
|
-
|
|
143
|
+
icons: [],
|
|
144
144
|
episodeNumbers: [],
|
|
145
145
|
date: null,
|
|
146
146
|
start: 1616128200000,
|
|
@@ -152,6 +152,21 @@ it('can mock epg grabber', done => {
|
|
|
152
152
|
actors: [],
|
|
153
153
|
writers: [],
|
|
154
154
|
adapters: [],
|
|
155
|
+
audio: {},
|
|
156
|
+
video: {},
|
|
157
|
+
images: [],
|
|
158
|
+
keywords: [],
|
|
159
|
+
languages: [],
|
|
160
|
+
lastChance: [],
|
|
161
|
+
length: [],
|
|
162
|
+
new: undefined,
|
|
163
|
+
origLanguages: [],
|
|
164
|
+
premiere: [],
|
|
165
|
+
previouslyShown: [],
|
|
166
|
+
reviews: [],
|
|
167
|
+
starRatings: [],
|
|
168
|
+
subtitles: [],
|
|
169
|
+
countries: [],
|
|
155
170
|
producers: [],
|
|
156
171
|
composers: [],
|
|
157
172
|
editors: [],
|
package/tests/parser.test.js
CHANGED
|
@@ -17,14 +17,14 @@ it('can parse channels.xml', () => {
|
|
|
17
17
|
site_id: '1',
|
|
18
18
|
xmltv_id: '1TV.com',
|
|
19
19
|
lang: 'fr',
|
|
20
|
-
|
|
20
|
+
icon: 'https://example.com/logos/1TV.png',
|
|
21
21
|
name: '1 TV'
|
|
22
22
|
})
|
|
23
23
|
expect(channels[1]).toMatchObject({
|
|
24
24
|
site: 'example.com',
|
|
25
25
|
site_id: '2',
|
|
26
26
|
lang: '',
|
|
27
|
-
|
|
27
|
+
icon: '',
|
|
28
28
|
xmltv_id: '2TV.com',
|
|
29
29
|
name: '2 TV'
|
|
30
30
|
})
|
|
@@ -44,14 +44,14 @@ it('can parse channels.xml with inline site attribute', () => {
|
|
|
44
44
|
site_id: '1',
|
|
45
45
|
xmltv_id: '1TV.com',
|
|
46
46
|
lang: 'fr',
|
|
47
|
-
|
|
47
|
+
icon: 'https://example.com/logos/1TV.png',
|
|
48
48
|
name: '1 TV'
|
|
49
49
|
})
|
|
50
50
|
expect(channels[1]).toMatchObject({
|
|
51
51
|
site: 'example.com',
|
|
52
52
|
site_id: '2',
|
|
53
53
|
lang: '',
|
|
54
|
-
|
|
54
|
+
icon: '',
|
|
55
55
|
xmltv_id: '2TV.com',
|
|
56
56
|
name: '2 TV'
|
|
57
57
|
})
|
|
@@ -69,14 +69,14 @@ it('can parse legacy channels.xml', () => {
|
|
|
69
69
|
site_id: '1',
|
|
70
70
|
xmltv_id: '1TV.com',
|
|
71
71
|
lang: 'fr',
|
|
72
|
-
|
|
72
|
+
icon: 'https://example.com/logos/1TV.png',
|
|
73
73
|
name: '1 TV'
|
|
74
74
|
})
|
|
75
75
|
expect(channels[1]).toMatchObject({
|
|
76
76
|
site: 'example.com',
|
|
77
77
|
site_id: '2',
|
|
78
78
|
lang: '',
|
|
79
|
-
|
|
79
|
+
icon: '',
|
|
80
80
|
xmltv_id: '2TV.com',
|
|
81
81
|
name: '2 TV'
|
|
82
82
|
})
|
package/tests/xmltv.test.js
CHANGED
|
@@ -8,14 +8,17 @@ const channels = [
|
|
|
8
8
|
new Channel({
|
|
9
9
|
xmltv_id: '1TV.co',
|
|
10
10
|
name: '1 TV',
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
site: 'example.com',
|
|
12
|
+
icon: 'https://example.com/channel_one_icon.jpg',
|
|
13
|
+
url: 'https://example.com/channel_one',
|
|
14
|
+
lcn: 36
|
|
13
15
|
}),
|
|
14
16
|
new Channel({
|
|
15
17
|
xmltv_id: '2TV.co',
|
|
16
18
|
name: '2 TV',
|
|
17
19
|
site: 'example.com',
|
|
18
|
-
lang: 'es'
|
|
20
|
+
lang: 'es',
|
|
21
|
+
logo: 'https://example.com/logos/2TV.png'
|
|
19
22
|
})
|
|
20
23
|
]
|
|
21
24
|
|
|
@@ -23,22 +26,81 @@ it('can generate xmltv', () => {
|
|
|
23
26
|
const programs = [
|
|
24
27
|
new Program(
|
|
25
28
|
{
|
|
26
|
-
title: 'Program 1',
|
|
27
|
-
sub_title: 'Sub-title & 1',
|
|
28
|
-
description: 'Description for Program 1',
|
|
29
|
-
url: 'http://example.com/title.html',
|
|
30
29
|
start: '2021-03-19T06:00:00.000Z',
|
|
31
30
|
stop: '2021-03-19T06:30:00.000Z',
|
|
32
|
-
|
|
31
|
+
title: 'Program 1',
|
|
32
|
+
subTitle: 'Sub-title & 1',
|
|
33
|
+
description: 'Description for Program 1',
|
|
33
34
|
date: '2022-05-06',
|
|
35
|
+
category: 'Test',
|
|
36
|
+
keyword: [
|
|
37
|
+
{ lang: 'en', value: 'physical-comedy' },
|
|
38
|
+
{ lang: 'en', value: 'romantic' }
|
|
39
|
+
],
|
|
40
|
+
language: [{ value: 'English' }],
|
|
41
|
+
origLanguage: [{ lang: 'en', value: 'French' }],
|
|
42
|
+
length: [{ units: 'minutes', value: '60' }],
|
|
43
|
+
url: 'http://example.com/title.html',
|
|
44
|
+
country: [{ value: 'US' }],
|
|
45
|
+
video: {
|
|
46
|
+
present: 'yes',
|
|
47
|
+
colour: 'no',
|
|
48
|
+
aspect: '16:9',
|
|
49
|
+
quality: 'HDTV'
|
|
50
|
+
},
|
|
51
|
+
audio: {
|
|
52
|
+
present: 'yes',
|
|
53
|
+
stereo: 'Dolby Digital'
|
|
54
|
+
},
|
|
34
55
|
season: 9,
|
|
35
56
|
episode: 239,
|
|
36
|
-
|
|
57
|
+
previouslyShown: [{ start: '20080711000000', channel: 'channel-two.tv' }],
|
|
58
|
+
premiere: [{ value: 'First time on British TV' }],
|
|
59
|
+
lastChance: [{ lang: 'en', value: 'Last time on this channel' }],
|
|
60
|
+
new: true,
|
|
61
|
+
subtitles: [
|
|
62
|
+
{ type: 'teletext', language: [{ value: 'English' }] },
|
|
63
|
+
{ type: 'onscreen', language: [{ lang: 'en', value: 'Spanish' }] }
|
|
64
|
+
],
|
|
37
65
|
rating: {
|
|
38
66
|
system: 'MPAA',
|
|
39
67
|
value: 'P&G',
|
|
40
68
|
icon: 'http://example.com/pg_symbol.png'
|
|
41
69
|
},
|
|
70
|
+
starRating: [
|
|
71
|
+
{
|
|
72
|
+
system: 'TV Guide',
|
|
73
|
+
value: '4/5',
|
|
74
|
+
icon: [{ src: 'stars.png' }]
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
system: 'IMDB',
|
|
78
|
+
value: '8/10'
|
|
79
|
+
}
|
|
80
|
+
],
|
|
81
|
+
review: [
|
|
82
|
+
{
|
|
83
|
+
type: 'text',
|
|
84
|
+
source: 'Rotten Tomatoes',
|
|
85
|
+
reviewer: 'Joe Bloggs',
|
|
86
|
+
lang: 'en',
|
|
87
|
+
value: 'This is a fantastic show!'
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
type: 'text',
|
|
91
|
+
source: 'IDMB',
|
|
92
|
+
reviewer: 'Jane Doe',
|
|
93
|
+
lang: 'en',
|
|
94
|
+
value: 'I love this show!'
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
type: 'url',
|
|
98
|
+
source: 'Rotten Tomatoes',
|
|
99
|
+
reviewer: 'Joe Bloggs',
|
|
100
|
+
lang: 'en',
|
|
101
|
+
value: 'https://example.com/programme_one_review'
|
|
102
|
+
}
|
|
103
|
+
],
|
|
42
104
|
director: [
|
|
43
105
|
{
|
|
44
106
|
value: 'Director 1',
|
|
@@ -57,7 +119,38 @@ it('can generate xmltv', () => {
|
|
|
57
119
|
'Director 2'
|
|
58
120
|
],
|
|
59
121
|
actor: ['Actor 1', 'Actor 2'],
|
|
60
|
-
writer: 'Writer 1'
|
|
122
|
+
writer: 'Writer 1',
|
|
123
|
+
image: [
|
|
124
|
+
{
|
|
125
|
+
type: 'poster',
|
|
126
|
+
size: '1',
|
|
127
|
+
orient: 'P',
|
|
128
|
+
system: 'tvdb',
|
|
129
|
+
value: 'https://tvdb.com/programme_one_poster_1.jpg'
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
type: 'poster',
|
|
133
|
+
size: '2',
|
|
134
|
+
orient: 'P',
|
|
135
|
+
system: 'tmdb',
|
|
136
|
+
value: 'https://tmdb.com/programme_one_poster_2.jpg'
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
type: 'backdrop',
|
|
140
|
+
size: '3',
|
|
141
|
+
orient: 'L',
|
|
142
|
+
system: 'tvdb',
|
|
143
|
+
value: 'https://tvdb.com/programme_one_backdrop_3.jpg'
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
type: 'backdrop',
|
|
147
|
+
size: '3',
|
|
148
|
+
orient: 'L',
|
|
149
|
+
system: 'tmdb',
|
|
150
|
+
value: 'https://tmdb.com/programme_one_backdrop_3.jpg'
|
|
151
|
+
}
|
|
152
|
+
],
|
|
153
|
+
icon: 'https://example.com/images/Program1.png?x=шеллы&sid=777'
|
|
61
154
|
},
|
|
62
155
|
channels[0]
|
|
63
156
|
),
|
|
@@ -74,6 +167,6 @@ it('can generate xmltv', () => {
|
|
|
74
167
|
const output = xmltv.generate({ channels, programs })
|
|
75
168
|
|
|
76
169
|
expect(output).toBe(
|
|
77
|
-
'<?xml version="1.0" encoding="UTF-8" ?><tv date="20220505">\r\n<channel id="1TV.co"><display-name>1 TV</display-name><icon src="https://example.com/
|
|
170
|
+
'<?xml version="1.0" encoding="UTF-8" ?><tv date="20220505">\r\n<channel id="1TV.co"><display-name>1 TV</display-name><icon src="https://example.com/channel_one_icon.jpg"/><url>https://example.com/channel_one</url><lcn>36</lcn></channel>\r\n<channel id="2TV.co"><display-name>2 TV</display-name><icon src="https://example.com/logos/2TV.png"/><url>https://example.com</url></channel>\r\n<programme start="20210319060000 +0000" stop="20210319063000 +0000" channel="1TV.co"><title>Program 1</title><sub-title>Sub-title & 1</sub-title><desc>Description for Program 1</desc><credits><director>Director 1<url system="TestSystem">http://example.com/director1.html</url><image>https://example.com/image1.jpg</image><image type="person" size="2" orient="P" system="TestSystem">https://example.com/image2.jpg</image></director><director>Director 2</director><actor>Actor 1</actor><actor>Actor 2</actor><writer>Writer 1</writer></credits><date>20220506</date><category>Test</category><keyword lang="en">physical-comedy</keyword><keyword lang="en">romantic</keyword><language>English</language><orig-language lang="en">French</orig-language><length units="minutes">60</length><country>US</country><url>http://example.com/title.html</url><episode-num system="xmltv_ns">8.238.0/1</episode-num><episode-num system="onscreen">S09E239</episode-num><video><present>yes</present><colour>no</colour><aspect>16:9</aspect><quality>HDTV</quality></video><audio><present>yes</present><stereo>Dolby Digital</stereo></audio><previously-shown start="20080711000000" channel="channel-two.tv"/><premiere>First time on British TV</premiere><last-chance lang="en">Last time on this channel</last-chance><new/><subtitles type="teletext"><language>English</language></subtitles><subtitles type="onscreen"><language lang="en">Spanish</language></subtitles><rating system="MPAA"><value>P&G</value><icon src="http://example.com/pg_symbol.png"/></rating><star-rating system="TV Guide"><value>4/5</value><icon src="stars.png"/></star-rating><star-rating system="IMDB"><value>8/10</value></star-rating><review type="text" source="Rotten Tomatoes" reviewer="Joe Bloggs" lang="en">This is a fantastic show!</review><review type="text" source="IDMB" reviewer="Jane Doe" lang="en">I love this show!</review><review type="url" source="Rotten Tomatoes" reviewer="Joe Bloggs" lang="en">https://example.com/programme_one_review</review><image type="poster" size="1" orient="P" system="tvdb">https://tvdb.com/programme_one_poster_1.jpg</image><image type="poster" size="2" orient="P" system="tmdb">https://tmdb.com/programme_one_poster_2.jpg</image><image type="backdrop" size="3" orient="L" system="tvdb">https://tvdb.com/programme_one_backdrop_3.jpg</image><image type="backdrop" size="3" orient="L" system="tmdb">https://tmdb.com/programme_one_backdrop_3.jpg</image><icon src="https://example.com/images/Program1.png?x=шеллы&sid=777"/></programme>\r\n<programme start="20210319060000 +0000" stop="20210319063000 +0000" channel="2TV.co"><title lang="es">Program 2</title></programme>\r\n</tv>'
|
|
78
171
|
)
|
|
79
172
|
})
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8" ?><tv date="20231201">
|
|
2
|
-
<channel id="1TV.com"><display-name>1 TV</display-name><icon src="https://example.com/logos/1TV.png"/><url>https://example.com</url></channel>
|
|
3
|
-
<channel id="2TV.com"><display-name>2 TV</display-name><icon src="http://example.com/logos/1TV.png?x=шеллы&sid=777"/><url>https://example.com</url></channel>
|
|
4
|
-
<channel id="3TV.com"><display-name>3 TV</display-name><icon src="http://example.com/logos/1TV.png?x=шеллы&sid=777"/><url>https://example2.com</url></channel>
|
|
5
|
-
<channel id="4TV.com"><display-name>4 TV</display-name><icon src="http://example.com/logos/1TV.png?x=шеллы&sid=777"/><url>https://example2.com</url></channel>
|
|
6
|
-
</tv>
|