tidied up the exsting code, added data objects and special helper classes

This commit is contained in:
Tim Caldwell
2017-09-01 21:50:12 +12:00
parent 9bdb02a3e9
commit 053bde35f6
8 changed files with 2854 additions and 179 deletions

View File

@@ -1,37 +1,52 @@
import json import json
import logging import logging
from collections import OrderedDict from collections import OrderedDict
import os import os
from random import randint from random import randint
#TODO : standise the paths to things, use constants for file names, standise the naming convention of files
import time import time
import inspect
PATH_TO_BROWSER = 'C:/TestFolderStucture' ######## sets names for the persistant data objects ########
NEXT_BANK_JSON = 'next_bank_number.json'
SETTINGS_JSON = 'settings.json'
BANK_DATA_JSON = 'display_data.json'
PATH_TO_DATA_OBJECTS = 'C:/Users/Tim/PycharmProjects/videoLooper/' ######## define how to get path to current dir and set up logging ########
def get_the_current_dir_path():
#TODO: investigate weird path formatting differences
current_file_path = inspect.stack()[0][1]
return os.path.split(current_file_path)[0] + '/'
EMPTY_BANK = dict(name='',location='',length=-1,start=-1,end=-1) def setup_logging():
logger = logging.getLogger('logfile')
logger = logging.getLogger('myapp') current_dir = get_the_current_dir_path()
hdlr = logging.FileHandler('C:/Users/Tim/PycharmProjects/videoLooper/myapp.log') hdlr = logging.FileHandler(current_dir + 'logfile.log')
formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s') formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
hdlr.setFormatter(formatter) hdlr.setFormatter(formatter)
logger.addHandler(hdlr) logger.addHandler(hdlr)
logger.setLevel(logging.INFO) logger.setLevel(logging.INFO)
return logger
logger = setup_logging()
######## sets paths and constants ########
PATH_TO_BROWSER = 'C:\TestFolderStucture' #TODO replace this with pi path name when i know what makes sense
PATH_TO_DATA_OBJECTS = get_the_current_dir_path()
EMPTY_BANK = dict(name='',location='',length=-1,start=-1,end=-1)
####<<<< data methods for browser tab >>>>#####
class data(object): class data(object):
######## a data class used mainly for managing the browser list ########
def __init__(self): def __init__(self):
self._open_folders = [] self._open_folders = []
self._browser_list = [] self._browser_list = []
def rewrite_browser_list(self): def rewrite_browser_list(self):
self._browser_list = generate_browser_list(PATH_TO_BROWSER, 0, self._open_folders) self._browser_list = generate_browser_list(PATH_TO_BROWSER, 0, self._open_folders)
def get_browser_data_for_display(self): def get_browser_data_for_display(self):
######## map the browser_list to format for displaying in asciimatics ########
if not self._browser_list: if not self._browser_list:
self.rewrite_browser_list() self.rewrite_browser_list()
@@ -48,34 +63,13 @@ class data(object):
self._open_folders.remove(folder_name) self._open_folders.remove(folder_name)
def get_all_looper_data_for_display(): def generate_browser_list(initial_path, current_level, open_folder_list):
######## starts the recursive process of listing all folders and video files to display ########
memory_bank = read_json('DisplayData.json')
loop_data = []
for index, bank in enumerate(memory_bank):
length = convert_int_to_string_for_display(bank["length"])
start = convert_int_to_string_for_display(bank["start"])
end = convert_int_to_string_for_display(bank["end"])
loop_data.append(([str(index),bank["name"],length,start,end],index))
return loop_data
def is_file_in_memory_bank(file_name, memory_bank=[]):
if not memory_bank:
memory_bank = read_json('DisplayData.json')
for index, bank in enumerate(memory_bank):
if(file_name == bank['name']):
return True , index
return False, ''
def generate_browser_list(current_path, current_level, open_folder_list):
global results global results
results = [] results = []
add_folder_to_browser_list(initial_path, current_level,open_folder_list)
add_folder_to_browser_list(current_path, current_level,open_folder_list) memory_bank = read_json(BANK_DATA_JSON)
memory_bank = read_json('DisplayData.json')
for browser_line in results: for browser_line in results:
is_file, file_name = extract_file_type_and_name_from_browser_format(browser_line['name']) is_file, file_name = extract_file_type_and_name_from_browser_format(browser_line['name'])
@@ -87,6 +81,8 @@ def generate_browser_list(current_path, current_level, open_folder_list):
return results return results
def add_folder_to_browser_list(current_path, current_level,open_folder_list): def add_folder_to_browser_list(current_path, current_level,open_folder_list):
######## adds the folders and mp4 files at the current level to the results list. recursively recalls at deeper level if folder is open ########
#TODO make note of / investigate what happens with multiple folders of same name
root, dirs, files = next(os.walk(current_path)) root, dirs, files = next(os.walk(current_path))
indent = ' ' * 4 * (current_level) indent = ' ' * 4 * (current_level)
@@ -105,24 +101,33 @@ def add_folder_to_browser_list(current_path, current_level,open_folder_list):
results.append(dict(name='{}{}'.format(indent, f), bank='-')) results.append(dict(name='{}{}'.format(indent, f), bank='-'))
def check_folder_state(folder_name,open_folder_list): def check_folder_state(folder_name,open_folder_list):
######## used for displaying folders as open or closed ########
if (folder_name in open_folder_list): if (folder_name in open_folder_list):
return True, '/' return True, '/'
else: else:
return False, '|' return False, '|'
def extract_file_type_and_name_from_browser_format(dir_name): def extract_file_type_and_name_from_browser_format(dir_name):
#logger.info('the name we got was {}'.format(dir_name)) ######## removes whitespace and folder state from display item ########
if(dir_name.endswith('|') or dir_name.endswith('/')): if(dir_name.endswith('|') or dir_name.endswith('/')):
return False , dir_name.lstrip()[:-1] return False , dir_name.lstrip()[:-1]
else: else:
return True , dir_name.lstrip() return True , dir_name.lstrip()
def get_length_for_file(location): def is_file_in_memory_bank(file_name, memory_bank=[]):
#TODO: will have omx.player get length of file probs.. ######## used for displaying the mappings in browser view ########
pass if not memory_bank:
memory_bank = read_json(BANK_DATA_JSON)
for index, bank in enumerate(memory_bank):
if(file_name == bank['name']):
return True , index
return False, ''
####<<<< responding to user input in browser tab >>>>#####
def create_new_bank_mapping_in_first_open(file_name): def create_new_bank_mapping_in_first_open(file_name):
memory_bank = read_json('DisplayData.json') ######## used for mapping current video to next available bank ########
memory_bank = read_json(BANK_DATA_JSON)
for index , bank in enumerate(memory_bank): for index , bank in enumerate(memory_bank):
if(not bank['name']): if(not bank['name']):
create_new_bank_mapping(index,file_name,memory_bank) create_new_bank_mapping(index,file_name,memory_bank)
@@ -130,12 +135,18 @@ def create_new_bank_mapping_in_first_open(file_name):
return False return False
def create_new_bank_mapping(bank_number,file_name,memory_bank=[]): def create_new_bank_mapping(bank_number,file_name,memory_bank=[]):
######## used for mapping current video to a specific bank ########
has_location , location = get_path_for_file(file_name) has_location , location = get_path_for_file(file_name)
length = get_length_for_file(location) length = get_length_for_file(location)
new_bank = dict(name=file_name, location=location, length=-1, start=-1, end=-1) new_bank = dict(name=file_name, location=location, length=-1, start=-1, end=-1)
update_a_banks_data(bank_number, new_bank, memory_bank) update_a_banks_data(bank_number, new_bank, memory_bank)
def get_length_for_file(location):
#TODO: will have omx.player get length of file probs..
pass
def get_path_for_file(file_name): def get_path_for_file(file_name):
######## returns full path for a given file name ########
for root, dirs, files in os.walk(PATH_TO_BROWSER): for root, dirs, files in os.walk(PATH_TO_BROWSER):
if file_name in files: if file_name in files:
print root print root
@@ -144,50 +155,54 @@ def get_path_for_file(file_name):
return False, '' return False, ''
def update_a_banks_data(bank_number, bank_info, memory_bank=[]): def update_a_banks_data(bank_number, bank_info, memory_bank=[]):
######## overwrite a given banks info with new data ########
if not memory_bank: if not memory_bank:
memory_bank = read_json('DisplayData.json') memory_bank = read_json(BANK_DATA_JSON)
memory_bank[bank_number] = bank_info memory_bank[bank_number] = bank_info
update_json('DisplayData.json', memory_bank) update_json(BANK_DATA_JSON, memory_bank)
def clear_all_banks(): def clear_all_banks():
memory_bank = read_json('DisplayData.json') memory_bank = read_json(BANK_DATA_JSON)
for index, bank in enumerate(memory_bank): for index, bank in enumerate(memory_bank):
memory_bank[index] = EMPTY_BANK memory_bank[index] = EMPTY_BANK
update_json('DisplayData.json', memory_bank) update_json(BANK_DATA_JSON, memory_bank)
def read_json(file_name): ####<<<< data methods for looper tab >>>>#####
with open(PATH_TO_DATA_OBJECTS + file_name) as data_file: def get_all_looper_data_for_display():
data = json.load(data_file) ######## read bank mappings from data object and format for displaying in asciimatics ########
memory_bank = read_json(BANK_DATA_JSON)
loop_data = []
for index, bank in enumerate(memory_bank):
length = convert_int_to_string_for_display(bank["length"])
start = convert_int_to_string_for_display(bank["start"])
end = convert_int_to_string_for_display(bank["end"])
loop_data.append(([str(index),bank["name"],length,start,end],index))
return data return loop_data
def update_json(file_name,data): ####<<<< data methods for looper tab >>>>#####
with open('{}{}'.format(PATH_TO_DATA_OBJECTS, file_name), 'w') as data_file:
json.dump(data, data_file)
def get_all_settings_data_for_display(): def get_all_settings_data_for_display():
settings = read_json('Settings.json') ######## read settings from data object and format for displaying in asciimatics ########
settings = read_json(SETTINGS_JSON)
display_settings = [] display_settings = []
for index, setting in enumerate(settings): for index, setting in enumerate(settings):
display_settings.append(([setting['name'],setting['value']],index)) display_settings.append(([setting['name'],setting['value']],index))
return display_settings return display_settings
def get_a_banks_data(bank_number):
memory_bank = read_json('DisplayData.json')
return memory_bank[bank_number]
def switch_settings(setting_name): def switch_settings(setting_name):
settings = read_json('Settings.json') ######## update the value of selected setting by cycling through valid options ########
settings = read_json(SETTINGS_JSON)
for index, setting in enumerate(settings): for index, setting in enumerate(settings):
if setting['name'] == setting_name: if setting['name'] == setting_name:
setting = cycle_setting_value(setting) setting = cycle_setting_value(setting)
update_json('Settings.json',settings) update_json(SETTINGS_JSON,settings)
def cycle_setting_value(setting): def cycle_setting_value(setting):
######## contains the valid setting values for each applicable option ########
if setting['name'] == 'PLAYBACK_MODE': if setting['name'] == 'PLAYBACK_MODE':
if setting['value'] == 'LOOPER': if setting['value'] == 'LOOPER':
setting['value'] = 'PLAYLIST' setting['value'] = 'PLAYLIST'
@@ -213,17 +228,18 @@ def cycle_setting_value(setting):
return setting return setting
####<<<< data methods for video_centre >>>>#####
def get_next_context(): def get_next_context():
next_bank_number = read_json('next_bank_number.json') ######## loads the bank details, uses settings to modify them and then set next bank number ########
memory_bank = read_json('DisplayData.json') next_bank_number = read_json(NEXT_BANK_JSON)
memory_bank = read_json(BANK_DATA_JSON)
next_bank_details = memory_bank[next_bank_number] next_bank_details = memory_bank[next_bank_number]
start_value = next_bank_details['start'] start_value = next_bank_details['start']
end_value = next_bank_details['end'] end_value = next_bank_details['end']
length = next_bank_details['length'] length = next_bank_details['length']
use_rand_start, use_sync_length, sync_length, playback_mode = get_context_options_from_settings() use_rand_start, use_sync_length, sync_length, playback_mode = get_context_options_from_settings()
set_next_bank_number_from_playback_mode(playback_mode,next_bank_number)
if use_rand_start and use_sync_length: if use_rand_start and use_sync_length:
start_value = randint(0, length - sync_length) start_value = randint(0, length - sync_length)
@@ -233,10 +249,14 @@ def get_next_context():
elif not use_rand_start and use_sync_length: elif not use_rand_start and use_sync_length:
end_value = min(length, start_value + sync_length) end_value = min(length, start_value + sync_length)
set_next_bank_number_from_playback_mode(playback_mode, next_bank_number)
context = dict(location=next_bank_details['location'],start=start_value,end=end_value, bank_number=next_bank_number) context = dict(location=next_bank_details['location'],start=start_value,end=end_value, bank_number=next_bank_number)
return context
def get_context_options_from_settings(): def get_context_options_from_settings():
settings = read_json('Settings.json') ######## looks up the settings data object and returns states of relevant options ########
settings = read_json(SETTINGS_JSON)
use_sync_length = False use_sync_length = False
sync_length = 0 sync_length = 0
use_rand_start = False use_rand_start = False
@@ -255,6 +275,7 @@ def get_context_options_from_settings():
return use_rand_start , use_sync_length , sync_length , playback_mode return use_rand_start , use_sync_length , sync_length , playback_mode
def set_next_bank_number_from_playback_mode(playback_mode, current_bank_number): def set_next_bank_number_from_playback_mode(playback_mode, current_bank_number):
######## sets next bank number by using playback mode logic ########
next_bank_number = 0 next_bank_number = 0
if playback_mode == 'LOOPER': if playback_mode == 'LOOPER':
next_bank_number = current_bank_number next_bank_number = current_bank_number
@@ -266,6 +287,17 @@ def set_next_bank_number_from_playback_mode(playback_mode, current_bank_number):
next_bank_number = current_bank_number next_bank_number = current_bank_number
update_json('next_bank_number.json',next_bank_number) update_json('next_bank_number.json',next_bank_number)
####<<<< generic methods for all tabs >>>>#####
def read_json(file_name):
with open(PATH_TO_DATA_OBJECTS + file_name) as data_file:
data = json.load(data_file)
return data
def update_json(file_name,data):
with open('{}{}'.format(PATH_TO_DATA_OBJECTS, file_name), 'w') as data_file:
json.dump(data, data_file)
def convert_int_to_string_for_display(time_in_seconds): def convert_int_to_string_for_display(time_in_seconds):
if time_in_seconds < 0: if time_in_seconds < 0:
return '' return ''

