#!/usr/local/bin/python2.7
# -*- coding: latin-1; -*-
#
# gdvrecv
# Grab a DV stream into a file
# Version 1.2
#
# Copyright (c) 2004
#      Henri Michelon
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
# 3. Neither the name of the author nor the names of its contributors
#    may be used to endorse or promote products derived from this software
#    without specific prior written permission.
# 
# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
# 
# $Id: gdvrecv,v 1.5 2004/12/09 20:37:05 hmichelon Exp $
#
import sys
import time
import os
import gc
import thread
import threading
import firewire
import avc_crom

import pygtk
pygtk.require('2.0')
import gtk


# Kino executable path
KINO_PATH = '/usr/X11R6/bin/kino'


# firewire.recv() callbacks
def start_callback(context, system_name):
  context.set_system(system_name)

def frame_callback(context, data):
  context.write(data)

def eagain_callback(context):
  context.wait()


# Callback context class
class GDVRecvRun(threading.Thread):

  def __init__(self, fd, context):
    threading.Thread.__init__(self)
    self.fd = fd
    self.context = context

  def run(self):
    firewire.recv(self.fd, self.context, start_callback, frame_callback, eagain_callback)
    self.context.end()


# Main class
class GDVRecv:

  def __init__(self):
    self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
    self.open_dev()

    self.window.set_size_request(620, 190)
    self.window.connect("delete_event", self.on_delete)
    self.window.connect("destroy", self.on_destroy)
    self.window.set_border_width(10)

    self.box_main = gtk.VBox(gtk.FALSE, 0)
    self.window.add(self.box_main)
    
    self.box_top = gtk.HBox(gtk.FALSE, 5)
    self.box_main.add(self.box_top)
    self.box_main.set_child_packing(self.box_top, gtk.FALSE, gtk.FALSE, 0, gtk.PACK_START)

    self.frame_camcorder = gtk.Frame()
    self.frame_camcorder.set_shadow_type(gtk.SHADOW_ETCHED_IN)
    self.box_top.add(self.frame_camcorder)
    self.box_top.set_child_packing(self.frame_camcorder, gtk.TRUE, gtk.TRUE, 0, gtk.PACK_START)

    self.box_camcorder = gtk.HBox()
    self.frame_camcorder.add(self.box_camcorder)

    self.image_camcorder = self.load_image("camcorder.png")
    self.box_camcorder.add(self.image_camcorder)
    self.box_camcorder.set_child_packing(self.image_camcorder, gtk.FALSE, gtk.FALSE, 0, gtk.PACK_START)
    self.label_vendor = gtk.Label()
    self.box_camcorder.add(self.label_vendor)
    self.box_camcorder.set_child_packing(self.label_vendor, gtk.FALSE, gtk.FALSE, 0, gtk.PACK_START)

    self.label_model = gtk.Label()
    self.box_camcorder.add(self.label_model)
    self.box_camcorder.set_child_packing(self.label_model, gtk.FALSE, gtk.FALSE, 0, gtk.PACK_START)

    self.button_grab = gtk.Button()
    self.button_grab.connect("clicked", self.on_grab, None)
    self.box_top.add(self.button_grab)
    self.box_top.set_child_packing(self.button_grab, gtk.FALSE, gtk.FALSE, 0, gtk.PACK_END)
    self.button_grab_box = gtk.HBox()
    self.button_grab.add(self.button_grab_box)
    self.button_grab_box.add(self.load_image("dv.png"))
    self.button_grab_label = gtk.Label("_Start recording \ntape to DV file")
    self.button_grab_label.set_use_underline(gtk.TRUE)
    self.button_grab_box.add(self.button_grab_label);

    self.sep1 = gtk.HSeparator()
    self.box_main.add(self.sep1)
    self.box_main.set_child_packing(self.sep1, gtk.TRUE, gtk.FALSE, 0, gtk.PACK_START)

    self.box_bottom = gtk.VBox(gtk.FALSE, 5)
    self.box_main.add(self.box_bottom)
    self.box_main.set_child_packing(self.box_bottom, gtk.TRUE, gtk.TRUE, 0, gtk.PACK_START)

    self.box_pleaswait = gtk.HBox(gtk.FALSE, 0)
    self.box_bottom.add(self.box_pleaswait)
    self.box_bottom.set_child_packing(self.box_pleaswait, gtk.FALSE, gtk.FALSE, 0, gtk.PACK_START)

    self.label_pleasewait = gtk.Label()
    self.box_pleaswait.add(self.label_pleasewait)
    self.box_pleaswait.set_child_packing(self.label_pleasewait, gtk.FALSE, gtk.FALSE, 0, gtk.PACK_START)

    self.box_outfile = gtk.HBox(gtk.FALSE, 0)
    self.box_bottom.add(self.box_outfile)
    self.box_bottom.set_child_packing(self.box_outfile, gtk.FALSE, gtk.FALSE, 0, gtk.PACK_START)

    self.label_outfile = gtk.Label()
    self.box_outfile.add(self.label_outfile)
    self.box_outfile.set_child_packing(self.label_outfile, gtk.FALSE, gtk.FALSE, 0, gtk.PACK_START)

    self.box_system = gtk.HBox(gtk.FALSE, 0)
    self.box_bottom.add(self.box_system)
    self.box_bottom.set_child_packing(self.box_system, gtk.FALSE, gtk.FALSE, 0, gtk.PACK_START)

    self.label_system = gtk.Label()
    self.box_system.add(self.label_system)
    self.box_system.set_child_packing(self.label_system, gtk.FALSE, gtk.FALSE, 0, gtk.PACK_START)

    self.box_frames = gtk.HBox(gtk.FALSE, 5)
    self.box_bottom.add(self.box_frames)
    self.box_bottom.set_child_packing(self.box_frames, gtk.FALSE, gtk.FALSE, 0, gtk.PACK_START)

    self.label_nframes = gtk.Label()
    self.box_frames.add(self.label_nframes);
    self.box_frames.set_child_packing(self.label_nframes, gtk.FALSE, gtk.FALSE, 0, gtk.PACK_START)
    
    self.label_frames = gtk.Label()
    self.box_frames.add(self.label_frames);
    self.box_frames.set_child_packing(self.label_frames, gtk.FALSE, gtk.FALSE, 0, gtk.PACK_START)

    self.box_time = gtk.HBox(gtk.FALSE, 5)
    self.box_bottom.add(self.box_time)
    self.box_bottom.set_child_packing(self.box_time, gtk.FALSE, gtk.FALSE, 0, gtk.PACK_START)

    self.label_ltime = gtk.Label()
    self.box_time.add(self.label_ltime);
    self.box_time.set_child_packing(self.label_ltime, gtk.FALSE, gtk.FALSE, 0, gtk.PACK_START)

    self.label_time = gtk.Label()
    self.box_time.add(self.label_time);
    self.box_time.set_child_packing(self.label_time, gtk.FALSE, gtk.FALSE, 0, gtk.PACK_START)

    self.window.show_all()
    if (self.find_avc()): sys.exit(0)


  def open_dev(self):
    """Open the firewire device"""
    self.fd = firewire.open_dev("/dev/fw0")
    if (self.fd is None) :
      self.error_box("Can't open the firewire device /dev/fw0")
      sys.exit(1)


  def find_avc(self):
    """search for an AV/C device"""
    data = firewire.get_dev(self.fd)
    self.dev = avc_crom.find_avc(self.fd, data['dev'])
    if (self.dev is None):
      r = self.yesno_box("No camcorder found.\nPlease plug and power on your camcorder.\nTry again ?")
      if (r == gtk.RESPONSE_NO):
        return 1
      else:
        return self.find_avc()
    self.label_vendor.set_markup("<b><big>" + avc_crom.get_vendor(self.fd, self.dev) + "</big></b>")
    self.label_model.set_markup("<b><big> " + avc_crom.get_model(self.fd, self.dev) + "</big></b>")
    return None
  
  
  def file_exists(self, filename):
    try:
      os.stat(filename)
      return filename
    except OSError:
      return None
  

  def load_image(self, filename):
    image = gtk.Image()
    if (self.file_exists("/usr/local/share/pixmaps/gdvrecv/" + filename)):
      image.set_from_file("/usr/local/share/pixmaps/gdvrecv/" + filename)
    elif (self.file_exists("/usr/X11R6/share/pixmaps/gdvrecv/" + filename)):
      image.set_from_file("/usr/X11R6/share/pixmaps/gdvrecv/" + filename)
    elif (self.file_exists("pixmaps/gdvrecv/" + filename)):
      image.set_from_file("pixmaps/gdvrecv/" + filename)
    elif (self.file_exists(filename)): image.set_from_file(filename)
    return image

                    

  def error_box(self, msg):
    dialog = gtk.MessageDialog(
      self.window,
      gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
      gtk.MESSAGE_WARNING,
      gtk.BUTTONS_OK,
      msg
    )
    dialog.run()
    dialog.destroy()


  def yesno_box(self, msg):
    dialog = gtk.MessageDialog(
      self.window,
      gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
      gtk.MESSAGE_QUESTION,
      gtk.BUTTONS_YES_NO,
      msg
    )
    result = dialog.run()
    dialog.destroy()
    return result


  def openfile_dialog(self, title):
    try:
      fb = gtk.FileChooserDialog(title, self.window,
                  gtk.FILE_CHOOSER_ACTION_SAVE,
                  (gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_SAVE,gtk.RESPONSE_OK))
    except:
      fb = gtk.FileSelection(title)
      fb.set_position(gtk.WIN_POS_CENTER)
    fb.show_all()
    result = fb.run()
    fb.hide()
    if (result == gtk.RESPONSE_OK):
      return fb.get_filename()
    return None


  def select_dvfile(self):
    filename = self.openfile_dialog("enter the name of the output file")
    if (filename is None) : return None
    if (self.file_exists(filename)):
      r = self.yesno_box("File %s already exists, overwrite ?" % ( filename ))
      if (r == gtk.RESPONSE_YES):
        return filename
      return self.select_dvfile()
    return filename


  def on_grab(self, widget, data=None):
    if (self.find_avc()) : return
    self.filename = self.select_dvfile()
    if (self.filename is None) : return
    if (self.find_avc()) : return
    gtk.main_iteration_do(gtk.FALSE)
    self.wfd = open(self.filename, "w")
    self.total = 0
    self.frames = 0
    self.button_grab.set_sensitive(gtk.FALSE)
    self.label_nframes.set_label("0")
    self.label_time.set_label("")
    self.label_outfile.set_label("Output file : " + self.filename)
    self.label_frames.set_label("Frames recorded")
    self.label_ltime.set_label("Recording time : ")
    self.label_system.set_label("System : ")
    self.label_pleasewait.set_markup("Press <b>'play'</b> on the camcorder to start recording.")
    self.label_pleasewait.show()
    self.label_system.show()
    self.label_nframes.show()
    self.label_frames.show()
    gtk.main_iteration_do(gtk.FALSE)
    run = GDVRecvRun(self.fd, self)
    run.run()

  
  def on_delete(self, widget, event, data=None):
    return gtk.FALSE


  def on_destroy(self, widget, data=None):
    gtk.main_quit()
    sys.exit(0)
    

  def seconds2str(self, seconds):
    if (seconds > 3600) :
      hours = seconds / 3600
      seconds -= hours * 60
    else:
      hours = 0
    if (seconds > 60):  
      minutes = seconds / 60
      seconds -= minutes * 60
    else:
      minutes = 0
    return "%02d:%02d:%02d" % ( hours, minutes, seconds )


  def write(self, data):
    for buf in data:
      self.wfd.write(buf)
      self.total += len(buf)
    self.frames = self.total / self.bpf  
    if ((self.frames % (self.fps / 2)) == 0):
      self.label_nframes.set_label(str(self.frames))
      self.label_time.set_label(self.seconds2str(self.frames / self.fps))
      while (gtk.events_pending() == gtk.TRUE):
        gtk.main_iteration_do(gtk.FALSE)


  def set_system(self, system):
    self.bpf = avc_crom.BPF[system]
    self.fps = avc_crom.FPS[system]
    gtk.main_iteration_do(gtk.FALSE)
    self.label_system.set_label("System : " + system)
    self.label_pleasewait.set_markup("Please wait, recording tape. Press <b>'stop'</b> on the camcorder to stop recording.")
    gtk.main_iteration_do(gtk.FALSE)


  def wait(self):  
    self.label_pleasewait.set_markup("Press <b>'play'</b> on the camcorder to start recording.")
    while (gtk.events_pending() == gtk.TRUE):
      gtk.main_iteration_do(gtk.FALSE)


  def end(self):  
    self.label_nframes.set_label(str(self.frames))
    self.label_time.set_label(self.seconds2str(self.frames / self.fps))
    self.wfd = None
    self.button_grab.set_sensitive(gtk.TRUE)
    if (self.file_exists(KINO_PATH)):
      r = self.yesno_box("Do you want to exit and edit the DV file in Kino ?")
      if (r == gtk.RESPONSE_YES):
        if (os.spawnlp(os.P_NOWAIT, KINO_PATH, KINO_PATH, self.filename)):
          gtk.main_quit()
          sys.exit(0)
        else:
          self.error_box("Error starting Kino (%s)" % KINO_PATH)


gdvrecv = GDVRecv()
gtk.gdk.threads_init()
gtk.main()
