#!/usr/bin/python

# capture_cam.py
#       --copyright--                   Copyright 2007 (C) Tranzoa, Co. All rights reserved.    Warranty: You're free and on your own here. This code is not necessarily up-to-date or of public quality.
#       --url--                         http://www.tranzoa.net/tzpython/
#       --email--                       pycode is the name to send to. tranzoa.com is the place to send to.
#       --bodstamps--
#       May 25, 2006            bar
#       May 25, 2006            bar     TZKeyReady and idle priority
#       June 2, 2006            bar     'r' key refreshes
#       June 11, 2006           bar     allow only q and ESC to quit
#       June 13, 2006           bar     handle capture failures
#       June 24, 2006           bar     handle write failures
#       October 12, 2007        bar     handle the new video board being in the first slot, more or less (devnum == 1 is the web cam now)
#       November 1, 2007        bar     vidcap needs importing, etc.
#       November 18, 2007       bar     turn on doxygen
#       November 27, 2007       bar     insert boilerplate copyright
#       May 17, 2008            bar     email adr
#       August 23, 2008         bar     error out if not under windows
#       December 22, 2008       bar     make the timestamp red
#       May 25, 2010            bar     tz_os_priority
#       December 11, 2010       bar     kludge for Ubuntu and one camera that works
#       December 12, 2010       bar     reorg
#                                       get time text and color in to linux version
#       December 13, 2010       bar     use smart color logic for linux version
#                                       uh. well, yeah, make it smart
#       November 29, 2011       bar     pyflake cleanup
#       May 27, 2012            bar     doxygen namespace
#       August 24, 2012         bar     --video
#                                       get working with a vga2usb frame grabber - kinda, not really
#                                       put hooks in for opencv 2.x
#       August 29, 2012         bar     can be an array of items
#       May 9, 2013             bar     able to run with 64-bit Windows' pillow subbing for PIL
#       May 25, 2014            bar     allow other sizes
#       November 17, 2014       bar     use cv2 so it can use the better camera
#                                       do the cameras up from zero rather than down from the last one as is inadvertantly found by glob
#       July 7, 2018            bar     needs changes for opencv v3
#       --eodstamps--
##      \file
#       \namespace              tzpython.capture_cam
#
#       Capture a web cam picture every so often to a file
#
#

import  glob
import  os
import  re
import  shutil
import  sys
import  tempfile
import  time


#       http://www.pythonware.com/products/pil/     or search for pillow at a uc irvine guy's page
from PIL    import  Image, ImageDraw, ImageFont


import  tzlib


NO_EXIST_ERROR  = "The device with the specified device number does not exist."

Device          = None
try :
    #       http://videocapture.sourceforge.net/
    from    VideoCapture import Device
    import  vidcap