View File

@@ -1,15 +1,16 @@
import logging import logging
import sys import sys
import traceback import traceback
from Tkinter import *
import time import time
import os
import math import math
from asciimatics.effects import RandomNoise from asciimatics.effects import RandomNoise
from asciimatics.event import KeyboardEvent from asciimatics.event import KeyboardEvent
from asciimatics.exceptions import ResizeScreenError, NextScene from asciimatics.exceptions import ResizeScreenError, NextScene
from asciimatics.scene import Scene from asciimatics.scene import Scene
from asciimatics.screen import Screen from dual_screen import Screen
from asciimatics.widgets import Frame, Layout, Divider, Button, ListBox, Widget, MultiColumnListBox, PopUpDialog, Text, \ from asciimatics.widgets import Frame, Layout, Divider, Button, ListBox, Widget, MultiColumnListBox, PopUpDialog, Text, \
Label Label
@@ -20,13 +21,7 @@ VIDEO_DISPLAY_TEXT = 'NOW [{}] {} NEXT [{}] {}'
VIDEO_DISPLAY_BANNER_LIST = ['[','-','-','-','-','-','-','-','-','-','-',']'] VIDEO_DISPLAY_BANNER_LIST = ['[','-','-','-','-','-','-','-','-','-','-',']']
VIDEO_DISPLAY_BANNER_TEXT = '{} {} {}' VIDEO_DISPLAY_BANNER_TEXT = '{} {} {}'
logger = logging.getLogger('myapp') logger = data_centre.setup_logging()
hdlr = logging.FileHandler('C:/Users/Tim/PycharmProjects/videoLooper/myapp.log')
formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
hdlr.setFormatter(formatter)
logger.addHandler(hdlr)
logger.setLevel(logging.INFO)
class Display(Frame): class Display(Frame):
def __init__(self, switch, screen,driver, on_load=None): def __init__(self, switch, screen,driver, on_load=None):
@@ -37,30 +32,24 @@ class Display(Frame):
title="r_e_c_u_r" title="r_e_c_u_r"
) )
self._last_frame = 0 self._last_frame = 0
self.popup_message = None
self.video_driver = driver self.video_driver = driver
self.my_frame_update_count = 40 self.my_frame_update_count = 40
layout_top = Layout([1,1,1]) layout_top = Layout([1,1,1])
self.add_layout(layout_top) self.add_layout(layout_top)
self.browser_button = Button("BROWSER", self.do_nothing()) self.browser_button = Button("BROWSER", self.do_nothing())
self.browser_button.disabled = True self.browser_button.disabled = True
#self.browser_button.is_tab_stop = False
if switch[0]: if switch[0]:
self.browser_button.custom_colour = "focus_button" self.browser_button.custom_colour = "focus_button"
self.looper_button = Button("LOOPER", self.do_nothing()) self.looper_button = Button("LOOPER", self.do_nothing())
self.looper_button.disabled = True self.looper_button.disabled = True
#self.looper_button.is_tab_stop = False
if switch[1]: if switch[1]:
self.looper_button.custom_colour = "focus_button" self.looper_button.custom_colour = "focus_button"
self.settings_button = Button("SETTINGS", self.do_nothing()) self.settings_button = Button("SETTINGS", self.do_nothing())
self.settings_button.disabled = True self.settings_button.disabled = True
#self.advance_button.is_tab_stop = False
if switch[2]: if switch[2]:
self.settings_button.custom_colour = "focus_button" self.settings_button.custom_colour = "focus_button"
layout_top.add_widget(Divider(), 0) layout_top.add_widget(Divider(), 0)
@@ -83,7 +72,7 @@ class Display(Frame):
self.fix() self.fix()
def do_nothing(self): def do_nothing(self):
return pass
def get_text_for_video_display(self): def get_text_for_video_display(self):
now_bank, now_status, next_bank, next_status, duration, video_length = self.video_driver.get_info_for_video_display() now_bank, now_status, next_bank, next_status, duration, video_length = self.video_driver.get_info_for_video_display()
@@ -109,8 +98,6 @@ class Display(Frame):
return 20 return 20
def get_focus_on_list(self, list): def get_focus_on_list(self, list):
#return self._layouts[self._focus]
return list.options[list.value][0][0] return list.options[list.value][0][0]
def process_event(self, event): def process_event(self, event):
@@ -120,6 +107,7 @@ class Display(Frame):
return super(Display, self).process_event(event) return super(Display, self).process_event(event)
class Browser(Display): class Browser(Display):
def __init__(self, screen, data, driver): def __init__(self, screen, data, driver):
super(Browser, self).__init__([1,0,0],screen,driver, on_load=self._reload_list) super(Browser, self).__init__([1,0,0],screen,driver, on_load=self._reload_list)
@@ -164,13 +152,8 @@ class Browser(Display):
self._reload_list(self._browser_data_view.value) self._reload_list(self._browser_data_view.value)
if event.key_code in [ord('c')]: if event.key_code in [ord('c')]:
#self.popup_message = PopUpDialog(self._screen, 'Deleting all banks',['|'])
#self._scene.add_effect()
data_centre.clear_all_banks() data_centre.clear_all_banks()
#time.sleep(1)
#self._scene.remove_effect(popup_message)
self._data_object.rewrite_browser_list() self._data_object.rewrite_browser_list()
self._reload_list(self._browser_data_view.value) self._reload_list(self._browser_data_view.value)
@@ -181,14 +164,11 @@ class Browser(Display):
logger.info('the BROWSER frame number is {}'.format(frame_no)) logger.info('the BROWSER frame number is {}'.format(frame_no))
super(Browser, self)._update(frame_no) super(Browser, self)._update(frame_no)
def _reload_list(self, new_value=None): def _reload_list(self, new_value=None):
self._browser_data_view.options = self._data_object.get_browser_data_for_display() self._browser_data_view.options = self._data_object.get_browser_data_for_display()
self._browser_data_view.value = new_value self._browser_data_view.value = new_value
class Looper(Display): class Looper(Display):
def __init__(self, screen, data,driver): def __init__(self, screen, data,driver):
super(Looper, self).__init__([0, 1, 0],screen,driver,on_load=self._reload_list,) super(Looper, self).__init__([0, 1, 0],screen,driver,on_load=self._reload_list,)
@@ -206,25 +186,13 @@ class Looper(Display):
self.fix() self.fix()
def process_event(self, event): def process_event(self, event):
# if isinstance(event, KeyboardEvent):
# if event.key_code in [ord('q'), ord('Q'), Screen.ctrl("c")]:
# raise StopApplication("User quit")
# elif event.key_code in [ord("r"), ord("R")]:
# self._reverse = not self._reverse
# elif event.key_code == ord("<"):
# self._sort = max(0, self._sort - 1)
# elif event.key_code == ord(">"):
# self._sort = min(7, self._sort + 1)
#self._last_frame = 0
# Now pass on to lower levels for normal handling of the event.
return super(Looper, self).process_event(event) return super(Looper, self).process_event(event)
def _reload_list(self, new_value=None): def _reload_list(self, new_value=None):
self._bank_data_view.options = data_centre.get_all_looper_data_for_display() self._bank_data_view.options = data_centre.get_all_looper_data_for_display()
self._bank_data_view.value = new_value self._bank_data_view.value = new_value
class Settings(Display): class Settings(Display):
def __init__(self, screen, data,driver): def __init__(self, screen, data,driver):
super(Settings, self).__init__([0, 0, 1], screen,driver,on_load=self._reload_list) super(Settings, self).__init__([0, 0, 1], screen,driver,on_load=self._reload_list)
@@ -255,6 +223,7 @@ class Settings(Display):
return super(Settings, self).process_event(event) return super(Settings, self).process_event(event)
class ScrollingMultiColumnListBox(MultiColumnListBox): class ScrollingMultiColumnListBox(MultiColumnListBox):
def __init__(self, height, columns, options, titles): def __init__(self, height, columns, options, titles):
super(ScrollingMultiColumnListBox, self).__init__(height, columns, options, titles) super(ScrollingMultiColumnListBox, self).__init__(height, columns, options, titles)
@@ -268,13 +237,6 @@ class ScrollingMultiColumnListBox(MultiColumnListBox):
super(ScrollingMultiColumnListBox,self).process_event(event) super(ScrollingMultiColumnListBox,self).process_event(event)
def inspect_browser_focus(dir_name):
if(dir_name.endswith('|') or dir_name.endswith('/')):
return False , dir_name.lstrip()[:-1]
else:
return True , dir_name.lstrip()
def create_video_display_banner(duration,video_length): def create_video_display_banner(duration,video_length):
banner_list = ['[','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-',']'] banner_list = ['[','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-',']']
max = len(banner_list) - 1 max = len(banner_list) - 1
@@ -288,25 +250,28 @@ def create_video_display_banner(duration,video_length):
return ''.join(banner_list) return ''.join(banner_list)
def demo(screen, tk):
def demo(screen):
scenes = [Scene([Browser(screen, data,video_driver)], -1), scenes = [Scene([Browser(screen, data,video_driver)], -1),
Scene([Looper(screen, data,video_driver)], -1), Scene([Looper(screen, data,video_driver)], -1),
Scene([Settings(screen, data,video_driver)], -1)] Scene([Settings(screen, data,video_driver)], -1)]
screen.play(scenes) screen.play(scenes,tk)
data = data_centre.data() data = data_centre.data()
video_driver = video_centre.video_driver() video_driver = video_centre.video_driver()
last_scene = None last_scene = None
tk = Tk()
canvas = Canvas(tk, width=500, height=400, bd=0, highlightthickness=0)
canvas.pack()
while True: while True:
try: try:
Screen.wrapper(demo, catch_interrupt=True) Screen.wrapper(demo, catch_interrupt=True, arguments=(tk,))
sys.exit(0) sys.exit(0)
except ResizeScreenError as e: except ResizeScreenError as e:
last_scene = e.scene last_scene = e.scene
except Exception as e: except Exception as e:
logger.error(traceback.format_exc()) logger.error(traceback.format_exc())
logger.error(str(e)) logger.error(str(e))
# Logs the error appropriately.

