Skip to content

Commit

Permalink
video branch up to date with main
Browse files Browse the repository at this point in the history
  • Loading branch information
paddywwoof committed Nov 20, 2024
2 parents 902fcf5 + 6540065 commit 5c6044c
Show file tree
Hide file tree
Showing 12 changed files with 523 additions and 230 deletions.
72 changes: 0 additions & 72 deletions .github/ISSUE_TEMPLATE/issue-report.md

This file was deleted.

1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

![picframe logo](https://github.com/helgeerbe/picframe/wiki/images/Picframe_Logo.png)

- [![PyPI version](https://badge.fury.io/py/picframe.svg)](https://badge.fury.io/py/picframe)
- [PictureFrame powered by pi3d](#pictureframe-powered-by-pi3d)
- [What Is PictureFrame?](#what-is-pictureframe)
- [History of PictureFrame](#history-of-pictureframe)
Expand Down
16 changes: 7 additions & 9 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,26 +24,24 @@ readme = "README.md"
license = {text = "MIT"}
requires-python = ">=3.7"
classifiers=[
"Development Status :: 5 - Production/Stable",
"Development Status :: 4 - Beta",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Multimedia :: Graphics :: Viewers",
]
dynamic = ["version"]
dependencies = [
"Pillow==9.5.0",
"Pillow>=10.2.0",
"defusedxml",
"pi3d>=2.49",
"pi3d>=2.52",
"PyYAML",
"paho-mqtt",
"IPTCInfo3",
"numpy",
"ninepatch",
"ninepatch>=0.2.0",
"pi_heif>=0.8.0"
]

Expand Down Expand Up @@ -76,4 +74,4 @@ style = "pep440"
versionfile_source = "src/picframe/_version.py"
versionfile_build = "picframe/_version.py"
tag_prefix = ""
parentdir_prefix = "picframe-"
parentdir_prefix = "picframe-"
19 changes: 15 additions & 4 deletions src/picframe/config/configuration_example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ viewer:
display_y: 0 # offset from top of screen (can be negative)
display_w: null # width of display surface (null->None will use max returned by hardware)
display_h: null # height of display surface
display_power: 0 # default=0. choices={0, 1}, 0 will use legacy `vcgencmd` and 1 will use `xset` to blank the display
use_glx: False # default=False. Set to True on linux with xserver running
use_sdl2: False # default=True. Needed for running pi3d with sdl2 on Pi2 and Pi3
display_power: 2 # default=0. choices={0, 1, 2}, 0 will use legacy `vcgencmd`, 1 will use `xset`, 2 will use 'wlr-randr' to blank the display
use_glx: False # default=False. Set to True on linux with xserver running. NB use_sdl2 might need to be False for this to work.
use_sdl2: True # default=True. pysdl2 can use display without xserver, it should be installed as a dependency of pi3d
# but might need `sudo apt install libsdl2-dev` if picframe gives errors about missing sdl2

mat_images: 0.01 # default=0.01, True, automatically mat all images. False, don't automatically mat any images. Real value, auto-mat all images with aspect ratio difference > than value
mat_type: null # default=null, A string containing the mat types to choose from when matting images. It can consist of any or
Expand All @@ -40,8 +41,12 @@ viewer:
show_clock: False # default=False, True shows clock overlay. False does not show clock overlay
clock_justify: "R" # default="R", clock justification L, C, or R
clock_text_sz: 120 # default=120, clock character size
clock_format: "%I:%M" # default="%I:%M", strftime format for clock string
clock_format: "%-I:%M" # default="%-I:%M", strftime format for clock string
clock_opacity: 1.0 # default=1.0 (0.0-1.0), alpha value of clock overlay
clock_top_bottom: "T" # default="T" ("T", "B"), whether to display clock at top or bottom of screen
clock_wdt_offset_pct: 3.0 # default=3.0 (1.0-10.0), used to calclate pixels between clock text and side of screen
clock_hgt_offset_pct: 3.0 # default=3.0 (1.0-10.0), used to calclate pixels between clock text and top/bottom of screen
# If text is found in the ramdisk /dev/shm/clock.txt then it will be displayed under the time

menu_text_sz: 40 # default=40, menu character size
menu_autohide_tm: 10.0 # default=10.0, time in seconds to show menu before auto hiding (0 disables auto hiding)
Expand All @@ -57,6 +62,7 @@ model:
reshuffle_num: 1 # default=1, times through before reshuffling
time_delay: 200.0 # default=200.0, time between consecutive slide starts - can be changed by MQTT
fade_time: 10.0 # default=10.0, change time during which slides overlap - can be changed by MQTT"
update_interval: 2.0 # default=2.0, time in seconds to wait between two consecutive scans for new files
shuffle: True # default=True, shuffle on reloading image files - can be changed by MQTT"
sort_cols: 'fname ASC' # default='fname ASC' can be any columns in the table with optional ASC or DESC separated by commas
# fname, last_modified, file_id, orientation, exif_datetime, f_number,
Expand Down Expand Up @@ -88,6 +94,8 @@ model:
["country"]]
db_file: "~/picframe_data/data/pictureframe.db3" # database used by PictureFrame
portrait_pairs: False
location_filter: "" # default="" filter clause for image location
tags_filter: "" # default="" filter clause for image tags
log_level: "WARNING" # default=WARNING, could beDEBUG, INFO, WARNING, ERROR, CRITICAL
log_file: "" # default="" for debugging set this to the path to a file. NB logging messages will
# appended indefinitely so don't forget this. You will need to tidy it up later
Expand All @@ -106,6 +114,9 @@ http:
use_http: False # default=False. Set True to enable http NB THIS SERVER IS FOR LOCAL NETWORK AND SHOULD NOT BE EXPOSED TO EXTERNAL ACCESS
path: "~/picframe_data/html" # path to where html files are located
port: 9000 # port used to serve pages by http server < 1024 requires root which is *bad* idea
auth: false # default=False. Set True if enable basic auth for http
username: admin # username for basic auth
password: null # password for basic auth. If set null generate random password in file basic_auth.txt in parent http directory
use_ssl: False
keyfile: "path/to/key.pem" # private-key
certfile: "path/to/cert.pem" # server certificate
Expand Down
60 changes: 14 additions & 46 deletions src/picframe/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,10 @@ def __init__(self, model, viewer):
self.__next_tm = 0
self.__date_from = make_date('1901/12/15') # TODO This seems to be the minimum date to be handled by date functions # noqa: E501
self.__date_to = make_date('2038/1/1')
self.__location_filter = ""
self.__where_clauses = {}
self.__sort_clause = "exif_datetime ASC"
self.publish_state = lambda x, y: None
self.keep_looping = True
self.__location_filter = ''
self.__tags_filter = ''
self.__interface_peripherals = None
self.__interface_mqtt = None
self.__interface_http = None
Expand Down Expand Up @@ -232,56 +229,22 @@ def matting_images(self, val):

@property
def location_filter(self):
return self.__location_filter
return self.__model.location_filter

@location_filter.setter
def location_filter(self, val):
self.__location_filter = val
if len(val) > 0:
self.__model.set_where_clause("location_filter", self.__build_filter(val, "location"))
else:
self.__model.set_where_clause("location_filter") # remove from where_clause
self.__model.force_reload()
self.__model.location_filter = val
self.__next_tm = 0

@property
def tags_filter(self):
return self.__tags_filter
return self.__model.tags_filter

@tags_filter.setter
def tags_filter(self, val):
self.__tags_filter = val
if len(val) > 0:
self.__model.set_where_clause("tags_filter", self.__build_filter(val, "tags"))
else:
self.__model.set_where_clause("tags_filter") # remove from where_clause
self.__model.force_reload()
self.__model.tags_filter = val
self.__next_tm = 0

def __build_filter(self, val, field):
if val.count("(") != val.count(")"):
return None # this should clear the filter and not raise an error
val = val.replace(";", "").replace("'", "").replace("%", "").replace('"', '') # SQL scrambling
tokens = ("(", ")", "AND", "OR", "NOT") # now copes with NOT
val_split = val.replace("(", " ( ").replace(")", " ) ").split() # so brackets not joined to words
filter = []
last_token = ""
for s in val_split:
s_upper = s.upper()
if s_upper in tokens:
if s_upper in ("AND", "OR"):
if last_token in ("AND", "OR"):
return None # must have a non-token between
last_token = s_upper
filter.append(s)
else:
if last_token is not None:
filter.append("{} LIKE '%{}%'".format(field, s))
else:
filter[-1] = filter[-1].replace("%'", " {}%'".format(s))
last_token = None
return "({})".format(" ".join(filter)) # if OR outside brackets will modify the logic of rest of where clauses

def text_is_on(self, txt_key):
return self.__viewer.text_is_on(txt_key)

Expand Down Expand Up @@ -353,11 +316,16 @@ def start(self):
if self.__http_config['use_http']:
from picframe import interface_http
model_config = self.__model.get_model_config()
self.__interface_http = interface_http.InterfaceHttp(self,
self.__http_config['path'],
model_config['pic_dir'],
model_config['no_files_img'],
self.__http_config['port']) # TODO: Implement TLS
self.__interface_http = interface_http.InterfaceHttp(
self,
self.__http_config['path'],
model_config['pic_dir'],
model_config['no_files_img'],
self.__http_config['port'],
self.__http_config['auth'],
self.__http_config['username'],
self.__http_config['password'],
) # TODO: Implement TLS
if self.__http_config['use_ssl']:
self.__interface_http.socket = ssl.wrap_socket(
self.__interface_http.socket,
Expand Down
3 changes: 3 additions & 0 deletions src/picframe/get_image_meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,9 @@ def get_exif(self, key):
for iso in iso_keys:
val = self.__get_if_exist(iso)
if val:
# If ISO is returned as a tuple, take the first element
if type(val) is tuple:
val = val[0]
break
else:
val = self.__get_if_exist(key)
Expand Down
17 changes: 11 additions & 6 deletions src/picframe/image_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class ImageCache:
'IPTC Caption/Abstract': 'caption',
'IPTC Object Name': 'title'}

def __init__(self, picture_dir, follow_links, db_file, geo_reverse, portrait_pairs=False):
def __init__(self, picture_dir, follow_links, db_file, geo_reverse, update_interval, portrait_pairs=False):
# TODO these class methods will crash if Model attempts to instantiate this using a
# different version from the latest one - should this argument be taken out?
self.__modified_folders = []
Expand All @@ -36,6 +36,7 @@ def __init__(self, picture_dir, follow_links, db_file, geo_reverse, portrait_pai
self.__follow_links = follow_links
self.__db_file = db_file
self.__geo_reverse = geo_reverse
self.__update_interval = update_interval
self.__portrait_pairs = portrait_pairs # TODO have a function to turn this on and off?
self.__db = self.__create_open_db(self.__db_file)
self.__db_write_lock = threading.Lock() # lock to serialize db writes between threads
Expand All @@ -54,7 +55,7 @@ def __loop(self):
while self.__keep_looping:
if not self.__pause_looping:
self.update_cache()
time.sleep(2.0)
time.sleep(self.__update_interval)
time.sleep(0.01)
self.__db_write_lock.acquire()
self.__db.commit() # close after update_cache finished for last time
Expand Down Expand Up @@ -358,8 +359,9 @@ def __get_modified_folders(self):
out_of_date_folders = []
sql_select = "SELECT * FROM folder WHERE name = ?"
for dir in [d[0] for d in os.walk(self.__picture_dir, followlinks=self.__follow_links)]:
if os.path.basename(dir)[0] == '.':
continue # ignore hidden folders
if os.path.basename(dir):
if os.path.basename(dir)[0] == '.':
continue # ignore hidden folders
mod_tm = int(os.stat(dir).st_mtime)
found = self.__db.execute(sql_select, (dir,)).fetchone()
if not found or found['last_modified'] < mod_tm or found['missing'] == 1:
Expand Down Expand Up @@ -414,7 +416,10 @@ def __insert_file(self, file, file_id=None):
self.__db.execute(file_insert, (dir, base, extension.lstrip("."), mod_tm))
else:
self.__db.execute(file_update, (dir, base, extension.lstrip("."), mod_tm, file_id))
self.__db.execute(meta_insert, vals)
try:
self.__db.execute(meta_insert, vals)
except:
self.__logger.error(f"###FAILED meta_insert = {meta_insert}, vals = {vals}")
self.__db_write_lock.release()

def __update_folder_info(self, folder_collection):
Expand Down Expand Up @@ -520,4 +525,4 @@ def __get_exif_info(self, file_path_name):

# If being executed (instead of imported), kick it off...
if __name__ == "__main__":
cache = ImageCache(picture_dir='/home/pi/Pictures', follow_links=False, db_file='/home/pi/db.db3', geo_reverse=None)
cache = ImageCache(picture_dir='/home/pi/Pictures', follow_links=False, db_file='/home/pi/db.db3', geo_reverse=None, update_interval=2)
Loading

0 comments on commit 5c6044c

Please sign in to comment.