Playback from cache
This commit is contained in:
parent
74769ffc47
commit
8529c3b403
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@ -28,7 +28,7 @@ jobs:
|
|||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get install gettext
|
sudo apt-get install --no-install-recommends gettext ffmpeg
|
||||||
sudo pip install coverage --install-option="--install-scripts=/usr/bin"
|
sudo pip install coverage --install-option="--install-scripts=/usr/bin"
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
|
@ -70,6 +70,10 @@ msgctxt "#30102"
|
|||||||
msgid "Go to Program"
|
msgid "Go to Program"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgctxt "#30103"
|
||||||
|
msgid "Download to cache"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
|
||||||
### CODE
|
### CODE
|
||||||
msgctxt "#30204"
|
msgctxt "#30204"
|
||||||
@ -144,6 +148,42 @@ msgctxt "#30717"
|
|||||||
msgid "This program is not available in the catalogue."
|
msgid "This program is not available in the catalogue."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgctxt "#30718"
|
||||||
|
msgid "Could not cache this episode since the cache folder is not set or does not exist."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgctxt "#30719"
|
||||||
|
msgid "Could not cache this episode since ffmpeg seems to be unavailable."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgctxt "#30720"
|
||||||
|
msgid "This episode is cached locally. Do you want to play from cache or stream it?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgctxt "#30721"
|
||||||
|
msgid "Stream"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgctxt "#30722"
|
||||||
|
msgid "Play from cache"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgctxt "#30723"
|
||||||
|
msgid "Starting download..."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgctxt "#30724"
|
||||||
|
msgid "Downloading... ({amount}%)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgctxt "#30725"
|
||||||
|
msgid "Download has finished. You can now play this episode from cache."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgctxt "#30726"
|
||||||
|
msgid "This episode is already cached. Do you want to download it again?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
|
||||||
### SETTINGS
|
### SETTINGS
|
||||||
msgctxt "#30800"
|
msgctxt "#30800"
|
||||||
@ -177,3 +217,15 @@ msgstr ""
|
|||||||
msgctxt "#30831"
|
msgctxt "#30831"
|
||||||
msgid "Update local metadata now"
|
msgid "Update local metadata now"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgctxt "#30840"
|
||||||
|
msgid "Playback from cache"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgctxt "#30841"
|
||||||
|
msgid "Allow to download episodes to a cache"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgctxt "#30843"
|
||||||
|
msgid "Select the folder for the cached episodes"
|
||||||
|
msgstr ""
|
||||||
|
@ -71,6 +71,10 @@ msgctxt "#30102"
|
|||||||
msgid "Go to Program"
|
msgid "Go to Program"
|
||||||
msgstr "Ga naar programma"
|
msgstr "Ga naar programma"
|
||||||
|
|
||||||
|
msgctxt "#30103"
|
||||||
|
msgid "Download to cache"
|
||||||
|
msgstr "Downloaden naar cache"
|
||||||
|
|
||||||
|
|
||||||
### CODE
|
### CODE
|
||||||
msgctxt "#30204"
|
msgctxt "#30204"
|
||||||
@ -145,6 +149,42 @@ msgctxt "#30717"
|
|||||||
msgid "This program is not available in the catalogue."
|
msgid "This program is not available in the catalogue."
|
||||||
msgstr "Dit programma is niet beschikbaar in de catalogus."
|
msgstr "Dit programma is niet beschikbaar in de catalogus."
|
||||||
|
|
||||||
|
msgctxt "#30718"
|
||||||
|
msgid "Could not cache this episode since the cache folder is not set or does not exist."
|
||||||
|
msgstr "Kon deze aflevering niet cachen omdat de cache folder niet is ingesteld is of niet bestaat."
|
||||||
|
|
||||||
|
msgctxt "#30719"
|
||||||
|
msgid "Could not cache this episode since ffmpeg seems to be unavailable."
|
||||||
|
msgstr "Kon deze aflevering niet cachen omdat ffmpeg niet beschikbaar lijkt te zijn."
|
||||||
|
|
||||||
|
msgctxt "#30720"
|
||||||
|
msgid "This episode is cached locally. Do you want to play from cache or stream it?"
|
||||||
|
msgstr "Deze aflevering is lokaal gecached. Wil je deze afspelen vanuit de cache of streamen?"
|
||||||
|
|
||||||
|
msgctxt "#30721"
|
||||||
|
msgid "Stream"
|
||||||
|
msgstr "Stream"
|
||||||
|
|
||||||
|
msgctxt "#30722"
|
||||||
|
msgid "Play from cache"
|
||||||
|
msgstr "Afspelen vanuit de cache"
|
||||||
|
|
||||||
|
msgctxt "#30723"
|
||||||
|
msgid "Starting download..."
|
||||||
|
msgstr "Bezig met starten van de download..."
|
||||||
|
|
||||||
|
msgctxt "#30724"
|
||||||
|
msgid "Downloading... ({amount}%)"
|
||||||
|
msgstr "Bezig met downloaden... ({amount}%)"
|
||||||
|
|
||||||
|
msgctxt "#30725"
|
||||||
|
msgid "Download has finished. You can now play this episode from cache."
|
||||||
|
msgstr "De download is voltooid. Je kan deze aflevering nu afspelen vanuit de cache."
|
||||||
|
|
||||||
|
msgctxt "#30726"
|
||||||
|
msgid "This episode is already cached. Do you want to download it again?"
|
||||||
|
msgstr "Deze aflevering is al gecached. Wil je deze opnieuw downloaden?"
|
||||||
|
|
||||||
|
|
||||||
### SETTINGS
|
### SETTINGS
|
||||||
msgctxt "#30800"
|
msgctxt "#30800"
|
||||||
@ -178,3 +218,15 @@ msgstr "Vernieuw de lokale metdata automatisch in de achtergrond"
|
|||||||
msgctxt "#30831"
|
msgctxt "#30831"
|
||||||
msgid "Update local metadata now"
|
msgid "Update local metadata now"
|
||||||
msgstr "De lokale metadata nu vernieuwen"
|
msgstr "De lokale metadata nu vernieuwen"
|
||||||
|
|
||||||
|
msgctxt "#30840"
|
||||||
|
msgid "Playback from cache"
|
||||||
|
msgstr "Afspelen vanuit de cache"
|
||||||
|
|
||||||
|
msgctxt "#30841"
|
||||||
|
msgid "Allow to download episodes to a cache"
|
||||||
|
msgstr "Toestaan om afleveringen te downloaden naar de cache"
|
||||||
|
|
||||||
|
msgctxt "#30843"
|
||||||
|
msgid "Select the folder for the cached episodes"
|
||||||
|
msgstr "Selecteer de map voor de gecachte afleveringen"
|
||||||
|
@ -92,6 +92,13 @@ def play(uuid):
|
|||||||
Player().play(uuid)
|
Player().play(uuid)
|
||||||
|
|
||||||
|
|
||||||
|
@routing.route('/download/catalog/<uuid>')
|
||||||
|
def download(uuid):
|
||||||
|
""" Download the requested item to cache """
|
||||||
|
from resources.lib.modules.player import Player
|
||||||
|
Player().download(uuid)
|
||||||
|
|
||||||
|
|
||||||
@routing.route('/play/page/<channel>/<page>')
|
@routing.route('/play/page/<channel>/<page>')
|
||||||
def play_from_page(channel, page):
|
def play_from_page(channel, page):
|
||||||
""" Play the requested item """
|
""" Play the requested item """
|
||||||
|
84
resources/lib/downloader.py
Normal file
84
resources/lib/downloader.py
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""Episode Downloader"""
|
||||||
|
|
||||||
|
from __future__ import absolute_import, division, unicode_literals
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger('downloader')
|
||||||
|
|
||||||
|
|
||||||
|
class Downloader:
|
||||||
|
""" Allows to download an episode to disk for caching purposes. """
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def check():
|
||||||
|
""" Check if we have ffmpeg installed."""
|
||||||
|
try:
|
||||||
|
proc = subprocess.Popen(['ffmpeg', '-version'], stderr=subprocess.PIPE)
|
||||||
|
except OSError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Wait for the process to finish
|
||||||
|
output = proc.stderr.readlines()
|
||||||
|
proc.wait()
|
||||||
|
|
||||||
|
# Check error code
|
||||||
|
if proc.returncode != 0:
|
||||||
|
_LOGGER.error(output)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# TODO: Check version
|
||||||
|
_LOGGER.debug('Output: %s', output)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def download(stream, output, progress_callback=None):
|
||||||
|
"""Download the stream to destination."""
|
||||||
|
try:
|
||||||
|
cmd = ['ffmpeg', '-y', '-loglevel', 'info', '-i', stream, '-codec', 'copy', output]
|
||||||
|
# `universal_newlines` makes proc.stderr.readline() also work on \r
|
||||||
|
proc = subprocess.Popen(cmd, stderr=subprocess.PIPE, universal_newlines=True)
|
||||||
|
except OSError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
regex_total = re.compile(r"Duration: (\d{2}):(\d{2}):(\d{2})")
|
||||||
|
regex_current = re.compile(r"time=(\d{2}):(\d{2}):(\d{2})")
|
||||||
|
|
||||||
|
# Keep looping over ffmpeg output
|
||||||
|
total = None
|
||||||
|
while True:
|
||||||
|
line = proc.stderr.readline()
|
||||||
|
if not line:
|
||||||
|
break
|
||||||
|
|
||||||
|
_LOGGER.debug('ffmpeg output: %s', line.rstrip())
|
||||||
|
|
||||||
|
# Read the current status that is printed every few seconds.
|
||||||
|
match = regex_current.search(line)
|
||||||
|
if match and progress_callback:
|
||||||
|
cancel = progress_callback(total, int(match.group(1)) * 3600 + int(match.group(2)) * 60 + int(match.group(3)))
|
||||||
|
if cancel:
|
||||||
|
proc.terminate()
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Read the total stream duration if we haven't found it already. It's there somewhere in the output. We'll find it.
|
||||||
|
if not total:
|
||||||
|
match = regex_total.search(line)
|
||||||
|
if match:
|
||||||
|
total = int(match.group(1)) * 3600 + int(match.group(2)) * 60 + int(match.group(3))
|
||||||
|
|
||||||
|
# Wait for ffmpeg to be fully finished
|
||||||
|
proc.wait()
|
||||||
|
|
||||||
|
# Check error code
|
||||||
|
if proc.returncode != 0:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
@ -118,12 +118,22 @@ class Menu:
|
|||||||
'duration': item.duration,
|
'duration': item.duration,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if kodiutils.get_setting_bool('episode_cache_enabled'):
|
||||||
|
context_menu = [(
|
||||||
|
kodiutils.localize(30103), # Download to cache
|
||||||
|
'Container.Update(%s)' %
|
||||||
|
kodiutils.url_for('download', uuid=item.uuid)
|
||||||
|
)]
|
||||||
|
else:
|
||||||
|
context_menu = []
|
||||||
|
|
||||||
return TitleItem(title=info_dict['title'],
|
return TitleItem(title=info_dict['title'],
|
||||||
path=kodiutils.url_for('play', uuid=item.uuid),
|
path=kodiutils.url_for('play', uuid=item.uuid),
|
||||||
art_dict=art_dict,
|
art_dict=art_dict,
|
||||||
info_dict=info_dict,
|
info_dict=info_dict,
|
||||||
stream_dict=stream_dict,
|
stream_dict=stream_dict,
|
||||||
prop_dict=prop_dict,
|
prop_dict=prop_dict,
|
||||||
is_playable=True)
|
is_playable=True,
|
||||||
|
context_menu=context_menu)
|
||||||
|
|
||||||
raise Exception('Unknown video_type')
|
raise Exception('Unknown video_type')
|
||||||
|
@ -4,8 +4,10 @@
|
|||||||
from __future__ import absolute_import, division, unicode_literals
|
from __future__ import absolute_import, division, unicode_literals
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
from resources.lib import kodiutils
|
from resources.lib import kodiutils
|
||||||
|
from resources.lib.downloader import Downloader
|
||||||
from resources.lib.viervijfzes.auth import AuthApi
|
from resources.lib.viervijfzes.auth import AuthApi
|
||||||
from resources.lib.viervijfzes.auth_awsidp import InvalidLoginException, AuthenticationException
|
from resources.lib.viervijfzes.auth_awsidp import InvalidLoginException, AuthenticationException
|
||||||
from resources.lib.viervijfzes.content import ContentApi, UnavailableException, GeoblockedException
|
from resources.lib.viervijfzes.content import ContentApi, UnavailableException, GeoblockedException
|
||||||
@ -18,6 +20,8 @@ class Player:
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
""" Initialise object """
|
""" Initialise object """
|
||||||
|
auth = AuthApi(kodiutils.get_setting('username'), kodiutils.get_setting('password'), kodiutils.get_tokens_path())
|
||||||
|
self._api = ContentApi(auth)
|
||||||
|
|
||||||
def play_from_page(self, channel, path):
|
def play_from_page(self, channel, path):
|
||||||
""" Play the requested item.
|
""" Play the requested item.
|
||||||
@ -30,17 +34,82 @@ class Player:
|
|||||||
# Play this now we have the uuid
|
# Play this now we have the uuid
|
||||||
self.play(episode.uuid)
|
self.play(episode.uuid)
|
||||||
|
|
||||||
@staticmethod
|
def play(self, uuid):
|
||||||
def play(item):
|
|
||||||
""" Play the requested item.
|
""" Play the requested item.
|
||||||
:type item: string
|
:type uuid: string
|
||||||
"""
|
"""
|
||||||
|
if kodiutils.get_setting_bool('episode_cache_enabled'):
|
||||||
|
# Check for a cached version
|
||||||
|
cached_file = self._check_cached_episode(uuid)
|
||||||
|
if cached_file:
|
||||||
|
kodiutils.play(cached_file)
|
||||||
|
return
|
||||||
|
|
||||||
# Workaround for Raspberry Pi 3 and older
|
# Workaround for Raspberry Pi 3 and older
|
||||||
omxplayer = kodiutils.get_global_setting('videoplayer.useomxplayer')
|
omxplayer = kodiutils.get_global_setting('videoplayer.useomxplayer')
|
||||||
if omxplayer is False:
|
if omxplayer is False:
|
||||||
kodiutils.set_global_setting('videoplayer.useomxplayer', True)
|
kodiutils.set_global_setting('videoplayer.useomxplayer', True)
|
||||||
|
|
||||||
|
# Resolve the stream
|
||||||
|
resolved_stream = self._fetch_stream(uuid)
|
||||||
|
if not resolved_stream:
|
||||||
|
kodiutils.end_of_directory()
|
||||||
|
return
|
||||||
|
|
||||||
|
# Play this item
|
||||||
|
kodiutils.play(resolved_stream)
|
||||||
|
|
||||||
|
def download(self, uuid):
|
||||||
|
""" Download the requested item to cache.
|
||||||
|
:type uuid: string
|
||||||
|
"""
|
||||||
|
# We can notify Kodi already that we won't be returning a listing.
|
||||||
|
# This also fixes an odd Kodi bug where a starting a Progress() without closing the directory listing causes Kodi to hang.
|
||||||
|
kodiutils.end_of_directory()
|
||||||
|
|
||||||
|
# Check ffmpeg
|
||||||
|
if not Downloader.check():
|
||||||
|
kodiutils.ok_dialog(message=kodiutils.localize(30719)) # Could not download this episode since ffmpeg seems to be unavailable.
|
||||||
|
return
|
||||||
|
|
||||||
|
# Check download folder
|
||||||
|
download_folder = kodiutils.get_setting('episode_cache_folder').rstrip('/')
|
||||||
|
if not os.path.exists(download_folder):
|
||||||
|
kodiutils.ok_dialog(message=kodiutils.localize(30718)) # Could not download this episode since the download folder is not set or does not exist.
|
||||||
|
return
|
||||||
|
|
||||||
|
# Check if we already have downloaded this file
|
||||||
|
download_path = '%s/%s.mp4' % (download_folder, uuid)
|
||||||
|
if os.path.isfile(download_path):
|
||||||
|
# You have already downloaded this episode. Do you want to download it again?
|
||||||
|
result = kodiutils.yesno_dialog(message=kodiutils.localize(30726))
|
||||||
|
if not result:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Download this item
|
||||||
|
downloader = Downloader()
|
||||||
|
progress = kodiutils.progress(message=kodiutils.localize(30723)) # Starting download...
|
||||||
|
|
||||||
|
def callback(total, current):
|
||||||
|
""" Callback function to update the progress bar. """
|
||||||
|
percentage = current * 100 / total
|
||||||
|
progress.update(int(percentage), kodiutils.localize(30724, amount=round(percentage, 2))) # Downloading... ({amount}%)
|
||||||
|
return progress.iscanceled()
|
||||||
|
|
||||||
|
# Resolve the stream and start the download
|
||||||
|
resolved_stream = self._fetch_stream(uuid)
|
||||||
|
status = downloader.download(resolved_stream, download_path, callback)
|
||||||
|
|
||||||
|
# Close the progress bar
|
||||||
|
progress.close()
|
||||||
|
|
||||||
|
if status:
|
||||||
|
kodiutils.ok_dialog(message=kodiutils.localize(30725)) # Download has finished. You can now play this episode from cache.
|
||||||
|
|
||||||
|
def _fetch_stream(self, uuid):
|
||||||
|
""" Fetches the HLS stream of the item.
|
||||||
|
:type uuid: string
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
# Check if we have credentials
|
# Check if we have credentials
|
||||||
if not kodiutils.get_setting('username') or not kodiutils.get_setting('password'):
|
if not kodiutils.get_setting('username') or not kodiutils.get_setting('password'):
|
||||||
@ -48,29 +117,45 @@ class Player:
|
|||||||
message=kodiutils.localize(30701)) # To watch a video, you need to enter your credentials. Do you want to enter them now?
|
message=kodiutils.localize(30701)) # To watch a video, you need to enter your credentials. Do you want to enter them now?
|
||||||
if confirm:
|
if confirm:
|
||||||
kodiutils.open_settings()
|
kodiutils.open_settings()
|
||||||
kodiutils.end_of_directory()
|
return None
|
||||||
return
|
|
||||||
|
|
||||||
# Fetch an auth token now
|
|
||||||
try:
|
try:
|
||||||
auth = AuthApi(kodiutils.get_setting('username'), kodiutils.get_setting('password'), kodiutils.get_tokens_path())
|
|
||||||
|
|
||||||
# Get stream information
|
# Get stream information
|
||||||
resolved_stream = ContentApi(auth).get_stream_by_uuid(item)
|
resolved_stream = self._api.get_stream_by_uuid(uuid)
|
||||||
|
|
||||||
except (InvalidLoginException, AuthenticationException) as ex:
|
except (InvalidLoginException, AuthenticationException) as ex:
|
||||||
_LOGGER.error(ex)
|
_LOGGER.error(ex)
|
||||||
kodiutils.ok_dialog(message=kodiutils.localize(30702, error=str(ex)))
|
kodiutils.ok_dialog(message=kodiutils.localize(30702, error=str(ex)))
|
||||||
kodiutils.end_of_directory()
|
return None
|
||||||
return
|
|
||||||
|
|
||||||
except GeoblockedException:
|
except GeoblockedException:
|
||||||
kodiutils.ok_dialog(heading=kodiutils.localize(30709), message=kodiutils.localize(30710)) # This video is geo-blocked...
|
kodiutils.ok_dialog(heading=kodiutils.localize(30709), message=kodiutils.localize(30710)) # This video is geo-blocked...
|
||||||
return
|
return None
|
||||||
|
|
||||||
except UnavailableException:
|
except UnavailableException:
|
||||||
kodiutils.ok_dialog(heading=kodiutils.localize(30711), message=kodiutils.localize(30712)) # The video is unavailable...
|
kodiutils.ok_dialog(heading=kodiutils.localize(30711), message=kodiutils.localize(30712)) # The video is unavailable...
|
||||||
return
|
return None
|
||||||
|
|
||||||
# Play this item
|
return resolved_stream
|
||||||
kodiutils.play(resolved_stream)
|
|
||||||
|
@staticmethod
|
||||||
|
def _check_cached_episode(uuid):
|
||||||
|
""" Check if this episode is available in the download cache.
|
||||||
|
:type uuid: string
|
||||||
|
"""
|
||||||
|
download_folder = kodiutils.get_setting('episode_cache_folder').rstrip('/')
|
||||||
|
if not download_folder or not os.path.exists(download_folder):
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Check if we already have downloaded this file
|
||||||
|
download_path = '%s/%s.mp4' % (download_folder, uuid)
|
||||||
|
if os.path.isfile(download_path):
|
||||||
|
# You have cached this episode. Do you want to play from your cache or stream it?
|
||||||
|
result = kodiutils.yesno_dialog(message=kodiutils.localize(30720),
|
||||||
|
yeslabel=kodiutils.localize(30721), # Stream
|
||||||
|
nolabel=kodiutils.localize(30722)) # Play from cache
|
||||||
|
|
||||||
|
if not result:
|
||||||
|
return download_path
|
||||||
|
|
||||||
|
return None
|
||||||
|
@ -11,4 +11,8 @@
|
|||||||
<setting label="30829" type="bool" id="metadata_update" default="true" subsetting="true"/>
|
<setting label="30829" type="bool" id="metadata_update" default="true" subsetting="true"/>
|
||||||
<setting label="30831" type="action" action="RunPlugin(plugin://plugin.video.viervijfzes/metadata/update)"/>
|
<setting label="30831" type="action" action="RunPlugin(plugin://plugin.video.viervijfzes/metadata/update)"/>
|
||||||
</category>
|
</category>
|
||||||
|
<category label="30840"> <!-- Playback from cache -->
|
||||||
|
<setting label="30841" type="bool" id="episode_cache_enabled" default="true"/>
|
||||||
|
<setting label="30843" type="folder" id="episode_cache_folder" source="local" option="writeable" enable="eq(-1,true)"/>
|
||||||
|
</category>
|
||||||
</settings>
|
</settings>
|
||||||
|
@ -5,4 +5,4 @@ from __future__ import absolute_import, division, unicode_literals
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
logging.basicConfig()
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
|
52
tests/test_downloader.py
Normal file
52
tests/test_downloader.py
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
""" Tests for the episode downloader """
|
||||||
|
|
||||||
|
# pylint: disable=missing-docstring,no-self-use
|
||||||
|
|
||||||
|
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from resources.lib import kodiutils
|
||||||
|
from resources.lib.downloader import Downloader
|
||||||
|
from resources.lib.viervijfzes.auth import AuthApi
|
||||||
|
from resources.lib.viervijfzes.content import ContentApi, Program
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger('test-downloader')
|
||||||
|
|
||||||
|
|
||||||
|
class TestDownloader(unittest.TestCase):
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(TestDownloader, self).__init__(*args, **kwargs)
|
||||||
|
auth = AuthApi(kodiutils.get_setting('username'), kodiutils.get_setting('password'), kodiutils.get_tokens_path())
|
||||||
|
self._api = ContentApi(auth, cache_path=kodiutils.get_cache_path())
|
||||||
|
|
||||||
|
def test_check(self):
|
||||||
|
""" Test if ffmpeg is installed. """
|
||||||
|
status = Downloader.check()
|
||||||
|
self.assertTrue(status)
|
||||||
|
|
||||||
|
@unittest.skipUnless(kodiutils.get_setting('username') and kodiutils.get_setting('password'), 'Skipping since we have no credentials.')
|
||||||
|
def test_download(self):
|
||||||
|
""" Test to download a stream. """
|
||||||
|
program = self._api.get_program('vier', 'de-mol')
|
||||||
|
self.assertIsInstance(program, Program)
|
||||||
|
|
||||||
|
episode = program.episodes[0]
|
||||||
|
stream = self._api.get_stream_by_uuid(episode.uuid)
|
||||||
|
filename = '/tmp/download-test.mp4'
|
||||||
|
|
||||||
|
def progress_callback(total, seconds):
|
||||||
|
_LOGGER.info('Downloading... Progress = %d / %d seconds', seconds, total)
|
||||||
|
|
||||||
|
# Terminate when we have downloaded 5 seconds, we just want to test this
|
||||||
|
return seconds > 5
|
||||||
|
|
||||||
|
status = Downloader().download(stream=stream, output=filename, progress_callback=progress_callback)
|
||||||
|
self.assertFalse(status) # status is false since we cancelled
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
Loading…
Reference in New Issue
Block a user