Kill YouTube Support and switch to VLC for playback
With this commit only SVT Play is currently supported. I feel that TubeFeeder is the best choice for YouTube on mobile linux and maintenance burden will be less if I don't compete with an allready nice app :) I might add support for other providers down the line if they are not supported in TubeFeeder. See: * https://github.com/Tubefeeder/TubeFeeder for more information about TubeFeeder I am also switching out gstreamer in favour of VLC with this commit.
This commit is contained in:
parent
c2502162a8
commit
3604862b84
11 changed files with 54 additions and 346 deletions
|
@ -2,4 +2,4 @@
|
||||||
|
|
||||||
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 gstreamer or through chrome cast.
|
||||||
|
|
||||||
Currently Swedish Public Service TV and YouTube is supported.
|
Currently Swedish Public Service TV is supported.
|
||||||
|
|
27
dpkg.lst
27
dpkg.lst
|
@ -1,28 +1,5 @@
|
||||||
python3-bs4
|
python3-bs4
|
||||||
gir1.2-gstreamer-1.0:amd64
|
|
||||||
gstreamer1.0-alsa:amd64
|
|
||||||
gstreamer1.0-clutter-3.0:amd64
|
|
||||||
gstreamer1.0-gl:amd64
|
|
||||||
gstreamer1.0-gtk3:amd64
|
|
||||||
gstreamer1.0-libav:amd64
|
|
||||||
gstreamer1.0-nice:amd64
|
|
||||||
gstreamer1.0-packagekit
|
|
||||||
gstreamer1.0-pipewire:amd64
|
|
||||||
gstreamer1.0-plugins-bad:amd64
|
|
||||||
gstreamer1.0-plugins-base:amd64
|
|
||||||
gstreamer1.0-plugins-base-apps
|
|
||||||
gstreamer1.0-plugins-good:amd64
|
|
||||||
gstreamer1.0-plugins-rtp
|
|
||||||
gstreamer1.0-plugins-ugly:amd64
|
|
||||||
gstreamer1.0-pulseaudio:amd64
|
|
||||||
gstreamer1.0-tools
|
|
||||||
gstreamer1.0-x:amd64
|
|
||||||
libgstreamer-gl1.0-0:amd64
|
|
||||||
libgstreamer-plugins-bad1.0-0:amd64
|
|
||||||
libgstreamer-plugins-base1.0-0:amd64
|
|
||||||
libgstreamer-plugins-good1.0-0:amd64
|
|
||||||
libgstreamer1.0-0:amd64
|
|
||||||
python3-wxgtk-media4.0
|
|
||||||
python3-wxgtk4.0
|
python3-wxgtk4.0
|
||||||
python3-feedparser
|
python3-feedparser
|
||||||
youtube-dl
|
python3-vlc
|
||||||
|
python3-pychromecast
|
||||||
|
|
30
install.sh
30
install.sh
|
@ -11,35 +11,7 @@ elif [[ "${1}" == "-h" ]]; then
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
sudo apt install python3-pip \
|
sudo apt install $(cat dpkg.lst | tr '\n' ' ')
|
||||||
python3-bs4 \
|
|
||||||
gir1.2-gstreamer-1.0 \
|
|
||||||
gstreamer1.0-alsa \
|
|
||||||
gstreamer1.0-clutter-3.0 \
|
|
||||||
gstreamer1.0-gl \
|
|
||||||
gstreamer1.0-gtk3 \
|
|
||||||
gstreamer1.0-libav \
|
|
||||||
gstreamer1.0-nice \
|
|
||||||
gstreamer1.0-packagekit \
|
|
||||||
gstreamer1.0-pipewire \
|
|
||||||
gstreamer1.0-plugins-bad \
|
|
||||||
gstreamer1.0-plugins-base \
|
|
||||||
gstreamer1.0-plugins-base-apps \
|
|
||||||
gstreamer1.0-plugins-good \
|
|
||||||
gstreamer1.0-plugins-rtp \
|
|
||||||
gstreamer1.0-plugins-ugly \
|
|
||||||
gstreamer1.0-pulseaudio \
|
|
||||||
gstreamer1.0-tools \
|
|
||||||
gstreamer1.0-x \
|
|
||||||
libgstreamer-gl1.0-0 \
|
|
||||||
libgstreamer-plugins-bad1.0-0 \
|
|
||||||
libgstreamer-plugins-base1.0-0 \
|
|
||||||
libgstreamer1.0-0 \
|
|
||||||
python3-wxgtk-media4.0 \
|
|
||||||
python3-wxgtk4.0 \
|
|
||||||
python3-feedparser \
|
|
||||||
youtube-dl
|
|
||||||
sudo pip3 install pychromecast
|
|
||||||
sudo cp src/main.py /usr/local/bin/cast
|
sudo cp src/main.py /usr/local/bin/cast
|
||||||
sudo chmod +x /usr/local/bin/cast
|
sudo chmod +x /usr/local/bin/cast
|
||||||
sudo cp -a src/{Channel,ChannelProvider,Items,Utils} /usr/lib/python3/dist-packages/
|
sudo cp -a src/{Channel,ChannelProvider,Items,Utils} /usr/lib/python3/dist-packages/
|
||||||
|
|
|
@ -1,2 +1 @@
|
||||||
pychromecast
|
pychromecast
|
||||||
youtube-search-python
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import threading
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
import feedparser
|
import feedparser
|
||||||
|
@ -64,7 +63,7 @@ class SVT(Channel):
|
||||||
if not resolved_link:
|
if not resolved_link:
|
||||||
continue
|
continue
|
||||||
thumbnail = make_bitmap_from_url(
|
thumbnail = make_bitmap_from_url(
|
||||||
thumbnail_link, wx.Size(self.m_screen_width, 150))
|
thumbnail_link, wx.Size(int(self.m_screen_width), 150))
|
||||||
item = Item(description, resolved_link, self.m_provider_name,
|
item = Item(description, resolved_link, self.m_provider_name,
|
||||||
published_parsed, thumbnail, title)
|
published_parsed, thumbnail, title)
|
||||||
self.m_items.append(item)
|
self.m_items.append(item)
|
||||||
|
@ -86,8 +85,10 @@ class SVT(Channel):
|
||||||
|
|
||||||
|
|
||||||
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(
|
api = json.loads(
|
||||||
requests.get('https://api.svt.se/video/{}'.format(svt_id)).text)
|
requests.get(url).text)
|
||||||
resolved_link = ''
|
resolved_link = ''
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -1,47 +0,0 @@
|
||||||
import feedparser
|
|
||||||
import wx
|
|
||||||
|
|
||||||
from Channel import Channel
|
|
||||||
from Items import Item
|
|
||||||
from Utils import add_video, hash_string, make_bitmap_from_url, video_exists
|
|
||||||
|
|
||||||
#from youtubesearchpython.search import VideosSearch
|
|
||||||
|
|
||||||
|
|
||||||
class YouTube(Channel):
|
|
||||||
def __init__(self, channel_id: str, name: str, logo: wx.Bitmap) -> None:
|
|
||||||
self.m_name = name
|
|
||||||
rss_url = 'https://www.youtube.com/feeds/videos.xml?channel_id={}'.format(
|
|
||||||
channel_id)
|
|
||||||
self.m_logo = logo
|
|
||||||
self.m_items: list[Item] = list()
|
|
||||||
super().__init__(channel_id, 'YouTube', rss_url, self.m_logo, name)
|
|
||||||
|
|
||||||
def parse_feed(self) -> None:
|
|
||||||
feed = feedparser.parse(self.get_feed())
|
|
||||||
entries = feed['entries']
|
|
||||||
|
|
||||||
for entry in entries:
|
|
||||||
video_id = hash_string(entry['id'])
|
|
||||||
if video_exists(video_id, self.m_id):
|
|
||||||
continue
|
|
||||||
title = str(entry['title'])
|
|
||||||
thumbnail_link = str(entry['media_thumbnail'][0]['url'])
|
|
||||||
description = str(entry['description'])
|
|
||||||
#video_search = VideosSearch(entry['id'], limit = 1).result()['result'][0]
|
|
||||||
resolved_link = entry['link']
|
|
||||||
print(resolved_link)
|
|
||||||
|
|
||||||
published_parsed = entry['published_parsed']
|
|
||||||
|
|
||||||
if not resolved_link:
|
|
||||||
continue
|
|
||||||
thumbnail = make_bitmap_from_url(thumbnail_link,
|
|
||||||
wx.Size(self.m_screen_width, 150))
|
|
||||||
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)
|
|
|
@ -1,18 +1,14 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import hashlib
|
import hashlib
|
||||||
import io
|
import io
|
||||||
import json
|
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import time
|
import time
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from os import environ, makedirs, path
|
from os import environ, makedirs, path
|
||||||
from typing import Callable, Union
|
from typing import Callable, Union
|
||||||
from urllib.parse import urlparse
|
|
||||||
from youtubesearchpython import ChannelsSearch
|
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
import wx
|
import wx
|
||||||
import youtube_dl
|
|
||||||
|
|
||||||
from Items import Item
|
from Items import Item
|
||||||
|
|
||||||
|
@ -27,34 +23,6 @@ SUB_TABLE = 'subscriptions'
|
||||||
VIDEO_TABLE = 'videos'
|
VIDEO_TABLE = 'videos'
|
||||||
|
|
||||||
|
|
||||||
def add_subscription(channel_id: str,
|
|
||||||
name: str,
|
|
||||||
basepath: str = BASEPATH,
|
|
||||||
filename: str = DB_FILE_NAME) -> None:
|
|
||||||
fullpath = path.join(basepath, filename)
|
|
||||||
thumbpath = path.join(basepath, 'thumbnails')
|
|
||||||
thumbnail = path.join(thumbpath, channel_id)
|
|
||||||
fullpath = path.join(basepath, filename)
|
|
||||||
if not path.isdir(thumbpath):
|
|
||||||
makedirs(thumbpath)
|
|
||||||
if not path.isfile(thumbnail):
|
|
||||||
channels_search = ChannelsSearch(name, limit=1).result()['result'][0] #type: ignore
|
|
||||||
bitmap = make_bitmap_from_url('https:' + channels_search['thumbnails'][0]['url'])
|
|
||||||
bitmap.SaveFile(thumbnail, wx.BITMAP_TYPE_PNG)
|
|
||||||
con = sqlite3.connect(fullpath)
|
|
||||||
cur = con.cursor()
|
|
||||||
create_query: str = '''CREATE TABLE IF NOT EXISTS {}
|
|
||||||
(channel_id TEXT PRIMARY KEY, channel_name TEXT, thumb_path TEXT)'''.format(
|
|
||||||
SUB_TABLE)
|
|
||||||
cur.execute(create_query)
|
|
||||||
con.commit()
|
|
||||||
upsert_query: str = '''INSERT INTO {} (channel_id, channel_name, thumb_path)
|
|
||||||
VALUES(?,?,?) ON CONFLICT(channel_id) DO NOTHING'''.format(
|
|
||||||
SUB_TABLE )
|
|
||||||
cur.execute(upsert_query, [channel_id, name, thumbnail])
|
|
||||||
con.commit()
|
|
||||||
|
|
||||||
|
|
||||||
def add_video(video_id: str,
|
def add_video(video_id: str,
|
||||||
channel_id: str,
|
channel_id: str,
|
||||||
provider_id: str,
|
provider_id: str,
|
||||||
|
@ -103,8 +71,6 @@ def add_video(video_id: str,
|
||||||
def get_default_logo(providerid: str = 'default') -> wx.Bitmap:
|
def get_default_logo(providerid: str = 'default') -> wx.Bitmap:
|
||||||
if providerid == 'SVT':
|
if providerid == 'SVT':
|
||||||
return wx.Bitmap('{}/assets/SVT.png'.format(MYPATH))
|
return wx.Bitmap('{}/assets/SVT.png'.format(MYPATH))
|
||||||
if providerid == 'YouTube':
|
|
||||||
return wx.Bitmap('{}/assets/YouTube.png'.format(MYPATH))
|
|
||||||
else:
|
else:
|
||||||
return wx.Bitmap('{}/assets/Default.png'.format(MYPATH))
|
return wx.Bitmap('{}/assets/Default.png'.format(MYPATH))
|
||||||
|
|
||||||
|
@ -213,18 +179,6 @@ def hash_string(string: str) -> str:
|
||||||
return hash_object.hexdigest()
|
return hash_object.hexdigest()
|
||||||
|
|
||||||
|
|
||||||
def import_from_newpipe(filename) -> None:
|
|
||||||
|
|
||||||
if path.isfile(filename):
|
|
||||||
with open(filename, 'r') as subs:
|
|
||||||
sub_data = json.loads(subs.read())
|
|
||||||
|
|
||||||
for channel in sub_data['subscriptions']:
|
|
||||||
if channel['service_id'] == 0:
|
|
||||||
channel_id = urlparse(channel['url']).path.split('/').pop()
|
|
||||||
add_subscription(channel_id, channel['name'])
|
|
||||||
|
|
||||||
|
|
||||||
def make_sized_button(parent_pnl: wx.Panel, bitmap_or_str: Union[wx.Bitmap,
|
def make_sized_button(parent_pnl: wx.Panel, bitmap_or_str: Union[wx.Bitmap,
|
||||||
str],
|
str],
|
||||||
text: str, callback: Callable) -> wx.BoxSizer:
|
text: str, callback: Callable) -> wx.BoxSizer:
|
||||||
|
@ -265,9 +219,9 @@ def make_bitmap_from_url(logo_url: str, size: wx.Size = SIZE) -> wx.Bitmap:
|
||||||
content_bytes = io.BytesIO(content)
|
content_bytes = io.BytesIO(content)
|
||||||
image = wx.Image(content_bytes, type=wx.BITMAP_TYPE_ANY, index=-1)
|
image = wx.Image(content_bytes, type=wx.BITMAP_TYPE_ANY, index=-1)
|
||||||
scale_factor = image.GetWidth() / size.GetWidth()
|
scale_factor = image.GetWidth() / size.GetWidth()
|
||||||
size.SetWidth(image.GetWidth() / scale_factor)
|
size.SetWidth(int(image.GetWidth() / scale_factor))
|
||||||
height = image.GetHeight()
|
height = image.GetHeight()
|
||||||
size.SetHeight(height / scale_factor)
|
size.SetHeight(int(height / scale_factor))
|
||||||
image.Rescale(size.GetWidth(), size.GetHeight())
|
image.Rescale(size.GetWidth(), size.GetHeight())
|
||||||
return wx.Bitmap(image)
|
return wx.Bitmap(image)
|
||||||
|
|
||||||
|
@ -275,9 +229,9 @@ def make_bitmap_from_url(logo_url: str, size: wx.Size = SIZE) -> wx.Bitmap:
|
||||||
def make_bitmap_from_file(path, size: wx.Size = SIZE) -> wx.Bitmap:
|
def make_bitmap_from_file(path, size: wx.Size = SIZE) -> wx.Bitmap:
|
||||||
image = wx.Image(path, type=wx.BITMAP_TYPE_ANY, index=-1)
|
image = wx.Image(path, type=wx.BITMAP_TYPE_ANY, index=-1)
|
||||||
scale_factor = image.GetWidth() / size.GetWidth()
|
scale_factor = image.GetWidth() / size.GetWidth()
|
||||||
size.SetWidth(image.GetWidth() / scale_factor)
|
size.SetWidth(int(image.GetWidth() / scale_factor))
|
||||||
height = image.GetHeight()
|
height = image.GetHeight()
|
||||||
size.SetHeight(height / scale_factor)
|
size.SetHeight(int(height / scale_factor))
|
||||||
image.Rescale(size.GetWidth(), size.GetHeight())
|
image.Rescale(size.GetWidth(), size.GetHeight())
|
||||||
return wx.Bitmap(image)
|
return wx.Bitmap(image)
|
||||||
|
|
||||||
|
@ -324,27 +278,6 @@ def resolve_svt_channel(svt_id: str) -> dict:
|
||||||
return channels[svt_id]
|
return channels[svt_id]
|
||||||
|
|
||||||
|
|
||||||
def resolve_youtube_link(link):
|
|
||||||
ydl_opts = {
|
|
||||||
'format':
|
|
||||||
'worstvideo[ext=mp4]+worstaudio[ext=m4a]/worstvideo+worstaudio',
|
|
||||||
'container': 'webm dash'
|
|
||||||
}
|
|
||||||
|
|
||||||
with youtube_dl.YoutubeDL(ydl_opts) as ydl:
|
|
||||||
try:
|
|
||||||
video = ydl.extract_info(link, download=False)
|
|
||||||
|
|
||||||
for form in video['formats']: # type: ignore
|
|
||||||
if form['height']:
|
|
||||||
if form['height'] < 480 and form['acodec'] != 'none':
|
|
||||||
link = form['url']
|
|
||||||
except youtube_dl.utils.ExtractorError and youtube_dl.utils.DownloadError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return link
|
|
||||||
|
|
||||||
|
|
||||||
def video_exists(video_id: str,
|
def video_exists(video_id: str,
|
||||||
channel_id: str,
|
channel_id: str,
|
||||||
basepath: str = BASEPATH,
|
basepath: str = BASEPATH,
|
||||||
|
|
BIN
src/Utils/assets/Cast.png
Normal file
BIN
src/Utils/assets/Cast.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 401 B |
|
@ -8,4 +8,4 @@ Exec=/usr/local/bin/cast
|
||||||
Icon=video-single-display-symbolic
|
Icon=video-single-display-symbolic
|
||||||
Terminal=false
|
Terminal=false
|
||||||
Categories=Security;Utility;
|
Categories=Security;Utility;
|
||||||
Keywords=YouTube;SVT;
|
Keywords=SVT;
|
||||||
|
|
208
src/main.py
208
src/main.py
|
@ -1,5 +1,4 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import os
|
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
from typing import Callable
|
from typing import Callable
|
||||||
|
@ -8,13 +7,11 @@ import pychromecast
|
||||||
import wx
|
import wx
|
||||||
import wx.lib.scrolledpanel as scrolled
|
import wx.lib.scrolledpanel as scrolled
|
||||||
import wx.media
|
import wx.media
|
||||||
from youtubesearchpython import ChannelsSearch
|
from vlc import Instance
|
||||||
|
|
||||||
from Channel import SVT, YouTube
|
from Channel import SVT
|
||||||
from ChannelProvider import ChannelProvider
|
from ChannelProvider import ChannelProvider
|
||||||
from Utils import (get_default_logo, get_subscriptions, import_from_newpipe,
|
from Utils import MYPATH, make_bitmap_from_file, make_sized_button
|
||||||
make_bitmap_from_file, make_bitmap_from_url,
|
|
||||||
make_sized_button, resolve_youtube_link)
|
|
||||||
|
|
||||||
WIDTH = int(720 / 2)
|
WIDTH = int(720 / 2)
|
||||||
HEIGHT = int(1440 / 2)
|
HEIGHT = int(1440 / 2)
|
||||||
|
@ -42,15 +39,17 @@ class Cast(wx.Frame):
|
||||||
self.m_chromecast_thr = threading.Thread(target=self.get_chromecasts,
|
self.m_chromecast_thr = threading.Thread(target=self.get_chromecasts,
|
||||||
args=(),
|
args=(),
|
||||||
kwargs={})
|
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_chromecast_thr.start()
|
||||||
self.m_sizer: wx.Sizer = wx.BoxSizer(wx.VERTICAL)
|
self.m_sizer: wx.Sizer = wx.BoxSizer(wx.VERTICAL)
|
||||||
self.m_panel: wx.lib.scrolledpanel.ScrolledPanel = scrolled.ScrolledPanel( # type: ignore
|
self.m_panel: wx.lib.scrolledpanel.ScrolledPanel = scrolled.ScrolledPanel( # type: ignore
|
||||||
self, -1, style=wx.VSCROLL)
|
self, -1, style=wx.VSCROLL)
|
||||||
self.m_control = None
|
|
||||||
self.m_panel.SetupScrolling(rate_y=SCROLL_RATE, scrollToTop=True)
|
self.m_panel.SetupScrolling(rate_y=SCROLL_RATE, scrollToTop=True)
|
||||||
self.m_panel.SetSizer(self.m_sizer)
|
self.m_panel.SetSizer(self.m_sizer)
|
||||||
self.m_providers: list[ChannelProvider] = self.get_providers()
|
self.m_providers: list[ChannelProvider] = self.get_providers()
|
||||||
self.show_provider_list(None)
|
self.show_channel_list(None, 0)
|
||||||
|
|
||||||
def add_back_button(self, callback: Callable) -> None:
|
def add_back_button(self, callback: Callable) -> None:
|
||||||
backbtn = wx.Button(self.m_panel,
|
backbtn = wx.Button(self.m_panel,
|
||||||
|
@ -60,38 +59,6 @@ class Cast(wx.Frame):
|
||||||
backbtn.Bind(wx.EVT_BUTTON, callback)
|
backbtn.Bind(wx.EVT_BUTTON, callback)
|
||||||
self.m_sizer.Add(backbtn)
|
self.m_sizer.Add(backbtn)
|
||||||
|
|
||||||
def add_youtube_buttons(self) -> None:
|
|
||||||
def search_callback(event, text: wx.TextCtrl) -> None:
|
|
||||||
reply = text.GetLineText(0)
|
|
||||||
text.Clear()
|
|
||||||
channels_search = ChannelsSearch(
|
|
||||||
reply, limit=1).result()['result'][0] #type: ignore
|
|
||||||
if 'id' in channels_search:
|
|
||||||
found_channel = YouTube.YouTube(
|
|
||||||
channels_search['id'],
|
|
||||||
channels_search['title'],
|
|
||||||
logo=make_bitmap_from_url(
|
|
||||||
'https:' + channels_search['thumbnails'][0]['url'],
|
|
||||||
size=wx.Size(68, 100))) #type: ignore
|
|
||||||
self.m_selected_provider.append_channel(found_channel)
|
|
||||||
|
|
||||||
text: wx.TextCtrl = wx.TextCtrl(self.m_panel, size=(WIDTH, BTN_HEIGHT))
|
|
||||||
self.m_sizer.Add(text)
|
|
||||||
searchbtn = wx.Button(self.m_panel,
|
|
||||||
-1,
|
|
||||||
label="Search",
|
|
||||||
size=(WIDTH, BTN_HEIGHT))
|
|
||||||
self.m_sizer.Add(searchbtn)
|
|
||||||
searchbtn.Bind(
|
|
||||||
wx.EVT_BUTTON,
|
|
||||||
lambda event, textctl=text: search_callback(event, textctl))
|
|
||||||
importbtn = wx.Button(self.m_panel,
|
|
||||||
-1,
|
|
||||||
label="Import from NewPipe",
|
|
||||||
size=(WIDTH, BTN_HEIGHT))
|
|
||||||
importbtn.Bind(wx.EVT_BUTTON, lambda event: self.show_importer(event))
|
|
||||||
self.m_sizer.Add(importbtn)
|
|
||||||
|
|
||||||
def get_chromecasts(self) -> None:
|
def get_chromecasts(self) -> None:
|
||||||
"""
|
"""
|
||||||
[TODO:description]
|
[TODO:description]
|
||||||
|
@ -122,44 +89,45 @@ class Cast(wx.Frame):
|
||||||
event, cindex),
|
event, cindex),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
stop_button = wx.Button(self.m_panel,
|
||||||
|
-1,
|
||||||
|
"Stop",
|
||||||
|
size=(WIDTH / 4, BTN_HEIGHT))
|
||||||
inner_sizer.Add(play_button, FLAGS)
|
inner_sizer.Add(play_button, FLAGS)
|
||||||
inner_sizer.Add(pause_button, FLAGS)
|
inner_sizer.Add(pause_button, FLAGS)
|
||||||
inner_sizer.Add(back_button, FLAGS)
|
inner_sizer.Add(back_button, FLAGS)
|
||||||
|
inner_sizer.Add(stop_button, FLAGS)
|
||||||
|
|
||||||
if not self.m_chromecast_thr.is_alive(
|
if not self.m_chromecast_thr.is_alive(
|
||||||
) and not self.m_selected_chromecast:
|
) and not self.m_selected_chromecast:
|
||||||
chromecast_button = wx.Button(self.m_panel,
|
btm = make_bitmap_from_file('{}/assets/Cast.png'.format(MYPATH), wx.Size(24,24))
|
||||||
|
cast_button = wx.BitmapButton(self.m_panel,
|
||||||
-1,
|
-1,
|
||||||
"Cast",
|
bitmap=btm,
|
||||||
size=(WIDTH / 4, BTN_HEIGHT))
|
size=(WIDTH / 4, BTN_HEIGHT))
|
||||||
chromecast_button.Bind(
|
cast_button.Bind(
|
||||||
wx.EVT_BUTTON,
|
wx.EVT_BUTTON,
|
||||||
lambda event, muri=uri, cindex=channel_index: self.
|
lambda event, muri=uri, cindex=channel_index: self.
|
||||||
select_chromecast(event, muri, cindex),
|
select_chromecast(event, muri, cindex),
|
||||||
)
|
)
|
||||||
inner_sizer.Add(chromecast_button, FLAGS)
|
inner_sizer.Add(cast_button, FLAGS)
|
||||||
elif self.m_selected_chromecast:
|
|
||||||
|
|
||||||
chromecast_button = wx.Button(self.m_panel,
|
|
||||||
-1,
|
|
||||||
"Stop Cast",
|
|
||||||
size=(WIDTH / 4, BTN_HEIGHT))
|
|
||||||
chromecast_button.Bind(
|
|
||||||
wx.EVT_BUTTON,
|
|
||||||
lambda event, muri=uri, cindex=channel_index: self.
|
|
||||||
stop_callback(event, muri, cindex),
|
|
||||||
)
|
|
||||||
inner_sizer.Add(chromecast_button, FLAGS)
|
|
||||||
|
|
||||||
if self.m_selected_chromecast:
|
if self.m_selected_chromecast:
|
||||||
self.cast(wx.media.EVT_MEDIA_LOADED, uri),
|
self.cast(wx.media.EVT_MEDIA_LOADED, uri),
|
||||||
play_button.Bind(wx.EVT_BUTTON, self.play_cast)
|
play_button.Bind(wx.EVT_BUTTON, self.play_cast)
|
||||||
pause_button.Bind(wx.EVT_BUTTON, self.pause_cast)
|
pause_button.Bind(wx.EVT_BUTTON, self.pause_cast)
|
||||||
|
stop_button.Bind(
|
||||||
|
wx.EVT_BUTTON,
|
||||||
|
lambda event, muri=uri, cindex=channel_index: self.
|
||||||
|
stop_callback(event, muri, cindex),
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.Bind(wx.media.EVT_MEDIA_LOADED,
|
self.Bind(wx.media.EVT_MEDIA_LOADED,
|
||||||
lambda event: wx.Frame.SetFocus(self))
|
lambda event: wx.Frame.SetFocus(self))
|
||||||
play_button.Bind(wx.EVT_BUTTON, self.play)
|
play_button.Bind(wx.EVT_BUTTON, self.play)
|
||||||
pause_button.Bind(wx.EVT_BUTTON, self.pause)
|
pause_button.Bind(wx.EVT_BUTTON, self.pause)
|
||||||
|
stop_button.Bind(wx.EVT_BUTTON, self.stop)
|
||||||
inner_sizer.Fit(self)
|
inner_sizer.Fit(self)
|
||||||
inner_sizer.Layout()
|
inner_sizer.Layout()
|
||||||
|
|
||||||
|
@ -168,7 +136,6 @@ class Cast(wx.Frame):
|
||||||
def get_providers(self) -> list[ChannelProvider]:
|
def get_providers(self) -> list[ChannelProvider]:
|
||||||
|
|
||||||
providers = list()
|
providers = list()
|
||||||
channels = list()
|
|
||||||
svt = ChannelProvider(
|
svt = ChannelProvider(
|
||||||
"SVT",
|
"SVT",
|
||||||
channels=[
|
channels=[
|
||||||
|
@ -182,87 +149,21 @@ class Cast(wx.Frame):
|
||||||
)
|
)
|
||||||
|
|
||||||
providers.append(svt)
|
providers.append(svt)
|
||||||
subscriptions = get_subscriptions()
|
|
||||||
if subscriptions:
|
|
||||||
for channel in subscriptions:
|
|
||||||
logo = make_bitmap_from_file(channel[2])
|
|
||||||
channels.append(YouTube.YouTube(channel[0], channel[1], logo))
|
|
||||||
else:
|
|
||||||
logo = make_bitmap_from_url(
|
|
||||||
'https://yt3.ggpht.com/ytc/AKedOLQ5L9xUSDxB2j6V3VC8L_HEwiKeHM21CgbSUyqe=s88-c-k-c0x00ffffff-no-rj'
|
|
||||||
)
|
|
||||||
channels.append(
|
|
||||||
YouTube.YouTube("UCs6A_0Jm21SIvpdKyg9Gmxw", "Pine 64", logo))
|
|
||||||
|
|
||||||
youtube = ChannelProvider("YouTube", channels=channels)
|
|
||||||
providers.append(youtube)
|
|
||||||
|
|
||||||
return providers
|
return providers
|
||||||
|
|
||||||
def show_importer(self, _) -> None:
|
|
||||||
|
|
||||||
with wx.FileDialog(self,
|
|
||||||
"Open Newpipe json file",
|
|
||||||
wildcard="Json files (*.json)|*.json",
|
|
||||||
style=wx.FD_OPEN
|
|
||||||
| wx.FD_FILE_MUST_EXIST) as file_dialog:
|
|
||||||
|
|
||||||
if file_dialog.ShowModal() == wx.ID_CANCEL:
|
|
||||||
return # the user changed their mind
|
|
||||||
|
|
||||||
# Proceed loading the file chosen by the user
|
|
||||||
subfile = file_dialog.GetPath()
|
|
||||||
channels = list()
|
|
||||||
logo = get_default_logo('YouTube')
|
|
||||||
if os.path.isfile(subfile):
|
|
||||||
import_from_newpipe(subfile)
|
|
||||||
subscriptions = get_subscriptions()
|
|
||||||
for channel in subscriptions:
|
|
||||||
yt_chan = YouTube.YouTube(channel[0], channel[1], logo)
|
|
||||||
yt_chan.refresh()
|
|
||||||
channels.append(yt_chan)
|
|
||||||
# Index 1 is YouTube
|
|
||||||
self.m_providers[1].set_channels(channels)
|
|
||||||
self.m_providers[1].make_latest()
|
|
||||||
self.show_channel_list(None, self.m_selected_provider_index)
|
|
||||||
|
|
||||||
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_channel_list(self, _, provider_index) -> None:
|
def show_channel_list(self, _, provider_index) -> None:
|
||||||
self.m_selected_provider_index = provider_index
|
self.m_selected_provider_index = provider_index
|
||||||
self.m_selected_provider = self.m_providers[provider_index]
|
self.m_selected_provider = self.m_providers[provider_index]
|
||||||
self.m_sizer.Clear(delete_windows=True)
|
self.m_sizer.Clear(delete_windows=True)
|
||||||
self.m_sizer = wx.BoxSizer(wx.VERTICAL)
|
self.m_sizer = wx.BoxSizer(wx.VERTICAL)
|
||||||
#self.m_sizer.AddSpacer(SPACER_HEIGHT * 4)
|
closebtn = wx.Button(self.m_panel,
|
||||||
bck_callback = lambda event: self.show_provider_list(event)
|
-1,
|
||||||
self.add_back_button(bck_callback)
|
label="Close",
|
||||||
|
size=(WIDTH, BTN_HEIGHT))
|
||||||
if self.m_selected_provider.get_name() == "YouTube":
|
closebtn.Bind(wx.EVT_BUTTON, lambda event: self.Destroy())
|
||||||
self.add_youtube_buttons()
|
self.m_sizer.Add(closebtn, 0, wx.ALL, 1)
|
||||||
|
|
||||||
channel_index = 0
|
channel_index = 0
|
||||||
|
|
||||||
|
@ -290,27 +191,10 @@ class Cast(wx.Frame):
|
||||||
back_callback = lambda event: self.show_channel_list(
|
back_callback = lambda event: self.show_channel_list(
|
||||||
event, self.m_selected_provider_index)
|
event, self.m_selected_provider_index)
|
||||||
self.add_back_button(back_callback)
|
self.add_back_button(back_callback)
|
||||||
if self.m_selected_provider.get_name() == 'YouTube' or (
|
if self.m_selected_provider.get_name() == 'SVT' and self.m_selected_channel.get_id() == 'feed':
|
||||||
self.m_selected_provider.get_name() == 'SVT'
|
|
||||||
and self.m_selected_channel.get_id() == 'feed'):
|
|
||||||
|
|
||||||
def refresh_callback(event):
|
def refresh_callback(event):
|
||||||
if self.m_selected_provider.get_name(
|
self.m_selected_channel.refresh()
|
||||||
) == 'YouTube' and channel_index == 0:
|
|
||||||
with wx.BusyInfo("Please wait, working..."):
|
|
||||||
for chan in self.m_selected_provider.get_channels():
|
|
||||||
chan.refresh()
|
|
||||||
wait = 1
|
|
||||||
while wait > 0:
|
|
||||||
wait = 0
|
|
||||||
for chan in self.m_selected_provider.get_channels(
|
|
||||||
):
|
|
||||||
if chan.wait():
|
|
||||||
wait += 1
|
|
||||||
time.sleep(1)
|
|
||||||
wx.GetApp().Yield()
|
|
||||||
else:
|
|
||||||
self.m_selected_channel.refresh()
|
|
||||||
self.show_video_list(event, channel_index)
|
self.show_video_list(event, channel_index)
|
||||||
|
|
||||||
refreshbtn = wx.Button(self.m_panel,
|
refreshbtn = wx.Button(self.m_panel,
|
||||||
|
@ -386,22 +270,12 @@ class Cast(wx.Frame):
|
||||||
:param _ event: unused
|
:param _ event: unused
|
||||||
:param uri str: the link to the video stream
|
:param uri str: the link to the video stream
|
||||||
"""
|
"""
|
||||||
if 'youtube' in uri:
|
media = self.m_vlc.media_new(uri)
|
||||||
uri = resolve_youtube_link(uri)
|
self.m_vlc_medialist.add_media(media)
|
||||||
|
self.m_vlc_listplayer.set_media_list(self.m_vlc_medialist)
|
||||||
|
|
||||||
self.m_sizer.Clear(delete_windows=True)
|
self.m_sizer.Clear(delete_windows=True)
|
||||||
self.m_sizer = wx.BoxSizer(wx.VERTICAL)
|
self.m_sizer = wx.BoxSizer(wx.VERTICAL)
|
||||||
if not self.m_selected_chromecast:
|
|
||||||
self.m_control = wx.media.MediaCtrl(
|
|
||||||
self.m_panel,
|
|
||||||
size=(WIDTH,HEIGHT/2),
|
|
||||||
szBackend=wx.media.MEDIABACKEND_GSTREAMER,
|
|
||||||
)
|
|
||||||
self.m_sizer.Add(self.m_control, FLAGS)
|
|
||||||
self.Bind(wx.media.EVT_MEDIA_FINISHED,
|
|
||||||
lambda event: self.show_video_list(event, 0))
|
|
||||||
self.Bind(wx.EVT_POWER_SUSPENDING,
|
|
||||||
lambda event: wx.EVT_POWER_SUSPENDING.Veto(event))
|
|
||||||
self.load_uri(uri)
|
|
||||||
self.m_sizer.Add(self.get_player_controls(channel_index, uri), FLAGS)
|
self.m_sizer.Add(self.get_player_controls(channel_index, uri), FLAGS)
|
||||||
self.m_panel.SetupScrolling(rate_y=SCROLL_RATE, scrollToTop=True)
|
self.m_panel.SetupScrolling(rate_y=SCROLL_RATE, scrollToTop=True)
|
||||||
self.m_panel.SetSizer(self.m_sizer)
|
self.m_panel.SetSizer(self.m_sizer)
|
||||||
|
@ -424,7 +298,7 @@ class Cast(wx.Frame):
|
||||||
self.m_sizer.Add(cancel_btn)
|
self.m_sizer.Add(cancel_btn)
|
||||||
|
|
||||||
for cast in self.m_chromecasts:
|
for cast in self.m_chromecasts:
|
||||||
friendly_name = cast.cast_info.friendly_name
|
friendly_name = cast.device.friendly_name
|
||||||
btn = wx.Button(self.m_panel,
|
btn = wx.Button(self.m_panel,
|
||||||
id=-1,
|
id=-1,
|
||||||
label=friendly_name,
|
label=friendly_name,
|
||||||
|
@ -498,14 +372,14 @@ class Cast(wx.Frame):
|
||||||
self.m_sizer.Fit(self)
|
self.m_sizer.Fit(self)
|
||||||
self.m_sizer.Layout()
|
self.m_sizer.Layout()
|
||||||
|
|
||||||
def load_uri(self, uri):
|
|
||||||
self.m_control.LoadURI(uri)
|
|
||||||
|
|
||||||
def play(self, _):
|
def play(self, _):
|
||||||
self.m_control.Play()
|
self.m_vlc_listplayer.play()
|
||||||
|
|
||||||
def pause(self, _):
|
def pause(self, _):
|
||||||
self.m_control.Pause()
|
self.m_vlc_listplayer.pause()
|
||||||
|
|
||||||
|
def stop(self, _):
|
||||||
|
self.m_vlc_listplayer.stop()
|
||||||
|
|
||||||
def quit(self, _):
|
def quit(self, _):
|
||||||
self.Destroy()
|
self.Destroy()
|
||||||
|
|
File diff suppressed because one or more lines are too long
Loading…
Add table
Reference in a new issue