Skip to content

Commit

Permalink
Verify xmltv output against the xmltv.dtd (#71)
Browse files Browse the repository at this point in the history
* Verify xmltv output against the xmltv.dtd
  • Loading branch information
michaelarnauts authored Jan 29, 2021
1 parent 201a513 commit 25f6fa0
Show file tree
Hide file tree
Showing 6 changed files with 653 additions and 37 deletions.
2 changes: 1 addition & 1 deletion resources/lib/modules/addon.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ def refresh(cls, show_progress=False):
progress.update(100, kodiutils.localize(30705)) # Updating channels and guide...

IptvSimple.write_playlist(channels)
IptvSimple.write_epg(epg)
IptvSimple.write_epg(epg, channels)

if kodiutils.get_setting_bool('iptv_simple_restart'):
if show_progress:
Expand Down
65 changes: 35 additions & 30 deletions resources/lib/modules/iptvsimple.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ def write_playlist(channels):
os.rename(playlist_path + '.tmp', playlist_path)

@classmethod
def write_epg(cls, epg_list):
def write_epg(cls, epg_list, channels):
"""Write EPG data"""
output_dir = kodiutils.addon_profile()

Expand All @@ -162,18 +162,23 @@ def write_epg(cls, epg_list):
fdesc.write('<!DOCTYPE tv SYSTEM "xmltv.dtd">\n'.encode('utf-8'))
fdesc.write('<tv>\n'.encode('utf-8'))

# Write channel info
for addon in channels:
for channel in addon.get('channels'):
if isinstance(channel, dict) and channel.get('id'):
fdesc.write('<channel id="{id}">\n'.format(id=cls._xml_encode(channel.get('id'))).encode('utf-8'))
fdesc.write(' <display-name>{name}</display-name>\n'.format(name=cls._xml_encode(channel.get('name'))).encode('utf-8'))
if channel.get('logo'):
fdesc.write(' <icon src="{logo}"/>\n'.format(logo=cls._xml_encode(channel.get('logo'))).encode('utf-8'))
fdesc.write('</channel>\n'.encode('utf-8'))

for epg in epg_list:
# RAW XMLTV data
if not isinstance(epg, dict):
fdesc.write(epg.encode('utf-8'))
fdesc.write('\n'.encode('utf-8'))
continue

# JSON-EPG format
# Write channel info
for _, key in enumerate(epg):
fdesc.write('<channel id="{key}"></channel>\n'.format(key=cls._xml_encode(key)).encode('utf-8'))

# Write program info
for _, key in enumerate(epg):
for item in epg[key]:
Expand Down Expand Up @@ -212,34 +217,13 @@ def _construct_epg_program_xml(cls, item, channel):
program += ' <title>{title}</title>\n'.format(
title=cls._xml_encode(title))

if item.get('description'):
program += ' <desc>{description}</desc>\n'.format(
description=cls._xml_encode(item.get('description')))

if item.get('subtitle'):
program += ' <sub-title>{subtitle}</sub-title>\n'.format(
subtitle=cls._xml_encode(item.get('subtitle')))

if item.get('episode'):
program += ' <episode-num system="onscreen">{episode}</episode-num>\n'.format(
episode=cls._xml_encode(item.get('episode')))

if item.get('image'):
program += ' <icon src="{image}"/>\n'.format(
image=cls._xml_encode(item.get('image')))

if item.get('date'):
program += ' <date>{date}</date>\n'.format(
date=cls._xml_encode(item.get('date')))

if item.get('genre'):
if isinstance(item.get('genre'), list):
for genre in item.get('genre'):
program += ' <category>{genre}</category>\n'.format(
genre=cls._xml_encode(genre))
else:
program += ' <category>{genre}</category>\n'.format(
genre=cls._xml_encode(item.get('genre')))
if item.get('description'):
program += ' <desc>{description}</desc>\n'.format(
description=cls._xml_encode(item.get('description')))

if item.get('credits'):
program += ' <credits>\n'
Expand Down Expand Up @@ -283,6 +267,27 @@ def _construct_epg_program_xml(cls, item, channel):

program += ' </credits>\n'

if item.get('date'):
program += ' <date>{date}</date>\n'.format(
date=cls._xml_encode(item.get('date')))

if item.get('genre'):
if isinstance(item.get('genre'), list):
for genre in item.get('genre'):
program += ' <category>{genre}</category>\n'.format(
genre=cls._xml_encode(genre))
else:
program += ' <category>{genre}</category>\n'.format(
genre=cls._xml_encode(item.get('genre')))

if item.get('image'):
program += ' <icon src="{image}"/>\n'.format(
image=cls._xml_encode(item.get('image')))

if item.get('episode'):
program += ' <episode-num system="onscreen">{episode}</episode-num>\n'.format(
episode=cls._xml_encode(item.get('episode')))

program += '</programme>\n'
return program

Expand Down
4 changes: 3 additions & 1 deletion tests/home/addons/plugin.video.example.three/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ def send_epg(): # pylint: disable=no-method-argument
epg = """<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE tv SYSTEM "xmltv.dtd">
<tv source-info-url="http://www.schedulesdirect.org/" source-info-name="Schedules Direct" generator-info-name="XMLTV/$Id: tv_grab_na_dd.in,v 1.70 2008/03/03 15:21:41 rmeden Exp $" generator-info-url="http://www.xmltv.org/">
<channel id="raw1.com"></channel>
<channel id="raw1.com">
<display-name>RAW 1</display-name>
</channel>
<programme start="20210123114255 +0100" stop="20210123121255 +0100" channel="raw1.com" catchup-id="plugin://plugin.video.test/play/raw/1">
<title>RAW 1</title>
<desc>RAW 1 description</desc>
Expand Down
28 changes: 25 additions & 3 deletions tests/home/addons/plugin.video.example/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@
from __future__ import absolute_import, division, print_function, unicode_literals

import datetime
import os
import sys
import tempfile

import dateutil.parser
import dateutil.tz
Expand Down Expand Up @@ -93,7 +91,7 @@ def send_epg(): # pylint: disable=no-method-argument
dict(
start=now.isoformat(),
stop=(now + datetime.timedelta(seconds=1800)).isoformat(),
title='This is a show with an & ampersant.',
title='This is a show with an & ampersand.',
description='This is the description of the show € 4 + 4 > 6',
subtitle='Pilot episode',
genre='Quiz',
Expand Down Expand Up @@ -140,6 +138,30 @@ def send_epg(): # pylint: disable=no-method-argument
image='https://example.com/image.png',
date='1987-06-15',
stream='plugin://plugin.video.example/play/something',
credits=[
{
"type": "director",
"name": "David Benioff"
},
{
"type": "director",
"name": "D.B. Weiss"
},
{
"type": "actor",
"name": "Kit Harington",
"role": "Jon Snow"
},
{
"type": "actor",
"name": "Emilia Clarke",
"role": "Daenerys Targaryen"
},
{
"type": "writer",
"name": "George R.R. Martin"
},
]
)
],
}
Expand Down
8 changes: 6 additions & 2 deletions tests/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import os
import time
import unittest
from xml.etree import ElementTree as etree
import lxml.etree

import xbmc
from mock import patch
Expand Down Expand Up @@ -50,7 +50,11 @@ def test_refresh(self):
self.assertTrue('#KODIPROP:inputstream=inputstream.ffmpegdirect' in data)

# Validate EPG
xml = etree.parse(epg_path)
xml = lxml.etree.parse(epg_path)
validator = lxml.etree.DTD('tests/xmltv.dtd')
self.assertTrue(validator.validate(xml), msg=validator.error_log)

# Verify if it contains the info we expect.
self.assertIsNotNone(xml.find('./channel[@id="channel1.com"]'))
self.assertIsNotNone(xml.find('./channel[@id="één.be"]'))
self.assertIsNotNone(xml.find('./channel[@id="raw1.com"]'))
Expand Down
Loading

0 comments on commit 25f6fa0

Please sign in to comment.