1
0
Fork 0
mirror of https://github.com/jellyfin/jellyfin-web synced 2025-03-30 19:56:21 +00:00
This commit is contained in:
Luke Pulverenti 2017-01-27 18:07:14 -05:00
parent 82bcca376f
commit 8a6884abef
494 changed files with 256 additions and 120180 deletions

View file

@ -1,177 +0,0 @@
#Emby Voice commands
Emby voice commands use json and regular expression to find corresponding commands.
With this solution the translation to other languages will be simplified and is fully compatible with regular expression for many programming languages.
###Json template :
```json
[
{
"group": "general",
"name": "General commands",
"defaultValues": {
"sourceid": "",
"deviceid": "",
"itemName": "",
"itemType": "",
"shuffle": false,
"filters": [],
"sortBy": "",
"sortOrder": "",
"limit": 100,
"category": ""
},
"items": [
{
"actionid": "show",
"sourceid": "movies",
"menuid" : "home",
"groupid" : "movie",
"deviceid": "displaymirroring",
"command": "(?<action>play|Listen to)\\s?(?<determiner1>my|me)?\\s?(?<source> music)\\s?(?<ArtistName>.*)?\\s?(?<deviceaction>on device|to device)\\s?(?<Devicename>.*)",
"altcommand": "(?<action>play|Listen to)\\s?(?<determiner1>my|me)?\\s?(?<source> music)\\s?(?<ArtistName>.*)?",
"itemName": "",
"itemType": "movie",
"shuffle": false,
"filters": [ ],
"sortBy": "",
"sortOrder": "Ascending",
"limit": 100,
"category": "",
"commandtemplates": [
"Show Movie based commands",
"Show Music based commands",
"Show Picture based commands",
"Show TV series based commands",
"Show general commands"
]
}
]
}
]
```
###Json hierarchy
>+ Group
- defaultValues
- Items
+ commandtemplates
###Json Description :
**Group**
>**groupid** : (mandatory) id of the group commands
>**name** : (mandatory) name of the group
**Items and defaultValues (mandatory)**
**actionid** : (mandatory) Liste of actions available
>- show
- play
- shuffle
- search
- control
- enable
- disable
- toggle
**sourceid** : (optional) source commands available
>- music
- movies
- tvseries
- livetv
- recordings
- latestepisodes
- home
- group
**menuid** : (optional) menu commands available
>- Commands for live TV
- livetv
- guide
- channels
- recordings
- scheduled
- series
- group
>- Commands for home menus
- home
- nextup
- favorites
- upcoming
- nowplaying
**groupid** : (optional) name of the group
You can define a group name specified in the json file
**deviceid** : (optional) devices commands available
>- displaymirroring
**Emby filters** : (optional)
>- itemName
- itemType
- shuffle : default value = false
- filters
- sortBy
- sortOrder
- limit : default value = 100
- category
**commandtemplates** (mandatory)
array : list of text commands
**command** : (mandatory)
regular expression used to filter commands
**Exemple :**
command: "(?<action>play|Listen to)\\s?(?<determiner1>my|me)?\\s?(?<source> music)\\s?(?<ArtistName>.*)?\\s?(?<deviceaction>on device|to device)\\s?(?<Devicename>.*)",
altcommand: "(?<action>play|Listen to)\\s?(?<determiner1>my|me)?\\s?(?<source> music)\\s?(?<ArtistName>.*)?",
####Regular expression description
```json
(?<action> or ?<MovieName>, etc) - are for defining watts is captured
my|me - indicate each of those words/phrases can be used
\\s? - is for spaces
(?<MovieName>.*)? - the ? at the end of the closing brackets represent an optional value
```
In the example below theses phrases can the match for an action
- Show my movies
- Show movies
- Display movies
- Go to movies
- etc.
**altcommand** : (optional)
alternate regular expression used to filter commands if the property **command** does not match
####Regular expression description
```json
?<action> or ?<MovieName>, etc - are for defining watts is captured
my|me - indicate each of those words/phrases can be used
\s? - is for spaces
(?<MovieName>.*)? - the ? at the end of the closing brackets represent an optional value
```
####Additional properties used by regular expression
>**action** : Linked to actionid
>**source** : Linked to sourceid
>**menu** : Linked to menuid
>**group** : Linked to groupid
>**device** : Linked to deviceid
>**determiner1 or determiner2 etc** : used just to capture words
>**moviename**
>**devicename**
>**songname**
>**artistname**
>**albumname**
>**seriename**
>**seasonname**
>**picturename**
>**authorname**