1
display_data.json Normal file
View File

@@ -0,0 +1 @@
[{"start": -1, "length": -1, "end": -1, "location": "", "name": "extremely_minimal_video_1.mp4"}, {"start": -1, "length": -1, "end": -1, "location": "C:\\TestFolderStucture\\minimal_videos/minimal_video_1.mp4", "name": "minimal_video_1.mp4"}, {"start": -1, "length": -1, "end": -1, "location": "C:\\TestFolderStucture\\minimal_videos/minimal_video_2.mp4", "name": "minimal_video_2.mp4"}, {"start": -1, "length": -1, "end": -1, "location": "", "name": ""}, {"start": -1, "length": -1, "end": -1, "location": "", "name": ""}, {"start": -1, "length": -1, "end": -1, "location": "", "name": ""}, {"start": -1, "length": -1, "end": -1, "location": "", "name": ""}, {"start": -1, "length": -1, "end": -1, "location": "", "name": ""}, {"start": -1, "length": -1, "end": -1, "location": "", "name": ""}, {"start": -1, "length": -1, "end": -1, "location": "", "name": ""}, {"start": -1, "length": -1, "end": -1, "location": "", "name": ""}, {"start": -1, "length": -1, "end": -1, "location": "", "name": ""}, {"start": -1, "length": -1, "end": -1, "location": "", "name": ""}, {"start": -1, "length": -1, "end": -1, "location": "", "name": ""}, {"start": -1, "length": -1, "end": -1, "location": "", "name": ""}]

