1092 lines
46 KiB
Python
1092 lines
46 KiB
Python
# -*- coding: utf-8 -*-
|
|
from __future__ import unicode_literals
|
|
from kodi_six import xbmc, xbmcaddon, xbmcgui, xbmcplugin
|
|
import sys
|
|
import os
|
|
import urllib
|
|
import json
|
|
import io
|
|
import re
|
|
import time
|
|
from datetime import date, datetime, timedelta
|
|
import random
|
|
from unidecode import unidecode
|
|
|
|
addonID = 'plugin.video.vidfltr'
|
|
addon = xbmcaddon.Addon(id=addonID)
|
|
pluginhandle = int(sys.argv[1])
|
|
translation = addon.getLocalizedString
|
|
addonDir = xbmc.translatePath(addon.getAddonInfo('path'))
|
|
defaultFanart = os.path.join(addonDir, 'resources/images/noicon.png')
|
|
#fanart = os.path.join(addonDir, 'noicon.png')
|
|
# don't use special folder icons as long as there is'nt a nice icon for every style
|
|
fanart = 'DefaultFolder.png'
|
|
icon = os.path.join(addonDir, 'noicon.png')
|
|
addon_work_folder = xbmc.translatePath("special://profile/addon_data/" + addonID)
|
|
jsonVideos = xbmc.translatePath("special://profile/addon_data/" + addonID + "/videos.json")
|
|
jsonArtists = xbmc.translatePath("special://profile/addon_data/" + addonID + "/artists.json")
|
|
jsonStyles = xbmc.translatePath("special://profile/addon_data/" + addonID + "/styles.json")
|
|
maxFileAge = int(addon.getSetting("maxFileAge"))
|
|
maxFileAge = maxFileAge * 60
|
|
mediatype = addon.getSetting("mediatype")
|
|
# show only official, fanmade or all videos?
|
|
videoselection = str(addon.getSetting("videoselection")).lower()
|
|
# despite this selection show all in similar playlists and more from?
|
|
relatedselection = str(addon.getSetting("relatedselection")).lower()
|
|
playLocalFile = str(addon.getSetting("playLocalFile")).lower()
|
|
filesinlists = int(addon.getSetting("filesinlists"))
|
|
useYTDL = addon.getSetting("useytdl")
|
|
# show if video is unofficial in title
|
|
showunoffintitle = addon.getSetting("showunoffintitle")
|
|
# summed up provider playcount considered to be played often
|
|
pcounthigh = int(addon.getSetting("pcounthigh"))
|
|
# local playcount considered to be played often
|
|
lcounthigh = int(addon.getSetting("lcounthigh"))
|
|
# addon developer playcount considered to be often played
|
|
acounthigh = int(addon.getSetting("acounthigh"))
|
|
# summed up provider comment count considered to be high
|
|
ccounthigh = int(addon.getSetting("ccounthigh"))
|
|
# like count considered to be high
|
|
likecounthigh = int(addon.getSetting("likecounthigh"))
|
|
# dislike count considered to be high
|
|
dislikecounthigh = int(addon.getSetting("dislikecounthigh"))
|
|
# needed for comapatibilty mode used with KODI <= 17 aka Krypton
|
|
xbmcversion = int(xbmc.getInfoLabel('System.BuildVersion')[:2])
|
|
|
|
# play from here. Does work in general but refreshes the container which is bit contra-productive in random lists ;)
|
|
# XBMC.PlayMedia(plugin://plugin.video.vidfltr/?mode=sortTitlesBy&url=year%7crandom%7c-1,isdir)
|
|
|
|
if not os.path.isdir(addon_work_folder):
|
|
os.mkdir(addon_work_folder)
|
|
|
|
def getVideos():
|
|
if not os.path.isfile(jsonVideos):
|
|
updateData()
|
|
else:
|
|
fileTime = os.path.getmtime(jsonVideos)
|
|
now = time.time()
|
|
if now - fileTime > maxFileAge:
|
|
updateData()
|
|
with io.open(jsonVideos, 'r', encoding='utf-8') as f:
|
|
data = json.load(f)
|
|
return data
|
|
|
|
def getArtists():
|
|
if not os.path.isfile(jsonArtists):
|
|
updateData()
|
|
else:
|
|
fileTime = os.path.getmtime(jsonArtists)
|
|
now = time.time()
|
|
if now - fileTime > maxFileAge:
|
|
updateData()
|
|
with io.open(jsonArtists, 'r', encoding='utf-8') as f:
|
|
data = json.load(f)
|
|
return data
|
|
|
|
def getStyles():
|
|
if not os.path.isfile(jsonStyles):
|
|
updateData()
|
|
else:
|
|
fileTime = os.path.getmtime(jsonStyles)
|
|
now = time.time()
|
|
if now - fileTime > maxFileAge:
|
|
updateData()
|
|
with io.open(jsonStyles, 'r', encoding='utf-8') as f:
|
|
data = json.load(f)
|
|
return data
|
|
|
|
def updateData():
|
|
try:
|
|
target = urllib.URLopener()
|
|
target.retrieve("https://vidfltr.slashproc.org/api/videos.json", jsonVideos)
|
|
target = urllib.URLopener()
|
|
target.retrieve("https://vidfltr.slashproc.org/api/artists.json", jsonArtists)
|
|
target = urllib.URLopener()
|
|
target.retrieve("https://vidfltr.slashproc.org/api/styles.json", jsonStyles)
|
|
except:
|
|
# return to getVideos/getArtists on network error or 404 or...
|
|
# ...to make it not fatal if the json can't be updated
|
|
return
|
|
|
|
def alphabet():
|
|
alphabet = ['#']
|
|
for letter in range(65, 91):
|
|
alphabet.append(chr(letter))
|
|
return alphabet
|
|
|
|
def main():
|
|
addDir(translation(30029), '', 'mainrandom', fanart)
|
|
addDir(translation(30012), '', 'mainsorted', fanart)
|
|
addDir(translation(30022), '&sort=all', 'styles', fanart)
|
|
addDir(translation(30005), '', 'artists', fanart)
|
|
addDir(translation(30118), '', 'countrycodes', fanart)
|
|
addDir(translation(30001), '', 'search', fanart)
|
|
addDir(translation(30006), '', 'updateData', fanart)
|
|
endOfDirectory()
|
|
|
|
|
|
def artists():
|
|
for alpha in alphabet():
|
|
addDir(alpha, '&style=artists&limit=' + alpha + '&start=0', 'showArtist', fanart)
|
|
endOfDirectory()
|
|
|
|
def countrycodes():
|
|
data = getArtists()
|
|
result = []
|
|
for entry in data:
|
|
countrycode = entry['countrycode']
|
|
if countrycode != "" and countrycode not in result:
|
|
result.append(countrycode)
|
|
elif countrycode == "" and "unknown" not in result:
|
|
result.append('unknown')
|
|
result.sort()
|
|
for entry in result:
|
|
addDir(entry, '&style=country&limit=' + entry + '&start=0', 'showArtist', fanart, len(result))
|
|
endOfDirectory()
|
|
|
|
def mainrandom():
|
|
addDir(translation(30034), '&limit=year&sort=random&start=0', 'sortTitlesBy', fanart)
|
|
addDir(translation(30035), '&limit=year&sort=random&start=0&hit=true', 'sortTitlesBy', fanart)
|
|
addDir(translation(30017), '&limit=all&sort=random&start=0', 'sortTitlesBy', fanart)
|
|
addDir(translation(30031), '&limit=all&sort=random&start=0&hit=true', 'sortTitlesBy', fanart)
|
|
addDir(translation(30117), '&sort=random', 'numbers', fanart)
|
|
addDir(translation(30022), '&sort=random', 'styles', fanart)
|
|
endOfDirectory()
|
|
|
|
def mainsorted():
|
|
addDir(translation(30032), '&limit=all&sort=date&start=0', 'sortTitlesBy', fanart)
|
|
addDir(translation(30033), '&limit=all&sort=date&start=0&hit=true', 'sortTitlesBy', fanart)
|
|
addDir(translation(30117), '&sort=sorted', 'numbers', fanart)
|
|
addDir(translation(30022), '&sort=sorted', 'styles', fanart)
|
|
endOfDirectory()
|
|
|
|
def numbers():
|
|
if sort == "random":
|
|
addDir(translation(30111), '&limit=all&sort=random&start=0&count=pcount', 'sortTitlesBy', fanart)
|
|
addDir(translation(30113), '&limit=all&sort=random&start=0&count=acount', 'sortTitlesBy', fanart)
|
|
addDir(translation(30114), '&limit=all&sort=random&start=0&count=comments', 'sortTitlesBy', fanart)
|
|
addDir(translation(30115), '&limit=all&sort=random&start=0&count=likes', 'sortTitlesBy', fanart)
|
|
addDir(translation(30116), '&limit=all&sort=random&start=0&count=dislikes', 'sortTitlesBy', fanart)
|
|
addDir(translation(30119), '&limit=all&sort=random&start=0&count=controversial', 'sortTitlesBy', fanart)
|
|
endOfDirectory()
|
|
if sort == "sorted":
|
|
addDir(translation(30111), '&limit=all&sort=count&start=0&count=pcount', 'sortTitlesBy', fanart)
|
|
addDir(translation(30113), '&limit=all&sort=count&start=0&count=acount', 'sortTitlesBy', fanart)
|
|
addDir(translation(30114), '&limit=all&sort=count&start=0&count=comments', 'sortTitlesBy', fanart)
|
|
addDir(translation(30115), '&limit=all&sort=count&start=0&count=likes', 'sortTitlesBy', fanart)
|
|
addDir(translation(30116), '&limit=all&sort=count&start=0&count=dislikes', 'sortTitlesBy', fanart)
|
|
addDir(translation(30119), '&limit=all&sort=count&start=0&count=controversial', 'sortTitlesBy', fanart)
|
|
endOfDirectory()
|
|
|
|
|
|
def play(url):
|
|
data = getVideos()
|
|
result = []
|
|
slug = url
|
|
if slug != "":
|
|
for entry in data:
|
|
if entry['slug'] == slug:
|
|
# play from local file.
|
|
# does only work on addon developer pc -- atleast for now
|
|
if playLocalFile == "true":
|
|
musicvideoid = "-1"
|
|
try:
|
|
# try to find music video in library with utc time from slug
|
|
dateadded = datetime.utcfromtimestamp(float(entry['slug']))
|
|
dateadded = str(dateadded)
|
|
jsonRespond = xbmc.executeJSONRPC('{"jsonrpc": "2.0", "params": {"sort": {"order": "ascending", "method": "title"}, "filter": {"operator": "contains", "field": "dateadded", "value": "%s"}, "properties": ["file", "playcount", "genre", "artist", "title"]}, "limits":{"end":0,"start":0}, "method": "VideoLibrary.GetMusicvideos", "id": "libMusicVideos"}' % dateadded)
|
|
local = json.loads(jsonRespond)
|
|
for vid in local["result"]["musicvideos"]:
|
|
playback_url = vid["file"]
|
|
musicvideoid = int(vid["musicvideoid"])
|
|
playcount = int(vid['playcount'])
|
|
genre = vid['genre']
|
|
artist = vid['artist']
|
|
artist = ''.join(artist)
|
|
title = vid['title']
|
|
title = (artist + ' - ' + title )
|
|
xbmc.log(msg="date from slug", level=xbmc.LOGDEBUG)
|
|
|
|
except:
|
|
try:
|
|
# if not found because of inconsistencies based on MEZ vs. MESZ try to find music video in library with time from dateadded
|
|
dateadded = entry['dateadded']
|
|
jsonRespond = xbmc.executeJSONRPC('{"jsonrpc": "2.0", "params": {"sort": {"order": "ascending", "method": "title"}, "filter": {"operator": "contains", "field": "dateadded", "value": "%s"}, "properties": ["file", "playcount", "genre", "artist", "title"]}, "limits":{"end":0,"start":0}, "method": "VideoLibrary.GetMusicvideos", "id": "libMusicVideos"}' % dateadded)
|
|
local = json.loads(jsonRespond)
|
|
for vid in local["result"]["musicvideos"]:
|
|
playback_url = vid["file"]
|
|
musicvideoid = int(vid["musicvideoid"])
|
|
playcount = int(vid['playcount'])
|
|
genre = vid['genre']
|
|
artist = vid['artist']
|
|
artist = ''.join(artist)
|
|
title = vid['title']
|
|
title = (artist + ' - ' + title )
|
|
xbmc.log(msg="date from dateadded", level=xbmc.LOGDEBUG)
|
|
except:
|
|
if useYTDL == "true":
|
|
# if play from local file is set but nonetheless not found use Provider-URL
|
|
xbmc.log(msg="not found, play from provider", level=xbmc.LOGDEBUG)
|
|
import YDStreamExtractor
|
|
idVideo = entry['purl']
|
|
ytdl = YDStreamExtractor.getVideoInfo(idVideo)
|
|
playback_url = ytdl.streamURL()
|
|
musicvideoid = "-1"
|
|
else:
|
|
# without youtube_dl
|
|
xbmc.log(msg="without ytdl", level=xbmc.LOGDEBUG)
|
|
playback_url = entry['url']
|
|
|
|
# default mode: play from provider -- either with ytdl or vimeo/youtube/dailymotion addon
|
|
else:
|
|
xbmc.log(msg="play from provider", level=xbmc.LOGDEBUG)
|
|
if useYTDL == "true":
|
|
xbmc.log(msg="with ytdl", level=xbmc.LOGDEBUG)
|
|
import YDStreamExtractor
|
|
idVideo = entry['purl']
|
|
ytdl = YDStreamExtractor.getVideoInfo(idVideo)
|
|
playback_url = ytdl.streamURL()
|
|
else:
|
|
# without youtube_dl
|
|
xbmc.log(msg="without ytdl", level=xbmc.LOGDEBUG)
|
|
playback_url = entry['url']
|
|
|
|
item = xbmcgui.ListItem(path=playback_url)
|
|
|
|
# avoids CCurlFile::Stat - Failed: Unsupported protocol(1) for plugin://
|
|
# disabled, unclear if this is the way to
|
|
#item.setContentLookup(False)
|
|
|
|
# add some Listitem info for local files
|
|
# should already be set in addVideo() but it isn't, doh!
|
|
if playLocalFile == "true" and not musicvideoid == "-1":
|
|
item.setInfo(type="video", infoLabels={"mediatype": mediatype, "dbid": musicvideoid, "title": title, "genre": genre })
|
|
|
|
# doesn't work
|
|
# item.setProperty('StartOffset', '56.4')
|
|
# item.setProperty("VolumeAmplification", "7")
|
|
# xbmc.log(msg=item.getProperty('VolumeAmplification'), level=xbmc.LOGNOTICE)
|
|
|
|
xbmc.log(msg=playback_url, level=xbmc.LOGNOTICE)
|
|
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, item)
|
|
|
|
def styles():
|
|
data = getVideos()
|
|
channels = []
|
|
xbmc.log(msg=sort, level=xbmc.LOGDEBUG)
|
|
for entry in data:
|
|
if entry['style'] not in channels:
|
|
channels.append(entry['style'])
|
|
length = len(channels)
|
|
channels.sort()
|
|
for channel in channels:
|
|
# don't use special folder icons as long as there is'nt a nice icon for every style
|
|
# addDir(channel, '&style=' + channel + '&sort=' + sort, 'showChannel', getFanart(channel), length)
|
|
# don't use special folder icons as long as there is'nt a nice icon for every style
|
|
addDir(channel, '&style=' + channel + '&sort=' + sort, 'showChannel', fanart, length)
|
|
endOfDirectory()
|
|
|
|
|
|
def showChannel(style, sort):
|
|
channel = style
|
|
if channel != "":
|
|
fanart = getFanart(channel)
|
|
xbmc.log(msg=channel, level=xbmc.LOGDEBUG)
|
|
if sort == "sorted" or sort == "all":
|
|
addDir(translation(30032), '&limit=' + channel + '&sort=date&start=0', 'sortTitlesBy', fanart, 6)
|
|
addDir(translation(30033), '&limit=' + channel + '&sort=date&start=0&hit=true', 'sortTitlesBy', fanart, 6)
|
|
# genre splitted artists, only menu entry which uses the old initials mode and
|
|
# therefore entries where the artist isn't at the beginning of the title are excluded :-(
|
|
addDir(translation(30005), channel, 'sortTitleInitials', fanart, 6)
|
|
if sort == "random" or sort == "all":
|
|
addDir(translation(30029), '&limit=' + channel + '&sort=random&start=0', 'sortTitlesBy', fanart, 6)
|
|
addDir(translation(30031), '&limit=' + channel + '&sort=random&start=0&hit=true', 'sortTitlesBy', fanart, 6)
|
|
endOfDirectory()
|
|
|
|
|
|
def sortTitleInitials(channel=""):
|
|
data = getVideos()
|
|
result = []
|
|
fanart = getFanart(channel)
|
|
if channel != "":
|
|
for entry in data:
|
|
if entry['style'] == channel:
|
|
if len(entry['title']) > 0:
|
|
l = entry['title'][0].upper()
|
|
if not re.match('^([a-z|A-Z])', l):
|
|
l = '#'
|
|
if l not in result:
|
|
if videoselection == "0":
|
|
if "true" in entry['official'].lower():
|
|
result.append(l)
|
|
elif videoselection == "1":
|
|
if "false" in entry['official'].lower():
|
|
result.append(l)
|
|
else:
|
|
result.append(l)
|
|
result.sort()
|
|
for entry in result:
|
|
addDir(entry, channel + '|' + entry, 'sortTitles', fanart, len(result))
|
|
endOfDirectory()
|
|
|
|
|
|
def sortArtistInitials(channel=""):
|
|
data = getVideos()
|
|
result = []
|
|
fanart = getFanart(channel)
|
|
if channel != "":
|
|
for entry in data:
|
|
for artist in entry['artists']:
|
|
# xbmc.log(msg=channel.encode('utf-8'), level=xbmc.LOGNOTICE)
|
|
# xbmc.log(msg=artist.encode('utf-8'), level=xbmc.LOGNOTICE)
|
|
if artist not in channel:
|
|
if len(artist) > 0:
|
|
l = artist[0].upper()
|
|
if not re.match('^([a-z|A-Z])', l):
|
|
l = '#'
|
|
if l not in result:
|
|
if videoselection == "0":
|
|
if "true" in entry['official'].lower():
|
|
result.append(l)
|
|
elif videoselection == "1":
|
|
if "false" in entry['official'].lower():
|
|
result.append(l)
|
|
else:
|
|
result.append(l)
|
|
result.sort()
|
|
for entry in result:
|
|
addDir(entry, channel + '|' + entry,
|
|
'sortArtists', fanart, len(result))
|
|
endOfDirectory()
|
|
|
|
|
|
def sortTitles(channelInitial=""):
|
|
xbmcplugin.setContent(pluginhandle, 'musicvideos')
|
|
data = getVideos()
|
|
result = []
|
|
params = channelInitial.split("|")
|
|
channel = ""
|
|
initial = ""
|
|
if len(params) > 1:
|
|
channel = params[0]
|
|
initial = params[1]
|
|
# else:
|
|
# channel = params[0]
|
|
fanart = getFanart(channel)
|
|
# xbmc.log(msg=initial.encode('utf-8'), level=xbmc.LOGNOTICE)
|
|
if channel != "":
|
|
xbmc.log(msg=channel.encode('utf-8'), level=xbmc.LOGDEBUG)
|
|
for entry in data:
|
|
if entry['style'] == channel:
|
|
i = entry['title'][0].upper()
|
|
if initial == '#':
|
|
if not re.match('^([a-z|A-Z])', i):
|
|
if videoselection == "0":
|
|
if "true" in entry['official'].lower():
|
|
result.append(entry)
|
|
elif videoselection == "1":
|
|
if "false" in entry['official'].lower():
|
|
result.append(entry)
|
|
else:
|
|
result.append(entry)
|
|
else:
|
|
if initial == i:
|
|
if videoselection == "0":
|
|
if "true" in entry['official'].lower():
|
|
result.append(entry)
|
|
elif videoselection == "1":
|
|
if "false" in entry['official'].lower():
|
|
result.append(entry)
|
|
else:
|
|
result.append(entry)
|
|
# sloooow
|
|
#elif channel in entry['artists']:
|
|
# result.append(entry)
|
|
result.sort(key=lambda entry: entry['title'].upper())
|
|
for entry in result:
|
|
addVideo(entry)
|
|
endOfDirectory()
|
|
|
|
def showArtist(style, limit, start):
|
|
# limit artists by first letter or country
|
|
data = getArtists()
|
|
result = []
|
|
# xbmc.log(msg=url, level=xbmc.LOGNOTICE)
|
|
# xbmc.log(msg=style, level=xbmc.LOGNOTICE)
|
|
# xbmc.log(msg=limit, level=xbmc.LOGNOTICE)
|
|
start = int(start)
|
|
end = start + filesinlists
|
|
nextstart = end + 1
|
|
if style == 'artists':
|
|
for entry in data:
|
|
i = unidecode(entry['artist'])[0].upper()
|
|
if limit == '#':
|
|
if not re.match('^([a-z|A-Z])', i):
|
|
result.append(entry)
|
|
else:
|
|
if limit == i:
|
|
result.append(entry)
|
|
elif style == 'country':
|
|
|
|
for entry in data:
|
|
i = entry['countrycode']
|
|
if limit == i:
|
|
result.append(entry)
|
|
elif limit == 'unknown':
|
|
result.append(entry)
|
|
maximum = len(result)
|
|
for entry in result[start:end]:
|
|
artist = entry['artist']
|
|
official = int(entry['official'])
|
|
unofficial = int(entry['unofficial'])
|
|
|
|
if entry['fanart'] != "":
|
|
fanart = entry['fanart']
|
|
elif entry['thumbnail'] != "":
|
|
fanart = entry['thumbnail']
|
|
else:
|
|
fanart = ""
|
|
|
|
if videoselection == "0":
|
|
if entry['official'] >= "1":
|
|
addDir("%s (%s)" % (artist, official), unidecode(artist.upper()), 'sortArtists', fanart, official)
|
|
elif videoselection == "1":
|
|
if entry['unofficial'] >= "1":
|
|
addDir("%s (%s)" % (artist, unofficial), unidecode(artist.upper()), 'sortArtists', fanart, unofficial)
|
|
elif videoselection == "2":
|
|
addDir("%s (%s/%s)" % (artist, official, unofficial), unidecode(artist.upper()), 'sortArtists', fanart, (official + unofficial))
|
|
if maximum > end:
|
|
fanart = 'DefaultFolder.png'
|
|
addDir(translation(30036), '&style=' + style + '&limit=' + limit + '&start=' + str(nextstart), 'showArtist', fanart)
|
|
endOfDirectory()
|
|
|
|
|
|
def sortArtists(channel=""):
|
|
# get videos for individual artist
|
|
xbmcplugin.setContent(pluginhandle, 'musicvideos')
|
|
data = getVideos()
|
|
result = []
|
|
# using param=string parameters does not work here because some obscure chars
|
|
# are lost in translation so the old implementation from Mediathek Direkt with
|
|
# splitting on | will be used. Have to take a deeper look to fix this...
|
|
params = channel.split("|")
|
|
# xbmc.log(msg=url, level=xbmc.LOGNOTICE)
|
|
channel = params[0]
|
|
if len(params) > 1:
|
|
start = params[1]
|
|
if channel != "" and channel != "relaartists":
|
|
fanart = getFanart(channel)
|
|
for entry in data:
|
|
artists_upper = [unidecode(artist.upper()) for artist in entry['artists']]
|
|
|
|
if channel in artists_upper:
|
|
if videoselection == "0":
|
|
if "true" in entry['official'].lower():
|
|
result.append(entry)
|
|
elif videoselection == "1":
|
|
if "false" in entry['official'].lower():
|
|
result.append(entry)
|
|
else:
|
|
result.append(entry)
|
|
#show "more from..." artists
|
|
if channel != "" and channel == "relartists":
|
|
fanart = getFanart(channel)
|
|
for entry in data:
|
|
if entry['slug'] == start:
|
|
artists_upper = [unidecode(artist.upper()) for artist in entry['artists']]
|
|
channels = []
|
|
for channel in artists_upper:
|
|
channels.append(channel)
|
|
xbmc.log(msg=str(channels), level=xbmc.LOGNOTICE)
|
|
|
|
# nested loops don't look like an optimal solution and are a bit slow
|
|
for entry in data:
|
|
for artist in channels:
|
|
if artist in [unidecode(name.upper()) for name in entry['artists']]:
|
|
if entry not in result:
|
|
if videoselection == "0":
|
|
if "true" in entry['official'].lower():
|
|
result.append(entry)
|
|
elif videoselection == "1":
|
|
if "false" in entry['official'].lower():
|
|
result.append(entry)
|
|
else:
|
|
result.append(entry)
|
|
|
|
|
|
result.sort(key=lambda entry: entry['title'].upper())
|
|
for entry in result:
|
|
# xbmc.log(msg=url, level=xbmc.LOGNOTICE)
|
|
addVideo(entry)
|
|
endOfDirectory()
|
|
|
|
def sortTitlesBy(limit, sort, start):
|
|
xbmcplugin.setContent(pluginhandle, 'musicvideos')
|
|
data = getVideos()
|
|
result = []
|
|
channel = limit
|
|
start = int(start)
|
|
end = start + filesinlists
|
|
nextstart = end + 1
|
|
# xbmc.log(msg=channel, level=xbmc.LOGNOTICE)
|
|
|
|
fanart = getFanart(channel)
|
|
# limit to hits
|
|
if hit != "" and hit == "true":
|
|
for entry in data:
|
|
if entry['hit'] == "true":
|
|
if videoselection == "0":
|
|
if "true" in entry['official']:
|
|
result.append(entry)
|
|
elif videoselection == "1":
|
|
if "false" in entry['official']:
|
|
result.append(entry)
|
|
elif videoselection == "2":
|
|
result.append(entry)
|
|
data = result
|
|
result = []
|
|
# played often at provider
|
|
if mycount != "" and mycount == "pcount" and sort != "count":
|
|
for entry in data:
|
|
# played often at provider
|
|
if int(entry['pcount']) >= pcounthigh:
|
|
if videoselection == "0":
|
|
if "true" in entry['official']:
|
|
result.append(entry)
|
|
elif videoselection == "1":
|
|
if "false" in entry['official']:
|
|
result.append(entry)
|
|
elif videoselection == "2":
|
|
result.append(entry)
|
|
# xbmc.log(msg=str(entry['pcount']), level=xbmc.LOGNOTICE)
|
|
data = result
|
|
result = []
|
|
# played often by addon developer
|
|
if mycount != "" and mycount == "acount" and sort != "count":
|
|
for entry in data:
|
|
if int(entry['acount']) >= acounthigh:
|
|
if videoselection == "0":
|
|
if "true" in entry['official']:
|
|
result.append(entry)
|
|
elif videoselection == "1":
|
|
if "false" in entry['official']:
|
|
result.append(entry)
|
|
elif videoselection == "2":
|
|
result.append(entry)
|
|
# xbmc.log(msg=str(entry['pcount']), level=xbmc.LOGNOTICE)
|
|
data = result
|
|
result = []
|
|
|
|
# often commented
|
|
if mycount != "" and mycount == "comments" and sort != "count":
|
|
for entry in data:
|
|
if int(entry['comments']) >= ccounthigh:
|
|
if videoselection == "0":
|
|
if "true" in entry['official']:
|
|
result.append(entry)
|
|
elif videoselection == "1":
|
|
if "false" in entry['official']:
|
|
result.append(entry)
|
|
elif videoselection == "2":
|
|
result.append(entry)
|
|
# xbmc.log(msg=str(entry['pcount']), level=xbmc.LOGNOTICE)
|
|
data = result
|
|
result = []
|
|
|
|
# often liked
|
|
if mycount != "" and mycount == "likes" and sort != "count":
|
|
for entry in data:
|
|
if int(entry['likes']) >= likecounthigh:
|
|
if videoselection == "0":
|
|
if "true" in entry['official']:
|
|
result.append(entry)
|
|
elif videoselection == "1":
|
|
if "false" in entry['official']:
|
|
result.append(entry)
|
|
elif videoselection == "2":
|
|
result.append(entry)
|
|
# xbmc.log(msg=str(entry['pcount']), level=xbmc.LOGNOTICE)
|
|
data = result
|
|
result = []
|
|
|
|
# often disliked
|
|
if mycount != "" and mycount == "dislikes" and sort != "count":
|
|
for entry in data:
|
|
if int(entry['dislikes']) >= dislikecounthigh:
|
|
if videoselection == "0":
|
|
if "true" in entry['official']:
|
|
result.append(entry)
|
|
elif videoselection == "1":
|
|
if "false" in entry['official']:
|
|
result.append(entry)
|
|
elif videoselection == "2":
|
|
result.append(entry)
|
|
# xbmc.log(msg=str(entry['pcount']), level=xbmc.LOGNOTICE)
|
|
data = result
|
|
result = []
|
|
|
|
# controversial based on nearly the same number of likes and dislikes
|
|
if mycount != "" and mycount == "controversial":
|
|
for entry in data:
|
|
if int(entry['controversial']) == int(1):
|
|
if videoselection == "0":
|
|
if "true" in entry['official']:
|
|
result.append(entry)
|
|
elif videoselection == "1":
|
|
if "false" in entry['official']:
|
|
result.append(entry)
|
|
elif videoselection == "2":
|
|
result.append(entry)
|
|
# xbmc.log(msg=str(entry['pcount']), level=xbmc.LOGNOTICE)
|
|
data = result
|
|
result = []
|
|
|
|
# limit by style
|
|
if channel != "" and channel != "all" and channel != "year" and channel != "related":
|
|
for entry in data:
|
|
if entry['style'] == channel:
|
|
if videoselection == "0":
|
|
if "true" in entry['official']:
|
|
result.append(entry)
|
|
elif videoselection == "1":
|
|
if "false" in entry['official']:
|
|
result.append(entry)
|
|
elif videoselection == "2":
|
|
result.append(entry)
|
|
# or limit to last year
|
|
# hm, either style or year, not both?
|
|
elif channel != "" and channel == "year":
|
|
lastyear = datetime.now() - timedelta(days=365)
|
|
for entry in data:
|
|
# dateadded = entry['dateadded']
|
|
# ehrm, first version worked for many days but now fails?
|
|
# related to https://bugs.python.org/issue27400 ?
|
|
try:
|
|
dateadded = datetime.strptime(entry['dateadded'], '%Y-%m-%d %H:%M:%S')
|
|
except TypeError:
|
|
import time
|
|
dateadded = datetime.fromtimestamp(time.mktime(time.strptime(entry['dateadded'], '%Y-%m-%d %H:%M:%S')))
|
|
if dateadded >= lastyear:
|
|
if videoselection == "0":
|
|
if "true" in entry['official']:
|
|
result.append(entry)
|
|
elif videoselection == "1":
|
|
if "false" in entry['official']:
|
|
result.append(entry)
|
|
elif videoselection == "2":
|
|
result.append(entry)
|
|
# related tracks (generated with musly)
|
|
elif channel != "" and channel == "related":
|
|
start = str(start)
|
|
# xbmc.log(msg=str(start), level=xbmc.LOGNOTICE)
|
|
for entry in data:
|
|
if entry['slug'] == start:
|
|
slugs = []
|
|
# add slug for which the playlist should be shown
|
|
slugs.extend(entry['slug'].split(", "))
|
|
# and add all related slugs
|
|
slugs.extend(entry['related'].split(", "))
|
|
#xbmc.log(msg=str(slugs), level=xbmc.LOGNOTICE)
|
|
for entry in data:
|
|
# add all related slugs
|
|
if entry['slug'] in slugs:
|
|
#xbmc.log(msg=str(result['artists']), level=xbmc.LOGNOTICE)
|
|
# filter out tracks with the same title, where only the video differs.
|
|
# if a related slug is listed earlier in data (= newer video) the
|
|
# main slug will be filtered out.
|
|
# In this case we add it again later, after sorting the result
|
|
if not filter(lambda result: result['title'] == entry['title'], result):
|
|
if videoselection != "2" and relatedselection != "true":
|
|
if videoselection == "0":
|
|
if "true" in entry['official']:
|
|
result.append(entry)
|
|
elif videoselection == "1":
|
|
if "false" in entry['official']:
|
|
result.append(entry)
|
|
else:
|
|
result.append(entry)
|
|
# slugs are sorted newest first because that's how they occur in the json
|
|
# so we have to sort the result to the order in related aka the musly order (with prepended main slug)
|
|
order_dict = {slug: index for index, slug in enumerate(slugs)}
|
|
result.sort(key=lambda x: order_dict[x["slug"]])
|
|
# check if the first entries slug is the main slug
|
|
# if not remove it and add back the main entry.
|
|
# I know, this is really ugly :-(
|
|
if result[0]['slug'] != start:
|
|
result.pop(0)
|
|
for entry in data:
|
|
if entry['slug'] == start:
|
|
result.insert(0, entry)
|
|
maximum = len(result)
|
|
# related is a list with max 21 entries, so we have to set start always to 0
|
|
start = 0
|
|
|
|
# "more from..." artists
|
|
elif channel != "" and channel == "relartists":
|
|
start = str(start)
|
|
for entry in data:
|
|
if entry['slug'] == start:
|
|
# add slug for which the artists should be shown
|
|
artists = ""
|
|
artists = entry['artists']
|
|
for entry in data:
|
|
xbmc.log(msg=str(artists), level=xbmc.LOGNOTICE)
|
|
# add all entries with artist in artists
|
|
if entry['artists'] in artists:
|
|
# xbmc.log(msg=str(entry), level=xbmc.LOGNOTICE)
|
|
if videoselection != "2" and relatedselection != "true":
|
|
if videoselection == "0":
|
|
if "true" in entry['official']:
|
|
result.append(entry)
|
|
elif videoselection == "1":
|
|
if "false" in entry['official']:
|
|
result.append(entry)
|
|
else:
|
|
result.append(entry)
|
|
maximum = len(result)
|
|
start = 0
|
|
|
|
# not used anywhere?
|
|
else:
|
|
for entry in data:
|
|
if videoselection == "0":
|
|
if "true" in entry['official']:
|
|
result.append(entry)
|
|
elif videoselection == "1":
|
|
if "false" in entry['official']:
|
|
result.append(entry)
|
|
elif videoselection == "2":
|
|
result.append(entry)
|
|
|
|
if sort != "" and sort == "random":
|
|
random.shuffle(result)
|
|
result = result[:filesinlists]
|
|
start = 0
|
|
end = filesinlists
|
|
maximum = filesinlists - 1
|
|
if sort != "" and sort == "date":
|
|
if channel != "":
|
|
modus = channel
|
|
else:
|
|
modus = 'all'
|
|
result = result
|
|
maximum = len(result)
|
|
if sort != "" and sort == "count":
|
|
if channel != "":
|
|
modus = channel
|
|
else:
|
|
modus = 'all'
|
|
# sort by negated count first and then by title in lexical order.
|
|
# If count would'nt be negated one had to use reverse=true
|
|
# But then the second sorting, by title would be reversed also
|
|
result = sorted(result, key = lambda i: (-1*float(i[mycount]), i['title']))
|
|
# itemgetter, should be faster (hm, but does'nt work: ValueError: could not convert string to float: pcount)
|
|
# importing "operator" for implementing itemgetter
|
|
#from operator import itemgetter
|
|
#result = sorted(result, key=itemgetter((float(mycount), 'title'),reverse = True))
|
|
|
|
maximum = len(result)
|
|
else:
|
|
maximum = len(result)
|
|
# play all (TODO)
|
|
# addDir(translation(30109), '', '', fanart)
|
|
# back to VIDFLTR (only useful if the back history would be cleared)
|
|
# if start > 0:
|
|
# addDir(translation(30108), '', 'main', fanart)
|
|
# hm, do I really want to replace the playlist on entering a folder?
|
|
# playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
|
|
# playlist.clear()
|
|
for entry in result[start:end]:
|
|
addVideo(entry, mycount)
|
|
if maximum > end and sort != "random" and channel != "year":
|
|
# next page link
|
|
if sort != "" and sort == "count":
|
|
addDir(translation(30036), '&limit=' + modus + '&sort=count&start=' + str(nextstart) + '&count=' + mycount, 'sortTitlesBy', fanart)
|
|
else:
|
|
addDir(translation(30036), '&limit=' + modus + '&sort=date&start=' + str(nextstart), 'sortTitlesBy', fanart)
|
|
endOfDirectory()
|
|
|
|
|
|
def endOfDirectory():
|
|
# xbmcplugin.addSortMethod(pluginhandle, xbmcplugin.SORT_METHOD_UNSORTED)
|
|
# xbmcplugin.addSortMethod(pluginhandle, xbmcplugin.SORT_METHOD_LABEL)
|
|
# xbmcplugin.addSortMethod(pluginhandle, xbmcplugin.SORT_METHOD_DATEADDED)
|
|
# xbmcplugin.addSortMethod(
|
|
# pluginhandle, xbmcplugin.SORT_METHOD_PROGRAM_COUNT)
|
|
# xbmcplugin.addSortMethod(
|
|
# pluginhandle, xbmcplugin.SORT_METHOD_VIDEO_RUNTIME)
|
|
# xbmcplugin.addSortMethod(pluginhandle, xbmcplugin.SORT_METHOD_GENRE)
|
|
xbmcplugin.endOfDirectory(pluginhandle)
|
|
|
|
def search(channel=""):
|
|
xbmcplugin.setContent(pluginhandle, 'musicvideos')
|
|
result = []
|
|
keyboard = xbmc.Keyboard('', translation(30002))
|
|
keyboard.doModal()
|
|
if keyboard.isConfirmed() and keyboard.getText():
|
|
# search_string = keyboard.getText().encode('utf8').lower()
|
|
search_string = keyboard.getText().lower()
|
|
if len(search_string) > 1:
|
|
data = getVideos()
|
|
for entry in data:
|
|
cEntry = entry
|
|
if search_string in cEntry['title'].lower():
|
|
if channel != "":
|
|
if cEntry['style'] == channel:
|
|
# cEntry['title'] = cEntry['style']+':
|
|
# '+cEntry['title']
|
|
if videoselection == "0":
|
|
if "true" in entry['official'].lower():
|
|
result.append(cEntry)
|
|
else:
|
|
result.append(cEntry)
|
|
else:
|
|
# cEntry['title'] = cEntry['style']+':
|
|
# '+cEntry['title']
|
|
if videoselection == "0":
|
|
if "true" in entry['official'].lower():
|
|
result.append(cEntry)
|
|
else:
|
|
result.append(cEntry)
|
|
elif search_string in cEntry['artist'].lower():
|
|
if channel != "":
|
|
if cEntry['style'] == channel:
|
|
# cEntry['title'] = cEntry['style']+':
|
|
# '+cEntry['title']
|
|
if videoselection == "0":
|
|
if "true" in entry['official'].lower():
|
|
result.append(cEntry)
|
|
else:
|
|
result.append(cEntry)
|
|
else:
|
|
# cEntry['title'] = cEntry['style']+':
|
|
# '+cEntry['title']
|
|
if videoselection == "0":
|
|
if "true" in entry['official'].lower():
|
|
result.append(cEntry)
|
|
else:
|
|
result.append(cEntry)
|
|
|
|
|
|
result.sort(key=lambda entry: entry['title'].lower())
|
|
for entry in result:
|
|
addVideo(entry)
|
|
endOfDirectory()
|
|
|
|
# not used (TODO?)
|
|
def searchDate(channelDate=""):
|
|
xbmcplugin.setContent(pluginhandle, 'musicvideos')
|
|
channel = ""
|
|
date = ""
|
|
params = channelDate.split('|')
|
|
channel = params[0]
|
|
if len(params) > 1:
|
|
date = params[1]
|
|
result = []
|
|
if date == "":
|
|
dialog = xbmcgui.Dialog()
|
|
date = dialog.numeric(1, translation(30007))
|
|
date = re.sub('[^0-9|^\/]', '0', date)
|
|
date = date.replace('/', '.')
|
|
if (channel != "") and (len(date) == 10):
|
|
data = getVideos()
|
|
for entry in data:
|
|
cEntry = entry
|
|
if (entry['style'] == channel) and (entry['date'] == date):
|
|
cEntry[1] = cEntry[2] + ': ' + cEntry[1]
|
|
if videoselection == "0":
|
|
if "true" in entry['official'].lower():
|
|
result.append(cEntry)
|
|
else:
|
|
result.append(cEntry)
|
|
result.sort(key=lambda entry: entry['title'].lower())
|
|
for entry in result:
|
|
addVideo(entry)
|
|
endOfDirectory()
|
|
|
|
# not used (TODO?)
|
|
def downloadFile(video_url):
|
|
# get best qualiy url
|
|
bq_url = getBestQuality(video_url)
|
|
# get filname from video_url
|
|
filename = video_url.split('/')[-1]
|
|
filetype = filename.split('.')[-1]
|
|
# open browser dialog to choose destination
|
|
dialog = xbmcgui.Dialog()
|
|
download_dir = dialog.browse(3, translation(30102), "files")
|
|
target = urllib.URLopener()
|
|
fullPath = xbmc.translatePath(download_dir + filename)
|
|
target.retrieve(video_url, fullPath)
|
|
dialog.ok(addonID, translation(30101), str(fullPath))
|
|
|
|
# only used in style directories
|
|
def getFanart(channel):
|
|
fanart = os.path.join(
|
|
addonDir, 'resources/images/fanart_' + channel + '.jpg')
|
|
if not os.path.isfile(fanart.encode('utf-8')):
|
|
fanart = icon
|
|
return fanart
|
|
|
|
|
|
def addDir(name, url, mode, iconimage, total=0):
|
|
u = sys.argv[0] + "?url=" + urllib.quote_plus(url.encode('utf-8')) + "&mode=" + str(mode)
|
|
# xbmc.log(msg=u, level=xbmc.LOGNOTICE)
|
|
ok = True
|
|
# if (xbmcversion < 17):
|
|
liz = xbmcgui.ListItem(name, iconImage=icon, thumbnailImage=iconimage)
|
|
# else:
|
|
# With offscreen=true large lists (=folder) load much faster (needs >= krypton)
|
|
# But at the end, the differences are minimal in VIDFLTR, so just drop it :-)
|
|
# liz = xbmcgui.ListItem(name, iconImage=icon, thumbnailImage=iconimage, offscreen=True)
|
|
description = ""
|
|
if mode == "showChannel":
|
|
data = getStyles()
|
|
for style in data:
|
|
if style['style'] == name:
|
|
description = style['description']
|
|
|
|
liz.setInfo(type="Video", infoLabels={"Title": name, "PlotOutline": description, "Plot": description, "Tagline": description})
|
|
if iconimage:
|
|
liz.setProperty("fanart_image", iconimage)
|
|
else:
|
|
liz.setProperty("fanart_image", defaultFanart)
|
|
ok = xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=u, listitem=liz, totalItems=total, isFolder=True)
|
|
return ok
|
|
|
|
|
|
def addVideo(entry, mycount="playcount"):
|
|
ok = True
|
|
|
|
# initiaize the global video playlist. The item will be added later
|
|
# playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
|
|
|
|
# Altlast. Sinn dahinter?
|
|
if len(entry) > 7:
|
|
dateadded = datetime.utcfromtimestamp(float(entry['slug']))
|
|
dateadded = str(dateadded)
|
|
slug = entry['slug']
|
|
url = '%s?mode=play&url=%s' % (sys.argv[0], slug)
|
|
channel = entry['artist']
|
|
artist = entry['artist']
|
|
artists = entry['artists']
|
|
director = entry['director']
|
|
title = entry['title']
|
|
title = title.replace(""", '"')
|
|
if "false" in entry['official'].lower() and "true" in showunoffintitle:
|
|
title = (title + ' (vid by ' + director + ')')
|
|
tracktitle = entry['tracktitle']
|
|
tracktitle = tracktitle.replace(""", '"')
|
|
style = entry['style']
|
|
if mycount == "pcount":
|
|
playcount = entry['pcount']
|
|
elif mycount == "lcount":
|
|
playcount = entry['lcount']
|
|
else:
|
|
playcount = entry['acount']
|
|
#slug = entry['slug']
|
|
if playLocalFile == "true":
|
|
description = "Provider: " + entry['provider'] + "\nprovider playcount: " + entry['pcount'] + "\nlikes: " + entry['likes'] + "\ndislikes: " + entry['dislikes'] + "\ncomments: " + entry['comments']
|
|
else:
|
|
description = ""
|
|
if "false" in entry['official'].lower():
|
|
description = (
|
|
"Unofficial Video by " + director + "\n" + description)
|
|
#link = entry['slug']
|
|
fanart = entry['thumbnail']
|
|
duration = entry['duration']
|
|
# if (xbmcversion < 17):
|
|
li = xbmcgui.ListItem(title)
|
|
# else:
|
|
# li = xbmcgui.ListItem(title, offscreen=True)
|
|
|
|
li.setInfo(type="video", infoLabels={ "mediatype": mediatype, "Title": title, "Originaltitle": tracktitle, "Genre": style, "Director": director, "PlotOutline": description, "Plot": description, "Tagline": style, "Artist": entry['artists'], "dateadded": dateadded, "playcount": playcount, "Duration": duration})
|
|
li.setArt({'thumb': fanart})
|
|
li.setProperty("fanart_image", fanart)
|
|
li.setProperty('IsPlayable', 'true')
|
|
|
|
# I could add all entries to the current, global playlist but it doesn't look right
|
|
# Imo at the end it's better if the user just uses "play from here" or the auto play next setting
|
|
# playlist.add(url=url, listitem=li)
|
|
|
|
li.addContextMenuItems([
|
|
(translation(30121),'XBMC.Container.Update(plugin://'+addonID+'/?mode=sortTitlesBy&url=%26start%3d'+slug+'%26limit%3drelated%26sort%3dnone)',),
|
|
(translation(30122),'XBMC.Container.Update(plugin://'+addonID+'/?mode=sortArtists&url=relartists%7C'+slug+')',)
|
|
])
|
|
ok = xbmcplugin.addDirectoryItem(handle=pluginhandle, url=url, listitem=li)
|
|
|
|
return ok
|
|
|
|
def parameters_string_to_dict(parameters):
|
|
paramDict = {}
|
|
if parameters:
|
|
paramPairs = parameters[1:].split("&")
|
|
for paramsPair in paramPairs:
|
|
paramSplits = paramsPair.split('=')
|
|
if (len(paramSplits)) == 2:
|
|
paramDict[paramSplits[0]] = paramSplits[1]
|
|
return paramDict
|
|
|
|
params = parameters_string_to_dict(sys.argv[2])
|
|
mode = urllib.unquote_plus(params.get('mode', '')).decode('utf-8')
|
|
url = urllib.unquote_plus(params.get('url', '')).decode('utf-8')
|
|
|
|
extraparams = parameters_string_to_dict(url)
|
|
limit = urllib.unquote_plus(extraparams.get('limit', '')).decode('utf-8')
|
|
sort = urllib.unquote_plus(extraparams.get('sort', '')).decode('utf-8')
|
|
start = urllib.unquote_plus(extraparams.get('start', '')).decode('utf-8')
|
|
start = str(start)
|
|
style = urllib.unquote_plus(extraparams.get('style', '')).decode('utf-8')
|
|
hit = urllib.unquote_plus(extraparams.get('hit', '')).decode('utf-8')
|
|
mycount = urllib.unquote_plus(extraparams.get('count', '')).decode('utf-8')
|
|
slug = urllib.unquote_plus(extraparams.get('slug', '')).decode('utf-8')
|
|
|
|
#xbmc.log(msg=str(sys.argv), level=xbmc.LOGNOTICE)
|
|
#xbmc.log(msg=str(mode), level=xbmc.LOGNOTICE)
|
|
#xbmc.log(msg=str(url), level=xbmc.LOGNOTICE)
|
|
#xbmc.log(msg=str(limit), level=xbmc.LOGDEBUG)
|
|
#xbmc.log(msg=str(sort), level=xbmc.LOGDEBUG)
|
|
#xbmc.log(msg=str(start), level=xbmc.LOGDEBUG)
|
|
#xbmc.log(msg=str(style), level=xbmc.LOGNOTICE)
|
|
|
|
if mode == 'updateData':
|
|
updateData()
|
|
elif mode == 'alphabet':
|
|
alphabet()
|
|
elif mode == 'showChannel':
|
|
showChannel(style, sort)
|
|
elif mode == 'search':
|
|
search(url)
|
|
elif mode == 'sortTitleInitials':
|
|
sortTitleInitials(url)
|
|
elif mode == 'sortTitles':
|
|
sortTitles(url)
|
|
elif mode == 'sortArtists':
|
|
sortArtists(url)
|
|
elif mode == 'showArtist':
|
|
showArtist(style, limit, start)
|
|
elif mode == 'sortTitlesBy':
|
|
sortTitlesBy(limit, sort, start)
|
|
elif mode == 'searchDate':
|
|
searchDate(url)
|
|
elif mode == 'mainsorted':
|
|
mainsorted()
|
|
elif mode == 'mainrandom':
|
|
mainrandom()
|
|
elif mode == 'numbers':
|
|
numbers()
|
|
elif mode == 'countrycodes':
|
|
countrycodes()
|
|
elif mode == 'artists':
|
|
artists()
|
|
elif mode == 'styles':
|
|
styles()
|
|
elif mode == 'stylesrandom':
|
|
stylesrandom()
|
|
elif mode == 'channelsrandom':
|
|
channelsrandom(url)
|
|
elif mode == 'related':
|
|
related(limit)
|
|
elif mode == 'play':
|
|
play(url)
|
|
else:
|
|
main()
|