Compare commits

...

20 Commits
0.0.1 ... main

@ -1,5 +1,18 @@
# cast
This program is intended for use on linux phones, and allow watching video streams, either directly on the phone via gstreamer or through chrome cast.
This program is intended for use on linux phones, and allow watching video streams, either directly on the phone via vlc or through chromecast.
Currently Swedish Public Service TV is supported.
Install like this on debian/mobian:
```
wget -O - https://repo.mic.ke/PUBLIC.KEY | gpg --dearmor --output micke-archive-unstable.gpg && sudo mv micke-archive-unstable.gpg /usr/share/keyrings
sudo wget -O /etc/apt/sources.list.d/debian-micke-unstable.list https://repo.mic.ke/debian/debian-micke-unstable.list
sudo apt update && sudo apt install cast
```
And like this with pip on any other distribution:
```
python3 -m pip install git+https://code.smolnet.org/micke/cast.git
```

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

Before

Width:  |  Height:  |  Size: 401 B

After

Width:  |  Height:  |  Size: 401 B

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Before

Width:  |  Height:  |  Size: 9.8 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

@ -7,5 +7,5 @@ Comment=Video Player
Exec=/usr/bin/cast
Icon=video-single-display-symbolic
Terminal=false
Categories=Security;Utility;
Categories=Video;
Keywords=SVT;