except ImportError :
    try :
        try :
            import  cv2 as cv
            import  cv2.cv  as highgui
            highgui.cvCreateCameraCapture   = lambda devnum         : highgui.CaptureFromCAM(devnum)
            highgui.cvGetCaptureProperty    = lambda cam, prop      : highgui.GetCaptureProperty(cam, prop)
            highgui.cvSetCaptureProperty    = lambda cam, prop, v   : highgui.SetCaptureProperty(cam, prop, v)
            highgui.cvQueryFrame            = lambda cam            : highgui.QueryFrame(cam)
            highgui.cvSaveImage             = None      # lambda fn, frame      : highgui.SaveImage(fn, frame)
        except ImportError :
            from    opencv  import  highgui          # http://www.ai.rug.nl/vakinformatie/pas/content/Highgui/opencvref_highgui.htm
        # import  opencv

        class   vidcap(object) :
            error           = ValueError

        class   Device(object) :

            devs            = {}
            caps            = {}

            def __init__(me, devnum = 0) :
                me.devnum   = devnum or 0
                me.cap      = me.caps.get(me.devnum, None)
                if  not me.cap :
                    me.cap  = cv.VideoCapture(me.devnum)
                    if  me.cap :
                        if  not me.cap.isOpened() :
                            me.cap  = None
                        else    :
                            # Set video to 320x240
                            me.cap.set(cv.cv.CV_CAP_PROP_FRAME_WIDTH, 320)
                            me.cap.set(cv.cv.CV_CAP_PROP_FRAME_HEIGHT, 240)
                            me.caps[me.devnum]  = me.cap
                            for i in xrange(50) :
                                me.getImage()                       # let the camera auto-param itself
                            pass
                        pass
                    pass
                if  not me.cap :
                    me.cam      = me.devs.get(me.devnum, None)
                    if  not me.cam :
                        me.cam  = highgui.cvCreateCameraCapture(me.devnum)
                        if not me.cam :
                            if  not os.path.exists("/dev/video%d" % me.devnum) :
                                raise vidcap.error(NO_EXIST_ERROR)
                            raise vidcap.error("Can't open video device %d" % me.devnum)
                        me.devs[me.devnum]  = me.cam
                        w       = highgui.cvGetCaptureProperty(me.cam, highgui.CV_CAP_PROP_FRAME_WIDTH)         # cannot call this again after reading an image for some reason - opencv crashes
                        h       = highgui.cvGetCaptureProperty(me.cam, highgui.CV_CAP_PROP_FRAME_HEIGHT)
                        if  (w  >= 1.0) and (h >= 1.0) :                                                        # if under 1, then multiply by 65k 0x10000 and round to get the real w, h values
                            highgui.cvSetCaptureProperty(me.cam, highgui.CV_CAP_PROP_FRAME_WIDTH,  324)
                            highgui.cvSetCaptureProperty(me.cam, highgui.CV_CAP_PROP_FRAME_HEIGHT, 240)
                            pass
                        pass
                    pass
                pass

            def getImage(me, timestamp = None, textcolor = None) :
                t           = time.time()
                ts          = time.asctime(time.localtime(t)).strip()
                img         = None
                if  me.cap  :
                    frame   = None
                    f       = me.cap.grab()
                    if  f   :
                        try :
                            r, frame    = me.cap.retrieve()
                            if  not r   :
                                frame   = None
                            pass
                        except ( cv.error, IndexError, ) :
                            pass
                        pass
                    pass
                else    :
                    frame           =  highgui.cvQueryFrame(me.cam)         # grab and retrieve don't help if the image is static - get it twice and compare? - attributes other than width/hite are all -1 (not available)

                if  frame != None   :
                    temp_dir        = None
                    if  highgui.cvSaveImage :
                        temp_dir    = tempfile.mkdtemp()
                        fn          = os.path.join(temp_dir, "capture_cam_py_temp.png")
                        highgui.cvSaveImage(fn, frame)
                        img         = Image.open(fn)
                        img.load()
                    else            :
                        img         = frame[:,:,::-1]
                        # pim         = Image.new('RGB', img.shape[:2])
                        # img         = pim
                        img         = Image.fromarray(img)

                    if  timestamp   :
                        ( w, h )    = img.size
                        dr          = ImageDraw.Draw(img)
                        fnt         = ImageFont.load_default()
                        ( tw, th )  = fnt.getsize(ts)
                        if  textcolor == None :
                            ar      = 0
                            ag      = 0
                            ab      = 0
                            for x in xrange(tw) :
                                for y in xrange(th) :
                                    px  = img.getpixel((x, y))
                                    ar += px[0]
                                    ag += px[0]
                                    ab += px[0]
                                pass
                            th      = max(1, th)
                            tw      = max(1, tw)
                            ar     /= (tw * th)
                            ag     /= (tw * th)
                            ab     /= (tw * th)
                            ar      = 0 if ar >= 128 else 255
                            ag      = 0 if ag >= 128 else 255
                            ab      = 0 if ab >= 128 else 255
                            textcolor   = (ar << 16) | (ag << 8) | ab
                        dr.text((2, h - th - 1), ts, fill = textcolor)

                    try         :
                        if  temp_dir :
                            shutil.rmtree(temp_dir)
                        pass
                    except ( OSError, IOError ) :
                        pass
                    pass

                return(img)

            #   Device
        pass
    except ImportError :
        # tzlib.print_exception()
        pass
    pass

import  TZCommandLineAtFile
import  TZKeyReady
import  replace_file


help_str = """
python %s (options) output_file

Creates an image file by capturing from the default device.

-d --delay      seconds     Set the delay time between snapshots.
-v --video      number      Set device number to try 1st (1...)

"""



def human_stop(d) :
    k   = None
    t   = time.time()
    while time.time() - t < d :
        time.sleep(0.9)

        k   = TZKeyReady.tz_key_ready()
        if  k : break
        pass


    if  k :
        if  (k.lower() == "q") or (k == chr(27)) :
            return(True)
        print
        print "q - quit"
        pass

    return(False)