View file

@ -1,17 +1 @@
define(['playbackManager'], function (playbackManager) {
'use strict';
function setActiveDevice(name) {
return function () {
playbackManager.trySetActiveDeviceName(name);
};
}
return function (result) {
if (result.properties.devicename) {
return setActiveDevice(result.properties.devicename);
}
return;
};
});
define(["playbackManager"],function(playbackManager){"use strict";function setActiveDevice(name){return function(){playbackManager.trySetActiveDeviceName(name)}}return function(result){if(result.properties.devicename)return setActiveDevice(result.properties.devicename)}});

View file

@ -1,20 +1 @@
define(['inputManager'], function (inputManager) {
'use strict';
function disableDisplayMirror() {
return function () {
inputManager.trigger('disabledisplaymirror');
};
}
return function (result) {
switch (result.item.deviceid) {
case 'displaymirroring':
return disableDisplayMirror();
default:
return;
}
};
});
define(["inputManager"],function(inputManager){"use strict";function disableDisplayMirror(){return function(){inputManager.trigger("disabledisplaymirror")}}return function(result){switch(result.item.deviceid){case"displaymirroring":return disableDisplayMirror();default:return}}});

View file

@ -1,20 +1 @@
define(['inputManager'], function (inputManager) {
'use strict';
function enableDisplayMirror() {
return function () {
inputManager.trigger('enabledisplaymirror');
};
}
return function (result) {
switch (result.item.deviceid) {
case 'displaymirroring':
return enableDisplayMirror();
default:
return;
}
};
});
define(["inputManager"],function(inputManager){"use strict";function enableDisplayMirror(){return function(){inputManager.trigger("enabledisplaymirror")}}return function(result){switch(result.item.deviceid){case"displaymirroring":return enableDisplayMirror();default:return}}});

View file

@ -1,102 +1 @@
define(['connectionManager', 'playbackManager', 'globalize'], function (connectionManager, playbackManager, globalize) {
'use strict';
/// <summary> Play items. </summary>
/// <param name="items"> The items. </param>
/// <param name="shuffle"> The shuffle. </param>
/// <returns> . </returns>
function playItems(items, shuffle) {
if (shuffle) {
items = shuffleArray(items);
}
if (items.length) {
var serverId = items[0].ServerId;
items = items.map(function (i) {
return i.Id;
});
playbackManager.play({
ids: items,
serverId: serverId
});
}
else {
require(['toast'], function (toast) {
toast(globalize.translate('sharedcomponents#NoItemsFound'));
});
}
}
/// <summary> Shuffle array. </summary>
/// <param name="array"> The array. </param>
/// <returns> . </returns>
function shuffleArray(array) {
var currentIndex = array.length, temporaryValue, randomIndex;
// While there remain elements to shuffle...
while (0 !== currentIndex) {
// Pick a remaining element...
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex -= 1;
// And swap it with the current element.
temporaryValue = array[currentIndex];
array[currentIndex] = array[randomIndex];
array[randomIndex] = temporaryValue;
}
return array;
}
return function (result) {
return function () {
var query = {
Limit: result.item.limit || 100,
UserId: result.userId,
ExcludeLocationTypes: "Virtual"
};
if (result.item.itemType) {
query.IncludeItemTypes = result.item.itemType;
}
var apiClient = connectionManager.currentApiClient();
if (result.item.sourceid === 'nextup') {
apiClient.getNextUpEpisodes(query).then(function (queryResult) {
playItems(queryResult.Items, result.item.shuffle);
});
}
if (result.item.shuffle) {
result.item.sortBy = result.sortBy ? 'Random,' + result.item.sortBy : 'Random';
}
query.SortBy = result.item.sortBy;
query.SortOrder = result.item.sortOrder;
query.Recursive = true;
if (result.item.filters.indexOf('unplayed') !== -1) {
query.IsPlayed = false;
}
if (result.item.filters.indexOf('played') !== -1) {
query.IsPlayed = true;
}
if (result.item.filters.indexOf('favorite') !== -1) {
query.Filters = 'IsFavorite';
}
apiClient.getItems(apiClient.getCurrentUserId(), query).then(function (queryResult) {
playItems(queryResult.Items, result.item.shuffle);
});
};
};
});
define(["connectionManager","playbackManager","globalize"],function(connectionManager,playbackManager,globalize){"use strict";function playItems(items,shuffle){if(shuffle&&(items=shuffleArray(items)),items.length){var serverId=items[0].ServerId;items=items.map(function(i){return i.Id}),playbackManager.play({ids:items,serverId:serverId})}else require(["toast"],function(toast){toast(globalize.translate("sharedcomponents#NoItemsFound"))})}function shuffleArray(array){for(var temporaryValue,randomIndex,currentIndex=array.length;0!==currentIndex;)randomIndex=Math.floor(Math.random()*currentIndex),currentIndex-=1,temporaryValue=array[currentIndex],array[currentIndex]=array[randomIndex],array[randomIndex]=temporaryValue;return array}return function(result){return function(){var query={Limit:result.item.limit||100,UserId:result.userId,ExcludeLocationTypes:"Virtual"};result.item.itemType&&(query.IncludeItemTypes=result.item.itemType);var apiClient=connectionManager.currentApiClient();"nextup"===result.item.sourceid&&apiClient.getNextUpEpisodes(query).then(function(queryResult){playItems(queryResult.Items,result.item.shuffle)}),result.item.shuffle&&(result.item.sortBy=result.sortBy?"Random,"+result.item.sortBy:"Random"),query.SortBy=result.item.sortBy,query.SortOrder=result.item.sortOrder,query.Recursive=!0,result.item.filters.indexOf("unplayed")!==-1&&(query.IsPlayed=!1),result.item.filters.indexOf("played")!==-1&&(query.IsPlayed=!0),result.item.filters.indexOf("favorite")!==-1&&(query.Filters="IsFavorite"),apiClient.getItems(apiClient.getCurrentUserId(),query).then(function(queryResult){playItems(queryResult.Items,result.item.shuffle)})}}});

