summaryrefslogtreecommitdiff
path: root/plugin.video.debconf/default.py
diff options
context:
space:
mode:
Diffstat (limited to 'plugin.video.debconf/default.py')
-rw-r--r--plugin.video.debconf/default.py315
1 files changed, 315 insertions, 0 deletions
diff --git a/plugin.video.debconf/default.py b/plugin.video.debconf/default.py
new file mode 100644
index 0000000..0671257
--- /dev/null
+++ b/plugin.video.debconf/default.py
@@ -0,0 +1,315 @@
+# -*- coding: UTF-8 -*-
+""" Kan Video plugin """
+
+# Copyright (c) 2017 Tzafrir Cohen
+# SPDX-License-Identifier: GPL-2.0+
+#
+# License-Filename: LICENSE.txt
+
+import datetime
+import re
+import sys
+import urllib
+import urlparse
+from bs4 import BeautifulSoup
+import requests
+import simplecache
+import xbmc
+import xbmcgui
+import xbmcplugin
+import yaml
+
+
+USER_AGENT = 'Kodi/plugin.video.debconf/1.0'
+PLUGIN_NAME = "plugin.video.debconf" # FIXME: a better way to get this?
+URL_BASE = 'https://salsa.debian.org/debconf-video-team/archive-meta/'
+
+
+class DummyCache:
+ """ Dummy replacement of SimpleCache """
+ def __init__(self):
+ pass
+
+ def get(self, cache_id, checksum=''):
+ return None
+
+ def set(self, cache_id, data, checksum='', expiration=None):
+ pass
+
+
+#SiteCache = simplecache.SimpleCache() # FIXME: avoid this global
+SiteCache = DummyCache() # Avoid caching for now
+
+
+class Page:
+ def __init__(self, argv):
+ self.base_url = argv[0]
+ self.addon_handle = int(argv[1])
+ self.args = urlparse.parse_qs(argv[2][1:])
+ self.mode = self.first_arg('mode')
+
+ def first_arg(self, name):
+ val = self.args.get(name, None)
+ if val is not None:
+ val = val[0]
+ return val
+
+ def build_url(self, query):
+ return self.base_url + '?' + urllib.urlencode(query)
+
+ def add_directory_item(self, **kwargs):
+ xbmcplugin.addDirectoryItem(handle=self.addon_handle, **kwargs)
+
+ def end_directory(self):
+ xbmcplugin.endOfDirectory(self.addon_handle)
+
+ def placeholder_folder(self, foldername):
+ """ A folder with a single dummy item """
+ url = 'http://localhost/some_video.mkv'
+ li = xbmcgui.ListItem(foldername + 'Not Implemented',
+ iconImage='DefaultVideo.png')
+ self.add_directory_item(url=url, listitem=li)
+ self.end_directory()
+
+ def __str__(self):
+ return "[base_url: {}, addon_handle: {}, mode: {}]" \
+ .format(self.base_url, self.addon_handle, self.mode)
+
+
+def read_url(url, name):
+ cache_id = PLUGIN_NAME + ".url." + name
+ url_text = SiteCache.get(cache_id)
+ if url_text:
+ return url_text
+
+ headers = {'user-agent': USER_AGENT}
+ response = requests.get(url, headers=headers)
+ if response.status_code != 200:
+ raise IOError("Invalid URL {}".format(url))
+
+ url_text = response.text
+ SiteCache.set(cache_id, url_text, expiration=datetime.timedelta(days=1))
+ return url_text
+
+
+def debconf_year_menu(page, year):
+ """ Show a menu of all videos from debconf of a specific year """
+ year_short = str(int(year) - 2000)
+ url = URL_BASE + 'raw/master/metadata/{}/debconf{}.yml'.format(year,
+ year_short)
+ metadata_text = read_url(url, year)
+ metadata = yaml.load(metadata_text)
+ base_url = metadata['conference']['video_base']
+ for video in metadata['videos']:
+ description = ''
+ if 'description' in video:
+ description = video['description']
+ title = video['title']
+ url = base_url + video['video']
+ li = xbmcgui.ListItem(title)
+ li.setProperty('IsPlayable', 'true')
+ li.setInfo('video', {
+ 'plot': description,
+ 'title': title,
+ 'year': year,
+ })
+ page.add_directory_item(url=url, listitem=li)
+ page.end_directory()
+
+
+def debconf_menu(page):
+ """ Present a list of pages of debconfs for each year """
+ # Figure the list of years:
+ index_url = URL_BASE + 'tree/master/metadata'
+ index_text = read_url(index_url, 'index')
+ parsed = BeautifulSoup(index_text, "html.parser")
+ anchors = parsed.find_all('a', class_='str-truncated')
+ years = [a.get('title') for a in anchors]
+
+ # Create a menu:
+ for year in years:
+ url = page.build_url({'mode': 'debconf_year', 'year': year})
+ li = xbmcgui.ListItem(u'Debconf Year ' + str(year))
+ page.add_directory_item(url=url, listitem=li, isFolder=True)
+ page.end_directory()
+
+
+VIDEO_SUFFIXES = ['avi', 'AVI', 'flv', 'mkv', 'mp4', 'mpeg', 'ogv', 'webm']
+VDNET_BASE = 'https://meetings-archive.debian.net/pub/debian-meetings/'
+
+
+def parse_vdnet_list(text):
+ """ Parses the full directory listing into a tree of videos """
+ data = {}
+ for line in text.split('\n'):
+ line = line.encode('utf-8')
+ items = line.rsplit('.', 1)
+ if len(items) < 2 or items[1] not in VIDEO_SUFFIXES:
+ continue
+ items = line.split('/')
+ year = items[0]
+ conference = items[1]
+ # More standard names:
+ for conf_name in ['debconf', 'dudesconf', 'fosdem', 'GPN']:
+ if re.match('^{}\d*$'.format(conf_name), conference):
+ conference = conf_name
+ if conference == 'ducc-it':
+ conference = 'ducc.it' # Right?
+ conference = re.sub('mini(|-?deb)conf', 'mini-debconf',
+ conference)
+ name = items[-1].split('.')[0]
+ quality = " ".join(items[2:-1])
+ if quality == '':
+ quality = 'default'
+ if 'year' not in data:
+ data['year'] = {}
+ if year not in data['year']:
+ data['year'][year] = {}
+ if conference not in data['year'][year]:
+ data['year'][year][conference] = {}
+ if name not in data['year'][year][conference]:
+ data['year'][year][conference][name] = {}
+ if quality not in data['year'][year][conference][name]:
+ data['year'][year][conference][name][quality] = {}
+ data['year'][year][conference][name][quality] = line
+ if 'conf' not in data:
+ data['conf'] = {}
+ if conference not in data['conf']:
+ data['conf'][conference] = {}
+ if year not in data['conf'][conference]:
+ data['conf'][conference][year] = {}
+ if name not in data['conf'][conference][year]:
+ data['conf'][conference][year][name] = {}
+ if quality not in data['conf'][conference][year][name]:
+ data['conf'][conference][year][name][quality] = {}
+ data['conf'][conference][year][name][quality] = line
+ return data
+
+
+def get_vdnet_data():
+ """ Download and parse the index data.
+
+ There should be no issue calling it many times as its output is cases.
+ """
+ index_url = URL_BASE + 'raw/master/data/video.debian.net.list'
+ # FIXME: no point in caching this download:
+ cache_id = PLUGIN_NAME + '.vdunet_data'
+ vdnet_data = SiteCache.get(cache_id)
+ if vdnet_data:
+ return vdnet_data
+
+ index_text = read_url(index_url, 'index')
+ vdnet_data = parse_vdnet_list(index_text)
+ SiteCache.set(cache_id, vdnet_data, expiration=datetime.timedelta(days=1))
+ return vdnet_data
+
+
+def vdnet_video_menu(page, conf, year, name):
+ """ The actual v.d.net video page. Shows the video in various qualities """
+ vdnet_data = get_vdnet_data()
+ for quality in sorted(vdnet_data['year'][year][conf][name].keys()):
+ title = '{} ({})'.format(name, quality)
+ url = VDNET_BASE + vdnet_data['year'][year][conf][name][quality]
+ li = xbmcgui.ListItem(title)
+ li.setProperty('IsPlayable', 'true')
+ li.setInfo('video', {
+ 'title': name,
+ 'year': year,
+ })
+ page.add_directory_item(url=url, listitem=li)
+ page.end_directory()
+
+
+def vdnet_confyear_menu(page, conf, year):
+ vdnet_data = get_vdnet_data()
+ for name in sorted(vdnet_data['year'][year][conf].keys()):
+ url = page.build_url({'mode': 'vdnet_video',
+ 'year': year, 'conf': conf, 'name': name})
+ li = xbmcgui.ListItem(name)
+ page.add_directory_item(url=url, listitem=li, isFolder=True)
+ page.end_directory()
+
+
+def vdnet_year_menu(page, year):
+ vdnet_data = get_vdnet_data()
+ for conf in sorted(vdnet_data['year'][year].keys()):
+ url = page.build_url({'mode': 'vdnet_confyear',
+ 'year': year, 'conf': conf})
+ li = xbmcgui.ListItem(conf)
+ page.add_directory_item(url=url, listitem=li, isFolder=True)
+ page.end_directory()
+
+
+def vdnet_conf_menu(page, conf):
+ vdnet_data = get_vdnet_data()
+ for year in sorted(vdnet_data['conf'][conf].keys()):
+ url = page.build_url({'mode': 'vdnet_confyear',
+ 'year': year, 'conf': conf})
+ li = xbmcgui.ListItem(year)
+ page.add_directory_item(url=url, listitem=li, isFolder=True)
+ page.end_directory()
+
+
+def vdnet_menu(page):
+ """ Videos from video.debian.net
+
+ (https://meetings-archive.debian.net/pub/debian-meetings/)
+ """
+ vdnet_data = get_vdnet_data()
+ for conf in sorted(vdnet_data['conf'].keys()):
+ url = page.build_url({'mode': 'vdnet_conf', 'conf': conf})
+ li = xbmcgui.ListItem(u'Conference ' + conf)
+ page.add_directory_item(url=url, listitem=li, isFolder=True)
+ for year in sorted(vdnet_data['year'].keys()):
+ url = page.build_url({'mode': 'vdnet_year', 'year': year})
+ li = xbmcgui.ListItem(u'Conferences in ' + str(year))
+ page.add_directory_item(url=url, listitem=li, isFolder=True)
+ page.end_directory()
+
+
+def main_menu(page):
+ """ Top-level menu """
+ url = page.build_url({'mode': 'debconf'})
+ li = xbmcgui.ListItem(u'Debconf Talks (full information)')
+ page.add_directory_item(url=url, listitem=li, isFolder=True)
+
+ url = page.build_url({'mode': 'vdnet'})
+ li = xbmcgui.ListItem(u'All video.debian.net Videos (just names)')
+ page.add_directory_item(url=url, listitem=li, isFolder=True)
+ page.end_directory()
+
+
+def main():
+ """ Select handler for the page """
+ page = Page(sys.argv)
+
+ if page.mode is None:
+ main_menu(page)
+ elif page.mode == 'debconf':
+ debconf_menu(page)
+ elif page.mode == 'debconf_year':
+ year = page.args['year'][0]
+ debconf_year_menu(page, year)
+ elif page.mode == 'vdnet':
+ vdnet_menu(page)
+ elif page.mode == 'vdnet_year':
+ year = page.args['year'][0]
+ vdnet_year_menu(page, year)
+ elif page.mode == 'vdnet_conf':
+ conf = page.args['conf'][0]
+ vdnet_conf_menu(page, conf)
+ elif page.mode == 'vdnet_confyear':
+ conf = page.args['conf'][0]
+ year = page.args['year'][0]
+ vdnet_confyear_menu(page, conf, year)
+ elif page.mode == 'vdnet_video':
+ conf = page.args['conf'][0]
+ year = page.args['year'][0]
+ name = page.args['name'][0]
+ vdnet_video_menu(page, conf, year, name)
+ else:
+ page.placeholder_folder("no page for mode " + page.mode)
+
+
+if __name__ == '__main__':
+ main()