#!/usr/bin/env python3
# pb_animate_image.py

# This script converts an image into a Pausch Bridge video file by sequencing
# out each row from top to bottom.  The source image must be 57 pixels wide.
# Each row is treated as a key frame spaced in time at the 'tempo' rate, with
# the video smoothly cross-fading between each row.

# This script assumes the availability of the OpenCV and numpy libraries. A
# recommended method for installing these in Python 3 follows:
#
#   pip3 install opencv-contrib-python
#
# General OpenCV information:   https://opencv.org/
# General NumPy information:    https://numpy.org/

#================================================================
# Import standard Python modules.
import argparse

# Import the numpy and OpenCV modules.
import numpy as np
import cv2 as cv

#================================================================
# Define the video properties using the canonical video format for the Pausch
# Bridge lighting system.
frame_rate   = 30
frame_width  = 228
frame_height = 8

# Specify a format code and file format.  The exact combinations of codec and
# file formats available are different for each platform.
codec_code = cv.VideoWriter.fourcc(*'png ') # PNG images, lossless, clean block edges
file_extension = 'avi'

#================================================================
# Generate successive frames of the video sequence.

def frame_generator(verbose, source, tempo):
    count = 0             # count of generated frames
    frame_time = 0.0      # time stamp for generated frame in seconds
    keyframe_phase = 0.0  # unit phase for the cross-fade, cycles over 0 to 1

    frame_interval = 1.0 / frame_rate                       # seconds between video frames
    keyframe_interval = 60.0 / tempo                        # seconds between key frames
    keyframe_rate = 1.0 / (frame_rate * keyframe_interval)  # phase / frame

    source_rows = source.shape[0]
    source_cols = source.shape[1]

    # Use the first two rows as the first keyframes.
    row0 = source[0:1, :, :]
    row1 = source[1:2, :, :]
    frame0 = cv.resize(row0, None, fx=4, fy=8, interpolation=cv.INTER_NEAREST)
    frame1 = cv.resize(row1, None, fx=4, fy=8, interpolation=cv.INTER_NEAREST)
    next_scanline = 2

    while True:
        # Cross-fade between successive key frames at the given tempo.  This will
        # return a new frame of integer pixels.
        frame = cv.addWeighted(frame0, (1.0 - keyframe_phase), frame1, keyframe_phase, 0.0)

        # Return the frame and advance the generator state.
        yield frame
        count += 1
        frame_time += frame_interval
        
        # Advance the cross-fade phase.
        keyframe_phase += keyframe_rate

        # Once the second keyframe is reached, generate the successor and reset the fade.
        if keyframe_phase > 1.0:
            keyframe_phase -= 1.0

            # Once all rows have been consumed, start returning a null result.
            if next_scanline >= source_rows:
                if verbose:
                    print(f"Generated {count} frames.")
                while True:
                    yield None
            else:
                frame0 = frame1
                row1 = source[next_scanline:next_scanline+1, :, :] 
                frame1 = cv.resize(row1, None, fx=4, fy=8, interpolation=cv.INTER_NEAREST)               
                next_scanline += 1
        

#================================================================
# Write a video file in the default format.

def write_video_file(filename, verbose, *args):

    # Open the writer with a path, format, frame rate, and size.
    out = cv.VideoWriter(filename, codec_code, frame_rate, (frame_width, frame_height))

    if verbose:
        print(f"Open file {filename} for output.")

    # Set up the frame generator.
    frame_sequence = frame_generator(verbose, *args)

    # Synthesize some frames and write them to the stream.
    while True:
        next_frame = next(frame_sequence)
        if next_frame is not None:
            out.write(next_frame)
        else:
            break

    # Release everything when done.
    out.release()

#================================================================
# Main script follows.
if __name__ == "__main__":
    parser = argparse.ArgumentParser(description = """Convert an image row by row to video frames, smoothly interpolating between rows. \
The source image must be exactly 57 pixels wide.""")
    parser.add_argument( '-v', '--verbose', action='store_true', help='Enable more detailed output.' )
    parser.add_argument( '--tempo', type=float, default=60.0, help='Tempo of key frames (image rows) in beats per minute (default: %(default)s)')
    parser.add_argument( '--input', type=str, default='timeline.png', help='Path of input image (default: %(default)s.')
    parser.add_argument( 'videopath', default='animation.avi', nargs='?', help='Path of output video file (default: %(default)s).')

    args = parser.parse_args()
    source = cv.imread(args.input)
    if args.verbose:
        print(f"Source image size: {source.shape}")
        
    write_video_file(args.videopath, args.verbose, source, args.tempo)
