#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os, sys, signal
import time
import glob
import subprocess
import codecs
import threading
import win32pipe,win32file
import win32api
import textwrap
import tempfile
import urllib
import urllib2
import socket
import atexit

from PIL import Image,ImageChops,ImageOps
from PIL import ImageDraw, ImageFont
from PIL.ExifTags import TAGS
from optparse import OptionParser
from multiprocessing import Process, Lock, Pipe
#from signal import signal

import RzIAVsubs   # for other module bellows
from RzIAVsubs import setnxpos,getnxpos

sys.path.append('regzamod')
from Common.RzCmnSubs import dbg,dbg2,dbg0,error,setdbg
from Common.RzCmnSubs import isAudio,isImage,isVideo,isWebVideo,isWebContents,conv_encoding
from Common.RzCmnSubs import gs_encode_sys,gs_debug,gs_verbose,gs_ff_loglev
from Common.RzCmnSubs import gs_python,gs_prog_ytdl,gs_prog_ytdl_s,gs_prog_streamer,gs_streamer_conf

g_multi_pipe=1			# external prog can use multi-pipe_input
g_edp=0					#disp error message on renderer screen
g_iav_logfile="iav_log.txt"
g_imgtemp_prefix="iav_imgtmp"
g_abps=448000   		# audio bitrate
g_asrat=48000   		# audio sampling rate
g_spos=0
g_svfmt="JPEG"   		#{JPEG|BMP}
g_quality=100
g_quickResize=True
g_fpi=10  # frames/image
g_fpi=5  # frames/image
g_imod=Image.ANTIALIAS	# {NEAREST,BILINEAR,BICUBIC,ANTIALIAS}
#g_gapless=1 # original type
#g_gapless=2 # new type: play gapless between audio changes, using multi-threading/pipes 
g_gapless=2
g_selfit_type=0		# type of fit image to screen =0:use given kickparams,=1:both fit,=2:width fit
g_timeout_url=6.0	# secs timeout for read url_contents(image data)
g_imgsize_min=13000 # image size(bytes) too short (seems read url_contents failed)
g_fmax=10   		# multi-read count
g_tmpid_max=20		# tempid for multi-read , must be enoufgh larger than g_fmax
g_timeout_url=10.0  # secs timeout for read url_contents(image data)
g_fps_o="24000/1001" # output video fps
g_exit=0


def audiocat(vlist,mpos,spos,mmax,pipe_w):
	dbg("audiocat: start, spos=%d" % (spos))
	if(gs_verbose>0):
		dbg("audiocat: vlist=%r" % (vlist))
	
	ytdl_opt="best[height=720]/bestvideo[height<=480]/best"
	ytdl_opt="bestaudio/best"

	if(spos>=len(vlist)):
		spos=0
	while(True):
		setnxpos(mpos+1,spos+1,mmax)
		bgv=vlist[spos]
		
		ffpara_v="-f vob -c:a ac3 -ab %d -ar %d -q:a 1 -loglevel %s -y -map 0:a" % (g_abps,g_asrat,gs_ff_loglev)
		if(isWebVideo(bgv)):
			dbg("audiocat: isWebVideo=true")
			bgv=bgv.replace("&","^&").replace("|","^|").replace("(","^(").replace(")","^)")   # escape DOS special chars
			ffpara_v="\"%s\" -i pipe:0 %s pipe:1" % (opt.progpath2,ffpara_v)
			
			#ytpara_v="\"%s\" -f \"best[height=720]/bestvideo[height<=480]/best\" \"%s\"" % (gs_prog_ytdl_s,bgv)
			ytpara_v="%s -f \"%s\" \"%s\"" % (gs_prog_ytdl_s,ytdl_opt,bgv)
			
			para_v="%s -o - | %s" % (ytpara_v,ffpara_v)
			dbg("audiocat-web: exec bgv proc para=%r" % para_v)
			p4=exec_prog_sh(para_v,sys.stdin,pipe_w,sys.stderr,False)
			p4.wait()
		else:
			bgvs=win32api.GetShortPathName(bgv)  #to pass unicode pathname
			bgvs=bgvs.encode("cp932")  # what the heck!! Python2.x Popen doesn't pass unicode args
			
			para_v="\"%s\" -i \"%s\"  %s pipe:1" % (opt.progpath2,bgvs,ffpara_v)
			dbg("audiocat: exec bgv proc para=%r" % para_v)
			p4=exec_prog_sh(para_v,sys.stdin,pipe_w,sys.stderr,False)
			p4.wait()
		spos+=1
		if(spos>=len(vlist)):
			spos=0

def named_pipe(name):
	millis = int(round(time.time() * 1000))
	pname="\\\\.\\pipe\\%s(%d)" % (name,millis)
	dbg("pname=%s" % pname)
	
	fh = win32pipe.CreateNamedPipe(pname,
              win32pipe.PIPE_ACCESS_DUPLEX,
              win32pipe.PIPE_TYPE_MESSAGE | win32pipe.PIPE_WAIT,
              1,65536,65536,300,None)
	return fh,pname

def pipe_win(fi,fo):
	dbg("pipe_rw: Start")
	bufsz=50000
	while True:
		#dbg("pipe_rw: read start")
		data=fi.read(bufsz)
		#dbg("pipe_rw: read end")
		if(data):
			rc,wc=win32file.WriteFile(fo,data,None)
		else:
			break
	dbg("pipe_rw: End")
	fi.close()

