commit 9bdb02a3e96951254ecfbb44e695486eba02d32f Author: Tim Caldwell Date: Thu Aug 17 23:41:36 2017 +1200 inital commit (rough sketch of idea at this point) diff --git a/data_centre.py b/data_centre.py new file mode 100644 index 0000000..d0c207d --- /dev/null +++ b/data_centre.py @@ -0,0 +1,275 @@ +import json + +import logging +from collections import OrderedDict + +import os +from random import randint + +#TODO : standise the paths to things, use constants for file names, standise the naming convention of files +import time + +PATH_TO_BROWSER = 'C:/TestFolderStucture' + +PATH_TO_DATA_OBJECTS = 'C:/Users/Tim/PycharmProjects/videoLooper/' + +EMPTY_BANK = dict(name='',location='',length=-1,start=-1,end=-1) + +logger = logging.getLogger('myapp') +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 data(object): + def __init__(self): + + self._open_folders = [] + self._browser_list = [] + + def rewrite_browser_list(self): + self._browser_list = generate_browser_list(PATH_TO_BROWSER, 0, self._open_folders) + + def get_browser_data_for_display(self): + if not self._browser_list: + self.rewrite_browser_list() + + browser_list_for_display = [] + for index , dir in enumerate(self._browser_list): + browser_list_for_display.append(([dir['name'],dir['bank']],index)) + #logger.info(browser_list_for_display) + return browser_list_for_display + + def update_open_folders(self, folder_name): + if folder_name not in self._open_folders: + self._open_folders.append(folder_name) + else: + self._open_folders.remove(folder_name) + + +def get_all_looper_data_for_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 + results = [] + + add_folder_to_browser_list(current_path, current_level,open_folder_list) + + memory_bank = read_json('DisplayData.json') + + for browser_line in results: + is_file, file_name = extract_file_type_and_name_from_browser_format(browser_line['name']) + if is_file: + is_banked, bank_number = is_file_in_memory_bank(file_name, memory_bank) + if is_banked: + browser_line['bank'] = str(bank_number) + + return results + +def add_folder_to_browser_list(current_path, current_level,open_folder_list): + root, dirs, files = next(os.walk(current_path)) + + indent = ' ' * 4 * (current_level) + for folder in dirs: + is_open, char = check_folder_state(folder,open_folder_list) + #print('{}{}{}'.format(indent, folder, char)) + results.append(dict(name='{}{}{}'.format(indent, folder, char), bank='x')) + if (is_open): + next_path = '{}/{}'.format(root, folder) + next_level = current_level + 1 + add_folder_to_browser_list(next_path, next_level,open_folder_list) + + for f in files: + if (os.path.splitext(f)[1] in ['.mp4']): + #print('{}{}'.format(indent, f)) + results.append(dict(name='{}{}'.format(indent, f), bank='-')) + +def check_folder_state(folder_name,open_folder_list): + if (folder_name in open_folder_list): + return True, '/' + else: + return False, '|' + +def extract_file_type_and_name_from_browser_format(dir_name): + #logger.info('the name we got was {}'.format(dir_name)) + if(dir_name.endswith('|') or dir_name.endswith('/')): + return False , dir_name.lstrip()[:-1] + else: + return True , dir_name.lstrip() + +def get_length_for_file(location): + #TODO: will have omx.player get length of file probs.. + pass + +def create_new_bank_mapping_in_first_open(file_name): + memory_bank = read_json('DisplayData.json') + for index , bank in enumerate(memory_bank): + if(not bank['name']): + create_new_bank_mapping(index,file_name,memory_bank) + return True + return False + +def create_new_bank_mapping(bank_number,file_name,memory_bank=[]): + has_location , location = get_path_for_file(file_name) + length = get_length_for_file(location) + new_bank = dict(name=file_name, location=location, length=-1, start=-1, end=-1) + update_a_banks_data(bank_number, new_bank, memory_bank) + +def get_path_for_file(file_name): + for root, dirs, files in os.walk(PATH_TO_BROWSER): + if file_name in files: + print root + return True, '{}/{}'.format(root,file_name) + else: + return False, '' + +def update_a_banks_data(bank_number, bank_info, memory_bank=[]): + if not memory_bank: + memory_bank = read_json('DisplayData.json') + memory_bank[bank_number] = bank_info + update_json('DisplayData.json', memory_bank) + +def clear_all_banks(): + memory_bank = read_json('DisplayData.json') + for index , bank in enumerate(memory_bank): + memory_bank[index] = EMPTY_BANK + update_json('DisplayData.json', memory_bank) + +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 get_all_settings_data_for_display(): + settings = read_json('Settings.json') + display_settings = [] + for index, setting in enumerate(settings): + display_settings.append(([setting['name'],setting['value']],index)) + 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): + settings = read_json('Settings.json') + + for index, setting in enumerate(settings): + if setting['name'] == setting_name: + setting = cycle_setting_value(setting) + + update_json('Settings.json',settings) + +def cycle_setting_value(setting): + if setting['name'] == 'PLAYBACK_MODE': + if setting['value'] == 'LOOPER': + setting['value'] = 'PLAYLIST' + elif setting['value'] == 'PLAYLIST': + setting['value'] = 'RANDOM' + else: + setting['value'] = 'LOOPER' + elif setting['name'] == 'SYNC_LENGTHS': + if setting['value'] == 'ON': + setting['value'] = 'OFF' + else: + setting['value'] = 'ON' + elif setting['name'] == 'RAND_START': + if setting['value'] == 'ON': + setting['value'] = 'OFF' + else: + setting['value'] = 'ON' + elif setting['name'] == 'VIDEO_OUTPUT': + if setting['value'] == 'HDMI': + setting['value'] = 'COMPOSITE' + else: + setting['value'] = 'HDMI' + + return setting + + +def get_next_context(): + next_bank_number = read_json('next_bank_number.json') + memory_bank = read_json('DisplayData.json') + next_bank_details = memory_bank[next_bank_number] + start_value = next_bank_details['start'] + end_value = next_bank_details['end'] + length = next_bank_details['length'] + + 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: + start_value = randint(0, length - sync_length) + end_value = start_value + sync_length + elif use_rand_start and not use_sync_length: + start_value = randint(0, end_value) + elif not use_rand_start and use_sync_length: + end_value = min(length, start_value + sync_length) + + context = dict(location=next_bank_details['location'],start=start_value,end=end_value, bank_number=next_bank_number) + +def get_context_options_from_settings(): + settings = read_json('Settings.json') + use_sync_length = False + sync_length = 0 + use_rand_start = False + playback_mode = '' + + for index, setting in enumerate(settings): + if setting['name'] == 'SYNC_LENGTHS' and setting['value'] == 'ON': + use_sync_length = True + elif setting['name'] == 'SYNC_LENGTHS_TO': + sync_length = setting['value'] + elif setting['name'] == 'RAND_START' and setting['value'] == 'ON': + use_rand_start = True + elif setting['name'] == 'PLAYBACK_MODE': + playback_mode = setting['value'] + + return use_rand_start , use_sync_length , sync_length , playback_mode + +def set_next_bank_number_from_playback_mode(playback_mode, current_bank_number): + next_bank_number = 0 + if playback_mode == 'LOOPER': + next_bank_number = current_bank_number + elif playback_mode == 'RANDOM': + #TODO: actually find which banks have value and only use those + next_bank_number = randint(0,14) + elif playback_mode == 'PLAYLIST': + #TODO: implement some playlist objects and logic at some point + next_bank_number = current_bank_number + update_json('next_bank_number.json',next_bank_number) + +def convert_int_to_string_for_display(time_in_seconds): + if time_in_seconds < 0: + return '' + elif time_in_seconds >= 6000: + return '99:99' + else: + return time.strftime("%M:%S", time.gmtime(time_in_seconds)) diff --git a/display_centre.py b/display_centre.py new file mode 100644 index 0000000..7c28d8e --- /dev/null +++ b/display_centre.py @@ -0,0 +1,312 @@ +import logging +import sys +import traceback + +import time + +import math +from asciimatics.effects import RandomNoise +from asciimatics.event import KeyboardEvent +from asciimatics.exceptions import ResizeScreenError, NextScene +from asciimatics.scene import Scene +from asciimatics.screen import Screen +from asciimatics.widgets import Frame, Layout, Divider, Button, ListBox, Widget, MultiColumnListBox, PopUpDialog, Text, \ + Label + +import data_centre +import video_centre + +VIDEO_DISPLAY_TEXT = 'NOW [{}] {} NEXT [{}] {}' +VIDEO_DISPLAY_BANNER_LIST = ['[','-','-','-','-','-','-','-','-','-','-',']'] +VIDEO_DISPLAY_BANNER_TEXT = '{} {} {}' + +logger = logging.getLogger('myapp') +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): + def __init__(self, switch, screen,driver, on_load=None): + super(Display, self).__init__(screen, + screen.height, + screen.width, + on_load=on_load, + title="r_e_c_u_r" + ) + self._last_frame = 0 + self.popup_message = None + self.video_driver = driver + self.my_frame_update_count = 40 + + + + layout_top = Layout([1,1,1]) + self.add_layout(layout_top) + + self.browser_button = Button("BROWSER", self.do_nothing()) + self.browser_button.disabled = True + #self.browser_button.is_tab_stop = False + if switch[0]: + self.browser_button.custom_colour = "focus_button" + + self.looper_button = Button("LOOPER", self.do_nothing()) + self.looper_button.disabled = True + #self.looper_button.is_tab_stop = False + if switch[1]: + self.looper_button.custom_colour = "focus_button" + + self.settings_button = Button("SETTINGS", self.do_nothing()) + self.settings_button.disabled = True + #self.advance_button.is_tab_stop = False + if switch[2]: + self.settings_button.custom_colour = "focus_button" + layout_top.add_widget(Divider(), 0) + layout_top.add_widget(Divider(), 1) + layout_top.add_widget(Divider(), 2) + layout_top.add_widget(self.browser_button, 0) + layout_top.add_widget(self.looper_button, 1) + layout_top.add_widget(self.settings_button, 2) + + layout_body = Layout([100], fill_frame=False) + self.add_layout(layout_body) + + layout_body.add_widget(Divider()) + video_display_text, video_banner_text = self.get_text_for_video_display() + self.player_info_label = Label(video_display_text) + self.player_info_banner = Label(video_banner_text) + layout_body.add_widget(self.player_info_banner) + layout_body.add_widget(self.player_info_label) + layout_body.add_widget(Divider()) + self.fix() + + def do_nothing(self): + return + + 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() + banner = create_video_display_banner(duration,video_length) + time_been = data_centre.convert_int_to_string_for_display(duration) + time_left = data_centre.convert_int_to_string_for_display(video_length - duration) + logger.info(VIDEO_DISPLAY_BANNER_TEXT.format(time_been,banner,time_left)) + return VIDEO_DISPLAY_BANNER_TEXT.format(time_been,banner,time_left),VIDEO_DISPLAY_TEXT.format(now_bank , now_status, next_bank, next_status, duration) + + def _update(self, frame_no): + if frame_no - self._last_frame >= self.frame_update_count or self._last_frame == 0: + self._last_frame = frame_no + + video_display_text, video_banner_text = self.get_text_for_video_display() + self.player_info_label._text = video_display_text + self.player_info_banner._text = video_banner_text + + super(Display, self)._update(frame_no) + + @property + def frame_update_count(self): + # Refresh once every 1 seconds by default. + return 20 + + def get_focus_on_list(self, list): + + #return self._layouts[self._focus] + return list.options[list.value][0][0] + + def process_event(self, event): + if isinstance(event, KeyboardEvent): + if event.key_code in [ord('n'), ord('N')]: + raise NextScene + + return super(Display, self).process_event(event) + +class Browser(Display): + def __init__(self, screen, data, driver): + super(Browser, self).__init__([1,0,0],screen,driver, on_load=self._reload_list) + + self._data_object = data + layout = Layout([100], fill_frame=True) + self.add_layout(layout) + self._browser_data_view = ScrollingMultiColumnListBox( + Widget.FILL_FRAME, + [50,10], + self._data_object.get_browser_data_for_display(), + titles=['path','bank'] + ) + layout.add_widget(self._browser_data_view) + + self.fix() + + def process_event(self, event): + numberMapping = [ord('q'),ord('w'),ord('e'),ord('r'),ord('t'),ord('y'),ord('u'),ord('i'),ord('o'),ord('p') ] + + if isinstance(event, KeyboardEvent): + if event.key_code in numberMapping: + + focus = self.get_focus_on_list(self._browser_data_view) + is_file, name = data_centre.extract_file_type_and_name_from_browser_format(focus) + if(is_file): + bank_number = numberMapping.index(event.key_code) + data_centre.create_new_bank_mapping(bank_number,name) + self._data_object.rewrite_browser_list() + self._reload_list(self._browser_data_view.value) + + if event.key_code in [ord('m')]: + focus = self.get_focus_on_list(self._browser_data_view) + is_file , name = data_centre.extract_file_type_and_name_from_browser_format(focus) + if(is_file): + data_centre.create_new_bank_mapping_in_first_open(name) + self._data_object.rewrite_browser_list() + self._reload_list(self._browser_data_view.value) + else: + self._data_object.update_open_folders(name) + self._data_object.rewrite_browser_list() + self._reload_list(self._browser_data_view.value) + + 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() + #time.sleep(1) + + #self._scene.remove_effect(popup_message) + self._data_object.rewrite_browser_list() + self._reload_list(self._browser_data_view.value) + + return super(Browser, self).process_event(event) + # Now pass on to lower levels for normal handling of the event. + + def _update(self, frame_no): + logger.info('the BROWSER frame number is {}'.format(frame_no)) + super(Browser, self)._update(frame_no) + + + + + def _reload_list(self, new_value=None): + + self._browser_data_view.options = self._data_object.get_browser_data_for_display() + self._browser_data_view.value = new_value + +class Looper(Display): + def __init__(self, screen, data,driver): + super(Looper, self).__init__([0, 1, 0],screen,driver,on_load=self._reload_list,) + + self._data_object = data + layout = Layout([100], fill_frame=True) + self.add_layout(layout) + self._bank_data_view = MultiColumnListBox( + Widget.FILL_FRAME, + [10,35,10,10,10], + data_centre.get_all_looper_data_for_display(), + titles=['bank','name','length','start','end']) + layout.add_widget(self._bank_data_view) + + self.fix() + + 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) + + def _reload_list(self, new_value=None): + self._bank_data_view.options = data_centre.get_all_looper_data_for_display() + self._bank_data_view.value = new_value + +class Settings(Display): + def __init__(self, screen, data,driver): + super(Settings, self).__init__([0, 0, 1], screen,driver,on_load=self._reload_list) + + self._data_object = data + layout = Layout([100], fill_frame=True) + self.add_layout(layout) + self._settings_data_view = MultiColumnListBox( + Widget.FILL_FRAME, + [30, 30], + data_centre.get_all_settings_data_for_display(), + titles=['setting', 'value']) + layout.add_widget(self._settings_data_view) + + self.fix() + + def _reload_list(self,new_value=None): + self._settings_data_view.options = data_centre.get_all_settings_data_for_display() + self._settings_data_view.value = new_value + + def process_event(self, event): + + if isinstance(event, KeyboardEvent): + if event.key_code in [ord('m')]: + focus = self.get_focus_on_list(self._settings_data_view) + data_centre.switch_settings(focus) + self._reload_list(self._settings_data_view.value) + + return super(Settings, self).process_event(event) + +class ScrollingMultiColumnListBox(MultiColumnListBox): + def __init__(self, height, columns, options, titles): + super(ScrollingMultiColumnListBox, self).__init__(height, columns, options, titles) + + def process_event(self, event): + if isinstance(event, KeyboardEvent): + if len(self._options) > 0 and event.key_code == Screen.KEY_UP and self._line == 0: + self._line = len(self._options) + elif len(self._options) > 0 and event.key_code == Screen.KEY_DOWN and self._line == len(self._options) - 1: + self._line = -1 + + 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): + banner_list = ['[','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-',']'] + max = len(banner_list) - 1 + if duration <= 0: + banner_list[0] = '<' + elif duration >= video_length: + banner_list[max] = '>' + else: + marker = int(math.floor(float(duration)/float(video_length)*(max-1))+1) + banner_list[marker] = '*' + + return ''.join(banner_list) + + + +def demo(screen): + scenes = [Scene([Browser(screen, data,video_driver)], -1), + Scene([Looper(screen, data,video_driver)], -1), + Scene([Settings(screen, data,video_driver)], -1)] + screen.play(scenes) + +data = data_centre.data() +video_driver = video_centre.video_driver() +last_scene = None +while True: + try: + Screen.wrapper(demo, catch_interrupt=True) + sys.exit(0) + except ResizeScreenError as e: + last_scene = e.scene + except Exception as e: + logger.error(traceback.format_exc()) + logger.error(str(e)) + # Logs the error appropriately. + diff --git a/video_centre.py b/video_centre.py new file mode 100644 index 0000000..2e0ecc3 --- /dev/null +++ b/video_centre.py @@ -0,0 +1,128 @@ +import time + +import data_centre + + +class video_driver(object): + def __init__(self): + + self.last_player = video_player() + self.current_player = video_player() + self.next_player = video_player() + + self.manual_next = False + + #self.video_driver.begin_playing() + + def begin_playing(self): + #TODO:von startup set up the intital data_structures with a preloaded intial clip in bank 0 + first_context = data_centre.get_next_context() + self.current_player.load_content(first_context) + + self.wait_for_first_load() + + def wait_for_first_load(self): + if self.current_player.is_loaded: + self.play_video() + else: + #need to wait here then + time.sleep(1) + self.wait_for_first_load() + + def switch_players(self): + self.last_player = self.current_player + self.current_player = self.next_player + self.next_player.set_to_default() + + def play_video(self): + self.current_player.play_content() + self.last_player.exit() + + next_context = data_centre.get_next_context() + self.next_player.load_content(next_context) + + self.wait_for_next_cycle() + + + def wait_for_next_cycle(self): + if(self.current_player.is_finished or self.manual_next): + self.manual_next = False + if self.next_player.is_loaded: + self.switch_players() + self.play_video() + else: + self.current_player.pause_content() + self.wait_for_next_load() + else: + #need to delay here and then + time.sleep(1) + self.wait_for_next_cycle() + + def wait_for_next_load(self): + if(self.next_player.is_loaded): + self.switch_players() + self.play_video() + else: + #need to delay here, and then + time.sleep(1) + self.widget.after(self.delay, self._status_loop) + self.wait_for_next_load() + + def get_info_for_video_display(self): + return self.current_player.bank_number, self.current_player.status, self.next_player.bank_number,\ + self.next_player.status, self.current_player.duration, self.current_player.video_length + + +class video_player(object): + def __init__(self): + self.is_loaded = False + self.is_finished = False + self.status = 'UNASSIGNED' + self.bank_number = '-' + self.duration = 0 + self.video_length = 10 + + def get_fake_duration(self): + self.duration = self.duration + 1 + return self.duration + + def play_content(self): + self.status = 'PLAYING' + time.sleep(1) + self.duration = 1 + time.sleep(1) + 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): + self.status = 'LOADING' + time.sleep(3) + self.status = 'LOADED' + self.is_loaded = True + #do the loading... + pass + + def set_to_default(self): + self.is_finished = False + self.is_loaded = False + + def exit(self): + pass + + def pause_content(self): + self.status = 'PAUSED'