View file

@ -1,11 +1 @@
define(['inputManager'], function (inputManager) {
'use strict';
return function (result) {
switch (result.item.deviceid) {
default:
return;
}
};
});
define(["inputManager"],function(inputManager){"use strict";return function(result){switch(result.item.deviceid){default:return}}});

View file

@ -1,134 +1 @@
define(['inputManager', 'connectionManager', 'embyRouter'], function (inputManager, connectionManager, embyRouter) {
'use strict';
function getMusicCommand(result) {
return function () {
inputManager.trigger('music');
};
}
function getMoviesCommand(result) {
return function () {
if (result.properties.movieName) {
//TODO: Find a way to display movie
var query = {
Limit: 1,
UserId: result.userId,
ExcludeLocationTypes: "Virtual",
NameStartsWith: result.item.itemType
};
if (result.item.itemType) {
query.IncludeItemTypes = result.item.itemType;
}
var apiClient = connectionManager.currentApiClient();
apiClient.getItems(apiClient.getCurrentUserId(), query).then(function (queryResult) {
if (queryResult.Items.length) {
embyRouter.showItem(queryResult.Items[0]);
}
});
} else {
inputManager.trigger('movies');
}
};
}
function getTVCommand(result) {
return function () {
inputManager.trigger('tv');
};
}
function getLiveTVCommand(result) {
return function () {
var act = result.item.menuid;
if (act) {
if (act.indexOf('livetv') !== -1) {
inputManager.trigger('livetv');
} else if (act.indexOf('guide') !== -1) {
inputManager.trigger('guide');
} else if (act.indexOf('channels') !== -1) {
inputManager.trigger('livetv');
} else if (act.indexOf('recordings') !== -1) {
inputManager.trigger('recordedtv');
} else if (act.indexOf('scheduled') !== -1) {
inputManager.trigger('recordedtv');
} else if (act.indexOf('series') !== -1) {
inputManager.trigger('recordedtv');
} else {
inputManager.trigger('livetv');
}
} else {
inputManager.trigger('livetv');
}
};
}
function getRecordingsCommand(result) {
return function () {
inputManager.trigger('recordedtv');
};
}
function getLatestEpisodesCommand(result) {
return function () {
inputManager.trigger('latestepisodes');
};
}
function getHomeCommand(result) {
return function () {
var act = result.item.menuid;
if (act) {
if (act.indexOf('home') !== -1) {
inputManager.trigger('home');
}
else if (act.indexOf('nextup') !== -1) {
inputManager.trigger('nextup');
}
else if (act.indexOf('favorites') !== -1) {
inputManager.trigger('favorites');
} else if (act.indexOf('upcoming') !== -1) {
inputManager.trigger('upcomingtv');
}
else if (act.indexOf('nowplaying') !== -1) {
inputManager.trigger('nowplaying');
}
else {
inputManager.trigger('home');
}
} else {
inputManager.trigger('home');
}
};
}
return function (result) {
switch (result.item.sourceid) {
case 'music':
return getMusicCommand(result);
case 'movies':
return getMoviesCommand(result);
case 'tvseries':
return getTVCommand(result);
case 'livetv':
return getLiveTVCommand(result);
case 'recordings':
return getRecordingsCommand(result);
case 'latestepisodes':
return getLatestEpisodesCommand(result);
case 'home':
return getHomeCommand(result);
case 'group':
return;
default:
return;
}
};
});
define(["inputManager","connectionManager","embyRouter"],function(inputManager,connectionManager,embyRouter){"use strict";function getMusicCommand(result){return function(){inputManager.trigger("music")}}function getMoviesCommand(result){return function(){if(result.properties.movieName){var query={Limit:1,UserId:result.userId,ExcludeLocationTypes:"Virtual",NameStartsWith:result.item.itemType};result.item.itemType&&(query.IncludeItemTypes=result.item.itemType);var apiClient=connectionManager.currentApiClient();apiClient.getItems(apiClient.getCurrentUserId(),query).then(function(queryResult){queryResult.Items.length&&embyRouter.showItem(queryResult.Items[0])})}else inputManager.trigger("movies")}}function getTVCommand(result){return function(){inputManager.trigger("tv")}}function getLiveTVCommand(result){return function(){var act=result.item.menuid;act?act.indexOf("livetv")!==-1?inputManager.trigger("livetv"):act.indexOf("guide")!==-1?inputManager.trigger("guide"):act.indexOf("channels")!==-1?inputManager.trigger("livetv"):act.indexOf("recordings")!==-1?inputManager.trigger("recordedtv"):act.indexOf("scheduled")!==-1?inputManager.trigger("recordedtv"):act.indexOf("series")!==-1?inputManager.trigger("recordedtv"):inputManager.trigger("livetv"):inputManager.trigger("livetv")}}function getRecordingsCommand(result){return function(){inputManager.trigger("recordedtv")}}function getLatestEpisodesCommand(result){return function(){inputManager.trigger("latestepisodes")}}function getHomeCommand(result){return function(){var act=result.item.menuid;act?act.indexOf("home")!==-1?inputManager.trigger("home"):act.indexOf("nextup")!==-1?inputManager.trigger("nextup"):act.indexOf("favorites")!==-1?inputManager.trigger("favorites"):act.indexOf("upcoming")!==-1?inputManager.trigger("upcomingtv"):act.indexOf("nowplaying")!==-1?inputManager.trigger("nowplaying"):inputManager.trigger("home"):inputManager.trigger("home")}}return function(result){switch(result.item.sourceid){case"music":return getMusicCommand(result);case"movies":return getMoviesCommand(result);case"tvseries":return getTVCommand(result);case"livetv":return getLiveTVCommand(result);case"recordings":return getRecordingsCommand(result);case"latestepisodes":return getLatestEpisodesCommand(result);case"home":return getHomeCommand(result);case"group":return;default:return}}});