#---- supply imageclips to iav_trans
def imgcat (proc,ctype,imglist,mpos,spos,mmax,opt,file_o,tempf):
	global g_spos
	global g_exit
	
	#param proc: target transcoder proc, to supply image to
	
	tm_start=time.time()
	dpsize=[opt.dpsize_w,opt.dpsize_h]
	you_are_already_dead=0
	error_on_black=0
	
	#---- fonts/mergins
	fh=18.0  # for 480p
	fh *=dpsize[1]/480.0
	font = ImageFont.truetype('c:\\windows\\fonts\\arialuni.ttf', int(fh))
	fw,fh=font.getsize("M")
	lfv= int(fh*0.8)		# linefeed height
	capt_mergin_x=10 	# caption mergin holizontal in pixcel
	capt_mergin_y=4  	# caption mergin vertical in pixcel 
	if(g_scale <1.0):
		capt_mergin_x += dpsize[0]*(1.0-g_scale)/2 
		capt_mergin_y += dpsize[1]*(1.0-g_scale)/2 
	
	#---- open log files
	dbg(">>==== RzIAVplayer.imgcat: Starting mpos=%d spos=%d mmax=%d imgcnt=%d " % (mpos,spos+1,mmax,len(imglist)))
	
	#---- timeseek
	cnt=0
	if(False and opt.timeseek>0):
		cnt=int(float(opt.timeseek)/opt.selintvl)
	else:
		#cnt=1
		cnt=0
	# timeseek has no-meanings in Image as video
	dbg("Ignore timeseek=%d, dummy out cnt=%d" % (opt.timeseek,cnt))
	opt.timeseek=0
	
	if(cnt>0):	
		try:
			dbg("timeseek=%d, dummy out cnt=%d" % (opt.timeseek,cnt))
			img=Image.new('RGB',dpsize,(0,0,0))
			#img=Image.new('RGB',(100,100),(0,0,0))
			for i in range(0,cnt):
				img.save(file_o,g_svfmt,quality=g_quality)
				file_o.flush()
		except IOError:
			if(proc.poll() is not None or file_o.closed):
				dbg("RzIAVplayer.imgcat: target proc seems terminated or pipe closed, let's stop")
				return -1
			else:
				error(g_edp,"RzIAVplayer.imgcat: IOError(1)")
				# but, continue
		except :
			dbg("RzIAVplayer.imgcat: Exception-1, some error occered, but go-on")
			#continue

	listsz=len(imglist)
	loop=1
	err_str=None	#reset
	err_dpcnt=0
	rc=0
	top=0
	i=0
	cnt=0
	ppos=spos
	ppos_prev=spos
	tmpid=0
	
	#dbg("RzIAVplayer.imgcat: loop start")
	while loop:
		tm_prev=time.time()
		play_pos=0
		skip=0
		dpcnt=0
		err_cnt=0
		for j in range(0,listsz):
			dbg(">>==== RzIAVplayer.imgcat: Loop Start No=%d/%d rpos=%d spos=%d " % (j+1,listsz,i+1,spos+1))
			#if(play_pos==0 and spos<j):
			#	continue
			dpcnt+=1
			cnt+=1
			if(cnt>=10):
				#lower cpu load for ffmpeg the slow_starter 
				time.sleep(0)
				cnt=0
			file=imglist[spos]
			urlf=0
			fname=os.path.basename(file)
			#dbg("RzIAVplayer.imgcat: file=%s" % (file))
			
			try:
				#---- push read_proc for pararell read
				fcnt=fifo_cnt();
				dbg("RzIAVplayer.imgcat: fifo_cnt=%d ppos=%d" % (fcnt,ppos))
				for k in range(0,g_fmax-fcnt):
					file=imglist[ppos]
					fname=os.path.basename(file)
					if(file.find("http://")>=0 or file.find("https://")>=0):
						fname=fname.replace("%2525","%")
						#dbg("RzIAVplayer.imgcat: No=%d before unquote fname=%s, file=%s " % (k,fname,file))
						
						fname=urllib.unquote(fname)
						fname=fname.encode('raw_unicode_escape').decode('utf-8') # for head-crashed python unquote
						
						#fname=fname.decode('utf-8')  	# no effects
						#fname=fname.decode('cp932') 	# freeze!
						#fname=fname.encode('cp932') 	# freeze! 
						
						tempfw="%s-%d.jpg" % (tempf,(tmpid))
						
						#dbg("RzIAVplayer.imgcat: No=%d after unquote fname=%s, tempfw=%s " % (k,fname,tempfw))
						p1=Process(target=url_read,args=(file,tempfw,ppos,fname))
						p1.start()
						
						fifo_push(p1,ppos,fname,file,tempfw,time.time())
						tmpid= tmpid+1
						if(tmpid>g_tmpid_max):
							tmpid=0
					else:
						fifo_push(None,ppos,fname,file,file,time.time())
					ppos_prev=ppos
					ppos= ppos+1
					if(ppos>=listsz):
						ppos=0
					
				#---- pop read_proc 
				(rc,p1,spos,fname,file,tempfw,tim1)=fifo_pop()
				tim2=time.time()
				timeout=g_timeout_url-(tim2-tim1)
				if(timeout<0.5):
					timeout=0.5

				#dbg("RzIAVplayer.imgcat: pop spos=%d rc=%d timeout=%f, p1=%r, fname=%s, file=%s, tempfw=%s ," % (spos+1,rc,timeout,p1,fname,file,tempfw))
				
				if(p1 is None):  
					#---- local file
					file=tempfw
				else:
					#---- web contents: wait&terminate read_proc
					file=tempfw
					tm_proc1=time.time()
					#--- timeout check
					if(True):
						# timeout check by join
						p1.join(timeout)        # join does't mean proc terminated, if it timeouted
						p1.terminate() 			# assure proc terminate, but ghost proc may remain in python
						p1.join()               # clear ghost, what the heck!
					else:
						# timeout check by watchdog_timer
						t1=threading.Thread(target=watchdog_timer, name="timer", args=(g_timeout_url,p1.pid))
						t1.start()
						p1.join() 
						t1.terminate()
					
					#--- assure proc terminate 
					# join does't mean proc terminated, if it timeouted
					if(True):
						p1.terminate() 
					elif(False and p1.isAlive()):  # isAlive() freeze, why?
						p1.terminate()
						skip=1
					
					tm_proc2=time.time()
					tm_proc_elapsed=tm_proc2-tm_proc1
					statinfo = os.stat(file)
					dbg("RzIAVplayer.imgcat: urlopen proc Ended exit=%d, read_size=%d, time_elapsed=%f, spos=%d, file=%s" % (p1.exitcode,statinfo.st_size,tm_proc_elapsed,spos+1,fname))
					
					if(p1.exitcode!=0):
						dbg("RzIAVplayer.imgcat: read seems failed (exitcode=%d !=0) --> skip" % (p1.exitcode))
						skip=1
					elif(statinfo.st_size<g_imgsize_min):
						dbg("RzIAVplayer.imgcat: read seems failed (read_size=%d <%d bytes) --> skip" % (statinfo.st_size,g_imgsize_min))
						skip=1
					elif(tm_proc_elapsed>=g_timeout_url):
						dbg("RzIAVplayer.imgcat: read seems failed (elapsed=%f > timeout=%f) --> skip" % (tm_proc_elapsed,g_timeout_url))
						skip=1
					else:
						dbg("RzIAVplayer.imgcat: read seems succeeded")
					
			except WindowsError,e:
				error(g_edp,"RzIAVplayer.imgcat: WindowsError, e=%r, clipNo.=%d, image='%s'" % (e,spos+1,fname))
				skip=1
				time.sleep(0.5)
			except IOError,e:
				error(g_edp,"RzIAVplayer.imgcat: IOError(2), e=%r, clipNo.=%d, image='%s'" % (e,spos+1,fname))
				skip=1
				#dummy_dispf=1
				time.sleep(0.5)
			#except Exception,e:
			#   this will hide python's standard traceback_dump when syntax errors
			#	error(g_edp,"RzIAVplayer.imgcat: Unknown Exception(1), e=%r, clipNo.=%d, image='%s'" % (e,spos+1,fname))
			#	traceback.print_exc(file=sys.stderr)
			#	proc.terminate()
			#	return (-1,spos)
			
			#dbg("RzIAVplayer.imgcat: end urlretrieve file=%s, skip=%d" % (file,skip))
			if(skip==0 and file!=file_o):
				try:
					if(gs_verbose>0):
						dbg("RzIAVplayer: play_pos=%d, spos=%d, image creating, file=%s" % (play_pos,spos,file))
					if(urlf>0):
						img=Image.open(fi)
					else:
						img=Image.open(file)
					exif=None
					try:
						exif=img._getexif()
					except AttributeError,e:
						dbg("imgcat: error on getexif, but Go-on")
						pass

					imsize=img.size
					if(g_quickResize and imsize[0]*imsize[1]/dpsize[0]/dpsize[1]>=9.0):
						dbg("RzIAVplayer.imgcat: big size image, use draft")
						dpwk=[dpsize[0]*2,dpsize[1]*2]
						img=img.draft('RGB',dpwk)
						imsize=img.size
						
					resize=[opt.dpsize_w,opt.dpsize_h]
					
					if(opt.selfit==1): # both(width,height) fit
						imr=float(imsize[0])/float(imsize[1])
						dpr=float(dpsize[0])/float(dpsize[1])
						dprr=dpr/g_aspect
						do_resize=0
						if(imr>dpr):
							do_resize=2
							resize[0]=dpsize[0]
							resize[1]=dpsize[0]/imr/dprr
							pos=(0,int((dpsize[1]-resize[1])/2))
						elif(imr<dpr):
							do_resize=2
							resize[0]=dpsize[1]*imr*dprr
							resize[1]=dpsize[1]
							pos=(int((dpsize[0]-resize[0])/2),0)
						elif(imsize[0]!=dpsize[0]):
							do_resize=1
							resize[0]=dpsize[0]
							resize[1]=dpsize[1]
						# for negate TV's overscan
						if(g_scale!=1.0):
							resize[0]*=g_scale
							resize[1]*=g_scale
							pos=(int((dpsize[0]-resize[0])/2),int((dpsize[1]-resize[1])/2))
							do_resize=2
							
						if(gs_verbose>0):	
							dbg("imsize=%f,%f" % (imsize[0],imsize[1]))
							dbg("dpsize=%f,%f" % (dpsize[0],dpsize[1]))
							dbg("resize=%f,%d" % (resize[0],resize[1]))
							dbg("imr=%f, dpr=%f" % (imr,dpr))
						
						if(do_resize>0):
							iresize=(int(resize[0]),int(resize[1]))
							img=img.resize(iresize,g_imod)
						if(do_resize>1):
							img2=Image.new('RGB',dpsize,(0,0,0))
							img2.paste(img,pos)
							img=img2
					else:
						#img=ImageOps.fit(img,dpsize,Image.BICUBIC,(0.5,0.5))
						img=ImageOps.fit(img,dpsize,g_imod,(0.5,0.5))
					
					#---- draw error msg ---------
					str=RzIAVsubs.getErrorMsg(1)
					if(str is not None):
						if(error_on_black):
							img=Image.new('RGB',dpsize,(0,0,0))
						err_str=str
						err_dpcnt=2
					if(err_dpcnt>0 and err_str is not None): 
						err_dpcnt-=1
						lines=textwrap.wrap(err_str, width=80)
						wmax=0
						for line in lines:
							w,h=font.getsize(line)
							if(w>wmax):
								wmax=w
						h=lfv*len(lines)
						xpos=dpsize[0]/2-wmax/2	#centering
						ypos=dpsize[1]/2-h/2	#centering
						for str in lines:
							dr = ImageDraw.Draw(img)
							sd=2	#shadow depth
							dr.text((xpos,ypos), 		str,font=font,fill='black')
							dr.text((xpos-sd,ypos-sd),	str,font=font,fill='yellow')
							ypos+=lfv
					else:
						err_str=None	#reset
						err_dpcnt=0
						
					#---- draw captions ---------
					sd=2	#shadow shift
					xpos=20
					drawtext=1
					if((spos % 2 )==0):
						align_h=0	#left bottom
						ypos=dpsize[1]-fh-capt_mergin_y
						lf=lfv	#line feed
					else:
						align_h=2	#right upper
						ypos=capt_mergin_y
						lf=-lfv	#line feed
					if(drawtext and opt.caption):	
						dr = ImageDraw.Draw(img)
						dpw=dpsize[0]
						if(True):
							str="Clip No. %d/%d File: %s" % (spos+1,len(imglist),fname)
							w,h=font.getsize(str)
							if(align_h==0): #left bottom
								xpos=capt_mergin_x
							elif(align_h==2): #right upper
								xpos=dpw-w-capt_mergin_x
							dr.text((xpos,ypos), 		str,font=font,fill='black')
							dr.text((xpos-sd,ypos-sd),	str,font=font,fill='white')
							ypos -=lf
							
							str_app=""
							if(exif is not None):
								str=get_exfield(exif,'DateTimeOriginal')
								if(str is None):
									str=get_exfield(exif,'DateTime')
								if(str is not None):
									str_app=str_app+", Date:%s" % str
								
								str=get_exfield(exif,'Model')
								if(str is not None):
									str_app=str_app+", Model:%s" % str
							else:
								#str_app=" (No EXIF Info)"
								pass
							
							#str="Size: %d x %d" % (imsize[0],imsize[1])
							str="Size: %d x %d%s" % (imsize[0],imsize[1],str_app)
							w,h=font.getsize(str)
							if(align_h==1):
								xpos=dpw/2-w/2
							elif(align_h==2):
								xpos=dpw-w-capt_mergin_x
							dr.text((xpos,ypos), 		str,font=font,fill='black')
							dr.text((xpos-sd,ypos-sd),	str,font=font,fill='white')
							ypos -=lf
						
					#---- save(write) image ---------
					if(dpcnt==1):
						#lpc=int(g_fpi*1.2)
						lpc=g_fpi
					else:
						lpc=g_fpi
					for i in range(lpc):
						img.save(file_o,g_svfmt,quality=g_quality)
					file_o.flush()
					
					err_cnt=0
					
					#file_o.flush()
					#setnxpos(mpos+1,spos,mmax=mmax)
					tm_now=time.time()
					dbg("imgcat: saved spos=%d imsize=%d,%d, Time Interval=%f, Elapsed=%f" % (spos+1,imsize[0],imsize[1],tm_now - tm_prev,tm_now - tm_start))
					tm_prev=tm_now;
					#time.sleep(1.0)
					
				except IOError,e:
					# will frequentry occur on web contents
					error(g_edp,"RzIAVplayer: IOError(2), e=%r, ClipNo.=%d, image_file='%s'" % (e,spos+1,fname))
					if((proc.poll() is not None or file_o.closed)):
						#seems main transcoder died/stooped, no need to retry
						dbg("RzIAVplayer: target proc terminated or pipe closed, let's stop")
						rc=1
						loop=0
						break
					elif(True):
						err_cnt+=1
						dbg("RzIAVplayer: IOError Exception, err_cnt =%d" % err_cnt)
						if(err_cnt>=listsz):
							error(1,"RzIAVplayer: IOError Exception, count over=%d, --> exit" % err_cnt)
							error_on_black=1
							g_exit=err_cnt
							proc.terminate()
							return(-2,spos)
							
							#--- normal return(complicated procedures) will cause long freeze !!
							#rc=1
							#loop=0
							#break
							#sys.exit(2)
							#you_are_already_dead=2
						time.sleep(0.5)
						#don't retry infinitly !!
					elif(False):
						#seems image read error, skip it and try next image
						time.sleep(1);
					else:
						error(g_edp,"RzIAVplayer.imgcat: IOError(2) e=%r, play_pos=%d, image='%s'" % (e,spos+1,fname))
						time.sleep(0.1)
				#except:
				#	error(g_edp,"RzIAVplayer: unknown Exception(2), e=%r, ClipNo.=%d, image_file='%s'" % ("",spos+1,fname))
				#	#should kill transcoder_proc, otherwise main proc will hung-up
				#	proc.terminate()
				#	sys.exit(3)
				#	return (-3,spos)

			#--- loop tail start
			dbg(">>==== RzIAVplayer.imgcat: Loop End No=%d/%d rpos=%d spos=%d " % (j+1,listsz,i+1,spos+1))
			i+=1
			skip=0
			spos+=1
			if(i>=listsz):
				i=0
			if(spos>=listsz):
				spos=0
			if(you_are_already_dead>0):
				you_are_already_dead -=1
				if(True or you_are_already_dead<=0):
					loop=0
					break
			#if(urlf>0):
			#	fo.close()
			#--- loop tail end
		play_pos=1

	#---- ending
	#file_o.close()  # don't close: use next
	tm_now=time.time()
	dbg(">>==== RzIAVplayer.imgcat: Stopping, mpos=%d spos=%d rc=%d timeElapsed=%.2f" % (mpos,spos+1,rc,tm_now - tm_start))
	#g_spos=spos
	#return (rc,spos)
	if(g_ppos is not None):
		fifo_unpop()
	g_spos=ppos
	return (rc,ppos)