2121
dual_screen.py Normal file

File diff suppressed because it is too large Load Diff

1
next_bank_number.json Normal file
View File

@@ -0,0 +1 @@
0

544
omxdriver.py Normal file
View File

@@ -0,0 +1,544 @@
import signal
import os
import sys
import dbus
import subprocess
from time import time,strftime
"""
12/6/2016 - rewrite to use dbus
2/11/2016 - connection needs to wait for dbus filemane to be populated
2/11/2016 - remove busy wait for conection
24/11/2016 - move pause after load to after first get_position to ensure omxplayer has loaded track before pause
24/11/2016 - report dbus exception messages in log
30/11/2016 - pause at start waits until position is not 0 as video has not started until it becomes -ve
2/12/2016 - make pause glitch tolerant, try again if fails
3/12/2016 - remove threading to stop pause and unpause for showing happening in wrong order
5/12/2016 - deal with situation where pause at end happened so late that video finished first
5/12/2016 - need to send nice-day when stop is received and paused for end as now do not intercept one from omxplayer
omxdriver hides the detail of using the omxplayer command from videoplayer
This is meant to be used with videoplayer.py
Its easy to end up with many copies of omxplayer.bin running if this class is not used with care. use pp_videoplayer.py for a safer interface.
External commands
----------------------------
__init__ just creates the instance and initialises variables (e.g. omx=OMXDriver())
load - processes the track up to where it is ready to display, at this time it pauses.
show - plays the video from where 'prepare' left off by resuming from the pause.
play - plays a track (not used by gapless)
pause/unpause - pause on/off
toggle_pause - toggles pause
control - sends controls to omxplayer.bin while track is playing (use stop and pause instead of q and p)
stop - stops a video that is playing.
terminate - Stops a video playing. Used when aborting an application.
kill - kill of omxplayer when it hasn't terminated at the end of a track.
Signals
----------
The following signals are produced while a track is playing
self.start_play_signal = True when a track is ready to be shown
self.end_play_signal= True when a track has finished due to stop or because it has come to an end
self.end_play_reason reports the reason for the end
Also is_running() tests whether the sub-process running omxplayer is present.
"""
class OMXDriver(object):
# adjust this to determine freeze after the first frame
after_first_frame_position =-50000 # microseconds
_LAUNCH_CMD = '/usr/bin/omxplayer --no-keys ' # needs changing if user has installed his own version of omxplayer elsewhere
KEY_MAP = { '-': 17, '+': 18, '=': 18} # add more keys here, see popcornmix/omxplayer github file KeyConfig.h
def __init__(self,widget,pp_dir):
self.widget=widget
self.pp_dir=pp_dir
self.start_play_signal=False
self.end_play_signal=False
self.end_play_reason='nothing'
self.duration=0
self.video_position = 0
self.pause_at_end_required=False
self.paused_at_end=False
self.pause_at_end_time=0
# self.pause_before_play_required='before-first-frame' #no,before-first-frame, after-first-frame
# self.pause_before_play_required='no'
self.paused_at_start='False'
self.paused=False
self.terminate_reason=''
# dbus and subprocess
self._process=None
self.__iface_root=None
self.__iface_props = None
self.__iface_player = None
def load(self, track, freeze_at_start,options,caller):
self.pause_before_play_required=freeze_at_start
self.caller=caller
track= "'"+ track.replace("'","'\\''") + "'"
# self.mon.log(self,'TIME OF DAY: '+ strftime("%Y-%m-%d %H:%M"))
self.dbus_user = os.environ["USER"]
self.id=str(int(time()*10))
self.dbus_name = "org.mpris.MediaPlayer2.omxplayer"+self.id
self.omxplayer_cmd = OMXDriver._LAUNCH_CMD + options + " --dbus_name '"+ self.dbus_name + "' " + track
# self.mon.log(self, 'dbus user ' + self.dbus_user)
# self.mon.log(self, 'dbus name ' + self.dbus_name)
# print self.omxplayer_cmd
print("Send command to omxplayer: "+ self.omxplayer_cmd)
# self._process=subprocess.Popen(self.omxplayer_cmd,shell=True,stdout=file('/home/pi/pipresents/pp_logs/stdout.txt','a'),stderr=file('/home/pi/pipresents/pp_logs/stderr.txt','a'))
self._process=subprocess.Popen(self.omxplayer_cmd,shell=True,stdout=file('/dev/null','a'),stderr=file('/dev/null','a'))
self.pid=self._process.pid
# wait for omxplayer to start then start monitoring thread
self.dbus_tries = 0
self.omx_loaded = False
self._wait_for_dbus()
return
def _wait_for_dbus(self):
connect_success=self.__dbus_connect()
if connect_success is True:
# print 'SUCCESS'
print('connected to omxplayer dbus after ' + str(self.dbus_tries) + ' centisecs')
# get duration of the track in microsecs if fails return a very large duration
# posibly faile because omxplayer is running but not omxplayer.bin
duration_success,duration=self.get_duration()
if duration_success is False:
print('get duration failed for n attempts using '+ str(duration/60000000)+ ' minutes')
# calculate time to pause before last frame
self.duration = duration
self.pause_at_end_time = duration - 350000
# start the thread that is going to monitor output from omxplayer.
self._monitor_status()
else:
self.dbus_tries+=1
self.widget.after(100,self._wait_for_dbus)
def _monitor_status(self):
# print '\n',self.id, '** STARTING ',self.duration
self.start_play_signal=False
self.end_play_signal=False
self.end_play_reason='nothing'
self.paused_at_end=False
self.paused_at_start='False'
self.delay = 50
self.widget.after(0,self._status_loop)
"""
freeze at start
'no' - unpause in show - test !=0
'before_first_frame' - don't unpause in show, test !=0
'after_first_frame' - don't unpause in show, test > -100000
"""
def _status_loop(self):
if self.is_running() is False:
# process is not running because quit or natural end - seems not to happen
self.end_play_signal=True
self.end_play_reason='nice_day'
# print ' send nice day - process not running'
return
else:
success, video_position = self.get_position()
# if video_position <= 0: print 'read position',video_position
if success is False:
# print 'send nice day - exception when reading video position'
self.end_play_signal=True
self.end_play_reason='nice_day'
return
else:
self.video_position=video_position
# if timestamp is near the end then pause
if self.pause_at_end_required is True and self.video_position>self.pause_at_end_time: #microseconds
# print 'pausing at end, leeway ',self.duration - self.video_position
pause_end_success = self.pause(' at end of track')
if pause_end_success is True:
# print self.id,' pause for end success', self.video_position
self.paused_at_end=True
self.end_play_signal=True
self.end_play_reason='pause_at_end'
return
else:
print 'pause at end failed, probably because of delay after detection, just run on'
self.widget.after(self.delay,self._status_loop)
else:
# need to do the pausing for preload after first timestamp is received 0 is default value before start
# print self.pause_before_play_required,self.paused_at_start,self.video_position,OMXDriver.after_first_frame_position
if (self.pause_before_play_required == 'after-first-frame' and self.paused_at_start == 'False' and self.video_position >OMXDriver.after_first_frame_position)\
or(self.pause_before_play_required != 'after-first-frame' and self.paused_at_start == 'False' and self.video_position !=0):
pause_after_load_success=self.pause('after load')
if pause_after_load_success is True:
# print self.id,' pause after load success',self.video_position
self.start_play_signal = True
self.paused_at_start='True'
else:
# should never fail, just warn at the moment
# print 'pause after load failed '+ + str(self.video_position)
print( str(self.id)+ ' pause after load fail ' + str(self.video_position))
self.widget.after(self.delay,self._status_loop)
else:
self.widget.after(self.delay,self._status_loop)
def show(self,freeze_at_end_required,initial_volume):
self.initial_volume=initial_volume
self.pause_at_end_required=freeze_at_end_required
# unpause to start playing
if self.pause_before_play_required =='no':
unpause_show_success=self.unpause(' to start showing')
# print 'unpause for show',self.paused
if unpause_show_success is True:
pass
# print self.id,' unpause for show success', self.video_position
else:
# should never fail, just warn at the moment
print(str(self.id)+ ' unpause for show fail ' + str(self.video_position))
def control(self,char):
val = OMXDriver.KEY_MAP[char]
print('>control received and sent to omxplayer ' + str(self.pid))
if self.is_running():
try:
self.__iface_player.Action(dbus.Int32(val))
except dbus.exceptions.DBusException as ex:
print('Failed to send control - dbus exception: {}'.format(ex.get_dbus_message()))
return
else:
print('Failed to send control - process not running')
return
# USE ONLY at end and after load
# return succces of the operation, several tries if pause did not work and no error reported.
def pause(self,reason):
print(self,'pause received '+reason)
if self.paused is False:
print('not paused so send pause '+reason)
tries=1
while True:
if self.send_pause() is False:
# failed for good reason
return False
status=self.omxplayer_is_paused() # test omxplayer after sending the command
if status == 'Paused':
self.paused = True
return True
if status == 'Failed':
# failed for good reason because of exception or process not running caused by end of track
return False
else:
# failed for no good reason
print(self, '!!!!! repeat pause ' + str(tries))
# print self.id,' !!!!! repeat pause ',self.video_position, tries
tries +=1
if tries >5:
# print self.id, ' pause failed for n attempts'
print('pause failed for n attempts')
return False
# repeat
# USE ONLY for show
def unpause(self,reason):
print('MON' + 'Unpause received '+ reason)
if self.paused is True:
print('MON' +'Is paused so Track will be unpaused '+ reason)
tries=1
while True:
if self.send_unpause() is False:
return False
status = self.omxplayer_is_paused() # test omxplayer
if status == 'Playing':
self.paused = False
self.paused_at_start='done'
self.set_volume(self.initial_volume)
return True
if status == 'Failed':
# failed for good reason because of exception or process not running caused by end of track
return False
else:
print('warn' + '!!!!! repeat unpause ' + str(tries))
# print self.id,' !!!! repeat unpause ',self.video_position, tries
tries +=1
if tries >5:
# print self.id, ' unpause failed for n attempts'
print('warn' + 'unpause failed for n attempts')
return False
def omxplayer_is_paused(self):
if self.is_running():
try:
result=self.__iface_props.PlaybackStatus()
except dbus.exceptions.DBusException as ex:
print('warn'+'Failed to test paused - dbus exception: {}'.format(ex.get_dbus_message()))
return 'Failed'
return result
else:
print('warn'+'Failed to test paused - process not running')
# print self.id,' test paused not successful - process'
return 'Failed'
def send_pause(self):
if self.is_running():
try:
self.__iface_player.Pause()
except dbus.exceptions.DBusException as ex:
print('warn'+'Failed to send pause - dbus exception: {}'.format(ex.get_dbus_message()))
return False
return True
else:
print('warn'+'Failed to send pause - process not running')
# print self.id,' send pause not successful - process'
return False
def send_unpause(self):
if self.is_running():
try:
self.__iface_player.Action(16)
except dbus.exceptions.DBusException as ex:
print('warn'+'Failed to send unpause - dbus exception: {}'.format(ex.get_dbus_message()))
return False
return True
else:
print('warn'+'Failed to send unpause - process not running')
# print self.id,' send unpause not successful - process'
return False
def pause_on(self):
print('mon'+'pause on received ')
# print 'pause on',self.paused
if self.paused is True:
return
if self.is_running():
try:
# self.__iface_player.Action(16)
self.__iface_player.Pause() # - this should work but does not!!!
self.paused=True
# print 'paused OK'
return
except dbus.exceptions.DBusException as ex:
print('warn'+'Failed to do pause on - dbus exception: {}'.format(ex.get_dbus_message()))
return
else:
print('warn'+'Failed to do pause on - process not running')
return
def pause_off(self):
print('mon'+'pause off received ')
# print 'pause off',self.paused
if self.paused is False:
return
if self.is_running():
try:
self.__iface_player.Action(16)
self.paused=False
# print 'not paused OK'
return
except dbus.exceptions.DBusException as ex:
print('warn'+'Failed to do pause off - dbus exception: {}'.format(ex.get_dbus_message()))
return
else:
print('warn'+'Failed to do pause off - process not running')
return
def go(self):
print('log'+'go received ')
self.unpause('for go')
def mute(self):
self.__iface_player.Mute()
def unmute(self):
self.__iface_player.Unmute()
def set_volume(self,millibels):
volume = pow(10, millibels / 2000.0);
self.__iface_props.Volume(volume)
def toggle_pause(self,reason):
print('log'+'toggle pause received '+ reason)
if not self.paused:
self.paused = True
else:
self.paused=False
if self.is_running():
try:
self.__iface_player.Action(16)
except dbus.exceptions.DBusException as ex:
print('warn'+'Failed to toggle pause - dbus exception: {}'.format(ex.get_dbus_message()))
return
else:
print('warn'+'Failed to toggle pause - process not running')
return
def stop(self):
print('log'+'>stop received and quit sent to omxplayer ' + str(self.pid))
# need to send 'nice day'
if self.paused_at_end is True:
self.end_play_signal=True
self.end_play_reason='nice_day'
# print 'send nice day for close track'
if self.is_running():
try:
self.__iface_root.Quit()
except dbus.exceptions.DBusException as ex:
print('warn'+'Failed to quit - dbus exception: {}'.format(ex.get_dbus_message()))
return
else:
print('warn'+'Failed to quit - process not running')
return
# kill the subprocess (omxplayer and omxplayer.bin). Used for tidy up on exit.
def terminate(self,reason):
self.terminate_reason=reason
self.stop()
def get_terminate_reason(self):
return self.terminate_reason
# test of whether _process is running
def is_running(self):
retcode=self._process.poll()
# print 'is alive', retcode
if retcode is None:
return True
else:
return False
# kill off omxplayer when it hasn't terminated at the end of a track.
# send SIGINT (CTRL C) so it has a chance to tidy up daemons and omxplayer.bin
def kill(self):
if self.is_running()is True:
self._process.send_signal(signal.SIGINT)
def get_position(self):
# don't test process as is done just before
try:
micros = self.__iface_props.Position()
return True,micros
except dbus.exceptions.DBusException as ex:
# print 'Failed get_position - dbus exception: {}'.format(ex.get_dbus_message())
return False,-1
def get_duration(self):
tries=1
while True:
success,duration=self._try_duration()
if success is True:
return True,duration
else:
print('warn'+ 'repeat get duration ' + str(tries))
tries +=1
if tries >5:
return False,sys.maxint*100
def _try_duration(self):
"""Return the total length of the playing media"""
if self.is_running() is True:
try:
micros = self.__iface_props.Duration()
return True,micros
except dbus.exceptions.DBusException as ex:
print('warn'+'Failed get duration - dbus exception: {}'.format(ex.get_dbus_message()))
return False,-1
else:
return False,-1
# *********************
# connect to dbus
# *********************
def __dbus_connect(self):
if self.omx_loaded is False:
# read the omxplayer dbus data from files generated by omxplayer
bus_address_filename = "/tmp/omxplayerdbus.{}".format(self.dbus_user)
bus_pid_filename = "/tmp/omxplayerdbus.{}.pid".format(self.dbus_user)
if not os.path.exists(bus_address_filename):
print('log'+'waiting for bus address file ' + bus_address_filename)
self.omx_loaded=False
return False
else:
f = open(bus_address_filename, "r")
bus_address = f.read().rstrip()
if bus_address == '':
print('log'+ 'waiting for bus address in file ' + bus_address_filename)
self.omx_loaded=False
return False
else:
# self.mon.log(self, 'bus address found ' + bus_address)
if not os.path.exists(bus_pid_filename):
print('warn'+ 'bus pid file does not exist ' + bus_pid_filename)
self.omx_loaded=False
return False
else:
f= open(bus_pid_filename, "r")
bus_pid = f.read().rstrip()
if bus_pid == '':
self.omx_loaded=False
return False
else:
# self.mon.log(self, 'bus pid found ' + bus_pid)
os.environ["DBUS_SESSION_BUS_ADDRESS"] = bus_address
os.environ["DBUS_SESSION_BUS_PID"] = bus_pid
self.omx_loaded = True
if self.omx_loaded is True:
session_bus = dbus.SessionBus()
try:
omx_object = session_bus.get_object(self.dbus_name, "/org/mpris/MediaPlayer2", introspect=False)
self.__iface_root = dbus.Interface(omx_object, "org.mpris.MediaPlayer2")
self.__iface_props = dbus.Interface(omx_object, "org.freedesktop.DBus.Properties")
self.__iface_player = dbus.Interface(omx_object, "org.mpris.MediaPlayer2.Player")
except dbus.exceptions.DBusException as ex:
# self.mon.log(self,"Waiting for dbus connection to omxplayer: {}".format(ex.get_dbus_message()))
return False
return True