@ -1,6 +1,8 @@
#!/usr/bin/env python3
import sys
import threading
import time
import os
from typing import Callable
import pychromecast
@ -11,7 +13,8 @@ from vlc import Instance
from Channel import SVT
from ChannelProvider import ChannelProvider
from Utils import MYPATH, make_bitmap_from_file, make_sized_button
from Utils import (get_all_svt_categories, make_bitmap_from_file,
make_sized_button, BASEPATH)
WIDTH = int(720 / 2)
HEIGHT = int(1440 / 2)
@ -27,12 +30,23 @@ FLAGS.Center()
class Cast(wx.Frame):
def __init__(self, *args, **kw):
"""__init__.
:param args:
:param kw:
"""
self.m_mode = 'normal'
self.asset_path: str = '/usr/share/cast'
url = None
if "url" in kw:
self.m_mode = 'single'
url = kw['url']
del kw['url']
super().__init__(*args, **kw)
if not os.path.isdir(BASEPATH):
os.mkdir(BASEPATH)
self.m_selected_chromecast = None
self.SetSizeHints(WIDTH, HEIGHT, maxW=WIDTH)
self.m_style = self.GetWindowStyle()
@ -40,7 +54,6 @@ class Cast(wx.Frame):
args=(),
kwargs={})
self.m_vlc = Instance()
self.m_vlc_medialist = self.m_vlc.media_list_new()
self.m_vlc_listplayer = self.m_vlc.media_list_player_new()
self.m_chromecast_thr.start()
self.m_sizer: wx.Sizer = wx.BoxSizer(wx.VERTICAL)
@ -49,7 +62,12 @@ class Cast(wx.Frame):
self.m_panel.SetupScrolling(rate_y=SCROLL_RATE, scrollToTop=True)
self.m_panel.SetSizer(self.m_sizer)
self.m_providers: list[ChannelProvider] = self.get_providers()
self.show_channel_list(None, 0)
if url:
self.show_player(None, url, 0)
elif len(self.m_providers) > 1:
self.show_provider_list(None)
else:
self.show_channel_list(None, 0)
def add_back_button(self, callback: Callable) -> None:
backbtn = wx.Button(self.m_panel,
@ -68,6 +86,7 @@ class Cast(wx.Frame):
self.m_chromecasts, self.m_browser = pychromecast.get_chromecasts()
def get_player_controls(self, channel_index: int, uri: str) -> wx.BoxSizer:
outer_sizer = wx.BoxSizer(wx.VERTICAL)
inner_sizer = wx.BoxSizer(wx.HORIZONTAL)
play_button = wx.Button(self.m_panel,
-1,
@ -79,28 +98,34 @@ class Cast(wx.Frame):
"Pause",
size=(WIDTH / 4, BTN_HEIGHT))
back_button = wx.Button(self.m_panel,
stop_button = wx.Button(self.m_panel,
-1,
"Back",
"Stop",
size=(WIDTH / 4, BTN_HEIGHT))
back_button.Bind(
wx.EVT_BUTTON,
lambda event, cindex=channel_index: self.show_video_list(
event, cindex),
)
stop_button = wx.Button(self.m_panel,
-1,
"Stop",
size=(WIDTH / 4, BTN_HEIGHT))
if self.m_mode == 'normal':
back_button = wx.Button(self.m_panel,
-1,
"Go Back",
size=(WIDTH, BTN_HEIGHT))
back_button.Bind(
wx.EVT_BUTTON,
lambda event, cindex=channel_index: self.show_video_list(
event, cindex),
)
else:
back_button = wx.Button(self.m_panel,
-1,
label="Close",
size=(WIDTH, BTN_HEIGHT))
back_button.Bind(wx.EVT_BUTTON, lambda event: self.Destroy())
outer_sizer.Add(back_button, FLAGS)
inner_sizer.Add(play_button, FLAGS)
inner_sizer.Add(pause_button, FLAGS)
inner_sizer.Add(back_button, FLAGS)
inner_sizer.Add(stop_button, FLAGS)
if not self.m_chromecast_thr.is_alive(
) and not self.m_selected_chromecast:
btm = make_bitmap_from_file('{}/assets/Cast.png'.format(MYPATH), wx.Size(24,24))
if self.has_usable_chromecasts():
btm = make_bitmap_from_file(
'{}/assets/Cast.png'.format(self.asset_path), wx.Size(24, 24))
cast_button = wx.BitmapButton(self.m_panel,
-1,
bitmap=btm,
@ -112,7 +137,6 @@ class Cast(wx.Frame):
)
inner_sizer.Add(cast_button, FLAGS)
if self.m_selected_chromecast:
self.cast(wx.media.EVT_MEDIA_LOADED, uri),
play_button.Bind(wx.EVT_BUTTON, self.play_cast)
@ -128,42 +152,75 @@ class Cast(wx.Frame):
play_button.Bind(wx.EVT_BUTTON, self.play)
pause_button.Bind(wx.EVT_BUTTON, self.pause)
stop_button.Bind(wx.EVT_BUTTON, self.stop)
outer_sizer.Add(inner_sizer, FLAGS)
inner_sizer.Fit(self)
inner_sizer.Layout()
outer_sizer.Fit(self)
outer_sizer.Layout()
return inner_sizer
return outer_sizer
def get_providers(self) -> list[ChannelProvider]:
providers = list()
svt = ChannelProvider(
"SVT",
channels=[
SVT.SVT("feed"),
SVT.SVT("ch-svt1"),
SVT.SVT("ch-svt2"),
SVT.SVT("ch-svt24"),
SVT.SVT("ch-barnkanalen"),
SVT.SVT("ch-kunskapskanalen"),
],
)
providers.append(svt)
chandict = {
"kanaler": {
"channels": [
"ch-svt1", "ch-svt2", "ch-svt24", "ch-barnkanalen",
"ch-kunskapskanalen"
],
"displayname":
"SVT Channel Streams"
}
}
chandict["program"] = {
"channels": ["feed", "allprograms"],
"displayname": "SVT Shows - Latest and A-Ö"
}
categories = list()
for category in get_all_svt_categories():
categories.append(category['id'])
chandict["kategorier"] = {
"channels": categories,
"displayname": "SVT All Categories"
}
for provider in ["kanaler", "program", "kategorier"]:
channels = list()
for id in chandict[provider]["channels"]:
channels.append(SVT.SVT(id))
svt = ChannelProvider(chandict[provider]["displayname"],
channels=channels)
providers.append(svt)
return providers
def has_usable_chromecasts(self) -> bool:
if self.m_chromecast_thr.is_alive():
return False
if self.m_selected_chromecast:
return False
result = False
for cast in self.m_chromecasts:
if cast.cast_type != 'audio':
result = True
break
return result
def show_channel_list(self, _, provider_index) -> None:
self.m_selected_provider_index = provider_index
self.m_selected_provider = self.m_providers[provider_index]
self.m_sizer.Clear(delete_windows=True)
self.m_sizer = wx.BoxSizer(wx.VERTICAL)
closebtn = wx.Button(self.m_panel,
-1,
label="Close",
size=(WIDTH, BTN_HEIGHT))
closebtn.Bind(wx.EVT_BUTTON, lambda event: self.Destroy())
self.m_sizer.Add(closebtn, 0, wx.ALL, 1)
if len(self.m_providers) > 1:
back_callback = lambda event: self.show_provider_list(event)
self.add_back_button(back_callback)
else:
closebtn = wx.Button(self.m_panel,
-1,
label="Close",
size=(WIDTH, BTN_HEIGHT))
closebtn.Bind(wx.EVT_BUTTON, lambda event: self.Destroy())
self.m_sizer.Add(closebtn, 0, wx.ALL, 1)
channel_index = 0
@ -182,6 +239,30 @@ class Cast(wx.Frame):
self.m_sizer.Fit(self)
self.m_sizer.Layout()
def show_provider_list(self, _) -> None:
self.m_sizer.Clear(delete_windows=True)
self.m_sizer = wx.BoxSizer(wx.VERTICAL)
closebtn = wx.Button(self.m_panel,
-1,
label="Close",
size=(WIDTH, BTN_HEIGHT))
closebtn.Bind(wx.EVT_BUTTON, lambda event: self.Destroy())
self.m_sizer.Add(closebtn, 0, wx.ALL, 1)
provider_index = 0
for provider in self.m_providers:
bitmap = provider.get_logo_as_bitmap()
callback = lambda event, pindex=provider_index: self.show_channel_list(
event, pindex)
btn_sizer: wx.BoxSizer = make_sized_button(self.m_panel, bitmap,
provider.get_name(),
callback)
self.m_sizer.Add(btn_sizer, 0, wx.ALL, 1)
provider_index += 1
self.m_panel.SetupScrolling(rate_y=SCROLL_RATE, scrollToTop=True)
self.m_panel.SetSizer(self.m_sizer)
self.m_sizer.Fit(self)
self.m_sizer.Layout()
def show_video_list(self, _, channel_index) -> None:
self.SetWindowStyle(self.m_style)
self.m_selected_channel = self.m_selected_provider.get_channel_by_index(
@ -191,7 +272,11 @@ class Cast(wx.Frame):
back_callback = lambda event: self.show_channel_list(
event, self.m_selected_provider_index)
self.add_back_button(back_callback)
if self.m_selected_provider.get_name() == 'SVT' and self.m_selected_channel.get_id() == 'feed':
if self.m_selected_provider.get_name().startswith(
'SVT') and self.m_selected_channel.get_id() not in [
"ch-svt1", "ch-svt2", "ch-svt24", "ch-barnkanalen",
"ch-kunskapskanalen"
]:
def refresh_callback(event):
self.m_selected_channel.refresh()
@ -206,9 +291,13 @@ class Cast(wx.Frame):
if self.m_selected_channel.wait():
with wx.BusyInfo("Please wait, working..."):
number_of_waits = 0
while self.m_selected_channel.wait():
number_of_waits += 1
time.sleep(1)
wx.GetApp().Yield()
if number_of_waits > 10:
break
btnindex = 0
for item in self.m_selected_channel.get_items(): # type: ignore
@ -271,8 +360,10 @@ class Cast(wx.Frame):
:param uri str: the link to the video stream
"""
media = self.m_vlc.media_new(uri)
self.m_vlc_medialist.add_media(media)
self.m_vlc_listplayer.set_media_list(self.m_vlc_medialist)
medialist = self.m_vlc.media_list_new()
medialist.add_media(media)
self.m_vlc_listplayer.set_media_list(medialist)
self.m_sizer.Clear(delete_windows=True)
self.m_sizer = wx.BoxSizer(wx.VERTICAL)
@ -298,7 +389,13 @@ class Cast(wx.Frame):
self.m_sizer.Add(cancel_btn)
for cast in self.m_chromecasts:
friendly_name = cast.device.friendly_name
if cast.cast_type == 'audio':
continue
friendly_name = "Unknown Chromecast"
try:
friendly_name = cast.device.friendly_name
except AttributeError:
friendly_name = cast.cast_info.friendly_name
btn = wx.Button(self.m_panel,
id=-1,
label=friendly_name,
@ -388,7 +485,12 @@ class Cast(wx.Frame):
if __name__ == "__main__":
# When this module is run (not imported) then create the app, the
# frame, show it, and start the event loop.
url = None
app: wx.App = wx.App()
frm: Cast = Cast(None, title="Cast")
if len(sys.argv) > 1:
url = sys.argv[1]
frm: Cast = Cast(None, title="Cast", url=url)
else:
frm: Cast = Cast(None, title="Cast")
frm.Show()
app.MainLoop()

@ -5,10 +5,17 @@ with open("README.md", "r", encoding="utf-8") as fh:
setuptools.setup(
name="cast",
version="0.0.1",
version="0.1.1",
author="Micke Nordin",
author_email="hej@mic.ke",
data_files = [('share/applications', ['data/org.smolnet.cast.desktop']),],
data_files=[('share/applications', ['data/org.smolnet.cast.desktop']),
('share/cast/assets', [
'data/assets/Barnkanalen.png', 'data/assets/Cast.png',
'data/assets/Default.png',
'data/assets/Kunskapskanalen.png', 'data/assets/SVT1.png',
'data/assets/SVT24.png', 'data/assets/SVT2.png',
'data/assets/SVT.png', 'data/assets/YouTube.png'
])],
description="A Public Service video player.",
long_description=long_description,
long_description_content_type="text/markdown",
@ -26,4 +33,3 @@ setuptools.setup(
python_requires=">=3.9",
scripts=["scripts/cast"],
)

@ -6,17 +6,19 @@ from datetime import datetime
import feedparser
import requests
import wx
from bs4 import BeautifulSoup
from Channel import Channel
from Items import Item
from Utils import (add_video, hash_string, make_bitmap_from_url,
from Utils import (add_video, get_all_svt_categories, get_all_svt_programs,
get_svt_category, get_svt_id, get_svt_thumb_from_id_changed,
get_svt_thumbnail, hash_string, make_bitmap_from_url,
resolve_svt_channel, video_exists)
default_rss_url = 'http://www.svtplay.se/rss.xml'
class SVT(Channel):
def __init__(self, channel_id: str) -> None:
chan_dict = resolve_svt_channel(channel_id)
logo = chan_dict['thumbnail']
@ -24,7 +26,6 @@ class SVT(Channel):
super().__init__(channel_id, 'SVT', default_rss_url, logo, name)
def parse_feed(self) -> None:
#self.m_items: list[Item] = list()
resolved_link = str()
description = str()
title = str()
@ -32,8 +33,11 @@ class SVT(Channel):
thumbnail: wx.Bitmap = wx.Bitmap()
published_parsed: datetime = datetime.now()
video_id = str()
categories: dict = dict()
for category in get_all_svt_categories():
categories[category['id']] = category
if self.m_id == 'feed' :
if self.m_id == 'feed':
feed = feedparser.parse(self.get_feed())
entries = feed['entries']
for entry in entries:
@ -46,16 +50,7 @@ class SVT(Channel):
thumbnail_link = str(link['href'])
break
page = requests.get(str(entry['link']))
soup = BeautifulSoup(page.text, 'html.parser')
for element in soup.find_all('a'):
href = element.get('href')
datart = element.get('data-rt')
if datart == 'top-area-play-button':
svt_id = href.split('=')[1].split('&')[0]
svt_id = get_svt_id(str(entry['link']))
resolved_link = self.resolve_link(svt_id)
description = str(entry['description'])
published_parsed = entry['published_parsed']
@ -63,12 +58,81 @@ class SVT(Channel):
if not resolved_link:
continue
thumbnail = make_bitmap_from_url(
thumbnail_link, wx.Size(int(self.m_screen_width), 150))
thumbnail_link, wx.Size(int(self.m_screen_width), 150), video_id)
item = Item(description, resolved_link, self.m_provider_name,
published_parsed, thumbnail, title)
self.m_items.append(item)
add_video(video_id, self.m_id, self.m_provider_name,
description, resolved_link, published_parsed,
thumbnail, title, 0)
elif self.m_id == 'allprograms':
entries = get_all_svt_programs()
for entry in entries:
url = entry['item']['urls']['svtplay']
video_id = hash_string(url.split('/')[1])
svt_id = str()
link = "https://svtplay.se{}".format(url)
if video_exists(video_id, 'allprograms'):
continue
thumbnail_link = get_svt_thumbnail(link)
svt_id = get_svt_id(link)
resolved_link = self.resolve_link(svt_id)
title = str(entry['heading'])
type = str(entry['item']['__typename'])
published_parsed = datetime.now()
swe_only = "no"
if entry['item']['restrictions']['onlyAvailableInSweden']:
swe_only = 'yes'
description = "{}\nOnly available in Sweden:{}\nType of program:{}".format(
title, swe_only, type)
if not resolved_link:
continue
thumbnail = make_bitmap_from_url(
thumbnail_link, wx.Size(int(self.m_screen_width), 150), video_id)
item = Item(description,
resolved_link,
self.m_provider_name,
published_parsed,
thumbnail,
title,
sort_key=entry['selection_name'])
self.m_items.append(item)
add_video(video_id, self.m_id, self.m_provider_name,
description, resolved_link, published_parsed,
thumbnail, title, 0)
elif self.m_id in categories.keys():
entries = get_svt_category(self.m_id)
for entry in entries:
elem = entry["item"]
imgformat = "wide"
if not "wide" in elem["images"].keys():
if "cleanWide" in elem["images"].keys():
imgformat = "cleanWide"
else:
continue
url = elem["urls"]["svtplay"]
video_id = hash_string(url)
svt_id = elem["videoSvtId"]
resolved_link = self.resolve_link(svt_id)
if not resolved_link:
continue
title = str(entry['heading'])
description = str(entry["description"])
published_parsed = datetime.now()
thumbnail_link = get_svt_thumb_from_id_changed(
elem['images'][imgformat]["id"],
elem['images'][imgformat]["changed"],
size=self.m_screen_width)
thumbnail = make_bitmap_from_url(
thumbnail_link, wx.Size(int(self.m_screen_width), 150), video_id)
item = Item(description, resolved_link, self.m_provider_name,
published_parsed, thumbnail, title)
self.m_items.append(item)
add_video(video_id, self.m_id, self.m_provider_name, description,
resolved_link, published_parsed, thumbnail, title, 0)
add_video(video_id, self.m_id, self.m_provider_name,
description, resolved_link, published_parsed,
thumbnail, title, 0)
else:
chan_dict = resolve_svt_channel(self.m_id)
@ -83,12 +147,10 @@ class SVT(Channel):
published_parsed, thumbnail, title)
self.m_items.append(item)
def resolve_link(self,svt_id) -> str:
def resolve_link(self, svt_id) -> str:
url = 'https://api.svt.se/video/{}'.format(svt_id)
print(url)
api = json.loads(
requests.get(url).text)
api = json.loads(requests.get(url).text)
resolved_link = ''
try:
@ -98,4 +160,3 @@ class SVT(Channel):
except KeyError:
pass
return resolved_link

@ -1,6 +1,5 @@
#/usr/bin/env python3
import threading
import time
from os import path
import wx
@ -16,7 +15,7 @@ class ChannelProvider:
self.m_id = providerid
self.m_logo: wx.Bitmap = get_default_logo(providerid)
self.m_channels: list[Channel] = channels
if len(self.m_channels) > 0 and not providerid == 'SVT':
if len(self.m_channels) > 0 and not providerid.startswith('SVT'):
self.m_thr = threading.Thread(target=self.make_latest,
args=(),
kwargs={})

@ -1,5 +1,6 @@
#!/usr/bin/env python3
from datetime import datetime
from typing import Union
import wx
@ -12,7 +13,9 @@ class Item(dict):
published: datetime,
thumbnail: wx.Bitmap,
title: str,
watchtime: int = 0):
watchtime: int = 0,
sort_key: Union[str, None] = None,
):
self.__dict__['description'] = description
self.__dict__['link'] = link
self.__dict__['provider_id'] = provider_id
@ -20,6 +23,7 @@ class Item(dict):
self.__dict__['thumbnail'] = thumbnail
self.__dict__['title'] = title
self.__dict__['watchtime'] = watchtime
self.__dict__['sort_key'] = sort_key
def __setitem__(self, key, item):
self.__dict__[key] = item

@ -1,6 +1,7 @@
#!/usr/bin/env python3
import hashlib
import io
import json
import sqlite3
import time
from datetime import datetime
@ -9,18 +10,20 @@ from typing import Callable, Union
import requests
import wx
from bs4 import BeautifulSoup
from Items import Item
HEIGHT = int(1440 / 2)
BTN_HEIGHT = 40
SIZE = wx.Size(68,100)
MYPATH = path.dirname(path.abspath(__file__))
SIZE = wx.Size(68, 100)
SCREEN_WIDTH = int(720 / 2)
BASEPATH = path.join(str(environ.get("HOME")), '.config/cast')
DB_FILE_NAME = 'cast.db'
SUB_TABLE = 'subscriptions'
VIDEO_TABLE = 'videos'
CAT_CACHE = None
CHAN_CACHE = None
def add_video(video_id: str,
@ -68,11 +71,99 @@ def add_video(video_id: str,
con.commit()
def get_default_logo(providerid: str = 'default') -> wx.Bitmap:
if providerid == 'SVT':
return wx.Bitmap('{}/assets/SVT.png'.format(MYPATH))
def get_svt_thumb_from_id_changed(id: str,
changed: str,
size: str = "480") -> str:
return "https://www.svtstatic.se/image/custom/{}/{}/{}".format(
size, id, changed)
def get_all_svt_categories() -> list:
global CAT_CACHE
if CAT_CACHE:
categories = CAT_CACHE
else:
return wx.Bitmap('{}/assets/Default.png'.format(MYPATH))
categories: list = list()
url = "https://www.svtplay.se/kategori"
data = get_svt_data(url)
for entry in data:
if 'genres' in entry.keys():
categories = entry['genres']
break
CAT_CACHE = categories
return categories
def get_all_svt_channels() -> dict:
url = "https://www.svtplay.se/kanaler"
result: dict = dict()
data = get_svt_data(url)
for entry in data:
if "channels" in entry:
for channel in entry["channels"]:
if type(entry["channels"][channel]) == type(list()):
for item in entry["channels"][channel]:
if item["__typename"] == "Channel" and "running" in item:
result[item["id"]] = {
"thumbnail":
make_bitmap_from_url(
get_svt_thumb_from_id_changed(
item["running"]["image"]['id'],
item["running"]["image"]['changed'],
SCREEN_WIDTH),
wx.Size(width=SCREEN_WIDTH, height=480)),
"name":
item["name"]
}
return result
def get_svt_category(category: str) -> list:
url = 'https://www.svtplay.se/kategori/{}?tab=all'.format(category)
data = get_svt_data(url)
programs = list()
for entry in data:
if "categoryPage" in entry.keys():
for tab in entry["categoryPage"]["lazyLoadedTabs"]:
if "selections" in tab.keys():
for selection in tab["selections"]:
programs += selection['items']
break
return programs
def get_svt_data(url: str) -> list:
result: list = list()
res = requests.get(url)
soup = BeautifulSoup(res.text, features="lxml")
data = json.loads(soup.find(
id="__NEXT_DATA__").string)["props"]["urqlState"] # type: ignore
for key in data.keys():
result.append(json.loads(data[key]["data"]))
return result
def get_all_svt_programs() -> list:
url = 'https://www.svtplay.se/program'
data = get_svt_data(url)
programs = list()
for entry in data:
if "programAtillO" in entry.keys():
for selection in entry["programAtillO"]["selections"]:
for item in selection["items"]:
item['selection_name'] = selection['name']
programs.append(item)
break
return programs
def get_default_logo(providerid: str = 'default',
path: str = '/usr/share/cast') -> wx.Bitmap:
if providerid.startswith('SVT'):
return wx.Bitmap('{}/assets/SVT.png'.format(path))
else:
return wx.Bitmap('{}/assets/Default.png'.format(path))
def get_latest(provider_id: str,
@ -141,6 +232,29 @@ def get_subscriptions(basepath: str = BASEPATH,
return subscriptions
def get_svt_id(link: str) -> str:
svt_id = str()
page = requests.get(link)
soup = BeautifulSoup(page.text, 'html.parser')
for element in soup.find_all('a'):
href = element.get('href')
datart = element.get('data-rt')
if datart == 'top-area-play-button':
svt_id = href.split('=')[1].split('&')[0]
return svt_id
def get_svt_thumbnail(link: str) -> str:
page = requests.get(link)
soup = BeautifulSoup(page.text, 'html.parser')
meta = soup.find(property="og:image")
image_link = meta["content"] #type: ignore
return image_link # type: ignore
def get_videos(channel_id: str,
basepath: str = BASEPATH,
filename: str = DB_FILE_NAME) -> list[Item]:
@ -182,7 +296,7 @@ def hash_string(string: str) -> str:
def make_sized_button(parent_pnl: wx.Panel, bitmap_or_str: Union[wx.Bitmap,
str],
text: str, callback: Callable) -> wx.BoxSizer:
btn_sizer = wx.StaticBoxSizer(wx.HORIZONTAL,parent_pnl)
btn_sizer = wx.StaticBoxSizer(wx.HORIZONTAL, parent_pnl)
if type(bitmap_or_str) == type(str):
if bitmap_or_str.startswith('http'): # type: ignore
bitmap = make_bitmap_from_url(bitmap_or_str) # type: ignore
@ -195,7 +309,7 @@ def make_sized_button(parent_pnl: wx.Panel, bitmap_or_str: Union[wx.Bitmap,
wx.ID_ANY,
bitmap,
style=btn_style,
size=wx.Size(100,68))
size=wx.Size(100, 68))
btn_logo.SetToolTip(text)
btn_sizer.Add(btn_logo, 0, wx.EXPAND, 1)
@ -203,8 +317,7 @@ def make_sized_button(parent_pnl: wx.Panel, bitmap_or_str: Union[wx.Bitmap,
wx.ID_ANY,
text,
style=wx.BORDER_NONE | wx.BU_AUTODRAW,
size=wx.Size(SCREEN_WIDTH - 100,
SIZE.GetHeight()))
size=wx.Size(SCREEN_WIDTH - 100, SIZE.GetHeight()))
btn_text.SetToolTip(text)
btn_sizer.Add(btn_text, 0, wx.EXPAND, 1)
parent_pnl.Bind(wx.EVT_BUTTON, callback, btn_logo)
@ -213,7 +326,16 @@ def make_sized_button(parent_pnl: wx.Panel, bitmap_or_str: Union[wx.Bitmap,
return btn_sizer
def make_bitmap_from_url(logo_url: str, size: wx.Size = SIZE) -> wx.Bitmap:
def make_bitmap_from_url(logo_url: str,
size: wx.Size = SIZE,
video_id: str = "") -> wx.Bitmap:
if not video_id:
video_id = hash_string(logo_url)
thumbpath = path.join(BASEPATH, 'thumbnails')
thumbnail = path.join(thumbpath, video_id)
if path.isfile(thumbnail):
return make_bitmap_from_file(thumbnail, size)
res = requests.get(logo_url)
content = res.content
content_bytes = io.BytesIO(content)
@ -223,7 +345,12 @@ def make_bitmap_from_url(logo_url: str, size: wx.Size = SIZE) -> wx.Bitmap:
height = image.GetHeight()
size.SetHeight(int(height / scale_factor))
image.Rescale(size.GetWidth(), size.GetHeight())
return wx.Bitmap(image)
bitmap = wx.Bitmap(image)
if not path.isdir(thumbpath):
makedirs(thumbpath)
if not path.isfile(thumbnail):
bitmap.SaveFile(thumbnail, wx.BITMAP_TYPE_PNG)
return bitmap
def make_bitmap_from_file(path, size: wx.Size = SIZE) -> wx.Bitmap:
@ -236,44 +363,34 @@ def make_bitmap_from_file(path, size: wx.Size = SIZE) -> wx.Bitmap:
return wx.Bitmap(image)
def resolve_svt_channel(svt_id: str) -> dict:
def resolve_svt_channel(svt_id: str, path: str = '/usr/share/cast') -> dict:
global CHAN_CACHE
channels = {
"ch-barnkanalen": {
"name":
"Barnkanalen",
"thumbnail":
make_bitmap_from_file('{}/assets/Barnkanalen.png'.format(MYPATH))
},
"ch-svt1": {
"name": "SVT 1",
"thumbnail":
make_bitmap_from_file('{}/assets/SVT1.png'.format(MYPATH))
},
"ch-svt2": {
"name": "SVT 2",
"thumbnail":
make_bitmap_from_file('{}/assets/SVT2.png'.format(MYPATH))
},
"ch-svt24": {
"name":
"SVT 24",
"thumbnail":
make_bitmap_from_file('{}/assets/SVT24.png'.format(MYPATH))
},
"ch-kunskapskanalen": {
"name":
"Kunskapskanalen",
"thumbnail":
make_bitmap_from_file(
'{}/assets/Kunskapskanalen.png'.format(MYPATH))
},
"feed": {
if CHAN_CACHE:
channels = CHAN_CACHE
else:
channels = get_all_svt_channels()
channels["feed"] = {
"name": "Senaste program",
"thumbnail":
make_bitmap_from_file('{}/assets/SVT.png'.format(MYPATH))
},
}
make_bitmap_from_file('{}/assets/SVT.png'.format(path))
}
channels["allprograms"] = {
"name": "Alla program",
"thumbnail":
make_bitmap_from_file('{}/assets/SVT.png'.format(path))
}
for category in get_all_svt_categories():
channels[category['id']] = {
"name":
category["name"],
"thumbnail":
make_bitmap_from_url(
get_svt_thumb_from_id_changed(
category['image']['id'], category['image']['changed']))
}
CHAN_CACHE = channels
return channels[svt_id]

@ -1,3 +1,4 @@
[DEFAULT]
Depends3: python3-bs4, python3-wxgtk4.0, python3-feedparser, python3-vlc, python3-pychromecast
Depends3: python3-bs4, python3-wxgtk4.0, python3-wxgtk-media4.0, python3-feedparser, python3-vlc, python3-pychromecast
Debian-Version: 1
Package3: cast

Loading…
Cancel
Save