Skip to content

Commit

Permalink
Initial release
Browse files Browse the repository at this point in the history
  • Loading branch information
bneijt committed Dec 30, 2018
0 parents commit b927552
Show file tree
Hide file tree
Showing 14 changed files with 308 additions and 0 deletions.
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/.idea
/build
/*.egg-info
/.pytest_cache
__pycache__
/venv
34 changes: 34 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@

SOURCES=$(shell find . -name *.py)
OUTPUT_PATH=build/plugin.video.ipfs

venv:
( \
virtualenv --python=python3.7 venv
source venv/bin/activate; \
pip install -r requirements.txt; \
)

test: install venv $(SOURCES)
venv/bin/py.test

install:
venv/bin/python setup.py develop

clean:
rm -rf build

build/plugin_video_ipfs.zip: build
cd build && zip -r plugin_video_ipfs.zip plugin.video.ipfs

build: $(SOURCES) fanart.jpg icon.png addon.xml
mkdir -p $(OUTPUT_PATH)/ipfs
cp -r src/*.py $(OUTPUT_PATH)
cp -r src/ipfs/*.py $(OUTPUT_PATH)/ipfs
cp -r resources $(OUTPUT_PATH)
cp icon.png $(OUTPUT_PATH)
cp addon.xml $(OUTPUT_PATH)
cp fanart.jpg $(OUTPUT_PATH)

package: build/plugin_video_ipfs.zip
unzip -t build/plugin_video_ipfs.zip
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# IPFS video plugin for Kodi

Plugin to browse and play video media from IPFS.

It has a configurable gateway and a configurable root CID that you can browse.

# Build from source

- `make package`
- Copy the `build/plugin_video_ipfs.zip`

# Viewing your own content

- Install IPFS on your media player
- Use `ipfs add -r -w yourdirectory` to insert your directory
- Configure plugin to have gateway point to `http://localhost:8080`
- Use the hash of the directory as the root CID in the plugin configuration

# Develop

- `make venv`
- `make test`
- `make build`

# License information

Source code license: [GPL v.3](http://www.gnu.org/copyleft/gpl.html)

Fanart is from https://pixabay.com/nl/beaded-spinneweb-raagbol-web-dauw-1630493/

Icon is from https://pixabay.com/nl/puzzel-spel-kubus-rubiks-kubus-1243091/
23 changes: 23 additions & 0 deletions addon.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<addon id="plugin.video.ipfs"
version="0.0.2"
name="IPFS"
provider-name="ipfs.video">
<requires>
<import addon="xbmc.python" version="2.25.0"/>
<import addon="script.module.requests" version="2.19.1"/>
</requires>
<extension point="xbmc.python.pluginsource" library="main.py">
<provides>video</provides>
</extension>
<extension point="xbmc.addon.metadata">
<summary lang="en">IPFS video viewing plugin</summary>
<description lang="en_GB">A plugin allowing access to IPFS video media.</description>
<website>https://ipfs.video/</website>
<assets>
<icon>icon.png</icon>
<fanart>fanart.jpg</fanart>
<screenshot>resources\screenshot-01.jpg</screenshot>
</assets>
</extension>
</addon>
Binary file added fanart.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

requests

pytest
25 changes: 25 additions & 0 deletions resources/language/resource.language.en_gb/strings.po
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Kodi Media Center language file
# Addon Name: IPFS
# Addon id: plugin.video.ipfs
# Addon Provider: ipfs.video
msgid ""
msgstr ""
"Project-Id-Version: Kodi Addons\n"
"Report-Msgid-Bugs-To: [email protected]\n"
"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Kodi Translation Team\n"
"Language-Team: English (http://www.transifex.com/projects/p/xbmc-addons/language/en/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: en\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

msgctxt "#32000"
msgid "Root CID"
msgstr ""

msgctxt "#32001"
msgid "IPFS Gateway"
msgstr ""
Binary file added resources/screenshot-01.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions resources/settings.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<settings>
<category label="5">
<setting label="32000" type="text" id="rootCid" default="QmR4Ji6iTa8LeVXZLSgWwEao3jdnyj7yFTaANKQuqoVEBw" />
<setting label="32001" type="text" id="ipfsGateway" default="https://ipfs.io" />
</category>
</settings>
11 changes: 11 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from setuptools import setup

setup(name='ipfs-video-kodi',
version='0.1',
description='The funniest joke in the world',
url='',
author='Flying Circus',
author_email='[email protected]',
license='MIT',
packages=["src"],
zip_safe=False)
37 changes: 37 additions & 0 deletions src/ipfs/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@

import requests
import random

def via(gateway):
return IPFS(gateway)

class IPFS:
def __init__(self, gateway):
assert len(gateway) > 0
self._gateway = gateway
self._cache = {}

def get(self, path, params):
r = requests.get(self._gateway + '/api/v0/dag/get', params=params)
r.raise_for_status()
return r

def list(self, hash):
"""Get the directory content of the given hash"""
assert type(hash) == str
if hash in self._cache:
if len(self._cache) > 50:
#Drop 10 keys
for k in random.sample(self._cache.keys(), 10):
del self._cache[k]
return self._cache[hash]

r = self.get('/api/v0/dag/get', params={"arg": hash})
r.raise_for_status()

entries = list(filter(lambda link: len(link['Name']) > 0 and '/' in link['Cid'], r.json()["links"]))
self._cache[hash] = entries
return entries

def link(self, hash):
return self._gateway + "/ipfs/" + hash
108 changes: 108 additions & 0 deletions src/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# -*- coding: utf-8 -*-
import sys

try:
#Python 3
from urllib.parse import urlencode, parse_qsl
except ImportError:
from urllib import urlencode
from urlparse import parse_qsl

import xbmcgui
import xbmcplugin
import ipfs

# Get the plugin url in plugin:// notation.
_url = sys.argv[0]
# Get the plugin handle as an integer number.
_handle = int(sys.argv[1])
_rootCid = xbmcplugin.getSetting(_handle, 'rootCid')
_ipfs = ipfs.via(xbmcplugin.getSetting(_handle, 'ipfsGateway'))

def self_url(**kwargs):
"""
Create a URL for calling the plugin recursively from the given set of keyword arguments.
:param kwargs: "argument=value" pairs
:type kwargs: dict
:return: plugin call URL
:rtype: str
"""
return '{0}?{1}'.format(_url, urlencode(kwargs))

def list_node(cid):
"""
Create a listing of the given ipfs cid
:param cid: content identifier
:type category: str
"""
# Set plugin category. It is displayed in some skins as the name
# of the current section.
xbmcplugin.setPluginCategory(_handle, cid)
# Set plugin content. It allows Kodi to select appropriate views
# for this type of content.
xbmcplugin.setContent(_handle, 'videos')
# Get the list of videos in the category.
links = _ipfs.list(cid)

for link in links:
is_folder = len(_ipfs.list(link['Cid']['/'])) > 0

list_item = xbmcgui.ListItem(label=link['Name'])
# Set additional info for the list item.
# 'mediatype' is needed for skin to display info for this ListItem correctly.
list_item.setInfo('video', {'title': link['Name'],
'mediatype': 'video'})
# TODO set thumbnails
# list_item.setArt({'thumb': video['thumb'], 'icon': video['thumb'], 'fanart': video['thumb']})

list_item.setProperty('IsPlayable', ('false' if is_folder else 'true'))

url = self_url(action=('list' if is_folder else 'play'), cid=link['Cid']['/'])
# Add our item to the Kodi virtual folder listing.
xbmcplugin.addDirectoryItem(_handle, url, list_item, is_folder)
# Add a sort method for the virtual folder items (alphabetically, ignore articles)
xbmcplugin.addSortMethod(_handle, xbmcplugin.SORT_METHOD_TITLE)
xbmcplugin.endOfDirectory(_handle)


def play_node(cid):
"""
Play a video by the provided cid.
:param cid: Content id
:type path: str
"""
play_item = xbmcgui.ListItem(path=_ipfs.link(cid))
xbmcplugin.setResolvedUrl(_handle, True, listitem=play_item)


def router(paramstring):
"""
Router function that calls other functions
depending on the provided paramstring
:param paramstring: URL encoded plugin paramstring
:type paramstring: str
"""
params = dict(parse_qsl(paramstring))

#Default action
if not params:
params['action'] = 'list'
params['cid'] = _rootCid

# Check the parameters passed to the plugin
if params['action'] == 'list':
list_node(params['cid'])
elif params['action'] == 'play':
play_node(params['cid'])
else:
raise ValueError('Invalid paramstring: {0}!'.format(paramstring))


if __name__ == '__main__':
# Call the router function and pass the plugin call parameters to it.
# We use string slicing to trim the leading '?' from the plugin call paramstring
router(sys.argv[2][1:])
22 changes: 22 additions & 0 deletions test/test_ipfs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
import unittest
import src.ipfs as ipfs

test_gateway = ipfs.via("http://51.15.122.1")


class TestIpfsMethods(unittest.TestCase):

def test_list_file_should_be_empty(self):
a = test_gateway.list("QmTNdv6MBhCjcGY5tpabi7aCeLZL65tmDzW37J9ZrFbZfL")
self.assertEqual(a, [])

def test_list_directory_should_work(self):
a = test_gateway.list("QmVZV84e6nSwfA8LppiS4KXKiAhpbqqGYzofHtecQjd9js")
self.assertEqual(len(a), 1)
self.assertEqual(a[0]['Name'], "pexel")



if __name__ == '__main__':
unittest.main()

0 comments on commit b927552

Please sign in to comment.