1
settings.json Normal file
View File

@@ -0,0 +1 @@
[{"name": "PLAYBACK_MODE", "value": "RANDOM"}, {"name": "PLAYLIST", "value": "[1,1,1,4,1,2,1,4]"}, {"name": "SYNC_LENGTHS", "value": "OFF"}, {"name": "SYNC_LENGTHS_TO", "value": "00:08"}, {"name": "RAND_START", "value": "OFF"}, {"name": "VIDEO_OUTPUT", "value": "HDMI"}]

View File

@@ -1,72 +1,81 @@
import time import time
#from omxdriver import OMXDriver <== for deving only
from Tkinter import Tk, Canvas
import data_centre import data_centre
class video_driver(object): class video_driver(object):
def __init__(self): def __init__(self, widget = None):
self.last_player = video_player() self.widget = widget
self.current_player = video_player() self.delay = 50
self.next_player = video_player() self.last_player = video_player(self.widget,'a')
self.current_player = video_player(self.widget,'b')
self.next_player = video_player(self.widget,'c')
self.manual_next = False self.manual_next = False
#self.video_driver.begin_playing() self.begin_playing()
def begin_playing(self): def begin_playing(self):
#TODO:von startup set up the intital data_structures with a preloaded intial clip in bank 0 #TODO: the first clip will be a demo
first_context = data_centre.get_next_context() first_context = data_centre.get_next_context()
#first_context = '/home/pi/pp_home/media/samplerloop3s.mp4'
self.current_player.load_content(first_context) self.current_player.load_content(first_context)
self.wait_for_first_load() self.wait_for_first_load()
def wait_for_first_load(self): def wait_for_first_load(self):
if self.current_player.is_loaded: if self.current_player.is_loaded():
self.play_video() self.play_video()
else: else:
#need to wait here then #load player states
time.sleep(1) self.widget.after(self.delay, self.wait_for_first_load)
self.wait_for_first_load()
def switch_players(self): def switch_players(self):
self.temp_player = self.last_player
self.last_player = self.current_player self.last_player = self.current_player
print('switch: last_player is {}'.format(self.last_player.name))
self.current_player = self.next_player self.current_player = self.next_player
self.next_player.set_to_default() print('switch: current_player is {}'.format(self.current_player.name))
self.next_player = self.temp_player
def play_video(self): print('switch: next_player is {}'.format(self.next_player.name))
self.current_player.play_content()
self.last_player.exit() self.last_player.exit()
def play_video(self):
print('{} is about to play'.format(self.current_player.name))
self.current_player.play_content()
#self.last_player.exit()
next_context = data_centre.get_next_context() next_context = data_centre.get_next_context()
#next_context = '/home/pi/pp_home/media/samplerloop3s.mp4'
self.next_player.load_content(next_context) self.next_player.load_content(next_context)
self.wait_for_next_cycle() self.wait_for_next_cycle()
def wait_for_next_cycle(self): def wait_for_next_cycle(self):
if(self.current_player.is_finished or self.manual_next):
if(self.current_player.is_finished() or self.manual_next):
print('{} is finished'.format(self.current_player.name))
self.manual_next = False self.manual_next = False
if self.next_player.is_loaded: if self.next_player.is_loaded():
print('{} is loaded on switchover'.format(self.next_player.name))
self.switch_players() self.switch_players()
self.play_video() self.play_video()
else: else:
print('{} is not loaded yet!'.format(self.next_player.name))
self.current_player.pause_content() self.current_player.pause_content()
self.wait_for_next_load() self.wait_for_next_load()
else: else:
#need to delay here and then self.widget.after(self.delay, self.wait_for_next_cycle)
time.sleep(1)
self.wait_for_next_cycle()
def wait_for_next_load(self): def wait_for_next_load(self):
if(self.next_player.is_loaded): if(self.next_player.is_loaded()):
self.switch_players() self.switch_players()
self.play_video() self.play_video()
else: else:
#need to delay here, and then self.widget.after(self.delay, self.wait_for_next_load)
time.sleep(1)
self.widget.after(self.delay, self._status_loop)
self.wait_for_next_load()
def get_info_for_video_display(self): def get_info_for_video_display(self):
return self.current_player.bank_number, self.current_player.status, self.next_player.bank_number,\ return self.current_player.bank_number, self.current_player.status, self.next_player.bank_number,\
@@ -74,55 +83,56 @@ class video_driver(object):
class video_player(object): class video_player(object):
def __init__(self): def __init__(self, widget, name):
self.is_loaded = False self.widget = widget
self.is_finished = False self.name = name
self.status = 'UNASSIGNED' self.status = 'UNASSIGNED'
self.bank_number = '-' self.bank_number = '-'
self.duration = 0 self.duration = 0
self.video_length = 10 self.video_length = 10
self.omx = OMXDriver(self.widget,'')
def is_loaded(self):
return self.omx.start_play_signal
def is_finished(self):
return self.omx.end_play_signal
def get_fake_duration(self): def get_fake_duration(self):
self.duration = self.duration + 1 self.duration = self.duration + 1
return self.duration return self.duration
def play_content(self): def play_content(self):
self.status = 'PLAYING' self.status = 'PLAYING'
time.sleep(1) print('{} is playing now'.format(self.name))
self.duration = 1 self.omx.pause_before_play_required = 'no'
time.sleep(1) self.omx.show(True,0)
self.duration = 2
time.sleep(1)
self.duration = 3
time.sleep(1)
self.duration = 4
time.sleep(1)
self.duration = 5
time.sleep(1)
self.duration = 6
time.sleep(1)
self.duration = 7
time.sleep(1)
self.duration = 8
time.sleep(1)
self.duration = 9
self.is_finished = True
pass
def load_content(self, context): def load_content(self, context):
self.status = 'LOADING' self.status = 'LOADING'
time.sleep(3) print('{} is loading now {}'.format(self.name,context))
self.status = 'LOADED' self.omx.load(context,'after-first-frame','','')
self.is_loaded = True
#do the loading...
pass
def set_to_default(self): def set_to_default(self):
self.is_finished = False self.omx.kill()
self.is_loaded = False self.omx = OMXDriver(self.widget,'')
def exit(self): def exit(self):
pass if(self.omx.omx_loaded is not None):
print('{} is exiting omx'.format(self.name))
self.omx.stop()
self.omx = OMXDriver(self.widget,'')
def pause_content(self): def pause_content(self):
self.status = 'PAUSED' self.status = 'PAUSED'
# tk = Tk()
#
# canvas = Canvas(tk,width=500,height=400, bd=0, highlightthickness=0)
# canvas.pack()
#
# driver = video_driver(canvas)
#
# while True:
# tk.update()