epg-grabber 0.26.0 → 0.27.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "epg-grabber",
3
- "version": "0.26.0",
3
+ "version": "0.27.2",
4
4
  "description": "Node.js CLI tool for grabbing EPG from different sites",
5
5
  "main": "src/index.js",
6
6
  "preferGlobal": true,
package/src/utils.js CHANGED
@@ -149,8 +149,10 @@ utils.escapeString = function (string, defaultValue = '') {
149
149
  .trim()
150
150
  }
151
151
 
152
- utils.convertToXMLTV = function ({ channels, programs }) {
153
- let output = `<?xml version="1.0" encoding="UTF-8" ?><tv>\r\n`
152
+ utils.convertToXMLTV = function ({ channels, programs, date = dayjs.utc() }) {
153
+ let output = `<?xml version="1.0" encoding="UTF-8" ?><tv date="${dayjs(date).format(
154
+ 'YYYYMMDD'
155
+ )}">\r\n`
154
156
  for (let channel of channels) {
155
157
  const id = utils.escapeString(channel['xmltv_id'])
156
158
  const displayName = utils.escapeString(channel.name)
@@ -178,11 +180,30 @@ utils.convertToXMLTV = function ({ channels, programs }) {
178
180
  const lang = program.lang || 'en'
179
181
  const xmltv_ns = createXMLTVNS(program.season, program.episode)
180
182
  const onscreen = createOnScreen(program.season, program.episode)
183
+ const date = program.date || ''
184
+ const credits = createCredits({
185
+ director: program.director,
186
+ actor: program.actor,
187
+ writer: program.writer,
188
+ adapter: program.adapter,
189
+ producer: program.producer,
190
+ composer: program.composer,
191
+ editor: program.editor,
192
+ presenter: program.presenter,
193
+ commentator: program.commentator,
194
+ guest: program.guest
195
+ })
181
196
  const icon = utils.escapeString(program.icon)
197
+ const sub_title = utils.escapeString(program.sub_title)
198
+ const url = program.url ? createURL(program.url, channel) : ''
182
199
 
183
200
  if (start && stop && title) {
184
201
  output += `<programme start="${start}" stop="${stop}" channel="${channel}"><title lang="${lang}">${title}</title>`
185
202
 
203
+ if (sub_title) {
204
+ output += `<sub-title>${sub_title}</sub-title>`
205
+ }
206
+
186
207
  if (description) {
187
208
  output += `<desc lang="${lang}">${description}</desc>`
188
209
  }
@@ -195,6 +216,10 @@ utils.convertToXMLTV = function ({ channels, programs }) {
195
216
  })
196
217
  }
197
218
 
219
+ if (url) {
220
+ output += url
221
+ }
222
+
198
223
  if (xmltv_ns) {
199
224
  output += `<episode-num system="xmltv_ns">${xmltv_ns}</episode-num>`
200
225
  }
@@ -202,11 +227,18 @@ utils.convertToXMLTV = function ({ channels, programs }) {
202
227
  if (onscreen) {
203
228
  output += `<episode-num system="onscreen">${onscreen}</episode-num>`
204
229
  }
230
+ if (date) {
231
+ output += `<date>${date}</date>`
232
+ }
205
233
 
206
234
  if (icon) {
207
235
  output += `<icon src="${icon}"/>`
208
236
  }
209
237
 
238
+ if (credits) {
239
+ output += `<credits>${credits}</credits>`
240
+ }
241
+
210
242
  output += '</programme>\r\n'
211
243
  }
212
244
  }
@@ -230,6 +262,105 @@ utils.convertToXMLTV = function ({ channels, programs }) {
230
262
  return `S${s}E${e}`
231
263
  }
232
264
 
265
+ function createURL(urlObj, channel = '') {
266
+ const urls = Array.isArray(urlObj) ? urlObj : [urlObj]
267
+ let output = ''
268
+ for (let url of urls) {
269
+ if (typeof url === 'string' || url instanceof String) {
270
+ url = { value: url }
271
+ }
272
+
273
+ let attr = url.system ? ` system="${url.system}"` : ''
274
+ if (url.value.includes('http')) {
275
+ output += `<url${attr}>${url.value}</url>`
276
+ } else if (channel) {
277
+ let chan = channels.find(c => c.xmltv_id.localeCompare(channel) === 0)
278
+ if (chan && chan.site) {
279
+ output += `<url${attr}>https://${chan.site}${url.value}</url>`
280
+ }
281
+ }
282
+ }
283
+
284
+ return output
285
+ }
286
+
287
+ function createImage(imgObj, channel = '') {
288
+ const imgs = Array.isArray(imgObj) ? imgObj : [imgObj]
289
+ let output = ''
290
+ for (let img of imgs) {
291
+ if (typeof img === 'string' || img instanceof String) {
292
+ img = { value: img }
293
+ }
294
+
295
+ const imageTypes = ['poster', 'backdrop', 'still', 'person', 'character']
296
+ const imageSizes = ['1', '2', '3']
297
+ const imageOrients = ['P', 'L']
298
+
299
+ let attr = ''
300
+
301
+ if (img.type && imageTypes.some(el => img.type.includes(el))) {
302
+ attr += ` type="${img.type}"`
303
+ }
304
+
305
+ if (img.size && imageSizes.some(el => img.size.includes(el))) {
306
+ attr += ` size="${img.size}"`
307
+ }
308
+
309
+ if (img.orient && imageOrients.some(el => img.orient.includes(el))) {
310
+ attr += ` orient="${img.orient}"`
311
+ }
312
+
313
+ if (img.system) {
314
+ attr += ` system="${img.system}"`
315
+ }
316
+
317
+ if (img.value.includes('http')) {
318
+ output += `<image${attr}>${img.value}</image>`
319
+ } else if (channel) {
320
+ let chan = channels.find(c => c.xmltv_id.localeCompare(channel) === 0)
321
+ if (chan && chan.site) {
322
+ output += `<image${attr}>https://${chan.site}${img.value}</image>`
323
+ }
324
+ }
325
+ }
326
+
327
+ return output
328
+ }
329
+
330
+ function createCredits(obj) {
331
+ let cast = Object.entries(obj)
332
+ .filter(x => x[1])
333
+ .map(([name, value]) => ({ name, value }))
334
+
335
+ let output = ''
336
+ for (let type of cast) {
337
+ const r = Array.isArray(type.value) ? type.value : [type.value]
338
+ for (let person of r) {
339
+ if (typeof person === 'string' || person instanceof String) {
340
+ person = { value: person }
341
+ }
342
+
343
+ let attr = ''
344
+ if (type.name.localeCompare('actor') === 0 && type.value.role) {
345
+ attr += ` role="${type.value.role}"`
346
+ }
347
+ if (type.name.localeCompare('actor') === 0 && type.value.guest) {
348
+ attr += ` guest="${type.value.guest}"`
349
+ }
350
+ output += `<${type.name}${attr}>${person.value}`
351
+
352
+ if (person.url) {
353
+ output += createURL(person.url)
354
+ }
355
+ if (person.image) {
356
+ output += createImage(person.image)
357
+ }
358
+
359
+ output += `</${type.name}>`
360
+ }
361
+ }
362
+ return output
363
+ }
233
364
  return output
234
365
  }
235
366
 
@@ -348,11 +479,24 @@ utils.parsePrograms = async function (data, config) {
348
479
  category: program.category || null,
349
480
  season: program.season || null,
350
481
  episode: program.episode || null,
482
+ sub_title: program.sub_title || null,
483
+ url: program.url || null,
351
484
  icon: program.icon || null,
352
485
  channel: channel.xmltv_id,
353
486
  lang: program.lang || channel.lang || config.lang || 'en',
354
487
  start: program.start ? dayjs(program.start).unix() : null,
355
- stop: program.stop ? dayjs(program.stop).unix() : null
488
+ stop: program.stop ? dayjs(program.stop).unix() : null,
489
+ date: program.date || null,
490
+ director: program.director || null,
491
+ actor: program.actor || null,
492
+ writer: program.writer || null,
493
+ adapter: program.adapter || null,
494
+ producer: program.producer || null,
495
+ composer: program.composer || null,
496
+ editor: program.editor || null,
497
+ presenter: program.presenter || null,
498
+ commentator: program.commentator || null,
499
+ guest: program.guest || null
356
500
  }
357
501
  })