View file

@ -1,20 +1 @@
define(['inputManager'], function (inputManager) {
'use strict';
function toggleDisplayMirror() {
return function () {
inputManager.trigger('toggledisplaymirror');
};
}
return function (result) {
switch (result.item.deviceid) {
case 'displaymirroring':
return toggleDisplayMirror();
default:
return;
}
};
});
define(["inputManager"],function(inputManager){"use strict";function toggleDisplayMirror(){return function(){inputManager.trigger("toggledisplaymirror")}}return function(result){switch(result.item.deviceid){case"displaymirroring":return toggleDisplayMirror();default:return}}});

File diff suppressed because one or more lines are too long

View file

@ -1,20 +1 @@
.voiceHelpContent {
max-width: 600px;
margin: auto;
}
.exampleCommand {
margin: 1em 0;
}
.exampleCommandText {
padding-left: .25em;
}
.defaultVoiceHelp {
margin-bottom: 2em;
}
.voiceInputContainer {
margin: 1.5em 0;
}
.voiceHelpContent{max-width:600px;margin:auto}.exampleCommand{margin:1em 0}.exampleCommandText{padding-left:.25em}.defaultVoiceHelp{margin-bottom:2em}.voiceInputContainer{margin:1.5em 0}

View file

@ -1,53 +1 @@
// <date>09.10.2015</date>
// <summary>voicecommands class</summary>
define(['require'], function (require) {
'use strict';
/// <summary> Process the command. </summary>
/// <param name="commandPath"> Full pathname of the command file. </param>
/// <param name="result"> The result. </param>
/// <returns> . </returns>
function processCommand(commandPath, result) {
return new Promise(function (resolve, reject) {
require([commandPath], function (command) {
var fn = command(result);
if (fn) {
result.fn = fn;
resolve(result);
} else {
reject();
}
});
});
}
return function (result) {
switch (result.item.actionid) {
case 'show':
return processCommand('./commands/showcommands.js', result);
case 'play':
return processCommand('./commands/playcommands.js', result);
case 'shuffle':
return processCommand('./commands/playcommands.js', result);
case 'search':
return processCommand('./commands/searchcommands.js', result);
case 'control':
return processCommand('./commands/controlcommands.js', result);
case 'enable':
return processCommand('./commands/enablecommands.js', result);
case 'disable':
return processCommand('./commands/disablecommands.js', result);
case 'toggle':
return processCommand('./commands/togglecommands.js', result);
default:
return Promise.reject();
}
};
});
define(["require"],function(require){"use strict";function processCommand(commandPath,result){return new Promise(function(resolve,reject){require([commandPath],function(command){var fn=command(result);fn?(result.fn=fn,resolve(result)):reject()})})}return function(result){switch(result.item.actionid){case"show":return processCommand("./commands/showcommands.js",result);case"play":return processCommand("./commands/playcommands.js",result);case"shuffle":return processCommand("./commands/playcommands.js",result);case"search":return processCommand("./commands/searchcommands.js",result);case"control":return processCommand("./commands/controlcommands.js",result);case"enable":return processCommand("./commands/enablecommands.js",result);case"disable":return processCommand("./commands/disablecommands.js",result);case"toggle":return processCommand("./commands/togglecommands.js",result);default:return Promise.reject()}}});