#---- get Exif Infos
def get_exfield (exif,field):
	if(exif is None):
		return None
	for (k,v) in exif.iteritems():
		if TAGS.get(k) == field:
			return v
	return None

#---- fifo funcs
ls_proc=[]
ls_ppos=[]
ls_fname=[]
ls_file=[]
ls_tempf=[]
ls_time=[]

g_proc=None
g_ppos=None
g_fname=None
g_file=None
g_tempf=None
g_time=None

def fifo_push (proc,ppos,fname,file,tempf,time):
	if(len(ls_proc)>=g_fmax):
		return -1
	ls_proc.append(proc)
	ls_ppos.append(ppos)
	ls_fname.append(fname)
	ls_file.append(file)
	ls_tempf.append(tempf)
	ls_time.append(time)
	return 0

def fifo_pop ():
	global g_proc,g_ppos,g_fname,g_file,g_tempf,g_time
	if(len(ls_proc)<=0):
		return (-1,None,None,None,None,None,None)
	p=0
	g_proc=ls_proc.pop(p)
	g_ppos=ls_ppos.pop(p)
	g_fname=ls_fname.pop(p)
	g_file=ls_file.pop(p)
	g_tempf=ls_tempf.pop(p)
	g_time=ls_time.pop(p)
	return (0,g_proc,g_ppos,g_fname,g_file,g_tempf,g_time)