358
502
  }
@@ -58,14 +58,7 @@ it('can grab single channel programs', done => {
58
58
  grabber
59
59
  .grab(channel, '2022-01-01', (data, err) => {
60
60
  if (err) {
61
- console.log(` Error: ${err.message}`)
62
61
  done()
63
- } else {
64
- console.log(
65
- ` ${data.channel.site} - ${data.channel.xmltv_id} - ${data.date.format(
66
- 'MMM D, YYYY'
67
- )} (${data.programs.length} programs)`
68
- )
69
62
  }
70
63
  })
71
64
  .then(programs => {
@@ -4,6 +4,8 @@ import axios from 'axios'
4
4
  import path from 'path'
5
5
  import fs from 'fs'
6
6
 
7
+ jest.useFakeTimers('modern').setSystemTime(new Date('2022-05-05'))
8
+
7
9
  it('can load valid config.js', () => {
8
10
  const config = utils.loadConfig(require(path.resolve('./tests/input/example.com.config.js')))
9
11
  expect(config).toMatchObject({
@@ -56,7 +58,9 @@ it('can convert object to xmltv string', () => {
56
58
  const programs = [
57
59
  {
58
60
  title: 'Program 1',
61
+ sub_title: 'Sub-title & 1',
59
62
  description: 'Description for Program 1',
63
+ url: 'http://example.com/title.html',
60
64
  start: 1616133600,
61
65
  stop: 1616135400,
62
66
  category: 'Test',
@@ -64,12 +68,19 @@ it('can convert object to xmltv string', () => {
64
68
  episode: 239,
65
69
  icon: 'https://example.com/images/Program1.png?x=шеллы&sid=777',
66
70
  channel: '1TV.com',
67
- lang: 'it'
71
+ lang: 'it',
72
+ date: '20220505',
73
+ director: {
74
+ value: 'Director 1',
75
+ url: { value: 'http://example.com/director1.html', system: 'TestSystem' }
76
+ },
77
+ actor: ['Actor 1', 'Actor 2'],
78
+ writer: 'Writer 1'
68
79
  }
69
80
  ]
70
81
  const output = utils.convertToXMLTV({ channels, programs })
71
82
  expect(output).toBe(
72
- '<?xml version="1.0" encoding="UTF-8" ?><tv>\r\n<channel id="1TV.com"><display-name>1 TV</display-name><icon src="https://example.com/logos/1TV.png"/><url>https://example.com</url></channel>\r\n<channel id="2TV.com"><display-name>2 TV</display-name><url>https://example.com</url></channel>\r\n<programme start="20210319060000 +0000" stop="20210319063000 +0000" channel="1TV.com"><title lang="it">Program 1</title><desc lang="it">Description for Program 1</desc><category lang="it">Test</category><episode-num system="xmltv_ns">8.238.0/1</episode-num><episode-num system="onscreen">S09E239</episode-num><icon src="https://example.com/images/Program1.png?x=шеллы&amp;sid=777"/></programme>\r\n</tv>'
83
+ '<?xml version="1.0" encoding="UTF-8" ?><tv date="20220505">\r\n<channel id="1TV.com"><display-name>1 TV</display-name><icon src="https://example.com/logos/1TV.png"/><url>https://example.com</url></channel>\r\n<channel id="2TV.com"><display-name>2 TV</display-name><url>https://example.com</url></channel>\r\n<programme start="20210319060000 +0000" stop="20210319063000 +0000" channel="1TV.com"><title lang="it">Program 1</title><sub-title>Sub-title &amp; 1</sub-title><desc lang="it">Description for Program 1</desc><category lang="it">Test</category><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><date>20220505</date><icon src="https://example.com/images/Program1.png?x=шеллы&amp;sid=777"/><credits><director>Director 1<url system="TestSystem">http://example.com/director1.html</url></director><actor>Actor 1</actor><actor>Actor 2</actor><writer>Writer 1</writer></credits></programme>\r\n</tv>'
73
84
  )
74
85
  })
75
86
 
@@ -91,7 +102,7 @@ it('can convert object to xmltv string without season number', () => {
91
102
  ]
92
103
  const output = utils.convertToXMLTV({ channels, programs })
93
104
  expect(output).toBe(
94
- '<?xml version="1.0" encoding="UTF-8" ?><tv>\r\n<channel id="1TV.com"><display-name>1 TV</display-name><icon src="https://example.com/logos/1TV.png"/><url>https://example.com</url></channel>\r\n<channel id="2TV.com"><display-name>2 TV</display-name><url>https://example.com</url></channel>\r\n<programme start="20210319060000 +0000" stop="20210319063000 +0000" channel="1TV.com"><title lang="it">Program 1</title><desc lang="it">Description for Program 1</desc><category lang="it">Test</category><episode-num system="xmltv_ns">0.238.0/1</episode-num><episode-num system="onscreen">S01E239</episode-num><icon src="https://example.com/images/Program1.png?x=шеллы&amp;sid=777"/></programme>\r\n</tv>'
105
+ '<?xml version="1.0" encoding="UTF-8" ?><tv date="20220505">\r\n<channel id="1TV.com"><display-name>1 TV</display-name><icon src="https://example.com/logos/1TV.png"/><url>https://example.com</url></channel>\r\n<channel id="2TV.com"><display-name>2 TV</display-name><url>https://example.com</url></channel>\r\n<programme start="20210319060000 +0000" stop="20210319063000 +0000" channel="1TV.com"><title lang="it">Program 1</title><desc lang="it">Description for Program 1</desc><category lang="it">Test</category><episode-num system="xmltv_ns">0.238.0/1</episode-num><episode-num system="onscreen">S01E239</episode-num><icon src="https://example.com/images/Program1.png?x=шеллы&amp;sid=777"/></programme>\r\n</tv>'
95
106
  )
96
107
  })
97
108
 
@@ -113,7 +124,7 @@ it('can convert object to xmltv string without episode number', () => {
113
124
  ]
114
125
  const output = utils.convertToXMLTV({ channels, programs })
115
126
  expect(output).toBe(
116
- '<?xml version="1.0" encoding="UTF-8" ?><tv>\r\n<channel id="1TV.com"><display-name>1 TV</display-name><icon src="https://example.com/logos/1TV.png"/><url>https://example.com</url></channel>\r\n<channel id="2TV.com"><display-name>2 TV</display-name><url>https://example.com</url></channel>\r\n<programme start="20210319060000 +0000" stop="20210319063000 +0000" channel="1TV.com"><title lang="it">Program 1</title><desc lang="it">Description for Program 1</desc><category lang="it">Test</category><icon src="https://example.com/images/Program1.png?x=шеллы&amp;sid=777"/></programme>\r\n</tv>'
127
+ '<?xml version="1.0" encoding="UTF-8" ?><tv date="20220505">\r\n<channel id="1TV.com"><display-name>1 TV</display-name><icon src="https://example.com/logos/1TV.png"/><url>https://example.com</url></channel>\r\n<channel id="2TV.com"><display-name>2 TV</display-name><url>https://example.com</url></channel>\r\n<programme start="20210319060000 +0000" stop="20210319063000 +0000" channel="1TV.com"><title lang="it">Program 1</title><desc lang="it">Description for Program 1</desc><category lang="it">Test</category><icon src="https://example.com/images/Program1.png?x=шеллы&amp;sid=777"/></programme>\r\n</tv>'
117
128
  )
118
129
  })
119
130
 
@@ -140,7 +151,7 @@ it('can convert object to xmltv string without categories', () => {
140
151
  const config = { site: 'example.com' }
141
152
  const output = utils.convertToXMLTV({ config, channels, programs })
142
153
  expect(output).toBe(
143
- '<?xml version="1.0" encoding="UTF-8" ?><tv>\r\n<channel id="1TV.com"><display-name>1 TV</display-name><icon src="https://example.com/logos/1TV.png"/><url>https://example.com</url></channel>\r\n<programme start="20210319060000 +0000" stop="20210319063000 +0000" channel="1TV.com"><title lang="it">Program 1</title></programme>\r\n</tv>'
154
+ '<?xml version="1.0" encoding="UTF-8" ?><tv date="20220505">\r\n<channel id="1TV.com"><display-name>1 TV</display-name><icon src="https://example.com/logos/1TV.png"/><url>https://example.com</url></channel>\r\n<programme start="20210319060000 +0000" stop="20210319063000 +0000" channel="1TV.com"><title lang="it">Program 1</title></programme>\r\n</tv>'
144
155
  )
145
156
  })
146
157
 
@@ -161,7 +172,110 @@ it('can convert object to xmltv string with multiple categories', () => {
161
172
  ]
162
173
  const output = utils.convertToXMLTV({ channels, programs })
163
174
  expect(output).toBe(
164
- '<?xml version="1.0" encoding="UTF-8" ?><tv>\r\n<channel id="1TV.com"><display-name>1 TV</display-name><icon src="https://example.com/logos/1TV.png"/><url>https://example.com</url></channel>\r\n<channel id="2TV.com"><display-name>2 TV</display-name><url>https://example.com</url></channel>\r\n<programme start="20210319060000 +0000" stop="20210319063000 +0000" channel="1TV.com"><title lang="it">Program 1</title><desc lang="it">Description for Program 1</desc><category lang="it">Test1</category><category lang="it">Test2</category><icon src="https://example.com/images/Program1.png?x=шеллы&amp;sid=777"/></programme>\r\n</tv>'
175
+ '<?xml version="1.0" encoding="UTF-8" ?><tv date="20220505">\r\n<channel id="1TV.com"><display-name>1 TV</display-name><icon src="https://example.com/logos/1TV.png"/><url>https://example.com</url></channel>\r\n<channel id="2TV.com"><display-name>2 TV</display-name><url>https://example.com</url></channel>\r\n<programme start="20210319060000 +0000" stop="20210319063000 +0000" channel="1TV.com"><title lang="it">Program 1</title><desc lang="it">Description for Program 1</desc><category lang="it">Test1</category><category lang="it">Test2</category><icon src="https://example.com/images/Program1.png?x=шеллы&amp;sid=777"/></programme>\r\n</tv>'
176
+ )
177
+ })
178
+
179
+ it('can convert object to xmltv string with multiple urls', () => {
180
+ const file = fs.readFileSync('./tests/input/example.com.channels.xml', { encoding: 'utf-8' })
181
+ const { channels } = utils.parseChannels(file)
182
+ const programs = [
183
+ {
184
+ title: 'Program 1',
185
+ description: 'Description for Program 1',
186
+ start: 1616133600,
187
+ stop: 1616135400,
188
+ category: ['Test1', 'Test2'],
189
+ url: [
190
+ 'https://example.com/noattr.html',
191
+ { value: 'https://example.com/attr.html', system: 'TestSystem' }
192
+ ],
193
+ icon: 'https://example.com/images/Program1.png?x=шеллы&sid=777',
194
+ channel: '1TV.com',
195
+ lang: 'it'
196
+ }
197
+ ]
198
+ const output = utils.convertToXMLTV({ channels, programs })
199
+ expect(output).toBe(
200
+ '<?xml version="1.0" encoding="UTF-8" ?><tv date="20220505">\r\n<channel id="1TV.com"><display-name>1 TV</display-name><icon src="https://example.com/logos/1TV.png"/><url>https://example.com</url></channel>\r\n<channel id="2TV.com"><display-name>2 TV</display-name><url>https://example.com</url></channel>\r\n<programme start="20210319060000 +0000" stop="20210319063000 +0000" channel="1TV.com"><title lang="it">Program 1</title><desc lang="it">Description for Program 1</desc><category lang="it">Test1</category><category lang="it">Test2</category><url>https://example.com/noattr.html</url><url system="TestSystem">https://example.com/attr.html</url><icon src="https://example.com/images/Program1.png?x=шеллы&amp;sid=777"/></programme>\r\n</tv>'
201
+ )
202
+ })
203
+
204
+ it('can convert object to xmltv string with multiple images', () => {
205
+ const file = fs.readFileSync('./tests/input/example.com.channels.xml', { encoding: 'utf-8' })
206
+ const { channels } = utils.parseChannels(file)
207
+ const programs = [
208
+ {
209
+ title: 'Program 1',
210
+ description: 'Description for Program 1',
211
+ start: 1616133600,
212
+ stop: 1616135400,
213
+ category: ['Test1', 'Test2'],
214
+ url: [
215
+ 'https://example.com/noattr.html',
216
+ { value: 'https://example.com/attr.html', system: 'TestSystem' }
217
+ ],
218
+ actor: {
219
+ value: 'Actor 1',
220
+ image: [
221
+ 'https://example.com/image1.jpg',
222
+ {
223
+ value: 'https://example.com/image2.jpg',
224
+ type: 'person',
225
+ size: '2',
226
+ system: 'TestSystem',
227
+ orient: 'P'
228
+ }
229
+ ]
230
+ },
231
+ icon: 'https://example.com/images/Program1.png?x=шеллы&sid=777',
232
+ channel: '1TV.com',
233
+ lang: 'it'
234
+ }
235
+ ]
236
+ const output = utils.convertToXMLTV({ channels, programs })
237
+ expect(output).toBe(
238
+ '<?xml version="1.0" encoding="UTF-8" ?><tv date="20220505">\r\n<channel id="1TV.com"><display-name>1 TV</display-name><icon src="https://example.com/logos/1TV.png"/><url>https://example.com</url></channel>\r\n<channel id="2TV.com"><display-name>2 TV</display-name><url>https://example.com</url></channel>\r\n<programme start="20210319060000 +0000" stop="20210319063000 +0000" channel="1TV.com"><title lang="it">Program 1</title><desc lang="it">Description for Program 1</desc><category lang="it">Test1</category><category lang="it">Test2</category><url>https://example.com/noattr.html</url><url system="TestSystem">https://example.com/attr.html</url><icon src="https://example.com/images/Program1.png?x=шеллы&amp;sid=777"/><credits><actor>Actor 1<image>https://example.com/image1.jpg</image><image type="person" size="2" orient="P" system="TestSystem">https://example.com/image2.jpg</image></actor></credits></programme>\r\n</tv>'
239
+ )
240
+ })
241
+
242
+ it('can convert object to xmltv string with multiple credits member', () => {
243
+ const file = fs.readFileSync('./tests/input/example.com.channels.xml', { encoding: 'utf-8' })
244
+ const { channels } = utils.parseChannels(file)
245
+ const programs = [
246
+ {
247
+ title: 'Program 1',
248
+ sub_title: 'Sub-title 1',
249
+ description: 'Description for Program 1',
250
+ url: 'http://example.com/title.html',
251
+ start: 1616133600,
252
+ stop: 1616135400,
253
+ category: 'Test',
254
+ season: 9,
255
+ episode: 239,
256
+ icon: 'https://example.com/images/Program1.png?x=шеллы&sid=777',
257
+ channel: '1TV.com',
258
+ lang: 'it',
259
+ date: '20220505',
260
+ director: {
261
+ value: 'Director 1',
262
+ url: { value: 'http://example.com/director1.html', system: 'TestSystem' }
263
+ },
264
+ actor: {
265
+ value: 'Actor 1',
266
+ role: 'Manny',
267
+ guest: 'yes',
268
+ url: { value: 'http://example.com/actor1.html', system: 'TestSystem' }
269
+ },
270
+ writer: [
271
+ { value: 'Writer 1', url: { value: 'http://example.com/w1.html', system: 'TestSystem' } },
272
+ { value: 'Writer 2', url: { value: 'http://example.com/w2.html', system: 'TestSystem' } }
273
+ ]
274
+ }
275
+ ]
276
+ const output = utils.convertToXMLTV({ channels, programs })
277
+ expect(output).toBe(
278
+ '<?xml version="1.0" encoding="UTF-8" ?><tv date="20220505">\r\n<channel id="1TV.com"><display-name>1 TV</display-name><icon src="https://example.com/logos/1TV.png"/><url>https://example.com</url></channel>\r\n<channel id="2TV.com"><display-name>2 TV</display-name><url>https://example.com</url></channel>\r\n<programme start="20210319060000 +0000" stop="20210319063000 +0000" channel="1TV.com"><title lang="it">Program 1</title><sub-title>Sub-title 1</sub-title><desc lang="it">Description for Program 1</desc><category lang="it">Test</category><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><date>20220505</date><icon src="https://example.com/images/Program1.png?x=шеллы&amp;sid=777"/><credits><director>Director 1<url system="TestSystem">http://example.com/director1.html</url></director><actor role="Manny" guest="yes">Actor 1<url system="TestSystem">http://example.com/actor1.html</url></actor><writer>Writer 1<url system="TestSystem">http://example.com/w1.html</url></writer><writer>Writer 2<url system="TestSystem">http://example.com/w2.html</url></writer></credits></programme>\r\n</tv>'
165
279
  )
166
280
  })
167
281