Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
petegallagher committed Jan 13, 2019
0 parents commit 566d4e0
Show file tree
Hide file tree
Showing 9 changed files with 989 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.vscode
tmp

674 changes: 674 additions & 0 deletions LICENSE.txt

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
plugin.video.zoneminder
=======================

- ZoneMinder plugin for Kodi 17+
- View live stream of cameras
- Requires Zoneminder API
- Supports username/password authentication
22 changes: 22 additions & 0 deletions addon.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="plugin.video.zoneminder" name="ZoneMinder" version="2.0.0" provider-name="Peter Gallagher">
<requires>
<import addon="xbmc.python" version="2.19.0"/>
<import addon="script.module.requests" version="2.3.0"/>
<import addon="script.module.simplejson" version="2.0.0"/>
</requires>
<extension point="xbmc.python.pluginsource" library="zoneminder.py">
<provides>video</provides>
</extension>
<extension point="xbmc.addon.metadata">
<platform>all</platform>
<summary lang="en_GB">View ZoneMinder video surveillance system</summary>
<description lang="en_GB">View the live stream of cameras via the Zoneminder video surveillance system.</description>
<license>GPL-3.0</license>
<source>https://github.com/petegallagher/plugin.video.zoneminder</source>
<assets>
<icon>resources/icon.png</icon>
<fanart>resources/fanart.jpg</fanart>
</assets>
</extension>
</addon>
Binary file added resources/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 resources/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
73 changes: 73 additions & 0 deletions resources/language/resource.language.en_gb/strings.po
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Kodi Media Center language file
# Addon Name: Zoneminder
# Addon id: plugin.video.zoneminder
# Addon Provider: Peter Gallagher
msgid ""
msgstr ""
"Project-Id-Version: Zoneminder\n"
"Report-Msgid-Bugs-To: https://github.com/petegallagher/plugin.video.zoneminder/issues/\n"
"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: en_GB\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#
# Configuration Options
#
msgctxt "#31000"
msgid "Connection"
msgstr ""

msgctxt "#31001"
msgid "Base URL"
msgstr ""

msgctxt "#31002"
msgid "Auth Enabled"
msgstr ""

msgctxt "#31003"
msgid "Username"
msgstr ""

msgctxt "#31004"
msgid "Password"
msgstr ""

msgctxt "#31005"
msgid "Frames per second (max)"
msgstr ""

msgctxt "#31006"
msgid "Default View"
msgstr ""

#
# Interface Strings
#
msgctxt "#32000"
msgid "Events"
msgstr ""

msgctxt "#32001"
msgid "Monitors"
msgstr ""

msgctxt "#32002"
msgid "Montage"
msgstr ""


#
# Error Messages
#
msgctxt "#33000"
msgid "Zoneminder Connection Error"
msgstr ""

msgctxt "#33001"
msgid "Login failed"
msgstr ""
10 changes: 10 additions & 0 deletions resources/settings.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<settings>
<category label="31000">
<setting id="base_url" type="text" label="31001" default="http://zmhost:8080/zm"/>
<setting id="is_authenticated" type="bool" label="31002" default="false"/>
<setting id="username" type="text" label="31003" default="admin" enable="eq(-1,true)"/>
<setting id="password" type="text" label="31004" default="admin" enable="eq(-2,true)" option="hidden" />
<setting id="fps" type="labelenum" label="31005" default="30" values="1|2|5|10|15|20|25|30|50"/>
<!-- <setting id="default_view" type="labelenum" label="31006" lvalues="32000|32001|32002"/> -->
</category>
</settings>
200 changes: 200 additions & 0 deletions zoneminder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
# -*- coding: utf-8 -*-
# Module: zoneminder
# Author: Peter Gallagher
# Created on: 2019-01-12
# License: GPL v.3 https://www.gnu.org/copyleft/gpl.html

import sys
from urllib import urlencode
from urlparse import parse_qsl
# Python3 equivalent
# from urllib.parse import quote, parse_qsl
import xbmcgui
import xbmcplugin
import xbmcaddon
import requests
import simplejson as json

# Global vars
# Get the plugin url in plugin:// notation.
_url = sys.argv[0]
# Get the plugin handle as an integer number.
_handle = int(sys.argv[1])
_addon = xbmcaddon.Addon()
_language = _addon.getLocalizedString
_base_url = _addon.getSetting('base_url').strip()
_auth_token = None
_auth_cookies = None

