You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
153 lines
4.6 KiB
Python
153 lines
4.6 KiB
Python
#!/usr/bin/env python3
|
|
import argparse
|
|
import curses
|
|
from pathlib import Path
|
|
import subprocess
|
|
import sys
|
|
from time import sleep
|
|
|
|
# Configuration
|
|
|
|
text_album_current = "Aktuelles Album"
|
|
text_album_root = "(Alle Fotoalben)"
|
|
text_closed_pictures = "Gehe zurück ..."
|
|
text_load_pictures = "Bilder werden geladen ..."
|
|
text_no_pictures_found = "Hier sind leider noch keine Bilder drinnen."
|
|
|
|
wait_no_pictures_found = 5 # seconds
|
|
|
|
indicator = "➤"
|
|
|
|
binding_up = [ord('k'), curses.KEY_UP, curses.KEY_PPAGE]
|
|
binding_down = [ord('j'), curses.KEY_DOWN, curses.KEY_NPAGE]
|
|
binding_accept = [ord('l'), ord(' '), ord('\n'), curses.KEY_HOME]
|
|
binding_back = [ord('h'), ord('q'), curses.KEY_BACKSPACE, curses.KEY_END]
|
|
binding_exit = [ord('Q')]
|
|
|
|
# Code
|
|
|
|
def stretch(text, length, char = ' '):
|
|
missing = length - len(text)
|
|
if 0 < missing:
|
|
text += char * missing
|
|
elif missing < 0:
|
|
text = text[0:length]
|
|
return text
|
|
|
|
def build_description(base_dir, desc_path):
|
|
rel_path = desc_path.relative_to(base_dir)
|
|
parts = list(rel_path.parts[:-1])
|
|
parts.append(rel_path.stem)
|
|
parts = [part.replace('_', ' ') for part in parts]
|
|
return ' - '.join(parts)
|
|
|
|
def show_pictures(files):
|
|
files = list(files) # copy list
|
|
files.insert(0, '/usr/bin/imvr')
|
|
subprocess.call(files)
|
|
return True
|
|
|
|
def search_pictures(out, path):
|
|
files = sorted([str(f) for f in path.iterdir() if f.is_file() and f.suffix.lower() in ['.png', '.jpg', '.jpeg', '.gif']])
|
|
if len(files) <= 0:
|
|
out(text_no_pictures_found)
|
|
sleep(wait_no_pictures_found)
|
|
return True
|
|
out(text_load_pictures)
|
|
ret = show_pictures(files)
|
|
out(text_closed_pictures)
|
|
return ret
|
|
|
|
def draw_select(win, dirs, sel):
|
|
y_size, x_size = win.getmaxyx()
|
|
# centered scroll
|
|
y_center = y_size // 2
|
|
for y in range(y_size):
|
|
i = sel + y - y_center
|
|
if i < 0 or len(dirs) <= i:
|
|
win.addstr(y, 1, " " * (x_size - 2), curses.A_NORMAL)
|
|
else:
|
|
text = " "
|
|
text += indicator if i == sel else (" " * len(indicator))
|
|
text += " "
|
|
text += dirs[i].name
|
|
text = stretch(text, x_size - 2)
|
|
text_format = curses.A_STANDOUT if i == sel else curses.A_NORMAL
|
|
win.addstr(y, 1, text, text_format)
|
|
return True
|
|
|
|
def draw_header(win, base_dir, path):
|
|
x_size = win.getmaxyx()[1]
|
|
desc = build_description(base_dir, path)
|
|
text = text_album_current + ": "
|
|
text += desc if len(desc) > 0 else text_album_root
|
|
win.addstr(1, 1, stretch(text, x_size - 2))
|
|
|
|
def show_select(win, base_dir, path):
|
|
if not path.is_dir():
|
|
raise Exception("'" + str(path) + "' is not a directory!")
|
|
dirs = sorted([d for d in path.iterdir() if d.is_dir()])
|
|
win.clear()
|
|
if len(dirs) <= 0:
|
|
draw_header(win, base_dir, path)
|
|
x_size = win.getmaxyx()[1]
|
|
def out(text):
|
|
win.addstr(3, 1, stretch(text, x_size - 2))
|
|
win.refresh()
|
|
return search_pictures(out, path)
|
|
sel = 0
|
|
while True:
|
|
draw_header(win, base_dir, path)
|
|
draw_select(win.subwin(3, 0), dirs, sel)
|
|
c = win.getch()
|
|
if c in binding_up:
|
|
sel = max(0, sel - 1)
|
|
elif c in binding_down:
|
|
sel = min(len(dirs) - 1, sel + 1)
|
|
elif c in binding_accept:
|
|
if not show_select(win, base_dir, dirs[sel]):
|
|
return False
|
|
win.clear()
|
|
elif c in binding_back:
|
|
return True
|
|
elif c in binding_exit:
|
|
return False
|
|
|
|
def select_ui(win, args):
|
|
curses.curs_set(0)
|
|
while show_select(win, args.dir, args.dir) and args.kiosk:
|
|
pass
|
|
if args.kiosk:
|
|
subprocess.call(['systemctl', 'poweroff'])
|
|
return True # exited as expected
|
|
|
|
def retry(fun):
|
|
last_return = False
|
|
while not last_return:
|
|
try:
|
|
last_return = fun()
|
|
except:
|
|
pass # TODO save log file with error
|
|
|
|
def main():
|
|
# parse args
|
|
parser = argparse.ArgumentParser(add_help=False)
|
|
parser.add_argument('--base-dir', type=Path, dest='dir', required=True)
|
|
parser.add_argument('--kiosk', dest='kiosk', action='store_true')
|
|
parser.add_argument('--build-desc-of', type=Path, dest='desc_path', default=None)
|
|
args = parser.parse_args()
|
|
# build description if requested
|
|
if args.desc_path is not None:
|
|
print(build_description(args.dir, args.desc_path))
|
|
return
|
|
# run menus in protective mode if kiosk mode is enabled
|
|
def run():
|
|
curses.wrapper(select_ui, args)
|
|
if args.kiosk:
|
|
retry(run)
|
|
else:
|
|
run()
|
|
|
|
if __name__ == '__main__':
|
|
main()
|