File diff suppressed because one or more lines are too long

View file

@ -1,60 +1 @@
define(['./voicecommands.js', './grammarprocessor.js', 'require'], function (voicecommands, grammarprocessor, require) {
'use strict';
var commandgroups;
function getCommandGroups() {
if (commandgroups) {
return Promise.resolve(commandgroups);
}
return new Promise(function (resolve, reject) {
var file = "grammar";
require(['text!./grammar/' + file + '.json'], function (response) {
commandgroups = JSON.parse(response);
resolve(commandgroups);
});
});
}
/// <summary> Process the transcript described by text. </summary>
/// <param name="text"> The text. </param>
/// <returns> . </returns>
function processTranscript(text) {
if (text) {
return getCommandGroups().then(function (commandgroups) {
var processor = grammarprocessor(commandgroups, text);
if (processor && processor.command) {
console.log("Command from Grammar Processor", processor);
return voicecommands(processor)
.then(function (result) {
console.log("Result of executed command", result);
if (result.item.actionid === 'show' && result.item.sourceid === 'group') {
return Promise.resolve({ error: "group", item: result.item, groupName: result.name, fn: result.fn });
} else {
return Promise.resolve({ item: result.item, fn: result.fn });
}
}, function () {
return Promise.reject({ error: "unrecognized-command", text: text });
});
} else {
return Promise.reject({ error: "unrecognized-command", text: text });
}
});
} else {
return Promise.reject({ error: "empty" });
}
}
/// <summary> An enum constant representing the window. voice input manager option. </summary>
return {
processTranscript: processTranscript,
getCommandGroups: getCommandGroups
};
});
define(["./voicecommands.js","./grammarprocessor.js","require"],function(voicecommands,grammarprocessor,require){"use strict";function getCommandGroups(){return commandgroups?Promise.resolve(commandgroups):new Promise(function(resolve,reject){var file="grammar";require(["text!./grammar/"+file+".json"],function(response){commandgroups=JSON.parse(response),resolve(commandgroups)})})}function processTranscript(text){return text?getCommandGroups().then(function(commandgroups){var processor=grammarprocessor(commandgroups,text);return processor&&processor.command?(console.log("Command from Grammar Processor",processor),voicecommands(processor).then(function(result){return console.log("Result of executed command",result),"show"===result.item.actionid&&"group"===result.item.sourceid?Promise.resolve({error:"group",item:result.item,groupName:result.name,fn:result.fn}):Promise.resolve({item:result.item,fn:result.fn})},function(){return Promise.reject({error:"unrecognized-command",text:text})})):Promise.reject({error:"unrecognized-command",text:text})}):Promise.reject({error:"empty"})}var commandgroups;return{processTranscript:processTranscript,getCommandGroups:getCommandGroups}});

