diff --git a/generators/app/index.js b/generators/app/index.js
index eb96ecb..e4ac29d 100644
--- a/generators/app/index.js
+++ b/generators/app/index.js
@@ -17,7 +17,7 @@ module.exports = yeoman.extend({
type: 'list',
name: 'type',
message: 'Choose the type of addon you want to create.',
- choices: ['Contextmenu', 'Module', 'Plugin', 'Resource', 'Script', 'Service'],
+ choices: ['Contextmenu', 'Module', 'Plugin', 'Resource', 'Script', 'Service', 'Subtitle'],
default: 0
}];
@@ -94,6 +94,13 @@ module.exports = yeoman.extend({
message: 'Your addon id, it should be in the format script.name and not contain spaces. (for e.g. script.test.hello)',
validate: helper.validateScriptName
});
+ } else if (this.props.type == 'Subtitle') {
+ prompts.push({
+ type: 'input',
+ name: 'scriptid',
+ message: 'Your addon id, it should be in the format service.subtitles.name and not contain spaces. (for e.g. service.subtitles.hello)',
+ validate: helper.validateSubtitleName
+ });
}
prompts.push({
@@ -199,6 +206,11 @@ module.exports = yeoman.extend({
this.templatePath('service.py'),
this.destinationPath('service.py')
);
+ } else if (this.props.type == 'Subtitle') {
+ this.fs.copy(
+ this.templatePath('subtitle.py'),
+ this.destinationPath('subtitle.py')
+ );
}
if (this.props.type != 'Module' && this.props.type != 'Resource' && this.props.type != 'Contextmenu') {
diff --git a/generators/app/templates/addon.xml b/generators/app/templates/addon.xml
index b2de1ec..e23ff73 100644
--- a/generators/app/templates/addon.xml
+++ b/generators/app/templates/addon.xml
@@ -13,7 +13,7 @@
<%= provides %>
<% } %><% if (props.type == 'Resource') {%><% } %><% if (props.type == 'Script') {%>
<%= provides %>
- <% } %><% if (props.type == 'Service') {%><% } %>
+ <% } %><% if (props.type == 'Service') {%><% } %><% if (props.type == 'Subtitle') {%><% } %>
<%= props.summary %>
diff --git a/generators/app/templates/subtitle.py b/generators/app/templates/subtitle.py
new file mode 100644
index 0000000..244d4bc
--- /dev/null
+++ b/generators/app/templates/subtitle.py
@@ -0,0 +1,151 @@
+# -*- coding: utf-8 -*-
+
+
+import urllib2
+import sys
+import urlparse
+import urllib
+import os
+import unicodedata
+import xbmcgui
+import xbmcplugin
+import xbmc
+import xbmcaddon
+import xbmcvfs
+
+ADDON = xbmcaddon.Addon()
+SCRIPT_ID = ADDON.getAddonInfo('id')
+PROFILE = xbmc.translatePath(ADDON.getAddonInfo('profile'))
+TEMP = os.path.join(PROFILE, 'temp', '')
+HANDLE = int(sys.argv[1])
+
+if not xbmcvfs.exists(TEMP):
+ xbmcvfs.mkdirs(TEMP)
+
+
+# function to retrieve parameters in a dictionary
+def getParams():
+ if len(sys.argv) > 2:
+ return dict(urlparse.parse_qsl(sys.argv[2].lstrip('?')))
+ return {}
+
+
+def normalizeString(str):
+ return unicodedata.normalize(
+ 'NFKD', unicode(unicode(str, 'utf-8'))
+ ).encode('ascii', 'ignore')
+
+
+def getInfo():
+ item = {}
+ item['temp'] = False
+ item['rar'] = False
+ # Year
+ item['year'] = xbmc.getInfoLabel("VideoPlayer.Year")
+ # Season
+ item['season'] = str(xbmc.getInfoLabel("VideoPlayer.Season"))
+ # Episode
+ item['episode'] = str(xbmc.getInfoLabel("VideoPlayer.Episode"))
+ item['tvshow'] = normalizeString(
+ xbmc.getInfoLabel("VideoPlayer.TVshowtitle")) # Show
+ # try to get original title
+ item['title'] = normalizeString(
+ xbmc.getInfoLabel("VideoPlayer.OriginalTitle"))
+ item['file_original_path'] = urllib.unquote(
+ # Full path of a playing file
+ xbmc.Player().getPlayingFile().decode('utf-8'))
+
+ if item['title'] == "":
+ # no original title, get just Title
+ item['title'] = normalizeString(xbmc.getInfoLabel("VideoPlayer.Title"))
+
+ # Check if season is "Special"
+ if item['episode'].lower().find("s") > -1:
+ item['season'] = "0"
+ item['episode'] = item['episode'][-1:]
+
+ if (item['file_original_path'].find("http") > -1):
+ item['temp'] = True
+
+ elif (item['file_original_path'].find("rar://") > -1):
+ item['rar'] = True
+ item['file_original_path'] = os.path.dirname(
+ item['file_original_path'][6:])
+
+ elif (item['file_original_path'].find("stack://") > -1):
+ stackPath = item['file_original_path'].split(" , ")
+ item['file_original_path'] = stackPath[0][8:]
+
+ item['filename'] = os.path.splitext(
+ os.path.basename(item['file_original_path']))[0]
+ return item
+
+
+def getLanguages(params):
+ langs = [] # ['scc','eng']
+ for lang in urllib.unquote(params['languages']).decode('utf-8').split(","):
+ langs.append(xbmc.convertLanguage(lang, xbmc.ISO_639_2))
+ return langs
+
+
+def append_subtitle(subname, language, params, sync=False, h_impaired=False):
+ # languange long name (for example english)
+ listitem = xbmcgui.ListItem(label=xbmc.convertLanguage(language, xbmc.ENGLISH_NAME),
+ # subtitle name displayed
+ label2=subname,
+ # languange 2 letter name (for example en)
+ thumbnailImage=xbmc.convertLanguage(language, xbmc.ISO_639_1))
+
+ # subtitles synced with the video
+ listitem.setProperty("sync", 'true' if sync else 'false')
+ # hearing impaired subs
+ listitem.setProperty("hearing_imp", 'true' if h_impaired else 'false')
+
+ url = "plugin://{url}/?{params}".format(
+ url=SCRIPT_ID, params=urllib.urlencode(params))
+ xbmcplugin.addDirectoryItem(
+ handle=HANDLE, url=url, listitem=listitem, isFolder=False)
+
+
+# add subtitles to the list from information kodi provides
+def Search(info, languages):
+ append_subtitle(
+ "Lost 1x01", "eng", {"action": "download", "id": 15}, sync=True)
+ append_subtitle("Lost 1x01", "ita", {"action": "download", "id": 15})
+ append_subtitle("Lost 1x01 720p", "eng", {"action": "download", "id": 15})
+
+
+# add subtitles to the list from manual user search
+def ManualSearch(searchstr, languages):
+ append_subtitle(
+ "Lost 1x01", "eng", {"action": "download", "id": 15}, sync=True)
+ append_subtitle("Lost 1x01", "ita", {"action": "download", "id": 15})
+ append_subtitle("Lost 1x01 720p", "eng", {"action": "download", "id": 15})
+
+
+# download the subtitle chosen by the user
+def Download(params):
+ id = params['id']
+ # download the file requested
+ url = "http://path.to/subtitle/{id}.srt".format(id=id)
+ file = os.path.join(TEMP, "{id}.srt".format(id=id))
+
+ response = urllib2.urlopen(url)
+ with open(file, "w") as local_file:
+ local_file.write(response.read())
+
+ # give the file to kodi
+ xbmcplugin.addDirectoryItem(
+ handle=HANDLE, url=file, listitem=xbmcgui.ListItem(label=file), isFolder=False)
+
+params = getParams()
+
+if 'action' in params:
+ if params['action'] == "search":
+ Search(getInfo(), getLanguages(params))
+ elif params['action'] == "manualsearch":
+ ManualSearch(params['searchstring'], getLanguages(params))
+ elif params['action'] == "download":
+ Download(params)
+
+xbmcplugin.endOfDirectory(HANDLE)
diff --git a/generators/app/validationHelper.js b/generators/app/validationHelper.js
index c593d65..c4eee51 100644
--- a/generators/app/validationHelper.js
+++ b/generators/app/validationHelper.js
@@ -24,6 +24,10 @@ helper.validateScriptName = function (str) {
return str.length > 'script.'.length;
};
+helper.validateSubtitleName = function (str) {
+ return str.length > 'service.subtitles.'.length;
+};
+
helper.validateScriptNameLength = function (str) {
return str.length > 2;
};
diff --git a/test/app.js b/test/app.js
index 7462c4f..496b8f4 100644
--- a/test/app.js
+++ b/test/app.js
@@ -312,3 +312,56 @@ describe('generate service', function () {
assert.fileContent('addon.xml', '');
});
});
+
+describe('generate subtitle', function () {
+ before(function () {
+ return helpers.run(path.join(__dirname, '../generators/app'))
+ .withPrompts({
+ type: 'Subtitle',
+ scriptid: 'subtitle.test',
+ scriptname: 'My subtitle name',
+ kodiVersion: '2.25.0',
+ platforms: 'all',
+ license: 'MIT',
+ authors: 'Me',
+ summary: 'My summary',
+ authorName: 'My real name',
+ email: 'test@test.de',
+ website: 'www.kodi.tv'
+ })
+ .toPromise();
+ });
+
+ it('creates subtitle files', function () {
+ assert.file([
+ 'addon.xml',
+ '.gitignore',
+ 'changelog.txt',
+ 'README.md',
+ 'subtitle.py',
+ 'tests/README.md',
+ 'resources/__init__.py',
+ 'resources/language/resource.language.en_gb/strings.po',
+ 'resources/language/README.md',
+ 'resources/lib/__init__.py',
+ 'resources/lib/README.md',
+ 'resources/settings.xml',
+ 'LICENSE'
+ ]);
+
+ assert.noFile([
+ 'context.py',
+ 'plugin.py',
+ 'script.py',
+ 'service.py'
+ ]);
+ });
+
+ it('check service addon.xml content', function () {
+ assert.fileContent('addon.xml', '');
+ assert.fileContent('addon.xml', ' provider-name="Me">');
+ assert.fileContent('addon.xml', 'all');
+ assert.fileContent('addon.xml', '');
+ });
+});
diff --git a/test/validationHelper.js b/test/validationHelper.js
index 65d1255..0f4fe94 100644
--- a/test/validationHelper.js
+++ b/test/validationHelper.js
@@ -50,6 +50,14 @@ describe('prompting validations → validateScriptName()', () => {
assert.equal(helper.validateScriptName('script'), false);
});
});
+describe('prompting validations → validateSubtitleName()', () => {
+ it('should work for service.subtitles.test', () => {
+ assert.equal(helper.validateSubtitleName('service.subtitles.test'), true);
+ });
+ it('should fail when to short', () => {
+ assert.equal(helper.validateSubtitleName('service.subtitles'), false);
+ });
+});
describe('prompting validations → validateScriptNameLength()', () => {
it('should work for anything longer than two letters', () => {
assert.equal(helper.validateScriptNameLength('My name'), true);