Only resolve youtubelinks when playing

This makes everything ALOT faster
main
Micke Nordin 2 years ago
parent 094b25f8cb
commit cf54794220
Signed by: micke
GPG Key ID: 0DA0A7A5708FE257

@ -22,7 +22,6 @@ libgstreamer-plugins-bad1.0-0:amd64
libgstreamer-plugins-base1.0-0:amd64
libgstreamer-plugins-good1.0-0:amd64
libgstreamer1.0-0:amd64
libmpv1
python3-wxgtk-media4.0
python3-wxgtk4.0
python3-feedparser

@ -1,2 +1,2 @@
pychromecast
python-mpv
youtube-search-python

@ -1,14 +1,11 @@
import threading
import feedparser
import wx
import time
import youtube_dl
from Channel import Channel
from Items import Item
from Utils import (add_video, get_default_logo, get_latest_video_timestamp, hash_string,
make_bitmap_from_url, video_exists)
#from youtubesearchpython.search import VideosSearch
class YouTube(Channel):
@ -22,11 +19,6 @@ class YouTube(Channel):
def parse_feed(self) -> None:
feed = feedparser.parse(self.get_feed())
ydl_opts = {
'format':
'worstvideo[ext=mp4]+worstaudio[ext=m4a]/worstvideo+worstaudio',
'container': 'webm_dash',
}
entries = feed['entries']
for entry in entries:
@ -36,22 +28,11 @@ class YouTube(Channel):
title = str(entry['title'])
thumbnail_link = str(entry['media_thumbnail'][0]['url'])
description = str(entry['description'])
link = ''
with youtube_dl.YoutubeDL(ydl_opts) as ydl:
try:
video = ydl.extract_info(entry['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
resolved_link = link
#video_search = VideosSearch(entry['id'], limit = 1).result()['result'][0]
resolved_link = entry['link']
print(resolved_link)
published_parsed = entry['published_parsed']
published_parsed = entry['published_parsed']
if not resolved_link:
continue

@ -11,9 +11,12 @@ from urllib.parse import urlparse
import requests
import wx
import youtube_dl
from Items import Item
HEIGHT = int(1440 / 2)
BTN_HEIGHT = 40
SIZE = wx.Size(100, 68)
MYPATH = path.dirname(path.abspath(__file__))
SCREEN_WIDTH = int(720 / 2)
@ -129,9 +132,10 @@ def get_latest(provider_id: str,
pass
return videos
def get_latest_video_timestamp(channel_id: str,
basepath: str = BASEPATH,
filename: str = DB_FILE_NAME) -> datetime:
basepath: str = BASEPATH,
filename: str = DB_FILE_NAME) -> datetime:
fullpath = path.join(basepath, filename)
try:
con = sqlite3.connect(fullpath)
@ -145,6 +149,7 @@ def get_latest_video_timestamp(channel_id: str,
pass
return datetime.fromtimestamp(timestamp)
def get_subscriptions(basepath: str = BASEPATH,
filename: str = DB_FILE_NAME) -> list[tuple[str, str]]:
subscriptions = list()
@ -223,17 +228,20 @@ def make_sized_button(parent_pnl: wx.Panel, bitmap_or_str: Union[wx.Bitmap,
else:
bitmap = bitmap_or_str
btn_style = wx.BORDER_NONE | wx.BU_AUTODRAW | wx.BU_EXACTFIT | wx.BU_NOTEXT
btn_logo = wx.BitmapButton(parent_pnl, wx.ID_ANY, bitmap, style=btn_style)
btn_logo.SetMinSize(SIZE)
btn_logo = wx.BitmapButton(parent_pnl,
wx.ID_ANY,
bitmap,
style=btn_style,
size=bitmap.GetSize())
btn_logo.SetToolTip(text)
btn_sizer.Add(btn_logo, 0, wx.BOTTOM | wx.EXPAND | wx.LEFT | wx.TOP, 1)
btn_text = wx.Button(parent_pnl,
wx.ID_ANY,
text,
style=wx.BORDER_NONE | wx.BU_AUTODRAW)
btn_text.SetMinSize(
wx.Size(SCREEN_WIDTH - SIZE.GetWidth(), SIZE.GetHeight()))
style=wx.BORDER_NONE | wx.BU_AUTODRAW,
size=wx.Size(SCREEN_WIDTH - SIZE.GetWidth(),
SIZE.GetHeight()))
btn_text.SetToolTip(text)
btn_sizer.Add(btn_text, 0, wx.BOTTOM | wx.RIGHT | wx.TOP | wx.EXPAND, 1)
parent_pnl.Bind(wx.EVT_BUTTON, callback, btn_logo)
@ -307,6 +315,27 @@ def resolve_svt_channel(svt_id: str) -> dict:
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,
channel_id: str,
basepath: str = BASEPATH,

@ -1,20 +1,19 @@
#!/usr/bin/env python3
import mpv
import json
import os
import threading
import time
from typing import Callable
from urllib.parse import urlparse
import pychromecast
import wx
import wx.lib.scrolledpanel as scrolled
import wx.media
from youtubesearchpython import ChannelsSearch
from Channel import SVT, YouTube
from ChannelProvider import ChannelProvider
from Utils import get_subscriptions, import_from_newpipe, make_sized_button
from Utils import (get_subscriptions, import_from_newpipe, make_sized_button,
resolve_youtube_link)
WIDTH = int(720 / 2)
HEIGHT = int(1440 / 2)
@ -25,6 +24,9 @@ SCROLL_RATE = 5
BM_BTN_STYLE = wx.BOTTOM | wx.EXPAND | wx.LEFT | wx.TOP
BTN_STYLE = wx.BORDER_NONE | wx.BU_AUTODRAW | wx.BU_EXACTFIT | wx.BU_NOTEXT
FLAGS = wx.SizerFlags()
FLAGS.Center()
class Cast(wx.Frame):
def __init__(self, *args, **kw):
@ -35,6 +37,7 @@ class Cast(wx.Frame):
super().__init__(*args, **kw)
self.m_selected_chromecast = None
self.SetSizeHints(WIDTH, HEIGHT, maxW=WIDTH)
self.m_style = self.GetWindowStyle()
self.m_chromecast_thr = threading.Thread(target=self.get_chromecasts,
args=(),
kwargs={})
@ -46,8 +49,6 @@ 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.m_selected_channel = None
# self.m_selected_provider_index = None
self.show_provider_list(None)
def add_back_button(self, callback: Callable) -> None:
@ -58,6 +59,31 @@ class Cast(wx.Frame):
backbtn.Bind(wx.EVT_BUTTON, callback)
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]
print(channels_search)
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:
"""
[TODO:description]
@ -66,6 +92,71 @@ 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:
inner_sizer = wx.BoxSizer(wx.HORIZONTAL)
play_button = wx.Button(self.m_panel,
-1,
"Play",
size=(WIDTH / 4, BTN_HEIGHT))
pause_button = wx.Button(self.m_panel,
-1,
"Pause",
size=(WIDTH / 4, BTN_HEIGHT))
back_button = wx.Button(self.m_panel,
-1,
"Back",
size=(WIDTH / 4, BTN_HEIGHT))
back_button.Bind(
wx.EVT_BUTTON,
lambda event, cindex=channel_index: self.show_video_list(
event, cindex),
)
inner_sizer.Add(play_button, FLAGS)
inner_sizer.Add(pause_button, FLAGS)
inner_sizer.Add(back_button, FLAGS)
if not self.m_chromecast_thr.is_alive(
) and not self.m_selected_chromecast:
chromecast_button = wx.Button(self.m_panel,
-1,
"Cast",
size=(WIDTH / 4, BTN_HEIGHT))
chromecast_button.Bind(
wx.EVT_BUTTON,
lambda event, muri=uri, cindex=channel_index: self.
select_chromecast(event, muri, cindex),
)
inner_sizer.Add(chromecast_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:
self.cast(wx.media.EVT_MEDIA_LOADED, uri),
play_button.Bind(wx.EVT_BUTTON, self.play_cast)
pause_button.Bind(wx.EVT_BUTTON, self.pause_cast)
else:
self.Bind(wx.media.EVT_MEDIA_LOADED,
lambda event: wx.Frame.SetFocus(self))
play_button.Bind(wx.EVT_BUTTON, self.play)
pause_button.Bind(wx.EVT_BUTTON, self.pause)
inner_sizer.Fit(self)
inner_sizer.Layout()
return inner_sizer
def get_providers(self) -> list[ChannelProvider]:
providers = list()
@ -88,8 +179,9 @@ class Cast(wx.Frame):
for channel in subscriptions:
channels.append(YouTube.YouTube(channel[0], channel[1]))
else:
channels.append(YouTube.YouTube("UCs6A_0Jm21SIvpdKyg9Gmxw", "Pine 64"))
channels.append(
YouTube.YouTube("UCs6A_0Jm21SIvpdKyg9Gmxw", "Pine 64"))
youtube = ChannelProvider("YouTube", channels=channels)
providers.append(youtube)
@ -97,11 +189,14 @@ class Cast(wx.Frame):
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:
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
return # the user changed their mind
# Proceed loading the file chosen by the user
subfile = file_dialog.GetPath()
@ -113,14 +208,14 @@ class Cast(wx.Frame):
yt_chan = YouTube.YouTube(channel[0], channel[1])
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)
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)
# self.m_sizer.AddSpacer(SPACER_HEIGHT * 4)
closebtn = wx.Button(self.m_panel,
-1,
label="Close",
@ -154,9 +249,7 @@ class Cast(wx.Frame):
self.add_back_button(bck_callback)
if self.m_selected_provider.get_name() == "YouTube":
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)
self.add_youtube_buttons()
channel_index = 0
@ -175,37 +268,42 @@ class Cast(wx.Frame):
self.m_sizer.Fit(self)
self.m_sizer.Layout()
def show_video_list(self, _,channel_index) -> None:
self.Show()
self.m_selected_channel = self.m_selected_provider.get_channel_by_index(channel_index)
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(
channel_index)
self.m_sizer.Clear(delete_windows=True)
self.m_sizer = wx.BoxSizer(wx.VERTICAL)
# self.m_sizer.AddSpacer(SPACER_HEIGHT * 4)
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() == 'YouTube' or (self.m_selected_provider.get_name() == 'SVT' and self.m_selected_channel.get_id() == 'feed'):
if self.m_selected_provider.get_name() == 'YouTube' or (
self.m_selected_provider.get_name() == 'SVT'
and self.m_selected_channel.get_id() == 'feed'):
def refresh_callback(event):
if self.m_selected_provider.get_name() == 'YouTube' and channel_index == 0:
if self.m_selected_provider.get_name(
) == '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():
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,
-1,
label="Refresh",
size=(WIDTH, BTN_HEIGHT))
-1,
label="Refresh",
size=(WIDTH, BTN_HEIGHT))
refreshbtn.Bind(wx.EVT_BUTTON, refresh_callback)
self.m_sizer.Add(refreshbtn)
@ -215,7 +313,6 @@ class Cast(wx.Frame):
time.sleep(1)
wx.GetApp().Yield()
btnindex = 0
for item in self.m_selected_channel.get_items(): # type: ignore
inner_sizer = wx.BoxSizer(wx.VERTICAL)
@ -228,7 +325,8 @@ class Cast(wx.Frame):
btn = wx.BitmapButton(self.m_panel,
id=btnindex,
bitmap=bitmap,
style=BM_BTN_STYLE)
style=BM_BTN_STYLE,
size=bitmap.GetSize())
btn.Bind(
wx.EVT_BUTTON,
lambda event, link=item["link"], cindex=channel_index: self.
@ -236,7 +334,10 @@ class Cast(wx.Frame):
)
inner_sizer.Add(title)
inner_sizer.Add(btn)
collapsable_pane = wx.CollapsiblePane(self.m_panel, wx.ID_ANY, "Details:")
collapsable_pane = wx.CollapsiblePane(self.m_panel,
wx.ID_ANY,
"Details:",
size=(WIDTH, 20))
inner_sizer.Add(collapsable_pane, 0, wx.GROW | wx.ALL, 5)
pane_win = collapsable_pane.GetPane()
description = wx.StaticText(pane_win, -1, item["description"])
@ -245,6 +346,7 @@ class Cast(wx.Frame):
pane_sizer.Add(description, wx.GROW | wx.ALL, 2)
pane_win.SetSizer(pane_sizer)
pane_sizer.SetSizeHints(pane_win)
def fit_and_layout(_):
pane_sizer.Layout()
pane_sizer.Fit(pane_win)
@ -253,7 +355,8 @@ class Cast(wx.Frame):
self.m_sizer.Fit(self)
self.m_sizer.Layout()
collapsable_pane.Bind(wx.EVT_COLLAPSIBLEPANE_CHANGED, lambda event: fit_and_layout(event) )
collapsable_pane.Bind(wx.EVT_COLLAPSIBLEPANE_CHANGED,
lambda event: fit_and_layout(event))
#inner_sizer.Add(description)
self.m_sizer.Add(inner_sizer)
self.m_sizer.AddSpacer(SPACER_HEIGHT)
@ -263,84 +366,48 @@ class Cast(wx.Frame):
self.m_sizer.Fit(self)
self.m_sizer.Layout()
def show_player(self, _, uri, channel_index: int):
def show_player(self, _, uri: str, channel_index: int):
"""
Show the video player
:param _ event: unused
:param uri str: the link to the video stream
"""
if 'youtube' in uri:
uri = resolve_youtube_link(uri)
self.m_sizer.Clear(delete_windows=True)
self.m_sizer = wx.BoxSizer(wx.VERTICAL)
inner_sizer = wx.BoxSizer(wx.HORIZONTAL)
self.m_control = wx.media.MediaCtrl(
self.m_panel,
size=(0,0),
szBackend=wx.media.MEDIABACKEND_GSTREAMER,
)
play_button = wx.Button(self.m_panel, -1, "Play")
pause_button = wx.Button(self.m_panel, -1, "Pause")
back_button = wx.Button(self.m_panel, -1, "Back")
back_button.Bind(
wx.EVT_BUTTON,
lambda event, cindex=channel_index: self.show_video_list(event, cindex),
)
self.m_control.Show()
self.m_sizer.Add(self.m_control)
inner_sizer.Add(play_button)
inner_sizer.Add(pause_button)
inner_sizer.Add(back_button)
if not self.m_chromecast_thr.is_alive(
) and not self.m_selected_chromecast:
chromecast_button = wx.Button(self.m_panel, -1, "Cast")
chromecast_button.Bind(
wx.EVT_BUTTON,
lambda event, muri=uri, cindex=channel_index: self.select_chromecast(event, muri, cindex),
if not self.m_selected_chromecast:
self.m_control = wx.media.MediaCtrl(
self.m_panel,
szBackend=wx.media.MEDIABACKEND_GSTREAMER,
)
inner_sizer.Add(chromecast_button)
self.m_sizer.Add(inner_sizer)
if self.m_selected_chromecast:
self.Bind(
wx.media.EVT_MEDIA_LOADED,
lambda event, muri=uri: self.cast(event, muri),
)
play_button.Bind(wx.EVT_BUTTON, self.play_cast)
pause_button.Bind(wx.EVT_BUTTON, self.pause_cast)
else:
self.Bind(wx.media.EVT_MEDIA_LOADED, self.play)
play_button.Bind(wx.EVT_BUTTON, self.play)
pause_button.Bind(wx.EVT_BUTTON, self.pause)
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.SetSizeHints(minW=WIDTH, minH=-1, maxH=HEIGHT)
self.load_uri(uri)
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_panel.SetupScrolling(rate_y=SCROLL_RATE, scrollToTop=True)
self.m_panel.SetSizer(self.m_sizer)
self.m_sizer.Fit(self)
self.m_sizer.Layout()
self.Hide()
def select_chromecast(self, _, uri, channel_index):
self.m_sizer.Clear(delete_windows=True)
self.m_sizer = wx.BoxSizer(wx.VERTICAL)
# self.m_sizer.AddSpacer(SPACER_HEIGHT * 4)
cancel_btn = wx.Button(self.m_panel,
-1,
"Cancel",
size=(WIDTH, BTN_HEIGHT),
style=wx.BU_EXACTFIT)
size=(WIDTH, BTN_HEIGHT))
cancel_btn.Bind(
wx.EVT_BUTTON,
lambda event, cindex=channel_index: self.show_video_list(
event, cindex),
)
self.m_sizer.Add(cancel_btn) #, wx.ALIGN_CENTER_VERTICAL)
self.m_sizer.Add(cancel_btn)
for cast in self.m_chromecasts:
friendly_name = cast.cast_info.friendly_name
@ -353,7 +420,7 @@ class Cast(wx.Frame):
lambda event, chromecast=cast, muri=uri, cindex=channel_index:
self.set_chromecast(event, chromecast, muri, cindex),
)
self.m_sizer.Add(btn) #, wx.ALIGN_CENTER_VERTICAL)
self.m_sizer.Add(btn)
self.m_panel.SetupScrolling(rate_y=SCROLL_RATE, scrollToTop=True)
self.m_panel.SetSizer(self.m_sizer)
self.m_sizer.Fit(self)
@ -375,7 +442,6 @@ class Cast(wx.Frame):
while True:
if player_state != cast.media_controller.status.player_state:
player_state = cast.media_controller.status.player_state
# print("Player state:", player_state)
if player_state == "PLAYING":
has_played = True
@ -392,12 +458,32 @@ class Cast(wx.Frame):
def pause_cast(self, event):
cast = self.m_selected_chromecast
cast.wait()
cast.media_controller.pause()
def play_cast(self, event):
cast = self.m_selected_chromecast
cast.wait()
cast.media_controller.play()
def stop_cast(self, event):
cast = self.m_selected_chromecast
cast.wait()
cast.media_controller.pause()
time.sleep(2)
cast.media_controller.stop()
self.m_selected_chromecast = None
def stop_callback(self, event, uri: str, channel_index: int):
self.stop_cast(event)
self.m_sizer.Clear(delete_windows=True)
self.m_sizer = wx.BoxSizer(wx.VERTICAL)
self.m_sizer.Add(self.get_player_controls(channel_index, uri))
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 load_uri(self, uri):
self.m_control.LoadURI(uri)

Loading…
Cancel
Save