View file

@ -1,99 +1 @@
define(['events'], function (events) {
'use strict';
var receiver = {
};
var currentRecognition = null;
function normalizeInput(text, options) {
if (options.requireNamedIdentifier) {
var srch = 'jarvis';
var index = text.toLowerCase().indexOf(srch);
if (index !== -1) {
text = text.substring(index + srch.length);
} else {
return null;
}
}
return text;
}
/// <summary> Starts listening for voice commands </summary>
/// <returns> . </returns>
function listen(options) {
return new Promise(function (resolve, reject) {
cancelListener();
var recognitionObj = window.SpeechRecognition ||
window.webkitSpeechRecognition ||
window.mozSpeechRecognition ||
window.oSpeechRecognition ||
window.msSpeechRecognition;
var recognition = new recognitionObj();
recognition.lang = options.lang;
recognition.continuous = options.continuous || false;
var resultCount = 0;
recognition.onresult = function (event) {
console.log(event);
if (event.results.length > 0) {
var resultInput = event.results[resultCount][0].transcript || '';
resultCount++;
resultInput = normalizeInput(resultInput, options);
if (resultInput) {
if (options.continuous) {
events.trigger(receiver, 'input', [
{
text: resultInput
}
]);
} else {
resolve(resultInput);
}
}
}
};
recognition.onerror = function () {
reject({ error: event.error, message: event.message });
};
recognition.onnomatch = function () {
reject({ error: "no-match" });
};
currentRecognition = recognition;
currentRecognition.start();
});
}
/// <summary> Cancel listener. </summary>
/// <returns> . </returns>
function cancelListener() {
if (currentRecognition) {
currentRecognition.abort();
currentRecognition = null;
}
}
receiver.listen = listen;
receiver.cancel = cancelListener;
/// <summary> An enum constant representing the window. voice input manager option. </summary>
return receiver;
});
define(["events"],function(events){"use strict";function normalizeInput(text,options){if(options.requireNamedIdentifier){var srch="jarvis",index=text.toLowerCase().indexOf(srch);if(index===-1)return null;text=text.substring(index+srch.length)}return text}function listen(options){return new Promise(function(resolve,reject){cancelListener();var recognitionObj=window.SpeechRecognition||window.webkitSpeechRecognition||window.mozSpeechRecognition||window.oSpeechRecognition||window.msSpeechRecognition,recognition=new recognitionObj;recognition.lang=options.lang,recognition.continuous=options.continuous||!1;var resultCount=0;recognition.onresult=function(event){if(console.log(event),event.results.length>0){var resultInput=event.results[resultCount][0].transcript||"";resultCount++,resultInput=normalizeInput(resultInput,options),resultInput&&(options.continuous?events.trigger(receiver,"input",[{text:resultInput}]):resolve(resultInput))}},recognition.onerror=function(){reject({error:event.error,message:event.message})},recognition.onnomatch=function(){reject({error:"no-match"})},currentRecognition=recognition,currentRecognition.start()})}function cancelListener(){currentRecognition&&(currentRecognition.abort(),currentRecognition=null)}var receiver={},currentRecognition=null;return receiver.listen=listen,receiver.cancel=cancelListener,receiver});