def test_get_marker_position_harder(self): tb = TimeBar(100, 100) assert tb.get_marker_postion(0) == 0 assert tb.get_marker_postion(1) == 1 assert tb.get_marker_postion(10) == 10 assert tb.get_marker_postion(20) == 20 assert tb.get_marker_postion(30) == 29 assert tb.get_marker_postion(40) == 39 assert tb.get_marker_postion(50) == 49 assert tb.get_marker_postion(99) == 97 assert tb.get_marker_postion(100) == 98
def test_get_marker_position_easy(self): tb = TimeBar(100, 102) assert tb.get_marker_postion(0) == 0 assert tb.get_marker_postion(1) == 1 assert tb.get_marker_postion(10) == 10 assert tb.get_marker_postion(20) == 20 assert tb.get_marker_postion(30) == 30 assert tb.get_marker_postion(40) == 40 assert tb.get_marker_postion(50) == 50 assert tb.get_marker_postion(99) == 99 assert tb.get_marker_postion(100) == 100
def __init__(self, movie: Movie): """ Player class plays a movie. It also stores the current position. It exposes the all frame numbers in real values. Therefore not encoded. Args: movie (ascii_movie.Movie): Movie Object that the player will play. """ self._movie = movie self._cursor = 0 # virtual cursor pointing to the current frame self._frame_count = 0 self._stopped = False self._clear_screen_setup_done = False for f in self._movie.frames: self._frame_count += f.display_time self.timebar = TimeBar(self._frame_count, self._movie.screen_width)
def test_get_marker_position_small(self): tb = TimeBar(100, 10) assert tb.get_marker_postion(0) == 0 assert tb.get_marker_postion(1) == 0 assert tb.get_marker_postion(10) == 1 assert tb.get_marker_postion(20) == 2 assert tb.get_marker_postion(30) == 2 assert tb.get_marker_postion(40) == 3 assert tb.get_marker_postion(50) == 4 assert tb.get_marker_postion(99) == 8 assert tb.get_marker_postion(100) == 8
def __init__(self, movie): """ Player class plays a movie. It also stores the current position. It exposes the all frame numbers in real values. Therefore not encoded. Args: movie (ascii_movie.Movie): Movie Object that the player will play. """ self._movie = movie self._cursor = 0 # virtual cursor pointing to the current frame self._frame_count = 0 self._stopped = False self._clear_screen_setup_done = False for f in self._movie.frames: self._frame_count += f.display_time self.timebar = TimeBar(self._frame_count, self._movie.screen_width)
def test_custom_spacer(self): tb = TimeBar(0, 15, spacer=".") assert tb._empty_timebar == "<.............>"
def test_fancy_decorators(self): tb = TimeBar(0, 15, left_decorator=u"*~~*~~", right_decorator=u"Œ") assert tb._empty_timebar == u"*~~*~~ Œ" assert len(tb._empty_timebar) == 15
def test_timebar_with_marker_short(self): tb = TimeBar(100, 8) assert len(tb.get_timebar(0)) == 8 assert tb.get_timebar(0).index("o") == 1 assert tb.get_timebar(0) == "<o >" assert len(tb.get_timebar(10)) == 8 assert tb.get_timebar(10).index("o") == 2 assert tb.get_timebar(10) == "< o >" assert len(tb.get_timebar(50)) == 8 assert tb.get_timebar(50).index("o") == 4 assert tb.get_timebar(50) == "< o >" assert len(tb.get_timebar(99)) == 8 assert tb.get_timebar(99).index("o") == 6 assert tb.get_timebar(99) == "< o>" assert len(tb.get_timebar(100)) == 8 assert tb.get_timebar(100).index("o") == 6 assert tb.get_timebar(100) == "< o>"
def test_empty_timebar(self): tb = TimeBar(0, 10) assert tb._empty_timebar == "< >"
def test_timebar_with_marker(self): tb = TimeBar(100, 102) assert len(tb.get_timebar(0)) == 102 assert tb.get_timebar(0).index("o") == 1 assert tb.get_timebar(0) == "<o >" assert len(tb.get_timebar(10)) == 102 assert tb.get_timebar(10).index("o") == 11 assert tb.get_timebar(10) == "< o >" assert len(tb.get_timebar(99)) == 102 assert tb.get_timebar(99).index("o") == 100 assert len(tb.get_timebar(100)) == 102 assert tb.get_timebar(100).index("o") == 100
def test_frame_num_larger_than_timebar_frame_count(self): tb = TimeBar(100, 102) bad_frame_timebar = tb.get_timebar(104) assert len(bad_frame_timebar) == 102 assert bad_frame_timebar == "< o>"
def test_long_timebar(self): tb = TimeBar(0, 100) assert tb._empty_timebar == "< " \ " " \ " >"
def test_timebar_with_marker(self): tb = TimeBar(100, 102) assert len(tb.get_timebar(0)) == 102 assert tb.get_timebar(0).index("o") == 1 assert tb.get_timebar( 0 ) == "<o >" assert len(tb.get_timebar(10)) == 102 assert tb.get_timebar(10).index("o") == 11 assert tb.get_timebar( 10 ) == "< o >" assert len(tb.get_timebar(99)) == 102 assert tb.get_timebar(99).index("o") == 100 assert len(tb.get_timebar(100)) == 102 assert tb.get_timebar(100).index("o") == 100
def test_too_short_timebar(self): with pytest.raises(ValueError) as excinfo: TimeBar(0, 1) assert str( excinfo.values ) == "This TimeBar is too short for these decorators: < o >"
class VT100Player(object): """ Some escape codes used within VT100 Streams @see: http://ascii-table.com/ansi-escape-sequences-vt-100.php """ ESC = chr(27) # VT100 escape character constant JMPHOME = ESC + "[H" # Move cursor to upper left corner CLEARSCRN = ESC + "[2J" # Clear entire screen CLEARDOWN = ESC + "[J" # Clear screen from cursor down def __init__(self, movie: Movie): """ Player class plays a movie. It also stores the current position. It exposes the all frame numbers in real values. Therefore not encoded. Args: movie (ascii_movie.Movie): Movie Object that the player will play. """ self._movie = movie self._cursor = 0 # virtual cursor pointing to the current frame self._frame_count = 0 self._stopped = False self._clear_screen_setup_done = False for f in self._movie.frames: self._frame_count += f.display_time self.timebar = TimeBar(self._frame_count, self._movie.screen_width) def play(self): """ Plays the movie """ self._stopped = False drift = 0 dropped_frames = 0 dropped_seconds = 0 destyling_applied = False movie = self._movie.clone() for frame in movie.frames: if self._stopped: return self._cursor += frame.display_time # We'll drop some frames to catch up, if we need to if frame.frame_seconds <= drift: drift -= frame.frame_seconds dropped_frames += 1 dropped_seconds += frame.frame_seconds continue # Skip this frame and don't even render it right_now = datetime.now() self._load_frame(frame, self._cursor) draw_time = datetime.now() - right_now sleep_time = frame.frame_seconds - draw_time.total_seconds() if sleep_time < 0: # When draw speed exceeds the total frame seconds, we record the drift so we can catch up later drift += abs(sleep_time) sleep_time = 0 # Don't really sleep at all if it's already taking too long elif sleep_time > 0: # When draw speed exceeds total frame seconds, we catch up, if there's catching up to do drift -= min(frame.frame_seconds, drift) if drift == 0: dropped_seconds = 0 elif dropped_seconds > DESTYLING_THRESHOLD_SECONDS and not destyling_applied: movie.remove_styling() print("Destyling applied to speed transmission") destyling_applied = True time.sleep(sleep_time) print(f"Dropped {dropped_frames} frames to speed connection") def stop(self): """ Stop the movie """ self._stopped = True def _load_frame(self, frame, frame_pos): """ Buffer the the frame and then call draw_frame to display it Args: frame (ascii_movie.Frame): Frame data to display frame_pos (int): Where the frame falls in the movie """ screenbuf = BytesIO() if not self._clear_screen_setup_done: screenbuf.write(self.CLEARSCRN.encode()) self._clear_screen_setup_done = True # center vertical, with respect to the time bar (like letter boxing) screenbuf.write(self._move_cursor(1, self._movie.top_margin)) for line in frame.data: screenbuf.write((line + "\r\n").encode()) self._update_timebar(screenbuf, frame_pos) # now rewind the internal buffer and fire the public event screenbuf.seek(0) self.draw_frame(screenbuf) def draw_frame(self, screen_buffer): """ Public event method, which can be used to get new Screens. This must be implemented by the user. Args: screen_buffer: its a file like object containing the VT100 screen buffer """ raise NotImplementedError("You must specify how to draw the frame.") def _update_timebar(self, screen_buffer, frame_pos): """ Writes at the bottom of the screen a line like this <.......o.....................> It should visualize a timeline with 'o' is the current position. Args: screen_buffer: file like object, where the data is written to frame_pos (int): current cursor position on frame """ # Move cursor to the bottom of the screen screen_buffer.write(self._move_cursor(1, self._movie.screen_height)) screen_buffer.write(self.timebar.get_timebar(frame_pos).encode()) def _move_cursor(self, x, y): """ Send VT100 commands: go to position X,Y Args: x (int): x coordinate (starting at 1) y (int): y coordinate (starting at 1) Returns: str: the VT100 code as a string """ if 0 >= x > self._movie.screen_width or 0 >= y > self._movie.screen_height: sys.stderr.write( "Warning, coordinates out of range. ({0}, {1})\n".format(x, y)) return "".encode() else: return (self.ESC + "[{0};{1}H".format(y, x)).encode()
def test_short_timebar(self): tb = TimeBar(0, 3) assert tb._empty_timebar == "< >"
class VT100Player(object): """ Some escape codes used within VT100 Streams @see: http://ascii-table.com/ansi-escape-sequences-vt-100.php """ ESC = chr(27) # VT100 escape character constant JMPHOME = ESC + "[H" # Move cursor to upper left corner CLEARSCRN = ESC + "[2J" # Clear entire screen CLEARDOWN = ESC + "[J" # Clear screen from cursor down def __init__(self, movie, framerate=24): """ Player class plays a movie. It also stores the current position. It exposes the all frame numbers in real values. Therefore not encoded. Args: movie (ascii_movie.Movie): Movie Object that the player will play. """ self._movie = movie self._cursor = 0 # virtual cursor pointing to the current frame self._frame_count = 0 self._framerate = int(framerate) self._stopped = False self._clear_screen_setup_done = False for f in self._movie.frames: self._frame_count += f.display_time self.timebar = TimeBar(self._frame_count, self._movie.screen_width) def play(self): """ Plays the movie The 'very long' 'if' condition is for the stability of the frame rate If the actuall time(unpredictable) is not in the time window of the current frame, just skip this frame """ self._stopped = False oldTime = time.time() r = self._framerate for frame in self._movie.frames: if self._stopped: return if (time.time() - oldTime) * r >= self._cursor and (time.time( ) - oldTime) * r < self._cursor + frame.display_time: self._cursor += frame.display_time self._load_frame(frame, self._cursor) time.sleep((self._cursor - (time.time() - oldTime) * r) / r) else: """ Skip playing this frame and continue as if it has been played Due to 'time.sleep', the 'else' clause can only be reached when the second condition is violated """ self._cursor += frame.display_time def stop(self): """ Stop the movie """ self._stopped = True def _load_frame(self, frame, frame_pos): """ Buffer the the frame and then call draw_frame to display it Args: frame (ascii_movie.Frame): Frame data to display frame_pos (int): Where the frame falls in the movie """ screenbuf = BytesIO() if not self._clear_screen_setup_done: screenbuf.write(self.CLEARSCRN.encode()) self._clear_screen_setup_done = True # center vertical, with respect to the time bar (like letter boxing) screenbuf.write(self._move_cursor(1, self._movie.top_margin)) for line in frame.data: screenbuf.write((line + "\r\n").encode()) self._update_timebar(screenbuf, frame_pos) # now rewind the internal buffer and fire the public event screenbuf.seek(0) self.draw_frame(screenbuf) def draw_frame(self, screen_buffer): """ Public event method, which can be used to get new Screens. This must be implemented by the user. Args: screen_buffer: its a file like object containing the VT100 screen buffer """ raise NotImplementedError("You must specify how to draw the frame.") def _update_timebar(self, screen_buffer, frame_pos): """ Writes at the bottom of the screen a line like this <.......o.....................> It should visualize a timeline with 'o' is the current position. Args: screen_buffer: file like object, where the data is written to frame_pos (int): current cursor position on frame """ # Move cursor to the bottom of the screen screen_buffer.write(self._move_cursor(1, self._movie.screen_height)) screen_buffer.write(self.timebar.get_timebar(frame_pos).encode()) def _move_cursor(self, x, y): """ Send VT100 commands: go to position X,Y Args: x (int): x coordinate (starting at 1) y (int): y coordinate (starting at 1) Returns: str: the VT100 code as a string """ if 0 >= x > self._movie.screen_width or 0 >= y > self._movie.screen_height: sys.stderr.write( "Warning, coordinates out of range. ({0}, {1})\n".format(x, y)) return "".encode() else: return (self.ESC + "[{0};{1}H".format(y, x)).encode()
class VT100Player(object): """ Some escape codes used within VT100 Streams @see: http://ascii-table.com/ansi-escape-sequences-vt-100.php """ ESC = chr(27) # VT100 escape character constant JMPHOME = ESC + "[H" # Move cursor to upper left corner CLEARSCRN = ESC + "[2J" # Clear entire screen CLEARDOWN = ESC + "[J" # Clear screen from cursor down def __init__(self, movie): """ Player class plays a movie. It also stores the current position. It exposes the all frame numbers in real values. Therefore not encoded. Args: movie (ascii_movie.Movie): Movie Object that the player will play. """ self._movie = movie self._cursor = 0 # virtual cursor pointing to the current frame self._frame_count = 0 self._stopped = False self._clear_screen_setup_done = False for f in self._movie.frames: self._frame_count += f.display_time self.timebar = TimeBar(self._frame_count, self._movie.screen_width) def play(self): """ Plays the movie """ self._stopped = False for frame in self._movie.frames: if self._stopped: return self._cursor += frame.display_time self._load_frame(frame, self._cursor) time.sleep(frame.display_time / 15) def stop(self): """ Stop the movie """ self._stopped = True def _load_frame(self, frame, frame_pos): """ Buffer the the frame and then call draw_frame to display it Args: frame (ascii_movie.Frame): Frame data to display frame_pos (int): Where the frame falls in the movie """ screenbuf = BytesIO() if not self._clear_screen_setup_done: screenbuf.write(self.CLEARSCRN.encode()) self._clear_screen_setup_done = True # center vertical, with respect to the time bar (like letter boxing) screenbuf.write(self._move_cursor(1, self._movie.top_margin)) for line in frame.data: screenbuf.write((line + "\r\n").encode()) self._update_timebar(screenbuf, frame_pos) # now rewind the internal buffer and fire the public event screenbuf.seek(0) self.draw_frame(screenbuf) def draw_frame(self, screen_buffer): """ Public event method, which can be used to get new Screens. This must be implemented by the user. Args: screen_buffer: its a file like object containing the VT100 screen buffer """ raise NotImplementedError("You must specify how to draw the frame.") def _update_timebar(self, screen_buffer, frame_pos): """ Writes at the bottom of the screen a line like this <.......o.....................> It should visualize a timeline with 'o' is the current position. Args: screen_buffer: file like object, where the data is written to frame_pos (int): current cursor position on frame """ # Move cursor to the bottom of the screen screen_buffer.write(self._move_cursor(1, self._movie.screen_height)) screen_buffer.write(self.timebar.get_timebar(frame_pos).encode()) def _move_cursor(self, x, y): """ Send VT100 commands: go to position X,Y Args: x (int): x coordinate (starting at 1) y (int): y coordinate (starting at 1) Returns: str: the VT100 code as a string """ if 0 >= x > self._movie.screen_width or 0 >= y > self._movie.screen_height: sys.stderr.write("Warning, coordinates out of range. ({0}, {1})\n".format(x, y)) return "".encode() else: return (self.ESC + "[{0};{1}H".format(y, x)).encode()