def fifo_unpop ():
	global g_proc,g_ppos,g_fname,g_file,g_tempf,g_time
	
	dbg("RzIAVplayer.fifo_unpop: spos=%d " % (g_ppos))
	
	ls_proc.insert(0,g_proc)
	ls_ppos.insert(0,g_ppos)
	ls_fname.insert(0,g_fname)
	ls_file.insert(0,g_file)
	ls_tempf.insert(0,g_tempf)
	ls_time.insert(0,time.time())

def fifo_cnt ():
	return len(ls_proc)

#---- read url contents & write to outfile
def url_read (url,outfile,spos,fname):
	#dbg("RzIAVplayer.url_read: Start, url=%s outfile=%s" % (url,outfile))
	try:
		if(False):
			fi= urllib.urlopen(url)
			fo=open(outfile,"w+b")
			fo.write(fi.read())
			fo.close()
		else:
			req = urllib2.Request(url, headers={ 'User-Agent': 'Mozilla/5.0' })
			fi= urllib2.urlopen(req)
			fo=open(outfile,"w+b")
			fo.write(fi.read())
			fo.close()
			
	except IOError,e:
		error("RzIAVplayer.url_read: IOError, e=%r, clipNo.=%d, image='%s'" % (e,spos+1,fname))
	#except Exception,e:
	#	error("RzIAVplayer.url_read: Unknown Exception, e=%r, clipNo.=%d, image='%s'" % ("",spos+1,fname))
	#	rc=-2
	
	#dbg("RzIAVplayer.url_read: End")
	