def get_image(devnum = 0, timestamp = False, textcolor = None) :
    cam     = Device(devnum = devnum)
    if  cam :

        # cam.displayCaptureFilterProperties()
        # cam.displayCapturePinProperties()
        # cam.saveSnapshot("image.jpg", timestamp = 1)

        ii = cam.getImage(timestamp = timestamp, textcolor = textcolor)

        if  not hasattr(cam, 'cam') :
            del(cam)

        if  ii :
            # ii = ii.convert("RGB")

            return(ii)

        raise ValueError("No image grabbed from %s" % str(devnum))
    raise ValueError(    "No camera for %s" % str(devnum))




if  __name__ == '__main__' :

    import  tz_os_priority


    if  not Device :
        print "I run only under Windows and a system with OpenCV!"
        sys.exit(101)

    program_name        = sys.argv.pop(0)
    TZCommandLineAtFile.expand_at_sign_command_line_files(sys.argv)


    delay       = 60 * 5
    vnum        = None
    ofile_name  = None

    if  tzlib.array_find(sys.argv, [ "--help", "-h", "-H", "-?", "/h", "/H", "/?" ] ) >= 0 :
        print help_str % os.path.basename(program_name)
        sys.exit(254)

    while True :
        oi  = tzlib.array_find(sys.argv, [ "--delay", "-wd", "-d",  ] )
        if  oi < 0 :    break
        del sys.argv[oi]
        delay   = float(sys.argv.pop(oi))

    while True :
        oi  = tzlib.array_find(sys.argv, [ "--video", "-v", ] )
        if  oi < 0 :    break
        del sys.argv[oi]
        vnum    = int(sys.argv.pop(oi))



    if  len(sys.argv) != 1 :
        print "No output file name!"
        sys.exit(101)


    ofile_name = sys.argv.pop(0)


    tz_os_priority.set_proc_to_idle_priority()

    errok       = False

    if  vnum   != None :
        devnums = [ max(0, abs(vnum) - 1) ]
    else        :
        devnums = []
        dnms    = glob.glob("/dev/video*")
        if  dnms :
            for dnm in dnms :
                g   = re.search(r"/dev/video(\d+)$", dnm)
                if  g :
                    devnums.append(int(g.group(1)))
                pass
            pass
        if  not len(devnums) :
            devnums = range(100)
        pass
    devnums.sort()

    while len(devnums) :
        d       = delay

        devnum  = devnums[0]
        print "Trying camera", devnum

        ii      = None
        try     :
            ii  = get_image(devnum = devnum, timestamp = 1, textcolor = None)

            ( twidth, thite ) = ii.size

            if  not (((twidth <= 352) and (thite <= 288)) or ((twidth == 320) and (thite == 240)) or ((twidth == 640) and (thite == 480))) :
                print "Wrong size", twidth, thite, "Forgetting device", devnum
                del(ii)
                ii      = None
                devnums.pop(0)
                continue

                print "Doesn't appear to be the web cam (dev%u width=%u hite=%u)!" % ( devnum, twidth, thite )

                sys.exit(101)


            ( p, fn )   = os.path.split(os.path.normpath(ofile_name))
            tfile_name  = os.path.join(p, "tmp_" + fn)

            try :
                ii.save(tfile_name)
                replace_file.replace_file(ofile_name, tfile_name, ofile_name + ".bak")
                print "Saved", time.ctime(), "Cam:%u %ux%u" % ( devnum, twidth, thite )
            except IOError :
                d /= 10
                print "Write error - will try again in", d, "seconds."
            pass

        except vidcap.error :
            e   = sys.exc_info()
            if  str(e[1]) == NO_EXIST_ERROR :
                print "Cannot find the video camera on %u!" % ( devnum )
                if  not errok :
                    sys.exit(119)
                if  human_stop(delay) :
                    break
                pass

            print "error on dev " + str(devnum) + " [" + str(e[0]) + "][" + str(e[1]) + "][" + str(e[2]) + "]"
            devnums.pop(0)
            if  vnum    > 0 :
                break
            pass

        except ValueError :
            d       = delay / 10

        if  ii and (not delay) :
            break

        if  human_stop(d) :
            break

        pass

    pass


#
#
# eof
