-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathparser.py
executable file
·177 lines (153 loc) · 6.49 KB
/
parser.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# vim: set expandtab tabstop=4 shiftwidth=4 :
# parser.py - Module for kodi_bot.py that implements the command parser.
# By: The Doctor <drwho at virtadpt dot net>
# 0x807B17C1 / 7960 1CDC 85C9 0B63 8D9F DD89 3BD8 FF2B 807B 17C1
# License: GPLv3
# v2.1 - Changed logger.warn() to logger.warning().
# v2.0 - Ported to Python 3.
# v1.0 - Initial release.
# TO-DO:
# - Make the online help interactive and subject based ("help video," "help
# music.")
# - Normalize the corpora to lowercase at load-time.
# Load modules.
import logging
import os
import sys
from fuzzywuzzy import fuzz
# Commands to support:
# * List all known media sources: video, pictures, music
# This is probably a helper method: The output will be used by other methods.
# Do this on startup and cache the results.
# Files.GetSources()
# - List a particular media source: video, pictures, music
# Only support media sources found earlier/above.
# * Search the Kodi box's library:
# "Do I have any music by...?"
# "Do I have any videos with...?"
# "Do I have a song named...?"
# - List known playlists.
# This is probably going to be a utility method.
# Playlist.GetPlaylists()
# - Trigger playback of an existing playlist.
# Use cached list of playlists?
# Playlist.GetItems()
# * See if Kodi is online. Maybe do this periodically?
# JSONRPC.Ping()
# - Ask what version of the JSON-RPC API is being used on the Kodi box.
# JSONRPC.Version()
# - Display a message on the Kodi box's screen.
# GUI.ShowNotification()
# - Shut down Kodi.
# System.Shutdown()
# - Restart Kodi.
# System.Reboot()
# * Find out if something is playing, and if so what it is (video, audio).
# This is a helper method.
# Player.GetActivePlayers()
# * Build a list of usable players in the Kodi instance.
# Players.GetPlayers()
# Cache the output. Need both the type and the ID code, because Kodi uses
# the ID codes as identifiers.
# - Initiate/terminate party mode.
# Player.SetPartymode([ID number of audio player], [True, False])
# * Pause/unpause playback.
# Player.PlayPause([0, 1])
# - "What is currently playing?"
# "What's currently playing?"
# Find out what is currently playing with Player.GetActivePlayers(). Then,
# Player.GetItem([ID code of running player])
# * Figure out how to specify, in broad strokes, what you want to listen to.
# "Play the sisters of mercy."
# "Play information society."
# "Play an episode of foo."
# "Play some art bell."
# "Play Floodland."
# "Shuffle Floodland."
# The media type/filetype will be used to pick the media player type to use.
# Get a list of artists with AudioLibrary.GetArtists()
# ...map what the user is asking for to the contents of that list. This might
# need to be a fuzzy match. I don't yet know how to do this.
# * Stop what is currently playing.
# Find out what is currently playing with Player.GetActivePlayers(). Then,
# Player.Stop([ID code of running player])
# - Update Kodi's databases.
# [Audio,Video]Library.Scan()
# Then rebuild the bot's internal indices?
# -
#
# What the parser returns:
# - No match? None.
# - Match?
# parsed_command = {}
# parsed_command["confidence"] = confidence in match
# parsed_command[""]
# load_corpora(): Trains the command parser on a list of corpora. Takes two
# arguments, a string that points to a directory full of text corpora, and a
# hash of command types. Returns a hash of command types containing the
# corpora to match against.
def load_corpora(corpora_dir, command_types):
logging.debug("Entered parser.load_corpora().")
command_type = ""
corpora_dir = os.path.abspath(corpora_dir)
for filename in os.listdir(corpora_dir):
logging.debug("Looking at corpus file %s." % filename)
# Generate the key for the hash.
command_type = filename.strip(".txt")
# Test the length of the corpus file. If it's 0, then skip the command
# class.
filename = os.path.join(corpora_dir, filename)
if not os.path.getsize(filename):
logging.warning("Corpus filename %s has a length of zero bytes. Skipping this command class." % filename)
command_types.pop(command_type)
continue
# Read the contents of the corpus file in.
try:
with open(filename, "r") as file:
command_types[command_type] = file.read().splitlines()
except:
logging.warning("Unable to open filename %s. Skipping command class." % os.path.join(corpora_dir, filename))
command_types.pop(command_type)
logging.debug("Recognized command types: %s" % str(list(command_types.keys())))
return command_types
# parse(): Function that parses commands from the message bus. Takes two args,
# the command to parse and a hash containing corpora to match against.
# A hash table with the best match is returned as a match or None on no match.
def parse(command, possible_commands):
logging.debug("Entered parser.parse().")
parsed_command = {}
parsed_command["confidence"] = 0
parsed_command["match"] = ""
# Clean up the incoming command.
command = command.strip()
command = command.strip('.')
command = command.strip(',')
command = command.strip('!')
command = command.strip('?')
command = command.lower()
# If the get request is empty (i.e., nothing in the queue), bounce.
if "no commands" in command:
return None
# Walk through the command corpora and see which one most closely matches.
for command_type in list(possible_commands.keys()):
logging.debug("Now matching against command class %s." % command_type)
for i in possible_commands[command_type]:
tmp = fuzz.token_sort_ratio(command, i.lower())
if tmp > parsed_command["confidence"]:
logging.debug("Replacing match with one of a higher confidence:")
logging.debug("New match: %s" % i)
logging.debug("Confidence of new match: %d" % tmp)
parsed_command["confidence"] = tmp
parsed_command["match"] = command_type
parsed_command["corpus"] = i
# Short-circuit evaluation by bouncing if we reach maximum
# confidence.
if parsed_command["confidence"] == 100:
logging.debug("Got a perfect match. No need to keep searching.")
return parsed_command
# Return whatever match we've got.
return parsed_command
if "__name__" == "__main__":
pass