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);