# Copyright © 2024 Jakob L. Kreuze # # 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 . """ Video plugin for animations posted to Newgrounds. """ import os import sys from urllib.parse import urlencode, parse_qsl from bs4 import BeautifulSoup import requests import xbmc import xbmcgui import xbmcplugin from xbmcaddon import Addon from xbmcvfs import translatePath URL = sys.argv[0] HANDLE = int(sys.argv[1]) ADDON_PATH = translatePath(Addon().getAddonInfo('path')) ICONS_DIR = os.path.join(ADDON_PATH, 'resources', 'images', 'icons') FANART_DIR = os.path.join(ADDON_PATH, 'resources', 'images', 'fanart') def get_url(**kwargs): """ Create a URL for calling the plugin recursively from the given set of keyword arguments. :param kwargs: "argument=value" pairs :return: plugin call URL :rtype: str """ return '{}?{}'.format(URL, urlencode(kwargs)) def list_genres(): """ Create the list of movie genres in the Kodi interface. """ xbmcplugin.setPluginCategory(HANDLE, 'Newgrounds') xbmcplugin.setContent(HANDLE, 'movies') genres = ["Featured", "Latest", "Popular"] for name in genres: list_item = xbmcgui.ListItem(label=name) # list_item.setArt({'icon': genre_info['icon'], 'fanart': genre_info['fanart']}) info_tag = list_item.getVideoInfoTag() info_tag.setMediaType('video') info_tag.setTitle(name) info_tag.setGenres([name]) url = get_url(action='listing', genre=name) is_folder = True xbmcplugin.addDirectoryItem(HANDLE, url, list_item, is_folder) xbmcplugin.addSortMethod(HANDLE, xbmcplugin.SORT_METHOD_LABEL_IGNORE_THE) xbmcplugin.endOfDirectory(HANDLE) def list_categories(genre='Featured'): """ Create the list of movie categories in the Kodi interface. """ xbmcplugin.setPluginCategory(HANDLE, 'Newgrounds') xbmcplugin.setContent(HANDLE, 'movies') CATEGORIES = { 'All': 0, 'Action': 45, 'Comedy - Original': 60, 'Comedy - Parody': 61, 'Drama': 47, 'Experimental': 49, 'Informative': 48, 'Music Video': 50, 'Other': 51, 'Spam': 55, } for name in CATEGORIES.keys(): list_item = xbmcgui.ListItem(label=name) # list_item.setArt({'icon': genre_info['icon'], 'fanart': genre_info['fanart']}) info_tag = list_item.getVideoInfoTag() info_tag.setMediaType('video') info_tag.setTitle(name) info_tag.setGenres([name]) url = get_url(action='listing', genre=genre, category=CATEGORIES[name]) is_folder = True xbmcplugin.addDirectoryItem(HANDLE, url, list_item, is_folder) xbmcplugin.addSortMethod(HANDLE, xbmcplugin.SORT_METHOD_LABEL_IGNORE_THE) xbmcplugin.endOfDirectory(HANDLE) def get_video_url(url): """ TODO """ xbmc.log('Getting info for {}'.format(url), 2) html = requests.get(url).text soup = BeautifulSoup(html, 'html.parser') video_id = soup.find('meta', property='og:url')['content'] \ .strip('https://www.newgrounds.com/portal/view/') response = requests.get('https://www.newgrounds.com/portal/video/{}'.format(video_id), headers={ 'X-Requested-With': 'XMLHttpRequest' }).json() preferences = ['1080p', '720p', '360p'] for preference in preferences: if preference in response['sources']: source = next(filter(lambda x: x['type'] == 'video/mp4', response['sources'][preference])) if source is not None: return source['src'] def get_addon_video_info(url): """ Scrape metadata from `url`. """ xbmc.log('Getting info for {}'.format(url), 2) html = requests.get(url).text soup = BeautifulSoup(html, 'html.parser') return { 'title': soup.find('div', id="embed_header").h2.string, 'thumbnail': soup.find('meta', property="og:image")['content'], 'url': url } def get_video_urls(genre='Featured', interval='today', sort='date', category=0, offset=0): URLS = { 'Featured': 'https://www.newgrounds.com/movies/featured', 'Latest': 'https://www.newgrounds.com/movies/browse', 'Popular': 'https://www.newgrounds.com/movies/popular', } assert(interval in ['today', 'yesterday', 'week', 'month', 'year', 'all']) assert(sort in ['date']) assert(isinstance(genre, int)) url = URLS[genre] response = requests.get(url, params={ 'interval': interval, 'sort': sort, 'genre': category, 'isAjaxRequest': True, 'offset': offset, 'inner': 1 }, headers={ 'X-Requested-With': 'XMLHttpRequest', }).json() soup = BeautifulSoup(response['content'], 'html.parser') return [tag["href"] for tag in soup.find_all('a', "inline-card-portalsubmission")] def list_videos(genre, category=0, offset=0): """ Create the list of playable videos in the Kodi interface. :param genre_index: the index of genre in the list of movie genres :type genre: str :type offset: int """ html = requests.get("https://www.newgrounds.com/movies").text soup = BeautifulSoup(html, 'html.parser') videos = [get_addon_video_info(url) for url in get_video_urls(category=category, interval='all', offset=offset)] xbmcplugin.setPluginCategory(HANDLE, "Featured") #, genre_info['genre']) xbmcplugin.setContent(HANDLE, 'movies') # Get the list of videos in the category. # videos = genre_info['movies'] # Iterate through videos. for video in videos: list_item = xbmcgui.ListItem(label=video['title']) list_item.setArt({'thumb': video['thumbnail']}) info_tag = list_item.getVideoInfoTag() info_tag.setMediaType('movie') info_tag.setTitle(video['title']) # info_tag.setGenres([genre_info['genre']]) # info_tag.setPlot(video['plot']) # info_tag.setYear(video['year']) list_item.setProperty('IsPlayable', 'true') url = get_url(action='play', video=video['url']) is_folder = False xbmcplugin.addDirectoryItem(HANDLE, url, list_item, is_folder) # Add a "continue" folder. list_item = xbmcgui.ListItem(label="Next Page") info_tag = list_item.getVideoInfoTag() info_tag.setMediaType('video') info_tag.setTitle("Next Page") url = get_url(action='listing', genre=genre, category=category, offset=offset+20) is_folder = True xbmcplugin.addDirectoryItem(HANDLE, url, list_item, is_folder) xbmcplugin.addSortMethod(HANDLE, xbmcplugin.SORT_METHOD_LABEL_IGNORE_THE) xbmcplugin.addSortMethod(HANDLE, xbmcplugin.SORT_METHOD_VIDEO_YEAR) xbmcplugin.endOfDirectory(HANDLE) def play_video(path): play_item = xbmcgui.ListItem(path=get_video_url(path)) play_item.setProperty('IsPlayable', 'true') xbmcplugin.setResolvedUrl(HANDLE, True, play_item) def router(paramstring): """ Router function that calls other functions depending on the provided paramstring :param paramstring: URL encoded plugin paramstring :type paramstring: str """ params = dict(parse_qsl(paramstring)) if not params: list_genres() elif params['action'] == 'listing' and 'category' in params: list_videos(params['genre'], category=params['category'], offset=int(params.get('offset', 0))) elif params['action'] == 'listing': list_categories(params['genre']) elif params['action'] == 'play': play_video(params['video']) else: raise ValueError(f'Invalid paramstring: {paramstring}!') if __name__ == '__main__': router(sys.argv[2][1:])