# -*- coding: utf-8 -*- # Kodi Addon: Youtube Library # Copyright 2015 Sleuteltje # # This file is part of plugin.video.youtubelibrary # Description: Functions to write NFO & Strm files # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import re import xbmcvfs import os import urllib from resources.lib import dev from resources.lib import vars from resources.lib import ytube from resources.lib import m_imdb ################## Generators #################### #Does a regex expression from the settings.xml. Will return None if it fails, the match otherwise def reg(se, txt): if se[:6] == 'regex(': #match = re.match(se, 'regex\((.*)\)') ma = se[6:] ma = ma[:-1] if ma is not None: m = re.search( ma, txt) #m = re.search( r'(\d+)', txt) if m: #Found the thing we were looking for with the given user regex dev.log('Regex '+ma+' found its match: '+m.group(0).encode('UTF-8')+' , '+m.group(1).encode('UTF-8')) return m.group(1) else: #Regex not found, return None dev.log('Regex given by user has not found anything: '+ma+' on '+txt.encode('UTF-8'), True) return None #Return the fallback else: dev.log('Regex given by user in settings.xml is not valid!'+se, True) return None else: return None #This is not a regex setting ## Movies #Generates the season and episode number #Vid: The video response from the youtube api #settings: The elementtree element containing the playlist settings #totalresults = The total results of the playlist, so that the episode can be calculated if the episode recognisition is set to pos #playlist = the id of the playlist we are checking episode numbers for def scan_movie(title, settings, year=False): search_imdb = int(settings.find('search_imdb').text) #Should we search on imdb for this movie? imdb_match_cutoff = int(settings.find('imdb_match_cutoff').text) deny_without_poster = False use_ytimage = settings.find('use_ytimage').text if use_ytimage == '2': #Dont add if no image on imdb found deny_without_poster = True #title = vid['snippet']['title'] dev.log('scan_movie('+title+', '+str(search_imdb)+','+str(imdb_match_cutoff)+', '+str(year)+')') if search_imdb == 2: ##2 = Just let the addon handle it return False dev.log('We should search on imdb for information about this movie') return m_imdb.search(title, imdb_match_cutoff, deny_without_poster, year) ## TVShows #Generates the season and episode number #Vid: The video response from the youtube api #settings: The elementtree element containing the playlist settings #totalresults = The total results of the playlist, so that the episode can be calculated if the episode recognisition is set to pos #playlist = the id of the playlist we are checking episode numbers for def episode_season(vid, settings, totalresults = False, playlist = False): ep = settings.find('episode').text #Grab the episode settings from the xml se = settings.find('season').text #dev.log('episode_season('+playlist+','+se+','+ep+')') found = False ##See if there should be standard season/episode recognisition for the season if se == 's02e12': regex = "s(eason)?\s*(\d+)\s*ep?(isode)?\s*(\d+)" m = re.search( regex, vid['snippet']['title']) if m: #Found the sxxexx dev.log('s2e12 '+regex+' found its match: '+m.group(0).encode('UTF-8')+' , '+m.group(1).encode('UTF-8')+' , '+m.group(2).encode('UTF-8')) #Try to replace the s01e01 in the title vid['snippet']['title'] = re.sub(regex, '', vid['snippet']['title'], 1) return m.group(2), m.group(4), vid #Regex not found, return None dev.log('s02e12 recognizition has not found anything: '+regex+' on '+vid['snippet']['title'].encode('UTF-8'), True) return '0', '0', vid if se == '02x12': regex = "(\d+)\s?x\s?(\d+)" m = re.search( regex, vid['snippet']['title']) if m: #Found the sxxexx dev.log('2x12 '+regex+' found its match: '+m.group(0).encode('UTF-8')+' , '+m.group(1).encode('UTF-8')+' , '+m.group(2).encode('UTF-8')) #Try to replace the s01e01 in the title vid['snippet']['title'] = re.sub(regex, '', vid['snippet']['title'], 1) return m.group(1), m.group(2), vid #Regex not found, return None dev.log('02x12 recognizition has not found anything: '+regex+' on '+vid['snippet']['title'].encode('UTF-8'), True) return '0', '0', vid ##Normal recongizitions #See if there should be a regex search for the season if se[:6] == 'regex(': match = reg(se, vid['snippet']['title']) if match != None: season = match[0] found = True if found == False: #If the episode has not been found yet, either it is not regex, or regex failed if se == 'year': #We want to save the season of the video as the year it is published dev.log('`~.~`~... PublishedAt: '+vid['snippet']['publishedAt']) d = ytube.convert_published(vid['snippet']['publishedAt']) season = d['year'] elif se.isdigit(): #If the season is set to a hardcoded number season = str(se) else: dev.log('Error: invalid season tag in settings.xml: '+se+', set season to 0', True) season = '0' found = False #See if there should be a regex search for the episode if ep[:6] == 'regex(': match = reg(ep, vid['snippet']['title']) if match != None: episode = match found = True if found == False: if ep == 'default': if playlist == False: dev.log('episode_season: Error: episode recognisition set to default, but no playlist id given') return [season, '0'] #Get the current episode number from episodes.xml from resources.lib import m_xml episode = m_xml.number_of_episodes(playlist, season) if episode == None: episode = 0 #dev.log('Default Eprec: '+str(episode)) episode = str(episode + 1) #dev.log('Default Eprec after +1: '+episode) elif ep == 'monthday': #We want the episode to be the month number + day number d = ytube.convert_published(vid['snippet']['publishedAt']) episode = d['month']+d['day'] elif ep == 'monthdayhour': #month number + day number + hour number d = ytube.convert_published(vid['snippet']['publishedAt']) episode = d['month']+d['day']+d['hour'] elif ep == 'monthdayhourminute': #month + day + hour + minute numbers d = ytube.convert_published(vid['snippet']['publishedAt']) episode = d['month']+d['day']+d['hour']+d['minute'] elif ep == 'pos': #The position in the playlist as episode number episode = str(int(totalresults) - int(vid['snippet']['position'])) elif ep.isdigit(): #A hardcoded number as episode number episode = str(ep) else: dev.log('Invalid episode setting in settings.xml! '+ep) episode = '0' return season, episode, vid ##Music Videos - songinfo #Finds the genre, artist, song, album, plot and year def get_songinfo(vid, settings, duration): artist = False featured = False song = False album = False tracknr = '' year = False genre = False studio = '' plot = False tags = [] vid_title = vid['snippet']['title'] vid_description = vid['snippet']['description'] vid_id = vid['contentDetails']['videoId'] vid_kind = 'song' dev.log('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~') dev.log('get_songinfo('+vid_title+'['+vid_id+'])') setting_genre = settings.find('genre').text #Grab the episode settings from the xml setting_genre_fallback = settings.find('genre_fallback').text setting_genre_hardcoded = settings.find('genre_hardcoded').text setting_artist = settings.find('artist').text setting_artist_fallback = settings.find('artist_fallback').text setting_artist_hardcoded = settings.find('artist_hardcoded').text setting_song_fallback = settings.find('song_fallback').text setting_album = settings.find('album').text setting_album_fallback = settings.find('album_fallback').text setting_album_hardcoded = settings.find('album_hardcoded').text setting_plot = settings.find('plot').text setting_plot_fallback = settings.find('plot_fallback').text setting_plot_hardcoded = settings.find('plot_hardcoded').text setting_year = settings.find('year').text setting_year_fallback = settings.find('year_fallback').text setting_year_hardcoded = settings.find('year_hardcoded').text setting_skip_audio = settings.find('skip_audio').text setting_skip_lyrics = settings.find('skip_lyrics').text setting_skip_live = settings.find('skip_live').text setting_skip_albums = settings.find('skip_albums').text dev.log('loaded settings') vid_title = vid_title.strip(' \t\n\r') ##Strip complete album from the title new_title = strip_album(vid_title) dev.log('New title after strip_album: '+new_title) if new_title != vid_title: vid_kind = 'album' vid_title = new_title dev.log('Vid title after strip_album: '+vid_title) ##Guess what kind of video this is (song, live song, album or concert) if int(duration) < 600: vid_kind = 'song' #Song is shorter than 10 minutes, so it must be a song / live song ''' if 'live' in vid_title.lower() or 'tour' in vid_title.lower(): #It must be a live song vid_kind = 'live' tags.append('live') elif 'lyrics' in vid_title.lower(): vid_kind = 'lyrics' tags.append('lyrics') elif 'audio' in vid_title.lower(): vid_kind = 'audio' tags.append('audio') ''' else: #Song is longer than 10 minutes, so it must be an album / concert vid_kind = 'album' if ' live ' in vid_title.lower() or ' live ' in vid_description.lower(): vid_kind = 'concert' tags.append('concert') else: tags.append('album') dev.log('(o.o) - Video Kind: '+vid_kind) ##Remove stuff between () [] or - - (like quality indicators, official video and such) from the title vid_title = strip_quality(vid_title) dev.log('Vid title after stripping quality: '+vid_title) new_title = strip_lyrics(vid_title) if vid_title != new_title: dev.log('Tagged as Lyrics video') vid_kind = 'lyrics' tags.append('lyrics') new_title += ' (Lyrics Video)' vid_title = new_title dev.log('Vid title after stripping lyrics: '+vid_title) new_title = strip_live(vid_title) if vid_title != new_title: dev.log('Tagged as Live Video') vid_kind = 'live' tags.append('live') new_title += '(Live)' vid_title = new_title dev.log('Vid title after stripping live: '+vid_title) new_title = strip_audio(vid_title) if vid_title != new_title: dev.log('Tagged as Audio video: '+new_title) vid_kind = 'audio' tags.append('audio') new_title += '(Audio)' vid_title = new_title dev.log('Vid title after stripping audio: '+vid_title+' ('+new_title+')') if setting_skip_audio == 'true' and vid_kind == 'audio': dev.log('Skip_audio is on, and video '+vid_title+' is an audio video', 1) return False if setting_skip_lyrics == 'true' and vid_kind == 'lyrics': dev.log('Skip_lyrics is on, and video '+vid_title+' is a lyric video', 1) return False if setting_skip_albums == 'true' and vid_kind == 'album': dev.log('Skip_albums is on, and video '+vid_title+' is an album video', 1) return False if setting_skip_live == 'true': if vid_kind == 'live' or vid_kind == 'concert': dev.log('Skip_live is on, and video '+vid_title+' is a live or concert video', 1) return False ##Try to find a tracknr m = re.search("\(?\[?(\d{1,2})\s\)\]\s?\.", vid_title) if m: tracknr = m.group(1) vid_title = vid_title.replace(m.group(0), '') dev.log('Vid title after stripping tracknr '+tracknr+': '+vid_title) ##Determine feautered artists featured, vid_title = get_featured(vid_title) ##Determine the Year year, vid_title = get_year(vid_title, vid_description, 'year', settings, vid) if year == False: year, vid_title = get_year(vid_title, vid_description, 'year_fallback', settings, vid) if year == False: #Year recognizition has failed / the fallback was do not add. So do not add this video dev.log('Year not found! Fallback was '+setting_year_fallback+' Video not added: '+vid_title+' ('+vid_id+')', 1) return False ##Try to determine the album from the title album, vid_title = get_album(vid_title, vid_description, 'album', settings, vid) if album == False: album, vid_title = get_album(vid_title, vid_description, 'album_fallback', settings, vid) ##Determine the artist & song #If the setting artist is hardcoded & the song recognisition is set to video title, we should leave it at that if setting_artist == 'hardcoded' and setting_song_fallback == 'video title' or setting_artist == 'hardcoded' and setting_song_fallback == 'video title (original)': dev.log('Artist is hardcoded and song is set to video title, leave it at that: '+setting_artist_hardcoded) artist = setting_artist_hardcoded song = vid_title featured = '' else: artist, song = get_artist_song(vid_title, vid_description, 'artist', settings, vid) #dev.log('after get_artist_song, Artist - Song: '+str(artist)+' - '+song) if artist == False: artist = get_hardcoded('artist_fallback', settings, vid) if artist == False: dev.log('Artist not found! Fallback was '+setting_artist_fallback+', video: '+vid_title+' ('+vid_id+') not added') return False else: ft = get_multiple_artists(artist) if ft != False: artist = ft.pop(0) #Assume the first artist is the main artist featured = ft artist = strip_audio(artist) #Strip (audio) and such from the name artist = strip_live(artist) #Strip live and such from the artist artist = strip_lyrics(artist) #Strip lyrics and such from the artist if song == False: song = get_hardcoded('song_fallback', settings, vid) if song == False: dev.log('Song not found! Fallback was '+setting_song_fallback+', video: '+vid_title+' ('+vid_id+') not added') return False dev.log('Artist - Song atm: '+artist+' - '+song) ##If the video kind is an album, the album is the song title if album == False: if vid_kind == 'album': album = song if album == False: dev.log('Album not found! Fallback was '+setting_album_fallback+', video: '+vid_title+' ('+vid_id+') not added') return False ##Determine the plot plot = get_plot(vid_description, 'plot', settings, vid) if plot == False: plot = get_plot(vid_description, 'plot_fallback', settings, vid) if plot == False: dev.log('Plot not found! Fallback was '+setting_plot_fallback+', video: '+vid_title+' ('+vid_id+') not added') return False ##Determine the genre genre = setting_genre_hardcoded if setting_song_fallback == 'video title (original)': song = vid['snippet']['title'] dev.log('Artist - Song determined: '+artist+' - '+song) if song is not None and setting_artist_hardcoded is not None: if song.lower().strip(' \t\n\r') == setting_artist_hardcoded.lower().strip(' \t\n\r'): dev.log('Turn around Artist & song, since we got that the wrong way around') #Assume we got the artist - song the wrong way around artist = song song = setting_artist_hardcoded vid_info = { 'title': song, 'artist': artist, 'album': album, 'genre': genre, #'runtime': ?, 'plot': plot, 'year': year, 'track': tracknr, 'tracknr': tracknr, 'studio': studio, 'tags': tags, 'featured': featured, } return vid_info #Strip unwanted text from the text def strip_album(text): album = ['complete album', 'full album'] text = strip_from_text(text, album) return remove_extra_spaces(text) def strip_audio(text): audio = ['audio only', 'audio'] text = strip_from_text(text, audio) return remove_extra_spaces(text) def strip_lyrics(text): lyrics = ['Lyrics on screen', 'with lyrics', 'w/ lyrics', 'lyric video', 'lyrics video', 'lyrics', 'lyric'] text = strip_from_text(text, lyrics) return remove_extra_spaces(text) def strip_live(text): live = ['live'] text = strip_from_text(text, live) return remove_extra_spaces(text) def strip_artist(text): artists = ['the music group', 'the band', 'the group'] text = strip_from_text(text, artists) return remove_extra_spaces(text) def strip_quality(text): hooks = ['original video with subtitles', 'original video', 'extended music video', 'Official Music Video', 'music video' 'official video hd', 'Official Video','Videoclip', 'Video Clip', 'video', 'clip officiel', 'clip', 'official', 'officiel'] text = strip_from_text(text, hooks) qualitys = ['hd 1080p', 'hd 720p', '1080p hd', '720p hd', '1080p quality', '720p quality', 'dvd quality', 'hd quality', 'high quality', '1080p', '720p', 'hd', 'hq'] text = strip_from_text(text, qualitys) return remove_extra_spaces(text) #Strips a list of words from the text, first checking ()[]-- and then -, and then just the word #text: The text to replace in #checks: List of words to replace in the text def strip_from_text(text, checks): for check in checks: #dev.log('Replacing '+check) #First check if for it between () or [] or - - regex = re.compile("(\(|\[|-)\s*"+re.escape(check)+"\s*(\)|\]|-)", re.IGNORECASE) #dev.log('Regex: '+ regex.pattern ) text = re.sub(regex, '', text) #Then check for them from the beginning of the string with a - behind it regex = re.compile("^\s*"+re.escape(check)+"\s*-\s*", re.IGNORECASE) text = re.sub(regex, '', text) #Then check for them with just in the text regex = re.compile(re.escape(check), re.IGNORECASE) text = re.sub(regex, '', text) return text #Removes spaces that are to much from the text, also strips spaces, tabs, returns, newlines & quotes from both ends of text def remove_extra_spaces(text): while ' ' in text: text = text.replace(' ', ' ') return text.strip(' \t\n\r"\'') def get_hardcoded(setting, settings, vid): if settings.find(setting).text == 'hardcoded': setting = setting.replace('_fallback', '') return dev.get_setting(setting+'_hardcoded', settings) if settings.find(setting).text == 'playlist channelname': return settings.find('channel').text if settings.find(setting).text == 'video channelname': return vid['snippet']['channelTitle'] if settings.find(setting).text == 'published year': return ytube.convert_published(vid['snippet']['publishedAt'])['year'] if settings.find(setting).text == 'playlist description': return settings.find('description').text if settings.find(setting).text == 'video description': return vid['snippet']['description'] return False def get_featured(text): text = strip_audio(text) text = strip_lyrics(text) text = strip_live(text) regex = "(ft|vs|featuring|with|w\/)\s*\.?\:?\s*([^-\n\r]+)" m = re.search(regex, text, re.IGNORECASE) if m: ft = get_multiple_artists(m.group(2)) if ft == False: return [m.group(2)], text #Replace the found string in the text text = text.replace(m.group(0), '') return ft, text return False, text def get_multiple_artists(text): text = strip_audio(text) text = strip_lyrics(text) text = strip_live(text) splits = [',', '&', 'and', '/'] for spl in splits: #Found featuring artist in the text, see if it are more than one. if spl in text: #Split all artist up by the , return text.split(spl) return False def get_album(vid_title, vid_description, setting, settings, vid): album = get_hardcoded(setting, settings, vid) if album != False: #The album was hardcoded return album, vid_title if settings.find(setting).text == 'video title and description': #First try to get the album from the description. regex = '(the album|as featured on)\s*:?\s*"?([^-\.\n\r"]+)' m = re.search(regex, vid_description) if m: #Album found! return remove_extra_spaces(m.group(2)), vid_title return False, vid_title def get_artist_song(vid_title, vid_description, setting, settings, vid): artist = get_hardcoded(setting, settings, vid) if artist != False: #The artist was hardcoded a, song = find_artist_song(vid_title, artist) if song == False: a, song = find_artist_song_description(vid_description) return artist, song if settings.find(setting).text == 'video title and description': #Try to grab the information #First try to get the artist and title from the video title. Like a normal title artist, song = find_artist_song(vid_title, dev.get_setting('artist_hardcoded', settings)) if artist != False and song != False: #Succesfull return artist, song #Artist - song lookup failed, more drastic lookups are needed artist, song = find_artist_song_description(vid_description) if artist != False and song != False: #Succesfull return artist, song return False, False def find_artist_song(text, hardcoded_artist): regex = "^([^-\n]+)(-|by|\|)\s*([^-:\n]+)$" m = re.search(regex, text, re.IGNORECASE) if m: dev.log('find_artist_song() Found Artist - Song: '+str(m.group(1).encode('UTF-8'))+' - '+str(m.group(3).encode('UTF-8'))) artist = remove_extra_spaces(m.group(1)) song = remove_extra_spaces(m.group(3)) if m.group(2) == 'by' or song.lower() == hardcoded_artist.lower(): #Turn artist and song around artist = m.group(3) song = m.group(1) if '(' in artist and ')' not in artist or ')' in artist and '(' not in artist: return False, False return artist, song return False, False def find_artist_song_description(text): regex = "([^\n\r]+)('s|')\s*(official)?\s*(music)?\s*video\s*for\s*([^\.\n\r]*)" m = re.search(regex, text, re.IGNORECASE) if m: dev.log('find_artist_song_description() 1st: Found Artist - Song: '+str(m.group(1)).encode('utf-8')+' - '+str(m.group(5)).encode('utf-8')) #found the artist and song artist = m.group(1) song = m.group(5) return artist, song regex = "(official)?\s*(music)?\s*video\s*for\s*(.*?)('|’)?s[^'\"‘]*('|\"|‘)(.*)('|\"|’)" m = re.search(regex, text, re.IGNORECASE) if m: dev.log('find_artist_song_desription() 5th: Found Artist - Song: '+str(m.group(3)).encode('utf-8')+' - '+str(m.group(6)).encode('utf-8')) #Found the artist and song return m.group(3), m.group(6) if m: dev.log('find_artist_song_description() 1st: Found Artist - Song: '+str(m.group(1)).encode('utf-8')+' - '+str(m.group(5)).encode('utf-8')) #found the artist and song artist = m.group(1) song = m.group(5) return artist, song regex = "(.*?)\s*(by|from|performing|\|)\s*(?:([^,\n\r-]*?)\s*(by|from|performing))?\s*([^,\.\n\r-]*)" #regex_complex = "(.*?)\s*(?:is a song)?\s*(-|by|from(?! their album)|performing|\|)\s*(?:([^,\n\r]*?)\s*(-|by|from(?! their album)|performing))?\s*([^,\.\n\r]*)" m = re.search(regex, text, re.IGNORECASE) if m: #found the artist and song if m.group(4) == 'performing': dev.log('find_artist_song_description() Found Artist - Song 2nd: '+str(m.group(3)).encode('utf-8')+' - '+str(m.group(5)).encode('utf-8')) artist = m.group(3) song = m.group(5) return artist, song artist = m.group(1) song = m.group(3) if song is None: regex = re.compile(re.escape("\s*(official\s*)?(music\s*)?video\s*for\s*"), re.IGNORECASE) artist = regex.sub('', artist) #Replace official music video for in the artist, since it could be left over song = m.group(5) if m.group(2) == 'by' or m.group(2) == 'from': dev.log('find_artist_song_description() Found Artist - Song 3th: '+str(song.encode('UTF-8'))+' - '+str(m.group(1).encode('UTF-8'))) artist = song song = m.group(1) return artist, song dev.log('find_artist_song_description() Found Artist - Song 4th: '+str(artist.encode('utf-8'))+' - '+str(song.encode('utf-8'))) dev.log('Description: '+text) return artist, song return False, False def get_year(vid_title, vid_description, setting, settings, vid): year = get_hardcoded(setting, settings, vid) if year != False: return year, vid_title if settings.find(setting).text == 'video title and description': year, vid_title = find_year(vid_title) if year != False: return year, vid_title year, vid_description = find_year(vid_description) if year != False: return year, vid_title return False, vid_title def find_year(text): #regex = "(\(?()c(opyright)?\)?)?\s*(\d{4})" #Group 4 = year regex = "(?:copyright|\(c\)|c)?\s*\[?\(?\s*(\d{4})\s*\)?\]?" dev.log('finding the year... ') m = re.search(regex, text, re.IGNORECASE) if m: dev.log('found the year') if len(m.group(1)) == 4: #Found a year! dev.log(u'Found a year!: '+m.group(1)+ ' Whole match: '+m.group(0)) text = text.replace(m.group(0), '') #Remove the copyright / year notice from the title return m.group(1), text return False, text def get_plot(vid_description, setting, settings, vid): plot = get_hardcoded(setting, settings, vid) if plot != False: return plot if settings.find(setting).text == 'lyrics in video description': regex = "lyrics\s*:?(?:\n|\r|\s)*((?:\n|\r|.)*)" m = re.search(regex, vid_description) if m: if len(m.group(1)) > 1: return m.group(1) return False ###### STRM GENERATOR ############## #Creates a .strm file # Name : The name of the strm file # folder: The name of the folder the strm file should be written in (Not the mainfolder, but the name of the show, so the strms get in that subdir) # videoid: The videoid of the youtube video we want to make a strm off # startpoint: The startpoint of the video (for starters, if you want it to start at 10:00 minutes in the video) # endpoint: The endpoint of the video (if you want to stop the video at 20:00 minutes) # # show: The name of the show (needed for a .strm file to this addon) # season: The season of this episode # episode: The episode number of this episode # # type: tv (''), musicvideo, music, movies # # artist: The artist # song: The song # album: The album # year: The year # # Returns the filename (without .strm) def write_strm(name, fold, videoid, show=None, season=None, episode=None, startpoint = None, endpoint = None, artist='', album='', song='', year='', type=''): #dev.log('strm('+name+', '+fold+', '+videoid+')') movieLibrary = vars.tv_folder #The path we should save in is the vars.tv_folder setting from the addon settings if type=='musicvideo': movieLibrary = vars.musicvideo_folder if type=='movies': movieLibrary = vars.movies_folder sysname = urllib.quote_plus(videoid) #Escape strings in the videoid if needed enc_name = dev.legal_filename(name) #Encode the filename to a legal filename folder = os.path.join(movieLibrary, fold) #Set the folder to the maindir/dir if vars.__settings__.getSetting("strm_link") == "Youtube Library": if type == 'musicvideo': content = 'plugin://plugin.video.youtubelibrary/?mode=playmusicvideo' if startpoint != None: content += '&startpoint='+startpoint if endpoint != None: content += '&endpoint='+endpoint content += '&id=%s&artist=%s&song=%s&album=%s&year=%s&filename=%s' % (sysname, artist, song, album, year, enc_name) #Set the content of the strm file with a link back to this addon for playing the video elif type == 'movies': content = 'plugin://plugin.video.youtubelibrary/?mode=playmovie&id=%s&filename=%s&folder=%s' % (sysname, enc_name, fold) #Set the content of the strm file with a link back to this addon for playing the video else: content = 'plugin://plugin.video.youtubelibrary/?mode=play&id=%s&show=%s&season=%s&episode=%s&filename=%s' % (sysname, show, season, episode, enc_name) #Set the content of the strm file with a link back to this addon for playing the video else: content = vars.KODI_ADDONLINK+'%s' % ( sysname) #Set the content of the strm file with a link to the official Kodi Youtube Addon xbmcvfs.mkdir(movieLibrary) #Create the maindirectory if it does not exists yet xbmcvfs.mkdir(folder) #Create this subfolder if it does not exist yet if type == '' or type == 'tv': folder = os.path.join(folder, 'Season '+season) #Set the folder to the maindir/dir xbmcvfs.mkdir(folder) #Create this subfolder if it does not exist yet stream = os.path.join(folder, enc_name + '.strm') #Set the file to maindir/name/name.strm file = xbmcvfs.File(stream, 'w') #Open / create this file for writing file.write(str(content.encode('UTF-8'))) #Write the content in the file file.close() #Close the file dev.log('write_strm: Written strm file: '+fold+'/'+enc_name+'.strm') return enc_name #### FILTERS #### def removetitle(title, removetitle): if removetitle == None: removetitle = '' if len(removetitle) > 0: #See if there are multiple lines if '|' in removetitle: strip = removetitle.split('|') for s in strip: #Check if we should do regex r = reg(s, title) if r is not None: s = r if s in title: title = title.replace(s, '') #Remove this line from the title else: #Check if this is a regex var of what should be removed rem = reg(removetitle, title) if rem is not None: removetitle = rem #Regex was succesfull, set removetitle to the found string so it can be removed as normal title = re.sub(removetitle, '', title, flags=re.IGNORECASE) #if removetitle in title: #title = title.replace(removetitle, '') return title def striptitle(title, striptitle): if striptitle == None: striptitle = '' if len(striptitle) > 0: #See if there are multiple lines if '|' in striptitle: strip = striptitle.split('|') for s in strip: if s in title: title = title[:title.index(s)] #Strip everything to the point where the line was found else: #Check if this is a regex var of what should be removed rem = reg(title, striptitle) if rem is not None: striptitle = rem #Regex was succesfull, set striptitle to the found string so it can be stripped as normal if striptitle in title: title = title[:title.index(striptitle)] #Strip everything to the point where the line was found return title def removedescription(description, removedescription): if removedescription == None: removedescription = '' if len(removedescription) > 0: #See if there are multiple lines if '|' in removedescription: strip = removedescription.split('|') for s in strip: if s in description: description = description.replace(s, '') #Remove this line from the description else: #Check if this is a regex var of what should be removed rem = reg(description, removedescription) if rem is not None: removedescription = rem #Regex was succesfull, set removedescription to the found string so it can be removed as normal if removedescription in description: description = description.replace(removedescription, '') return description def stripdescription(description, stripdescription): if stripdescription == None: stripdescription = '' if len(stripdescription) > 0: #See if there are multiple lines if '|' in stripdescription: strip = stripdescription.split('|') for s in strip: if s in description: description = description[:description.index(s)] #Strip everything to the point where the line was found else: #Check if this is a regex var of what should be removed rem = reg(description, stripdescription) if rem is not None: stripdescription = rem #Regex was succesfull, set stripdescription to the found string so it can be stripped as normal if stripdescription in description: description = description[:description.index(stripdescription)] #Strip everything to the point where the line was found return description ## Movies #Strip unwanted text from the text def movie_strip_full(text): album = ['fullhdmovie', 'fullmovie', 'full movie online', 'full movie', 'complete movie', 'new movie', 'movie', 'fullhdfilm', 'fullfilm', 'full film online', 'full film', 'complete film', 'new film', 'film', 'cabaret', 'w / subtitles', 'with subtitles', 'new', 'free'] text = strip_from_text(text, album) return remove_extra_spaces(text) def movie_strip_quality(text): album = ['fullhd', 'hdready', 'hd', '720p', '1080p', 'sd', 'dvd quality', 'dvd', 'good quality'] text = strip_from_text(text, album) return remove_extra_spaces(text) def movie_get_year(vid_title, vid_description): year, vid_title = find_year(vid_title) if year != False: return year, vid_title, vid_description year, vid_description = find_year(vid_description) if year != False: return year, vid_title, vid_description return False, vid_title, vid_description def movie_get_director(vid_title, vid_description): year, vid_title = find_director(vid_title) if year != False: return year, vid_title, vid_description year, vid_description = find_director(vid_description) if year != False: return year, vid_title, vid_description return False, vid_title, vid_description def find_director(text): regex = "(?:director|directed)\s?(?:by)?\s?:?\s?\n?((?:[^\n.]+))" m = re.search(regex, text, re.IGNORECASE) if m: if len(m.group(1)) > 0: #Found a year! dev.log(u'Found a director!: '+m.group(1)+ ' Whole match: '+m.group(0)) text = text.replace(m.group(0), '') #Remove the copyright / year notice from the title return m.group(1), text return False, text def smart_search(info): #Try to get the year year, title, description = movie_get_year(info['title'], info['description']) if year is not False: #Found a year dev.log('Smart search: found a year: '+year) info['year'] = year info['year_new'] = year info['title'] = title info['description'] = description #Try to get the director director, title, description = movie_get_director(info['title'], info['description']) if director is not False: #Found a director dev.log('Smart Search: found a director: '+director) info['director'] = director info['title'] = title info['description'] = description #Strip Unwanted title stuff info['title'] = movie_strip_full(info['title']) info['title'] = movie_strip_quality(info['title']) return info ###### NFO GENERATOR ############## #Creates a .nfo file # Name : The name of the nfo file # folder: The name of the folder the nfo file should be written in (Not the mainfolder, but the name of the show, so the nfo get in that subdir) # vid: THe video youtube response we want to add # settings: The elementtree containing the settings from the playlist # season: The season of the episode #episode: The episode number #duration: The duration of the video #overwrite_title: If you want to use another title than the vid['snippet']['title'] #overwrite_description: Same as above, but for description #type: tv (''), musicvideo, music, movies #movie: movie-information #usefilters: If filters like striptitle should be done on the title #statistics: the statistics object from the ytapi containing ratings and such def write_nfo(name, fold, vid, settings, season='', episode='', duration='0', overwrite_title=None, overwrite_description=None, musicvideo=None, type='', movie=None, usefilters=True, statistics = None): #dev.log('write_nfo('+name+', '+fold+')') info = {} #Create a dict for all info we gonna determine for in the .nfo movieLibrary = vars.tv_folder #Use the directory from the addon settings if type=='musicvideo': movieLibrary = vars.musicvideo_folder elif type=='movies': movieLibrary = vars.movies_folder snippet = vid['snippet'] info['year_new'] = False info['rating'] = '' info['votes'] = '' if statistics is not None: thumbsup = int(statistics['likeCount']) thumbsdown = int(statistics['dislikeCount']) info['votes'] = thumbsup + thumbsdown info['rating'] = thumbsup / info['votes'] info['votes'] = ''+str(info['votes'])+'' info['rating'] = ''+str(info['rating'])+'' info['title'] = snippet['title'] info['original_title'] = snippet['title'] if overwrite_title != None: info['title'] = overwrite_title info['description'] = snippet['description'] if overwrite_description != None: info['description'] = overwrite_description if usefilters is True: info['title'] = removetitle(info['title'], settings.find('removetitle').text) info['title'] = striptitle(info['title'], settings.find('striptitle').text) info['description'] = removedescription(info['description'], settings.find('removedescription').text) info['description'] = stripdescription(info['description'], settings.find('stripdescription').text) #Grab the best possible thumbnail #if 'maxres' in snippet['thumbnails']: # thumbnail = snippet['thumbnails']['maxres'] if 'standard' in snippet['thumbnails']: info['thumbnail'] = snippet['thumbnails']['standard']['url'] elif 'high' in snippet['thumbnails']: info['thumbnail'] = snippet['thumbnails']['high']['url'] elif 'medium' in snippet['thumbnails']: info['thumbnail'] = snippet['thumbnails']['medium']['url'] elif 'default' in snippet['thumbnails']: info['thumbnail'] = snippet['thumbnails']['default']['url'] else: info['thumbnail'] = settings.find('thumbnail').text #Grab the published date and convert it to a normal date d = ytube.convert_published(snippet['publishedAt']) info['normaldate'] = d['year']+'/'+d['month']+'/'+d['day'] info['year'] = d['year'] #Convert the duration (seconds) in number of minutes info['duration'] = duration info['durationminutes'] = int(int(duration) / 60) info['durationhms'] = dev.convert_sec_to_hms(duration) if type == 'musicvideo': content = write_nfo_musicvideo(info, settings, musicvideo) elif type == 'movies': content = write_nfo_movies(info, settings) else: content = write_nfo_tv(info, settings, season, episode) if content is False: #Video got skipped for some reason return False xbmcvfs.mkdir(movieLibrary) #Create the maindirectory if it does not exists yet #enc_name = name.translate('\/:*?"<>|').strip('.') #Escape special characters in the name enc_name = dev.legal_filename(name) folder = os.path.join(movieLibrary, fold) #Set the folder to the maindir/dir xbmcvfs.mkdir(folder) #Create this subfolder if it does not exist yet if type == '' or type == 'tv': folder = os.path.join(folder, 'Season '+season) #Set the folder to the maindir/dir xbmcvfs.mkdir(folder) #Create this subfolder if it does not exist yet stream = os.path.join(folder, enc_name + '.nfo') #Set the file to maindir/name/name.strm file = xbmcvfs.File(stream, 'w') #Open / create this file for writing file.write(str(content.encode("utf-8"))) #Write the content in the file file.close() #Close the file dev.log('write_nfo: Written nfo file: '+fold+'/'+enc_name+'.nfo') def write_nfo_movies(info, settings): #Prepare optional xml tags if they are set info['director'] = settings.find('channel').text set = '' if settings.find('set') and len(settings.find('set').text) > 0: set = ''+settings.find('set').text+'' #If smart_search is enabled, try to grab info like, year, director and actors from the title & description on youtube, and clean up the title and description in the process if settings.find('smart_search').text == '1' or settings.find('smart_search').text == 'true': info = smart_search(info) #Grab the movie information from the generator movie = False if settings.find('search_imdb').text != '2': #2 equals "No, let the addon handle it" movie = scan_movie(info['title'], settings, info['year_new']) if movie == False and settings.find('search_imdb').text == '1': #1 equals "Yes, dont add if IMDB fails" dev.log('Search_imdb is set to "Yes, dont add if IMDB fails", so skipping this movie') return False #Skip this video, it did not make it past the movies filters #Has the movie been found on IMDB? if movie is not False: #The movie has been found on imdb dev.log('The Movie has been found on IMDB; '+movie['image']) dev.log('Use_ytimage is on: '+settings.find('use_ytimage').text) content_art = '' if 'nopicture' in movie['image'] and settings.find('use_ytimage').text is not '3': #3 = never dev.log('No image on imdb found, so using Youtube Image') content_art = """ %(thumb)s %(thumb)s %(banner)s %(fanart)s """ % { 'thumb' : info['thumbnail'], 'banner' : settings.find('banner').text, 'fanart' : info['thumbnail'] } content = """ %(durationminutes)s %(duration)s %(set)s %(art)s %(url)s""" % { 'durationminutes': info['durationminutes'], 'duration': info['duration'], 'art' : content_art, 'url' : movie['url'], 'set' : set, } else: dev.log('This movie has not been found on IMDB') #Create the contents of the xml file content = """ %(title)s %(original_title)s %(plot)s %(genre)s %(year)s Youtube %(director)s %(thumb)s %(thumb)s %(banner)s %(fanart)s %(durationminutes)s %(duration)s %(set)s %(tags)s %(rating)s %(votes)s """ % { 'title': info['title'].strip(' \t\n\r'), 'original_title' : info['original_title'].strip(' \t\n\r'), 'plot': info['description'].strip(' \t\n\r'), 'genre' : settings.find('genre').text, 'year' : info['normaldate'], 'director': info['director'], 'thumb': info['thumbnail'], 'banner' : settings.find('banner').text, 'fanart' : info['thumbnail'], 'date': info['normaldate'], 'durationminutes': info['durationminutes'], 'duration': info['duration'], 'set' : set, 'tags' : get_tags_xml(settings), 'rating' : info['rating'], 'votes' : info['votes'], } return content ## TV def write_nfo_tv(info, settings, season, episode): content = """ %(title)s %(season)s %(episode)s %(plot)s %(thumb)s %(channel)s %(channel)s %(date)s %(date)s Youtube %(durationminutes)s %(duration)s """ % { 'title': info['title'].strip(' \t\n\r'), 'plot': info['description'].strip(' \t\n\r'), 'channel': settings.find('channel').text, 'thumb': info['thumbnail'], 'date': info['normaldate'], 'season': season, 'episode': episode, 'durationminutes': info['durationminutes'], 'duration': info['duration'] } return content ## MUSICVIDEO (create the content of the .nfo file for musicvideos) # musicvideo : list containing info about the musicvideo def write_nfo_musicvideo(info, settings, musicvideo): if musicvideo == None: return False #Grab the featured artists and convert them to xml featured_xml = '' if musicvideo['featured'] != False: for artist in musicvideo['featured']: featured_xml += ''+artist.strip(' \t\n\r')+'' #Grab the tags and convert them to xml tags_xml = '' if musicvideo['tags'] != False: for tag in musicvideo['tags']: tags_xml += ''+tag.strip(' \t\n\r')+'' tags = settings.find('tags') if tags is not None: tags = settings.find('tags').text if '/' in tags: multi_tags = tags.split('/') for tag in multi_tags: tags_xml += ''+tag.strip(' \t\n\r')+'' elif tags.strip(' \t\n\r') is not '': tags_xml += ''+tags.strip(' \t\n\r')+'' genre = musicvideo['genre'] if genre is None: genre = '' #Create the contents of the xml file content = """ %(title)s %(artist)s %(featured)s %(album)s %(genre)s %(durationminutes)s %(plot)s %(year)s %(studio)s %(tracknr)s %(thumb)s %(fanart)s %(duration)s %(tags)s """ % { 'title': musicvideo['title'].strip(' \t\n\r'), 'artist': musicvideo['artist'].strip(' \t\n\r'), 'featured': featured_xml, 'album': musicvideo['album'].strip(' \t\n\r'), 'genre': genre.strip(' \t\n\r'), 'plot': musicvideo['plot'].strip(' \t\n\r'), 'year': musicvideo['year'].strip(' \t\n\r'), 'studio': musicvideo['studio'].strip(' \t\n\r'), 'thumb': info['thumbnail'], 'durationhms': info['durationhms'], 'tracknr': musicvideo['tracknr'].strip(' \t\n\r'), 'fanart': settings.find('fanart').text, 'tags': tags_xml, 'durationminutes': info['durationminutes'], 'duration': info['duration'] } return content #Gets the tags from the settings and converts it to xml for the .nfo file def get_tags_xml(settings): tags = settings.find('tags') tags_xml = '' if tags is not None: tags = settings.find('tags').text if '/' in tags: multi_tags = tags.split('/') tags_xml = '' for tag in multi_tags: tags_xml += ''+tag.strip(' \t\n\r')+'' elif tags.strip(' \t\n\r') is not '': tags_xml = ''+tags.strip(' \t\n\r')+'' return tags_xml #Writes the NFO for the tvshow #fold: The folder the nfo should be written to #settings: The elementtree element containing the playlist xml settings def write_tvshow_nfo(fold, settings): dev.log('write_tvshow_nfo('+fold+')') name = 'tvshow' movieLibrary = vars.tv_folder #Use the directory from the addon settings #Grab the published date and convert it to a normal date d = ytube.convert_published(settings.find('published').text) normaldate = d['year']+'-'+d['month']+'-'+d['day'] #Grab the tags and convert them to xml tags_xml = get_tags_xml(settings) #Create the contents of the xml file content = u""" %(title)s %(title)s %(year)s %(plot)s %(genre)s %(date)s %(date)s %(studio)s %(thumb)s %(thumb)s %(banner)s %(fanart)s %(tags)s """ % { 'title': settings.find('title').text, 'plot': settings.find('description').text, 'year': d['year'], 'genre': settings.find('genre').text, 'studio': settings.find('channel').text, 'thumb': settings.find('thumb').text, 'banner': settings.find('banner').text, 'fanart': settings.find('fanart').text, 'date': normaldate, 'tags': tags_xml, } xbmcvfs.mkdir(movieLibrary) #Create the maindirectory if it does not exists yet enc_name = dev.legal_filename(name) #Set the filename to a legal filename folder = os.path.join(movieLibrary, fold) #Set the folder to the maindir/dir xbmcvfs.mkdir(folder) #Create this subfolder if it does not exist yet stream = os.path.join(folder, enc_name + '.nfo') #Set the file to maindir/name/name.strm import codecs # process Unicode text #with codecs.open(stream,'w',encoding='utf8') as f: # f.write(content) # f.close() file = xbmcvfs.File(stream, 'w') #Open / create this file for writing file.write(str(content.encode("utf-8"))) #Write the content in the file file.close() #Close the file dev.log('write_tvshow_nfo: Written tvshow.nfo file: '+fold+'/'+enc_name+'.nfo') #If the setting download_images is true, we should also download the images as actual files into the directory if vars.__settings__.getSetting("download_images") == "true": extrafanart = os.path.join(folder, 'extrafanart') #Set to extrafanart xbmcvfs.mkdir(extrafanart) #Create this subfolder if it does not exist yet dev.log('download_images enabled, so downloading images to '+folder) download_img(settings.find('thumb').text, folder+"/folder.jpg") download_img(settings.find('banner').text, folder+"/banner.jpg") download_img(settings.find('fanart').text, folder+"/fanart.jpg") download_img(settings.find('fanart').text, extrafanart+"/fanart.jpg") #Writes the NFO for the artist #fold: The folder the nfo should be written to #settings: The elementtree element containing the playlist xml settings def write_artist_nfo(fold, settings): dev.log('write_artist_nfo('+fold+')') name = 'artist' movieLibrary = vars.musicvideo_folder #Use the directory from the addon settings #Grab the published date and convert it to a normal date d = ytube.convert_published(settings.find('published').text) normaldate = d['year']+'-'+d['month']+'-'+d['day'] #Grab the tags and convert them to xml tags = settings.find('tags') tags_xml = '' if tags is not None: tags = settings.find('tags').text if '/' in tags: multi_tags = tags.split('/') tags_xml = '' for tag in multi_tags: tags_xml += ''+tag.strip(' \t\n\r')+'' elif tags.strip(' \t\n\r') is not '': tags_xml = ''+tags.strip(' \t\n\r')+'' #Create the contents of the xml file content = u""" %(title)s %(genre)s %(date)s %(thumb)s %(thumb)s %(banner)s %(fanart)s %(tags)s """ % { 'title': settings.find('title').text, 'genre': settings.find('genre').text, 'thumb': settings.find('thumb').text, 'banner': settings.find('banner').text, 'fanart': settings.find('fanart').text, 'date': normaldate, 'tags': tags_xml, } xbmcvfs.mkdir(movieLibrary) #Create the maindirectory if it does not exists yet enc_name = dev.legal_filename(name) #Set the filename to a legal filename folder = os.path.join(movieLibrary, fold) #Set the folder to the maindir/dir xbmcvfs.mkdir(folder) #Create this subfolder if it does not exist yet stream = os.path.join(folder, enc_name + '.nfo') #Set the file to maindir/name/name.strm import codecs # process Unicode text #with codecs.open(stream,'w',encoding='utf8') as f: # f.write(content) # f.close() file = xbmcvfs.File(stream, 'w') #Open / create this file for writing file.write(str(content.encode("utf-8"))) #Write the content in the file file.close() #Close the file dev.log('write_tvshow_nfo: Written artist.nfo file: '+fold+'/'+enc_name+'.nfo') #If the setting download_images is true, we should also download the images as actual files into the directory if vars.__settings__.getSetting("download_images") == "true": extrafanart = os.path.join(folder, 'extrafanart') #Set to extrafanart xbmcvfs.mkdir(extrafanart) #Create this subfolder if it does not exist yet dev.log('download_images enabled, so downloading images to '+folder) download_img(settings.find('thumb').text, folder+"/folder.jpg") download_img(settings.find('banner').text, folder+"/banner.jpg") download_img(settings.find('fanart').text, folder+"/fanart.jpg") download_img(settings.find('fanart').text, extrafanart+"/fanart.jpg") def download_img(thumbUrl, filename, overwrite=False): #import codecs #import zipfile #import time #import fnmatch #import util, helper #from util import * import urllib2 import xbmcgui import os.path import tempfile if os.path.isfile(filename) and overwrite is False: return False # fetch thumbnail and save to filepath try: target = filename if(filename.startswith('smb://')): #download file to local folder and copy it to smb path with xbmcvfs target = os.path.join(tempfile.gettempdir(), os.path.basename(filename)) req = urllib2.Request(thumbUrl) req.add_unredirected_header('User-Agent', 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.0.1410.64 Safari/537.31') f = open(target,'wb') f.write(urllib2.urlopen(req).read()) f.close() if(filename.startswith('smb://')): xbmcvfs.copy(target, filename) xbmcvfs.delete(target) except Exception, (exc): xbmcgui.Dialog().ok('ERROR', 'Could not create image!') dev.log("ERROR: Could not create file: '%s'. Error message: '%s'" %(str(filename), str(exc))) """def download_img(url, filename, overwrite=False): import os.path if os.path.isfile(filename) and overwrite is False: return False try: urllib.urlretrieve(url, filename) except IOError: #Replace the https with http try: urllib.urlretrieve(url.replace('https://', 'http://'), filename) except: dev.log('Could not download: '+url, 1) return True """