def get_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
"""
# Python3 equivalent
# return '{0}?{1}'.format(_url, quote(kwargs))
return '{0}?{1}'.format(_url, urlencode(kwargs))

def play_video(path):
"""
Play a video by the provided path.
:param path: Fully-qualified video URL
:type path: str
"""
# Create a playable item with a path to play.
play_item = xbmcgui.ListItem(path=path)
# Pass the item to the Kodi player.
xbmcplugin.setResolvedUrl(_handle, True, listitem=play_item)

def error_message(message, title='Error'):
sys.stderr.write(message)
xbmcgui.Dialog().ok(title, message)

def login ():
login_url = "/".join([_addon.getSetting('base_url').strip(), "api/host/login.json"])
creds = {
'user': _addon.getSetting('username').strip(),
'pass': _addon.getSetting('password').strip()
}
try:
r = requests.post(login_url, data=creds)
except requests.exceptions.RequestException as e:
error_message(title=_language(33000), message=str(e))

if r.status_code != 200:
# Login failed
error_message(title=_language(33001), message='{}: {}'.format(r.status_code, r.reason))

j = json.loads(r.text)

auth_token = j['credentials']
auth_cookies = r.cookies
return auth_token, auth_cookies

def get_active_monitors ():
# Get monitors from Zoneminder API
monitors_url = '{base_url}/api/monitors.json'.format(base_url=_base_url)
r = requests.get(monitors_url, cookies=_auth_cookies)
# Parse JSON response
j = json.loads(r.text)

active_monitors = []
for monitor in j['monitors']:
monitor = monitor['Monitor']

if monitor['Enabled'] != '1':
# If monitor is disabled in Zoneminder we don't want to display it so move to next item in array.
continue

active_monitor = dict()
active_monitor['id'] = monitor['Id']
active_monitor['name'] = monitor['Name']
active_monitor['video'] = '{base_url}/cgi-bin/nph-zms?scale=auto&width={width}&height={height}&mode=jpeg&maxfps={fps}&monitor={monitor_id}&{auth}'.format(
base_url=_base_url,
width=monitor['Width'],
height=monitor['Height'],
fps=_addon.getSetting('fps'),
monitor_id=monitor['Id'],
auth=_auth_token
)

active_monitor['thumb'] = '{base_url}/cgi-bin/nph-zms?scale=auto&width={width}&height={height}&mode=single&maxfps={fps}&monitor={monitor_id}&{auth}'.format(
base_url=_addon.getSetting('base_url'),
width=monitor['Width'],
height=monitor['Height'],
fps=_addon.getSetting('fps'),
monitor_id=monitor['Id'],
auth=_auth_token
)

active_monitors.append(active_monitor)

return active_monitors

def list_monitors():
"""
Create the list of playable videos in the Kodi interface.
:param category: Category name
:type category: str
"""
# Set plugin category. It is displayed in some skins as the name
# of the current section.
xbmcplugin.setPluginCategory(_handle, _language(32001))
# 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.
videos = get_active_monitors()
# Iterate through videos.
for video in videos:
# Create a list item with a text label and a thumbnail image.
list_item = xbmcgui.ListItem(label=video['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': video['name'],
'mediatype': 'video'})
# Set graphics (thumbnail, fanart, banner, poster, landscape etc.) for the list item.
# Here we use the same image for all items for simplicity's sake.
# In a real-life plugin you need to set each image accordingly.
list_item.setArt({'thumb': video['thumb'], 'icon': video['thumb'], 'fanart': video['thumb']})
# Set 'IsPlayable' property to 'true'.
# This is mandatory for playable items!
list_item.setProperty('IsPlayable', 'true')
# Create a URL for a plugin recursive call.
# Example: plugin://plugin.video.example/?action=play&video=http://www.vidsplay.com/wp-content/uploads/2017/04/crab.mp4
url = get_url(action='play', video=video['video'])
# Add the list item to a virtual Kodi folder.
# is_folder = False means that this item won't open any sub-list.
is_folder = False
# 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_LABEL_IGNORE_THE)
# Finish creating a virtual folder.
xbmcplugin.endOfDirectory(_handle)

def router(paramstring):
"""
Router function that calls other functions
depending on the provided paramstring
:param paramstring: URL encoded plugin paramstring
:type paramstring: str
"""
# Log into Zoneminder
global _auth_token, _auth_cookies
_auth_token, _auth_cookies = login()

# Parse a URL-encoded paramstring to the dictionary of
# {<parameter>: <value>} elements
params = dict(parse_qsl(paramstring))

# Check the parameters passed to the plugin
if params:
if params['action'] == 'listing':
# Display the list of videos in a provided category.
list_videos(params['category'])
elif params['action'] == 'play':
# Play a video from a provided URL.
play_video(params['video'])
else:
# If the provided paramstring does not contain a supported action
# we raise an exception. This helps to catch coding errors,
# e.g. typos in action names.
raise ValueError('Invalid paramstring: {0}!'.format(paramstring))
else:
# If the plugin is called from Kodi UI without any parameters,
# display the list of video categories
list_monitors()


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:])

0 comments on commit 566d4e0

Please sign in to comment.