mixer.py 7.06 KB
#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
Author: Caio Marcelo Campoy Guedes
E-Mail: caiomcg@gmail.com

Author: Erickson Silva
E-Mail: erickson.silva@lavid.ufpb.br

Author: Jorismar Barbosa
E-Mail: jorismar.barbosa@lavid.ufpb.br

Author: Wesnydy Lima Ribeiro
E-Mail: wesnydy@lavid.ufpb.br
"""

import json
import logging
import os
import pika
import PikaManager
import subprocess

from thread import start_new_thread
from time import sleep
from urllib import urlretrieve

def make_dir_if_exists(path):
    if not os.path.exists(path):
        os.makedirs(path)

# Logging configuration.
logger = logging.getLogger('mixer')
logger.setLevel(logging.DEBUG)

fh = logging.FileHandler('/home/vlibras/log/mixer.log')
fh.setLevel(logging.DEBUG)

ch = logging.StreamHandler()
ch.setLevel(logging.INFO)

formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
fh.setFormatter(formatter)
ch.setFormatter(formatter)

logger.addHandler(fh)
logger.addHandler(ch)

# Manager of queues connections.
#manager = PikaManager.PikaManager("150.165.205.10", "test", "test")

manager = PikaManager.PikaManager("rabbit")

PATH_MIXED_VIDEO = os.getenv("VLIBRAS_VIDEO_MIXED")
#Create Path if Needed
make_dir_if_exists(PATH_MIXED_VIDEO)

def main_video_height(main_video):
    """
    Extract height information of video.

    Parameters
    ----------
    main_video : string
        Video to obtain height.

    Returns
    -------
    string
        None if failed to extract info. The height if extraction has been successfuly.
    """
    logger.info("Extracting resolution of main video")
    try:
        # Obtains the main video height using ffprobe
        ffprobe = subprocess.Popen(
            [
                "ffprobe",
                 "-loglevel", "error",
                 "-select_streams", "v:0",
                 "-print_format", "json",
                 "-show_entries", 'stream=height',
                 main_video
            ],
            stdout=subprocess.PIPE,
            shell=False
        )
        # The results comes in a json
        video_height = json.loads(ffprobe.communicate()[0])
        # Returns the height obtained
        return video_height['streams'][0]['height']
    except:
        logger.error("Error when extracting resolution, default will be used")
        return None

def secondary_video_heigth(main_height, window_size):
    """
    Calculates the height of window.

    Parameters
    ----------
    main_height : string
        Height of main video.
    window_size : string
        User choice of size of window.

    Returns
    -------
    number
        The height of window.
    """
    logger.info("Calculating the resolution of the libras window")
    # Set the default height of main video if a height is not given
    if main_height is None:
        main_height = 324
    # Calculates the height of the small window
    if window_size == 'small':
        return int(0.3 * int(main_height))
    # Calculates the height of the large window
    elif window_size == 'large':
        return int(0.5 * int(main_height))
    # Calculates the height of the medium window (default)
    else:
        return int(0.4 * int(main_height))

def secondary_video_position(window_position):
    """
    Defines the position of window.

    Parameters
    ----------
    window_position : string
        User choice of position of window.

    Returns
    -------
    string
        The configurations of position of window.
    """
    logger.info("Defining the position of the libras window")
    # Overlap the window at top Left on main video
    if window_position == 'top_left':
        return "10:10"
    # Overlap the window at top right on main video
    elif window_position == 'top_right':
        return "main_w-overlay_w-10:10"
    # Overlap the window at bottom left on main video
    elif window_position == 'bottom_left':
        return "10:main_h-overlay_h-10"
    # Overlap the window at bottom right on main video (default)
    else:
        return "main_w-overlay_w-10:main_h-overlay_h-10"

def run(ch, method, properties, body):
    """
    Execute the worker.

    Parameters
    ----------
    ch : object
        Channel of communication.
    method : function
        Callback method.
    properties : object
        Message containing a set of 14 properties.
    body : string
        Json string containing the necessary arguments for workers.
    """
    logger.info("Processing request " + properties.correlation_id.encode("utf-8"))
    body = json.loads(body)
    try:
        logger.info("Downloading main video")
        main_video = urlretrieve(body["video"].encode("utf-8"))[0]
    except IOError as ex:
        logger.error("Download of video fail")
        return
    # Get the main video height
    main_height = main_video_height(main_video)
    # Calculates the window height based on the main video height
    window_heigth = secondary_video_heigth(main_height, body["window_size"].encode("utf-8"))
    # The width is proportionally to height (represented by -1)
    window_width = '-1'
    # Get the window position regarding to the main video
    window_pos = secondary_video_position(body["window_position"].encode("utf-8"))
    # Defines the window movie
    movie = 'movie=' + body["libras-video"].encode("utf-8")
    # Defines the scale of window movie
    scale = 'scale=' + str(window_width) + ':' + str(window_heigth)
    # Defines the overlay position
    overlay = '[movie] overlay=' + window_pos + ' [out]'
    # -Vf param
    filter_graph = ','.join([movie, scale, 'setpts=PTS-STARTPTS', overlay])
    # Generates the output file path
    mixed_video = os.path.join(PATH_MIXED_VIDEO, properties.correlation_id.encode("utf-8")+".mp4")
    # Mix videos using ffmpeg
    print ("Mixing videos...")
    logger.info("Mixing videos")
    try:
        subprocess.call(
            [
                "ffmpeg",
                "-loglevel", "error",
                "-i", main_video,
                "-y",
                "-vf", filter_graph,
                "-qscale", "0",
                "-strict", "experimental",
                "-vcodec", "libx264",
                "-preset", "fast",
                "-r", "30",
                "-threads", "4",
                mixed_video
            ],
            shell=False
        )
        logger.info("Mixing successfuly")
    except OSError as ex:
        logger.error("Mixing fail")
        print ("Error")
    # Add mixed video to the body
    body["mixed_video"] = mixed_video

    logger.info("Cleaning temp files")
    os.remove(main_video)
    os.remove(body["libras-video"])

    logger.info("Sending mixed video to the videos queue")
    manager.send_to_queue("videos", body, properties)
    print ("Ok")

def keep_alive(conn_send, conn_receive):
	while True:
		sleep(30)
		try:
			conn_send.process_data_events()
			conn_receive.process_data_events()
		except:
			continue

start_new_thread(keep_alive, (manager.get_conn_send(), manager.get_conn_receive()))

# Starts listening
print("Mixer listening...")
while True:
    try:
        manager.receive_from_queue("libras", run)
    except KeyboardInterrupt:
        manager.close_connections()
        os._exit(0)