#---- watchdog
def watchdog_pipe (pc,ppid,pid,tempf):
	import time
	# this mechanism work well, to know the parent death
	dbg2("===== watchdog: started, mypid=%d parent pid=%d target_pid=%d" % (os.getpid(),ppid,pid))
	time1=time.time()
	
	try:
		#read pipe in blocking mode
		#i.e. wait untill parent die, b/c parent never send to the pipe
		pc.recv()
	except EOFError:
		dbg("===== watchdog: EOFError on pipe.recv")
		#parent seems to be killed
		pass	#Noop
	except IOError:
		dbg("===== watchdog: IOError on pipe.recv")
		pass	#Noop
	
	dbg2("===== watchdog: kill pid=%d" % pid)
	try:
		#os.kill(pid,signal.SIGKILL)
		os.kill(pid,9)
		if(tempf.find(g_imgtemp_prefix)>=0):
			os.remove(tempf)
	except WindowsError:
		dbg2("WindowsError: pid=%d may not exists" % pid)

	time2=time.time()
	time=time2-time1
	dbg2("watchdog: play time=%f" % time)
	#sys.exit()
	os._exit(0)
	
#---- IAVtranscoder body
def iav_trans (args,cliptype,cliplist,mpos,spos,mmax,opt,gapless,tempf,pw):
	#msg="iav_trans: args=%s" % args
	#dbg(msg)
	dbg("iav_trans: args="+str(args))
	#---- exec transcoder body
	buffered=0
	cwd="./"
	try:
		if(buffered):
			p = subprocess.Popen(args, bufsize=4000000, shell=False, cwd=cwd, stdin=subprocess.PIPE,
		    	stdout=None, stderr=None,close_fds=False)	
		else:
			p = subprocess.Popen(args, bufsize=-1, shell=False, cwd=cwd, stdin=subprocess.PIPE,
			    stdout=subprocess.PIPE, stderr=None,close_fds=False)
	
	except OSError:
		error(g_edp,"RzIAVplayer: Failed to exec %s" % args)
		return (1)
		
	#---- kick watchdog proc ------
	ret=0
	pp,pc=Pipe()
	Process(target=watchdog_pipe,args=(pc,os.getpid(),p.pid,tempf)).start()
	
	if(gapless>0):
		#---- use imgcat thread to gapless:endless play
		t = threading.Thread(target=imgcat, args=(p,cliptype,cliplist,mpos,spos,mmax,opt,p.stdin,tempf))
		t.start()

	if(gapless==1):
		#--- read ffmpeg stdout & write to vo(video_out)
		loop=1
		try:
			while (loop):
				#small buffer may cause audio stattering
				buf=p.stdout.read(500000)
				if(buf):
					vo.write(buf)
				else:
					loop=0
			
		except IOError:		
			#seems be killed 
			#if this judge is not defined, python will never die by SIGs in while loop
			error(g_edp,"RzIAVplayer: IOError in pipe read/write --> exit")
			#sys.exit(1)
			os._exit(1)
		
		# caution: can't catch signals while waiting by join/wait
		while (p.poll() is None):
			time.sleep(1)
		ret=p.wait()
		while t.isAlive():
			time.sleep(1)
		t.join()
	elif(gapless==2 and pw is not None):
		#--- read ffmpeg stdout & write to pw
		loop=1
		try:
			while (loop):
				#small buffer may cause audio stattering
				buf=p.stdout.read(500000)
				if(buf):
					pw.write(buf)
				else:
					loop=0
			
		except IOError:		
			#seems be killed 
			#if this judge is not defined, python will never die by SIGs in while loop
			error(g_edp,"RzIAVplayer: IOError in pipe read/write --> exit")
			#sys.exit(1)
			os._exit(1)
		
		# caution: can't catch signals while waiting by join/wait
		while (p.poll() is None):
			time.sleep(1)
		ret=p.wait()
		while t.isAlive():
			time.sleep(1)
		t.join()
	else:
		(rc,spos)=imgcat(p,cliptype,cliplist,mpos,spos,mmax,opt,p.stdin,tempf)
		ret=p.wait()
	return (ret,g_spos)
	
