Skip to content

Instantly share code, notes, and snippets.

@alexrjs
Last active January 13, 2024 18:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alexrjs/8ba281290ccef33fea919f21519cf1e5 to your computer and use it in GitHub Desktop.
Save alexrjs/8ba281290ccef33fea919f21519cf1e5 to your computer and use it in GitHub Desktop.
PoC Youtube Channel Checker
"""
- Helper module for ui app actionbar
- Author: alexrjs
- License: Unlicense
"""
# imports
from flet import Container, Row, Text, UserControl
from flet import colors
# classes
class ActionBar(UserControl):
"""Action bar"""
def __init__(self) -> None:
super().__init__()
def build(self) -> Row:
"""Fill action bar"""
return Row(
controls=[
Container(
content=Text("Action Bar - not implemented, yet.", color=colors.RED),
expand=True,
),
],
)
"""
- Helper module for ui app
- Author: alexrjs
- License: Unlicense
"""
# imports
from copy import deepcopy
from types import SimpleNamespace
from flet import Column, Page, Row, UserControl
from flet import CrossAxisAlignment
from helpers.db.storage import Storage
from helpers.info import report
from helpers.ui.bars.action import ActionBar
from helpers.ui.bars.status import StatusBar
from helpers.ui.boxes.channels import ListBoxChannels
from helpers.ui.boxes.videos import GridViewVideos
# classes
class FletApp(UserControl):
"""Flet app"""
def __init__(self, page_:Page=None, config_:dict=None) -> None:
"""Init"""
if not page_: raise AttributeError("Page is None (FletApp)")
if not config_: raise AttributeError("Config is None (FletApp)")
if type(config_) is not SimpleNamespace: raise AttributeError("Config is not a SimpleNamespace (FletApp)")
super().__init__()
self.page = page_
self.config = deepcopy(config_)
self.elements = Storage()
self.update()
def fillChannels(self, channels_:dict=None) -> None:
"""Fill channels listbox"""
if not channels_: raise AttributeError("Channels is None (FletApp)")
if type(channels_) is not dict: raise AttributeError("Channels is not a list (FletApp)")
self.elements.channelContainer.channelsList.controls = []
if not self.elements.channelContainer.fillChannels(channels_): report("Channels not filled (FletApp.fillChannels)")
self.elements.statusbar.setStatus("Channels loaded.")
return True
def build(self) -> Column:
"""Build app ui"""
self.elements.actionbar = ActionBar()
self.elements.channelContainer = ListBoxChannels()
self.elements.videosContainer = GridViewVideos()
self.elements.content = Row(
controls=[
self.elements.channelContainer,
self.elements.videosContainer,
],
vertical_alignment=CrossAxisAlignment.STRETCH,
expand=True,
)
self.elements.statusbar = StatusBar()
return Column(
controls=[
# Row: Action bar
self.elements.actionbar,
# Row: Main content
self.elements.content,
# Row: Status bar
self.elements.statusbar
],
horizontal_alignment=CrossAxisAlignment.STRETCH,
expand=True,
)
"""
- Helper module for ui listbox channels
- Author: alexrjs
- License: Unlicense
"""
from flet import Column, Container, ContainerTapEvent, ControlEvent, Divider, ListTile, ListView, PopupMenuButton, PopupMenuItem, Text, UserControl
from flet import TextAlign, TextThemeStyle
from flet import alignment, border, border_radius, colors, icons
from helpers.info import report
class ListEntryChannel(UserControl):
"""Channel listbox entry"""
def __init__(self, parent, **kwargs):
super().__init__(parent, **kwargs)
self._id = kwargs['id'] if 'id' in kwargs else None
self._name = kwargs['name'] if 'name' in kwargs else None
self._selected = kwargs['selected'] if 'selected' in kwargs else None
self._onSelect = kwargs['onSelect'] if 'onSelect' in kwargs else None
self._label = self.addLabel()
self._label.text = self._name
self._label.onSelect = self._onSelect
if self._selected:
self._label.select()
def _onSelect(self, sender, args):
"""On select channel"""
self._selected = True
def _onDeselect(self, sender, args):
"""On deselect channel"""
self._selected = False
class ListBoxChannels(UserControl):
"""Channels listbox"""
def __init__(self):
super().__init__()
self.channelsList = None
self.channelsBox = None
self.selectedChannel = None
def channelClicked(self, event_:ContainerTapEvent=None):
#print(event_.control, event_.data, event_.name, event_.page, event_.target)
if self.selectedChannel:
self.selectedChannel.content.title.color = colors.WHITE
self.selectedChannel.content.subtitle.color = colors.GREY_400
self.selectedChannel.bgcolor = colors.TRANSPARENT
self.selectedChannel.update()
self.selectedChannel = event_.control
self.selectedChannel.content.title.color = colors.BLACK
self.selectedChannel.content.subtitle.color = colors.BLACK45
self.selectedChannel.bgcolor = colors.GREEN_300
self.selectedChannel.update()
#fillChannelVideos(event_)
def channelUpdate(self, event_:ControlEvent=None):
print(f"Status: {event_.control.data} updated, not implemented, yet.")
def channelDisable(self, event_:ControlEvent=None):
print(f"Status: {event_.control.data} disabled, not implemented, yet.")
def fillChannels(self, channels_:dict=None) -> bool:
if not channels_: return report('No channels configured (fillChannels)')
try:
_sorted = dict(reversed(sorted(channels_.items(), key=lambda x: x[1]['updated'])))
for _channel, _values in _sorted.items():
_subtitle = f"Videos: {_values['count']}\n"
_subtitle += f"Frequency: {_values['frequency']}\n"
_subtitle += f"Checked: {_values['checked']}\n"
_subtitle += f"Updated: {_values['updated']}"
self.channelsList.controls.append(
Container(
content=ListTile(
title=Text(_channel),
data=_channel,
col=_channel,
subtitle=Text(_subtitle,style=TextThemeStyle.BODY_SMALL,color=colors.GREY_400),
trailing=PopupMenuButton(
icon=icons.MORE_VERT,
items=[
PopupMenuItem(text="Update",on_click=self.channelUpdate,data=_channel),
PopupMenuItem(text="Disable",on_click=self.channelDisable,data=_channel),
],
),
),
alignment=alignment.top_center,
on_click=self.channelClicked,
col=_channel,
),
)
self.channelsList.update()
except Exception as _e:
print('Exception (fillChannels):', _e)
return report('Exception (fillChannels)!!!')
return True
def build(self):
self.channelsList = ListView(
expand=False,
spacing=3,
padding=5,
auto_scroll=False,
)
self.channelsBox = Column(
controls=[
Text(
"Channels",
text_align=TextAlign.CENTER,
color=colors.WHITE,
style=TextThemeStyle.TITLE_MEDIUM,
width=250,
),
Divider(height=-1, color=colors.WHITE),
self.channelsList
],
spacing=3,
)
return Container(
content=self.channelsBox,
border=border.all(color=colors.WHITE, width=1),
border_radius=border_radius.all(5),
width=250,
)
"""
- Simple UI for youtube checker
- Author: alexrjs
- License: Unlicense
"""
# imports
from os import path
from flet import app, Page, MainAxisAlignment
from helpers.info import report
from helpers.db.assets import getVideoThumbnail
from helpers.db.config import getConfig
from helpers.db.storage import Storage
from helpers.db.video import getChannelVideos, getChannels
from helpers.ui.app import FletApp
from helpers.ui.config import getVAllignment
# variables
storage = Storage()
storage.assetBase = path.dirname(path.abspath(__file__))
storage.fileBase = '/Users/homeboy/Sources/py/youtube'
storage.channelsConfig = f'{storage.fileBase}/config.json'
storage.channelVideosBase = f'{storage.fileBase}/channels'
# def fillChannelVideos(event_) -> bool:
# if not event_: return report('No event configured (fillChannelVideos)')
# #if not hasattr(event_, 'channel'): return report('No channel configured (fillChannelVideos)')
# #_channel = event_.channel
# _channel = event_.control.col
# if not _channel: return report('No channel configured (fillChannelVideos)')
# if not getChannelVideos(f'{storage.channelVideosBase}/{_channel}/videos.json'): report("fillChannelVideos() failed")
# _gvVideos = storage.elements['gvVideos']
# if not _gvVideos: return report('No gvVideos configured (fillChannelVideos)')
# _gvVideos.controls = []
# storage.statusText.value = f"Status: {_channel} video infos are loading..."
# storage.statusText.update()
# _sorted = dict(reversed(sorted(storage.videos.items(), key=lambda x: x[1]['added'] if x else None)))
# for _videoId, _videoData in _sorted.items():
# #print(_videoData['title'])
# if _videoData['added'] in ['Ignored', 'Deleted']: continue
# _src = getVideoThumbnail(_channel, _videoId)
# _title = _videoData['title'] if len(_videoData['title']) < 30 else f"{_videoData['title'][0:30]}..."
# _length = _videoData["length"]
# _length = f'{_length}s' if _length > 0 else str(_length)
# _gvVideos.controls.append(
# ft.Card(
# content=ft.Container(
# content=ft.Column(
# [
# ft.Image(
# src=_src,
# width=320,
# height=200,
# fit=ft.ImageFit.FILL,
# repeat=ft.ImageRepeat.NO_REPEAT,
# border_radius=ft.border_radius.all(5),
# tooltip=_videoData['title'],
# ),
# ft.ListTile(
# title=ft.Text(_title),
# subtitle=ft.Text(
# f"Published: {_videoData['date'][0:10]}\nAdded: {_videoData['added']}\nLength: {_length}\nState: {_videoData['state']}"
# ),
# ),
# ft.Row(
# [
# ft.TextButton("Delete",disabled=True),
# ft.TextButton("Download",disabled=True),
# ft.TextButton("Visit",url=f"https://www.youtube.com/watch?v={_videoId}",url_target="_blank")
# ],
# alignment=ft.MainAxisAlignment.END,
# ),
# ],
# horizontal_alignment=ft.CrossAxisAlignment.STRETCH,
# ),
# width=400,
# padding=10,
# alignment=ft.alignment.top_center,
# )
# )
# )
# _gvVideos.scroll_to(0, 500)
# _gvVideos.update()
# storage.statusText.value = f"Status: {_channel} video infos loaded - Ok"
# storage.statusText.update()
# return True
def main(page_: Page) -> None:
if not page_: report("error", "page_ is None!", True)
_config = getConfig()
if not _config: return report("_config is None!")
page_.title = 'FletApp' if not hasattr(_config.ui, 'title') else _config.ui.title
page_.theme_mode = 'system' if not hasattr(_config.ui, 'theme') else _config.ui.theme
page_.window_maximized = False if not hasattr(_config.ui, 'maximized') else _config.ui.maximized
page_.window_full_screen = False if not hasattr(_config.ui, 'fullscreen') else _config.ui.fullscreen
page_.vertical_alignment = MainAxisAlignment.NONE if not hasattr(_config.ui, "vertical_alignment") else getVAllignment(_config.ui.vertical_alignment)
storage.app = FletApp(page_, _config)
if not storage.app: return report("No app created!")
page_.add(storage.app)
storage.app.elements.statusbar.setStatus("Ready.")
# get and fill channels
storage.channels = getChannels(storage.channelsConfig)
if not storage.channels: return report("getChannels() failed")
if not storage.app.fillChannels(storage.channels): return report("fillChannels() failed")
if __name__ == "__main__":
exit(app(target=main))
from os import makedirs, path
from json import load
from shutil import copy
from types import SimpleNamespace
import flet as ft
storage = SimpleNamespace()
def report(message_:str="", type_:str="error", report_:bool=False):
print(f"[{type_}] {message_}")
return report_
def getConfig() -> SimpleNamespace:
_configJson = {
"ui": {
"title": "Flet Checker UI",
"theme": "dark",
"maximized": True,
"fullscreen": False,
},
"app": {
"name": "Flet Checker UI",
"version": "0.0.1",
"author": "Flet",
"description": "Flet checker UI",
"license": "MIT",
"repository": ""
}
}
_config = SimpleNamespace(**_configJson)
for _key in _configJson:
setattr(_config, _key, SimpleNamespace(**_configJson[_key]))
return _config
def getVAllignment(alignment:str="center"):
match alignment.lower():
case "center":
return ft.MainAxisAlignment.CENTER
case "top" | "start":
return ft.MainAxisAlignment.START
case "bottom" | "end":
return ft.MainAxisAlignment.END
case _:
return ft.MainAxisAlignment.NONE
def initApp(page_:ft.Page=None, config_:SimpleNamespace=None) -> bool:
if not page_: report("error", "page_ is None", True)
if not config_: report("error", "config_ is None", True)
page_.title = config_.ui.title
page_.theme_mode = config_.ui.theme
page_.window_maximized = config_.ui.maximized
page_.window_full_screen = config_.ui.fullscreen
if hasattr(config_.ui, "vertical_alignment"): page_.vertical_alignment = getVAllignment(config_.ui.vertical_alignment)
return True
# def channelClickedOld(info:str=None):
# return lambda i,j=info: fillChannelVideos(i, j) #print(f"Clicked ", j, i)
def channelClicked(event_:ft.ContainerTapEvent=None):
#print(event_.control, event_.data, event_.name, event_.page, event_.target)
if hasattr(storage, 'selectedChannel'):
storage.selectedChannel.content.title.color = ft.colors.WHITE
storage.selectedChannel.content.subtitle.color = ft.colors.GREY_400
storage.selectedChannel.bgcolor = ft.colors.TRANSPARENT
storage.selectedChannel.update()
storage.selectedChannel = event_.control
storage.selectedChannel.content.title.color = ft.colors.BLACK
storage.selectedChannel.content.subtitle.color = ft.colors.BLACK45
storage.selectedChannel.bgcolor = ft.colors.GREEN_300
storage.selectedChannel.update()
fillChannelVideos(event_)
def channelUpdate(event_:ft.ControlEvent=None):
storage.statusText.value = f"Status: {event_.control.data} updated, not implemented, yet."
storage.statusText.update()
def channelDisable(event_:ft.ControlEvent=None):
storage.statusText.value = f"Status: {event_.control.data} disabled, not implemented, yet."
storage.statusText.update()
def initUI(page_:ft.Page=None) -> bool:
if not page_: report("error", "page_ is None", True)
try:
storage.lvChannels = ft.ListView(
expand=False,
spacing=3,
padding=5,
auto_scroll=False,
)
# for i in range(0, 5):
# lv.controls.append(
# ft.ListTile(
# title=ft.Text(f"Line {i}"),
# subtitle=ft.Text(f"Videos: 36\nFrequency: Weekly\nChecked: 2024-01-01\nUpdated: 2024-01-01",font_family="consolas", size=10),
# on_click=channelClicked(f"Line {i}"),
# ),
# # ft.Container(
# # content=ft.Text(
# # f"Line {i}",
# # color=ft.colors.WHITE,
# # ),
# # alignment=ft.alignment.center,
# # on_click=channelClicked(f"Line {i}"),
# # ),
# )
lbChannels = ft.Column(
controls=[
ft.Text(
"Channels",
text_align=ft.TextAlign.CENTER,
color=ft.colors.WHITE,
style=ft.TextThemeStyle.TITLE_MEDIUM,
width=250,
),
ft.Divider(height=-1, color=ft.colors.WHITE),
ft.Container(content=storage.lvChannels, expand=True)
],
spacing=3,
)
gvVideos = ft.GridView(
expand=True,
auto_scroll=False,
#horizontal=True,
#runs_count=5,
max_extent=450,
#child_aspect_ratio=1.0,
spacing=5,
#run_spacing=5,
)
storage.statusText = ft.Text("Status: OK", color=ft.colors.WHITE, style=ft.TextThemeStyle.BODY_SMALL)
page_.add(
ft.SafeArea(
content=ft.Column(
controls=[
ft.Row(
controls=[
ft.Container(
content=ft.Text("Action Bar - not implemented, yet."),
expand=True,
),
],
),
ft.Row(
controls=[
ft.Container(
content=lbChannels, #ft.Text("Container 1"),
border=ft.border.all(color=ft.colors.WHITE, width=1),
border_radius=ft.border_radius.all(5),
width=250
),
ft.Container(
expand=True,
content=gvVideos,
#content=ft.Text("Container 2"),
#bgcolor=ft.colors.RED_100,
),
],
expand=True,
vertical_alignment=ft.CrossAxisAlignment.STRETCH,
),
ft.Row(
controls=[
ft.Container(
content=storage.statusText,
expand=True,
),
],
),
],
expand=True,
horizontal_alignment=ft.CrossAxisAlignment.STRETCH,
),
expand=True,
)
)
storage.elements = {
"lbChannels": lbChannels,
"gvVideos": gvVideos,
}
return True
except Exception as _e:
report("error", _e, True)
return False
def getChannels(file_:str=None) -> bool:
if not file_: report("error", f"{file_} is None (getChannels)")
if not path.exists(file_): report(f"{file_} does not exist (getChannels)")
if not path.isfile(file_): report(f"{file_} is not a file (getChannels)")
if not path.splitext(file_)[1] == ".json": report(f"{file_} is not a json file (getChannels)")
try:
with open(file_) as _jf:
_jd = load(_jf)
if _jd is None: raise IOError('Error (getChannels): Config file not found!')
storage.channels = {} if 'channels' not in _jd else _jd['channels']
if not hasattr(storage, 'channels'): return report('No channels configured (getChannels)')
except Exception as _e:
print('Exception (getChannels):', _e)
return report('Exception (getChannels)!!!')
return True
def fillChannels(channels_:dict=None) -> bool:
if not channels_: return report('No channels configured (fillChannels)')
if not hasattr(storage, 'lvChannels'): return report('No lvChannels configured (fillChannels)')
if storage.lvChannels is None: return report('lvChannels is None (fillChannels)')
try:
_sorted = dict(reversed(sorted(channels_.items(), key=lambda x: x[1]['updated'])))
for _channel, _values in _sorted.items():
_subtitle = f"Videos: {_values['count']}\n"
_subtitle += f"Frequency: {_values['frequency']}\n"
_subtitle += f"Checked: {_values['checked']}\n"
_subtitle += f"Updated: {_values['updated']}"
storage.lvChannels.controls.append(
ft.Container(
content=ft.ListTile(
title=ft.Text(_channel),
data=_channel,
col=_channel,
subtitle=ft.Text(_subtitle,style=ft.TextThemeStyle.BODY_SMALL,color=ft.colors.GREY_400),
trailing=ft.PopupMenuButton(
icon=ft.icons.MORE_VERT,
items=[
ft.PopupMenuItem(text="Update",on_click=channelUpdate,data=_channel),
ft.PopupMenuItem(text="Disable",on_click=channelDisable,data=_channel),
],
),
#on_click=channelClicked #channelClickedOld(_channel),
),
alignment=ft.alignment.top_center,
on_click=channelClicked,
col=_channel,
),
)
storage.lvChannels.update()
except Exception as _e:
print('Exception (fillChannels):', _e)
return report('Exception (fillChannels)!!!')
return True
def getChannelVideos(file_:str=None) -> bool:
if not file_: report("error", f"{file_} is None (getChannelVideos)")
if not path.exists(file_): report(f"{file_} does not exist (getChannelVideos)")
if not path.isfile(file_): report(f"{file_} is not a file (getChannelVideos)")
if not path.splitext(file_)[1] == ".json": report(f"{file_} is not a json file (getChannelVideos)")
try:
with open(file_) as _jf:
_jd = load(_jf)
if _jd is None: raise IOError('Error (getChannelVideos): Video file not found!')
storage.videos = _jd
if not hasattr(storage, 'videos'): return report('No videos configured (getChannelVideos)')
except Exception as _e:
print('Exception (getChannelVideos):', _e)
return report(f'Exception (getChannelVideos) with file "{file_}"!!!')
return True
def getImage(channel_:str=None, video_id:str=None):
if not channel_: return report('No channel configured (getImage)')
if not video_id: return report('No video_id configured (getImage)')
_file = f'/Users/homeboy/Sources/py/youtube/channels/{channel_}/images/'
if not path.exists(_file): makedirs(_file)
if not path.exists(_file): return report(f"{_file} does not exist (getImage)")
_file += f'{video_id}.jpg'
if path.exists(_file): return _file
_url = f'https://i.ytimg.com/vi/{video_id}/mqdefault.jpg'
try:
from urllib.request import urlretrieve
urlretrieve(_url, _file)
return _file
except Exception as _e:
print('Exception (getImage):', _e)
report('Exception (getImage)!!!')
if path.exists('default.jpg'): copy('default.jpg', _file)
_file = path.abspath(f'default.jpg')
return _file
def fillChannelVideos(event_) -> bool:
if not event_: return report('No event configured (fillChannelVideos)')
#if not hasattr(event_, 'channel'): return report('No channel configured (fillChannelVideos)')
#_channel = event_.channel
_channel = event_.control.col
if not _channel: return report('No channel configured (fillChannelVideos)')
if not getChannelVideos(f'/Users/homeboy/Sources/py/youtube/channels/{_channel}/videos.json'): report("fillChannelVideos() failed")
_gvVideos = storage.elements['gvVideos']
if not _gvVideos: return report('No gvVideos configured (fillChannelVideos)')
_gvVideos.controls = []
storage.statusText.value = f"Status: {_channel} video infos are loading..."
storage.statusText.update()
_sorted = dict(reversed(sorted(storage.videos.items(), key=lambda x: x[1]['added'] if x else None)))
for _videoId, _videoData in _sorted.items():
#print(_videoData['title'])
if _videoData['added'] in ['Ignored', 'Deleted']: continue
_src = getImage(_channel, _videoId)
_title = _videoData['title'] if len(_videoData['title']) < 30 else f"{_videoData['title'][0:30]}..."
_length = _videoData["length"]
_length = f'{_length}s' if _length > 0 else str(_length)
_gvVideos.controls.append(
ft.Card(
content=ft.Container(
content=ft.Column(
[
ft.Image(
src=_src,
width=320,
height=200,
fit=ft.ImageFit.FILL,
repeat=ft.ImageRepeat.NO_REPEAT,
border_radius=ft.border_radius.all(5),
tooltip=_videoData['title'],
),
ft.ListTile(
title=ft.Text(_title),
subtitle=ft.Text(
f"Published: {_videoData['date'][0:10]}\nAdded: {_videoData['added']}\nLength: {_length}\nState: {_videoData['state']}"
),
),
ft.Row(
[
ft.TextButton("Delete",disabled=True),
ft.TextButton("Download",disabled=True),
ft.TextButton("Visit",url=f"https://www.youtube.com/watch?v={_videoId}",url_target="_blank")
],
alignment=ft.MainAxisAlignment.END,
),
],
horizontal_alignment=ft.CrossAxisAlignment.STRETCH,
),
width=400,
padding=10,
alignment=ft.alignment.top_center,
)
)
)
_gvVideos.scroll_to(0, 500)
_gvVideos.update()
storage.statusText.value = f"Status: {_channel} video infos loaded - Ok"
storage.statusText.update()
return True
def main(page: ft.Page) -> None:
_config = getConfig()
if not _config: return report("_config is None")
if not initApp(page, _config): return report("initApp() failed")
if not initUI(page): return report("initUI() failed")
if not getChannels('/Users/homeboy/Sources/py/youtube/config.json'): return report("getChannels() failed")
if not fillChannels(storage.channels): return report("fillChannels() failed")
if __name__ == "__main__":
exit(ft.app(target=main))
{
"channels": {
"AlexiBexi": {
"checked": "2024-01-08",
"count": 36,
"frequency": "weekly",
"id": "UCzH549YlZhdhIqhtvz7XHmQ",
"name": "AlexiBexi",
"updated": "2024-01-08"
},
"Apfeltalk": {
"checked": "2024-01-08",
"count": 52,
"frequency": "weekly",
"id": "UC7XqBsdIv5Yyjrq5JXLzwpw",
"name": "Apfeltalk",
"updated": "2024-01-08"
},
"Apfelwelt": {
"checked": "2024-01-12",
"count": 88,
"frequency": "bidaily",
"id": "UCVChR9pqkB2Y6jpY8MlesoA",
"name": "Apfelwelt",
"updated": "2024-01-10"
},
"AwesomeOpenSource": {
"checked": "2024-01-02",
"count": 39,
"frequency": "monthly",
"id": "UCwFpzG5MK5Shg_ncAhrgr9g",
"name": "AwesomeOpenSource",
"updated": "2024-01-02"
},
"RafaelZeier": {
"checked": "2024-01-13",
"count": 222,
"frequency": "daily",
"id": "UCMW8XBBRkjrF_fweALWaZyw",
"name": "RafaelZeier",
"updated": "2024-01-11"
},
"mpoxDE": {
"checked": "2024-01-13",
"count": 40,
"frequency": "daily",
"id": "UCJZnRJATAFN5rbXpewp7Vyw",
"name": "mpoxDE",
"updated": "2024-01-11"
}
},
"updated": "2024-01-13"
}
"""
- Helper module for config database
- Author: alexrjs
- License: Unlicense
"""
from types import SimpleNamespace
from helpers.info import report
def getConfig() -> SimpleNamespace:
"""Get config object"""
#TODO: Read config from file
_configJson = {
"ui": {
"title": "Flet Checker UI",
"theme": "dark",
"maximized": True,
"fullscreen": False,
},
"infos": {
"statusBar": "Ready."
},
"app": {
"name": "Flet Checker UI",
"version": "0.0.1",
"author": "Flet",
"description": "Flet checker UI",
"license": "MIT",
"repository": ""
}
}
_config = SimpleNamespace(**_configJson)
try:
for _key in _configJson:
setattr(_config, _key, SimpleNamespace(**_configJson[_key]))
return _config
except Exception as e:
report(f"Error converting key '{_key}' value: {e}")
return None
"""
- Helper module for video database
- Author: alexrjs
- License: Unlicense
"""
from os import path
from json import load
from helpers.info import report
from helpers.db.storage import Storage
def getChannels(file_:str=None) -> dict:
if not file_: report("error", f"{file_} is None (getChannels)")
if not path.exists(file_): report(f"{file_} does not exist (getChannels)")
if not path.isfile(file_): report(f"{file_} is not a file (getChannels)")
if not path.splitext(file_)[1] == ".json": report(f"{file_} is not a json file (getChannels)")
try:
with open(file_) as _jf:
_jd = load(_jf)
if _jd is None: raise IOError('Error (getChannels): Config file not found!')
if 'channels' not in _jd: return report('No channels configured (getChannels)')
return {} if 'channels' not in _jd else _jd['channels']
except Exception as _e:
print('Exception (getChannels):', _e)
return report('Exception (getChannels)!!!')
return True
def getChannelVideos(file_:str=None) -> dict:
if not file_: report("error", f"{file_} is None (getChannelVideos)")
if not path.exists(file_): report(f"{file_} does not exist (getChannelVideos)")
if not path.isfile(file_): report(f"{file_} is not a file (getChannelVideos)")
if not path.splitext(file_)[1] == ".json": report(f"{file_} is not a json file (getChannelVideos)")
try:
with open(file_) as _jf:
_jd = load(_jf)
if _jd is None: raise IOError('Error (getChannelVideos): Video file not found!')
return _jd
except Exception as _e:
print('Exception (getChannelVideos):', _e)
report(f'Exception (getChannelVideos) with file "{file_}"!!!')
return None
"""
- Helper module for reporting stuff
- Author: alexrjs
- License: Unlicense
"""
def report(message_:str="", type_:str="error", report_:bool=False) -> bool:
"""Prints a message to the console and returns a boolean value."""
print(f"[{type_}] {message_}")
return report_
"""
- Helper module for ui app statusbar
- Author: alexrjs
- License: Unlicense
"""
# imports
from flet import Container, Row, Text, UserControl
from flet import colors, TextThemeStyle
# classes
class StatusBar(UserControl):
"""Status bar"""
def __init__(self) -> None:
"""Init"""
super().__init__()
def setStatus(self, status:str='') -> None:
"""Set status"""
self.status.value = status
self.update()
def build(self) -> Row:
"""Fill action bar"""
self.status = Text('', color=colors.WHITE, style=TextThemeStyle.BODY_SMALL)
return Row(
controls=[
Container(
content=self.status,
expand=True,
),
],
)
"""
- Data storage class for config
- Author: alexrjs
- License: Unlicense
"""
import json
from types import SimpleNamespace
class Storage(SimpleNamespace):
"""Storage class for config"""
def has(self, key):
return hasattr(self, key)
def dumpToJson(self, file_path):
"""Dump the content to a JSON file for debug purposes"""
try:
with open(file_path, 'w') as f:
json.dump(self.__dict__, f, indent=4)
except Exception as e:
raise IOError(f"An error occurred while dumping to JSON: {e}")
def __enter__(self):
if isinstance(self.__dict__, dict):
return self.__dict__.__enter__()
else:
raise AttributeError("'Storage' object has no attribute '__enter__'")
def __exit__(self, exc_type, exc_value, traceback):
if isinstance(self.__dict__, dict):
return self.__dict__.__exit__(exc_type, exc_value, traceback)
else:
raise AttributeError("'Storage' object has no attribute '__exit__'")
.
├── checkeruipoc.py
├── assets
│ └── default.jpg
├── checkerui.py
├── config.json
├── helpers
│ ├── __init__.py
│ ├── db
│ │ ├── __init__.py
│ │ ├── assets.py
│ │ ├── config.py
│ │ ├── storage.py
│ │ └── video.py
│ ├── info.py
│ └── ui
│ ├── __init__.py
│ ├── app.py
│ ├── bars
│ │ ├── __init__.py
│ │ ├── action.py
│ │ └── status.py
│ ├── boxes
│ │ ├── __init__.py
│ │ ├── channels.py
│ │ └── videos.py
│ └── config.py
├── requirements.txt
"""
- Helper module for ui listbox channels
- Author: alexrjs
- License: Unlicense
"""
from flet import Container, GridView, UserControl
from flet import border, border_radius, colors
class GridEntryVideo(UserControl):
"""Video grid entry"""
def __init__(self, parent, **kwargs):
super().__init__(parent, **kwargs)
self._id = kwargs['id'] if 'id' in kwargs else None
self._name = kwargs['name'] if 'name' in kwargs else None
self._selected = kwargs['selected'] if 'selected' in kwargs else None
self._onSelect = kwargs['onSelect'] if 'onSelect' in kwargs else None
self._label = self.addLabel()
self._label.text = self._name
self._label.onSelect = self._onSelect
if self._selected:
self._label.select()
def _onSelect(self, sender, args):
"""On select channel"""
self._selected = True
def _onDeselect(self, sender, args):
"""On deselect channel"""
self._selected = False
class GridViewVideos(UserControl):
"""Channels listbox"""
def __init__(self):
super().__init__()
self.videosGrid = None
def build(self):
self.videosGrid = GridView(
expand=True,
auto_scroll=False,
max_extent=450,
spacing=5,
)
return Container(
content=self.videosGrid,
border=border.all(color=colors.WHITE, width=1),
border_radius=border_radius.all(5),
expand=True,
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment