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 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):
        """
        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_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_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>"
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()