def catch_sig (sig,frame):
	dbg("Interrupted sig=%d" % (sig))
	#sys.exit(1)
	os._exit(1)

def exec_prog (args,logf):
	dbg("exec_prog: args=%s" % args)
	p=None
	try:
		cwd="./"
		p = subprocess.Popen(args, shell=False, cwd=cwd, stdin=subprocess.PIPE,
	    	stdout=None, stderr=None,
	    	close_fds=False)
	except OSError:
		dbg("Failed to exec %s" % args)
	return p

def exec_prog_sh (args,stdi,stdo,stde,clfd):
	dbg("exec_prog_sh args=%r" % args)
	p=None
	try:
		cwd="./"
		p = subprocess.Popen(args, shell=True, cwd=cwd, stdin=stdi, stdout=stdo,
			stderr=stde, close_fds=clfd)
	except OSError:
		dbg("exec_prog_sh: Failed to exec %s" % args)
	return p

#----------------------------------------------------------------------------------
# main
#----------------------------------------------------------------------------------
if __name__ == '__main__':
	
	#setdbg(gs_debug)
	dbg("RzIAVplayer: Start, argv=%r" % sys.argv)
	tm_start0=time.time()
	
	#---- signals
	#signal.signal(signal.SIGINT,catch_sig)
	#signal.signal(signal.SIGTERM,catch_sig)
	
	#---- change default encoding
	if(True):
		#dbg("RzIAVplayer.main: defaultencoding=%s" % sys.getdefaultencoding())
		reload(sys)
		sys.setdefaultencoding('utf-8')
		#dbg("RzIAVplayer.main: changed defaultencoding=%s" % sys.getdefaultencoding())
	
	#---- get main args 
	if(len(sys.argv)<3):
		print "usage: RzIAVplayer.py {image_in} {video_out} [options]"
		sys.exit(1)
	myprog=sys.argv[0]
	image_in=sys.argv[1]
	video_out=sys.argv[2]
	image_in,enc=conv_encoding(image_in)  # force utf-8 (arg may be ms932)
	
	#---- get options
	parser = OptionParser()
	
	parser.add_option("--temp",		action="store",	dest="temp",type="string", default=".")
	parser.add_option("--sess_id",	action="store",	dest="sess_id",type="string", default="0")
	
	#---- image media tags
	parser.add_option("--img_pos",	action="store",	dest="img_pos",type="int", default=0)
	parser.add_option("--filename",	action="store",	dest="filename",default="unknown")
	parser.add_option("--title",	action="store",	dest="title",	default="")
	parser.add_option("--artist",	action="store",	dest="artist",	default="unknown")
	parser.add_option("--album",	action="store",	dest="album",	default="unknown")
	parser.add_option("--genre",	action="store",	dest="genre",	default="default")
	
	#---- audio clips
	parser.add_option("--clippath",	action="store",	dest="clippath",	type="string",	default="")
	parser.add_option("--clippath2",action="store",	dest="clippath2",	type="string",	default="")
	parser.add_option("--dbmode",	action="store",	dest="dbmode",		type="string",	default="simple")
	parser.add_option("--seltype",	action="store",	dest="seltype",		type="string",	default="")
	parser.add_option("--selct",	action="store",	dest="selcnt",		type="int",		default=-1)
	parser.add_option("--selintvl",	action="store",	dest="selintvl",	type="int",		default=0)
	parser.add_option("--selsort",	action="store",	dest="selsort",		type="string",	default="name")
	parser.add_option("--selfit",	action="store",	dest="selfit",		type="int",		default=1)
	parser.add_option("--selmedia",	action="store",	dest="selmedia",	type="string",	default="image")
	
	#---- display info
	parser.add_option("--timeseek",	action="store",	dest="timeseek",	type="float",default=0.0)
	parser.add_option("--m2ts",		action="store",	dest="m2ts",		type="int"	,default=0)
	parser.add_option("--h264",		action="store",	dest="h264",		type="int"	,default=0)
	parser.add_option("--remux",	action="store",	dest="remux",		type="int"	,default=0)
	parser.add_option("--asp_hack",	action="store",	dest="asp_hack",	type="int"	,default=0)
	parser.add_option("--dpsize_w",	action="store",	dest="dpsize_w",	type="int"	,default=856)
	parser.add_option("--dpsize_h",	action="store",	dest="dpsize_h",	type="int"	,default=482)
	parser.add_option("--caption",	action="store",	dest="caption",		type="int"	,default=1)
	parser.add_option("--progpath",	action="store",	dest="progpath",	type="string",default="win32/ffmpeg.exe")
	parser.add_option("--progpath2",action="store",	dest="progpath2",	type="string",default="win32/ffmpeg-RZ.exe")
	parser.add_option("--gapless",	action="store",	dest="gapless",		type="int"	,default=0)
	
	#---- auto play
	parser.add_option("--auto_play"		,action="store",	dest="auto_play",		type="int",		default=0)
	parser.add_option("--auto_play_spos",action="store",	dest="auto_play_spos",	type="int",		default=0)

	(opt,args) = parser.parse_args()

	#---- test settings start -----------
	test=False
	if(test):
		clippath="C:/Documents and Settings/papa/My Documents/My Pictures/SceneClips"
		opt.dpsize_w=720
		opt.dpsize_h=482
		opt.dpsize_w=856
		opt.dpsize_h=482
		opt.dpsize_w=1280
		opt.dpsize_h=720
	#---- test settings end -----------
	
	#---- init/default settings -----------
	opt.temp=opt.temp+"/iav"
	RzIAVsubs.init(opt.temp,opt.sess_id)
	tempf=opt.temp+"/"+g_imgtemp_prefix+"("+opt.sess_id+")"
	#atexit.register(lambda: os.remove(tempf))

	g_scale=1.0
	aspect="16:9"
	g_aspect=1.77778
	if(opt.dpsize_h>480 and opt.asp_hack>0):
		aspect="4:3"  		# REGZA can't display mpeg2video of aspect 16/9 when over 480p,
		g_aspect=1.77778 	# REGZA always expand any aspect to it's full screen (16:9)
	if(opt.dpsize_w==720 and opt.dpsize_h==480):
		aspect="16:9"
		g_scale=0.95 # for negate TV's overscan
	if(opt.title==""):
		opt.title=opt.filename
	opt.selmedia="image"
	
	if(g_selfit_type!=0):
		opt.selfit=g_selfit_type

	if(opt.gapless):
		gapless=g_gapless
	else:
		gapless=0
	
	size=(opt.dpsize_w,opt.dpsize_h)

	if(gs_verbose>0):
		dbg("======== %s: Start =======================" % os.path.basename(myprog))
		dbg("image_in="+image_in)
		dbg("video_out="+video_out)
		dbg("clippath="+opt.clippath)
		dbg("clippath2="+opt.clippath2)
		dbg("selmedia=%s, selfit=%d, size=(%d,%d)" % (opt.selmedia,opt.selfit,size[0],size[1]))
		dbg("opt="+str(opt))
	
	# sys.stderr test
	#aaaa
	
	#---- get image list
	cliptype="image"
	cliplist=[]
	slideshow=0
	base_path=""
	if(opt.seltype.lower()=="slideshow"):
		slideshow=1
		fp=open(image_in)
		i=0
		for line in fp:
			line=line.strip().decode("utf_8")  # image_in list must be encoded by utf_8
			cliplist.append(line)
			if(i==0):
				base_path=os.path.dirname(line.strip())
				i+=1
	else:
		cliplist=[image_in]
		base_path=os.path.dirname(image_in)

	# force utf-8 (args may be ms932)
	base_path,env=conv_encoding(base_path)  
	opt.clippath,enc=conv_encoding(opt.clippath)
	opt.clippath2,enc=conv_encoding(opt.clippath2)
		
	#---- get audio list
	#---- path1
	clippath=opt.clippath
	if(os.path.abspath(clippath)!=clippath):# relative path
		clippath=os.path.join(base_path,clippath)
	alist=RzIAVsubs.getAudiolist(clippath)
	#---- path2
	if(len(alist)<=0 and len(opt.clippath2)>0):	# try defaults
		clippath=opt.clippath2
		clippath,enc=conv_encoding(clippath)
		if(os.path.abspath(clippath)!=clippath):# relative path
			clippath=os.path.join(base_path,clippath)
		alist=RzIAVsubs.getAudiolist(clippath)
	#---- default path
	if(len(alist)<=0):	# try last defaults
		error(1,"AudioClips: clippath=%s not found. Use default clips." % clippath)
		clippath="regzamod/RzIAVplayer/DefaultClips"
		alist=RzIAVsubs.getAudiolist(clippath)
	dbg("audio clippath=%s" % (clippath))

	#---- start pos of image/audio list
	mpos=0
	spos=0
	mmax=len(cliplist)
	amax=len(alist)
	(mpos,spos)=getnxpos(1,amax,clippath,0)
	apos=spos % len(alist)
	#audio_in=alist[apos].encode(encode_p)
	audio_in=alist[apos]
	dbg("getnxpos: mpos=%d, spos=%d, apos=%d" % (mpos,spos,apos))
	
	#---- setup transcoder params
	use_concat=0
	fps_o=g_fps_o  # output fps
	if(g_multi_pipe): 
		bgm_fdh,bgm_fdn= named_pipe("pipe_win")  # pipe for bgv producer
	if(slideshow):
		if(gapless==2):
			#pw,pr=Pipe()
			# -acodec copy will be better for performance, b/c muxer re-encode audio.
			# but -acodec copy can't path-through some acodecs
			#format="mpegts"
			#acodec="ac3 -ab %d" % g_abps
			format="vob"
			acodec="copy"
		else:
			# audio must be encoded here:transcoder, b/c muxer will not run.
			# but,audio_encode by transcoder may cause audio_stuttering
			format="vob"
			acodec="ac3 -ab %d" % g_abps
			
		#fps_img=1.0/opt.selintvl
		#fps_imgstr="%d/%d" % (g_fpi,int(opt.selintvl*1.4))
		fps_imgstr="%d/%d" % (g_fpi,int(opt.selintvl))
		
		#shouldn't include path name in para: para.split() may split path its'self by space etc.
		if(g_svfmt=="BMP"):
			para =" -ss %f -loglevel %s -y -f image2pipe -r %s -i pipe:0.bmp" % (opt.timeseek,gs_ff_loglev,fps_imgstr)
			para+=" -q:v 1 -f %s -vcodec mpeg2video -acodec %s -r %s -map 1:v -map 0:a" % (format,acodec,fps_o)
			para+=" -shortest"
		else:
			para =" -loglevel %s -y -f image2pipe -r %s -vcodec mjpeg -i pipe:0" % (gs_ff_loglev,fps_imgstr)
			para+=" -q:v 1 -f %s -vcodec mpeg2video -acodec %s -r %s -map 1:v -map 0:a"  % (format,acodec,fps_o)
			para+=" -shortest"
			
		if(gapless==1):
			para+=" -aspect %s pipe:%d" % (aspect,1) 
		elif(gapless==2):
			#para+=" -aspect %s pipe:%d" % (aspect,pw.fileno()) # pipe not stdin/out doesn't work well
			para+=" -aspect %s pipe:%d" % (aspect,1)
		else:
			para+=" -aspect %s %s" % (aspect,video_out)
		#mpara=para.split(" ")	# sequence of spaces cause null item
		mpara=para.split()	# split by spaces, recognize sequence of space as one space
		if(g_multi_pipe):
			prog=opt.progpath2
			args=[prog,"-i",bgm_fdn]+mpara
		elif(use_concat):
			prog=opt.progpath2
			fp=open("audio.cca","w+")
			abs=os.path.abspath(audio_in)
			for i in range(0,2):
				print >>fp,"file '%s'" % abs
			fp.close()
			args=[prog,"-f","concat","-i","audio.cca"]+mpara
		else:
			if(g_svfmt=="BMP"):
				prog=opt.progpath2
			else:
				prog=opt.progpath2
			args=[prog,"-i",audio_in]+mpara
	else:
		#--- single play
		fps_imgstr="%d/%d" % (g_fpi,int(opt.selintvl))
		prog=opt.progpath2
		if(g_svfmt=="BMP"):
			para =" -ss %f -shortest -loglevel %s -y -f image2pipe -r %s -i pipe:0.bmp" % (opt.timeseek,gs_ff_loglev,fps_imgstr)
		else:
			para =" -ss %f -shortest -loglevel %s -y -f image2pipe -r %s -vcodec mjpeg -i -" % (opt.timeseek,gs_ff_loglev,fps_imgstr)
		para+=" -q:v 0 -f mpegts -vcodec mpeg2video -acodec ac3 -ab %d -r %s" % (g_abps,fps_o)
		para+=" -t %d -aspect %s %s" % (opt.selintvl,aspect,video_out)
		mpara=para.split()
		args=[prog,"-i",audio_in]+mpara

	#tempf=os.tempnam(tempfile.gettempdir(),g_imgtemp_prefix)
	#atexit.register(lambda: os.remove(tempf))

	if(slideshow and gapless):
		# don't work well: sometime occer audio stuttering
		if(gapless==1):
			# gapless==1: exec without muxer
			# pms <--(video_out)-- transcoder:iav_trans() <--(pipe)-- imgcat()
			# to retain open after iav_trans once finished
			vo=open(video_out,"wb+") 
		elif(gapless==2):
			# gapless==2: exec with muxer
			# pms <--(video_out)-- muxer <--(pipe)-- transcoder:iav_trans() <--(pipe)-- imgcat()
			# kick muxer
			# audio_encode/re-encode by muxer will improve audio_stuttering on audio changes
			#para=" -ss %f -i pipe:0 -f vob -vcodec copy -acodec ac3 -ab %d" % (opt.timeseek,g_abps) # -f vob couse terrible many warning logs (buffer underflow ...)
			#para=" -ss %f -i pipe:0 -f mpegts -vcodec copy -acodec copy -async 1" % (opt.timeseek) # cause statter
			
			#para=" -ss %f -i pipe:0 -f mpegts -vcodec copy -acodec ac3 -ab %d" % (opt.timeseek,g_abps)
			#para=" -ss %f -i pipe:0 -f mpegts -vcodec copy -acodec copy" % (0)
			para=" -i pipe:0 -f mpegts -vcodec copy -acodec copy" # new ffmpeg cause error on seek for pipe!
			para +=" -loglevel %s -y %s" % (gs_ff_loglev,video_out)  
			args0=para.split()
			#args0=[opt.progpath]+args0  #use old ffmpeg
			args0=[opt.progpath2]+args0  #use new ffmpeg
			p0=exec_prog(args0,g_iav_logfile)
		
		#---- exec transcoder
		cnt=1
		spos=opt.img_pos
		if(opt.auto_play>0):
			# pos controlled by serverSide for next/prev play
			spos=opt.auto_play_spos 
			
		while(True):
			#---- exec bgm producer
			dbg("g_multi_pipe=%d, cliptype=%s" % (g_multi_pipe,cliptype))
			if(g_multi_pipe):
				pr2,pw2=os.pipe()
				fr2= os.fdopen(pr2)
				fw2= os.fdopen(pw2)
				t3=threading.Thread(target=audiocat, name="audiocat", args=(alist,mpos,spos,mmax,fw2))
				t3.start()

				t2=threading.Thread(target=pipe_win, name="pipe_win", args=(fr2,bgm_fdh))
				t2.start()
			
			#---- exec main transcoder
			dbg("---- main: iav_trans No.%d start spos=%d" % (cnt,spos))
			if(gapless==2):
				(rc,spos)=iav_trans(args,cliptype,cliplist,mpos,spos,mmax,opt,gapless,tempf,p0.stdin)
			else:
				(rc,spos)=iav_trans(args,cliptype,cliplist,mpos,spos,mmax,opt,gapless,tempf,None)
			time.sleep(0)
			
			#next img_pos(spos) will be returned by iav_trans its'self
			dbg("---- main: iav_trans end spos=%d" % (spos))

			#---- prepair for next
			if(g_exit):
				dbg("---- main: iav_trans error g_exit=%d, finally exit" % (g_exit))
				break
			elif(rc!=0):
				#dbg("---- main: iav_trans error exit=%d" % (rc))
				#sys.exit(rc)
				dbg("---- main: iav_trans error exit=%d, but Go-on" % (rc))
				time.sleep(1)
			
			if(not g_multi_pipe):
				cnt+=1
				apos+=1
				if(apos>=len(alist)):
					apos=0
				#audio_in=alist[apos].encode(encode_p)
				audio_in=alist[apos]
				args=[prog,"-i",audio_in]+mpara
			setnxpos(mpos+1,apos,mmax=mmax)
		#vo.close()
	else:
		# no audio stuttering, but long blackout when music change
		(rc,spos)=iav_trans(args,cliptype,cliplist,mpos,opt.img_pos,mmax,opt,0,tempf,None)

	tm_end0=time.time()
	dbg("main: End time elapsed=%.2f" % (tm_end0-tm_start0))
	#sys.exit(g_exit)
	os._exit(g_exit)
