#-------------------------------------------------------------------------------
# Name:        Exo2Srt Converter (JP/EN Combined version)
# Purpose:     Convert EXO files generated by AviUtl(JP/EN Combined) to Srt subtitle
#
# Author:      MaverickTse
#
# Created:     23/11/2013
# Copyright:   (c) MaverickTse 2013
# Licence:     MIT
#-------------------------------------------------------------------------------
import sys
import collections
import re
import os
import traceback
from string import Template
#----------------------------------------
# Global variables
#----------------------------------------
src_encoding= "sjis" #default EXO is in Shift-JIS. If error occurred complaining can't decode file, change this to your system codepage
#For a list of usable encodings, check:
#http://docs.python.org/2.4/lib/standard-encodings.html

out_encoding= "utf-8" #encoding for SRT file. Usually utf-8 works well.

srt_template= Template("$ID\n$Stime --> $Etime\n$Content\n\n") #Srt format template

#---------------------------------------------------------
#Genaral users should not need to change anything below
#--------------------------------------------------------
def setFrameDuration(src_str):
    rg= r"rate=(\d+)"
    rg2= r"scale=(\d+)"
    p= re.compile(rg, re.M)
    p2= re.compile(rg2, re.M)
    m= p.search(src_str)
    m2= p2.search(src_str)
    if m:
        fps= m.group(1)
        fps_scale= m2.group(1)
        ifps= float(fps)/float(fps_scale)
        return 1000/ifps
    else:
        return -1


def readEXO(filename):  #filename as string
    with open(filename, encoding= src_encoding) as handle:
        src_string= handle.read()
    return src_string   # return the file's text as string

def getTimeStringFromFrame(frameNum, frameLen):
    fullMs= int(frameNum* frameLen)
    ms= fullMs % 1000
    hr= int(fullMs // 3600000)
    min= int((fullMs-hr*3600000)//60000)
    sec= int((fullMs-ms)//1000-hr*3600-min*60)
    format_str= "{:0>2}" #leading zero making up 2 digits
    hr_str= format_str.format(hr)
    min_str= format_str.format(min)
    sec_str= format_str.format(sec)
    ms_str= "{:0>3}".format(ms) #leading zeros making up 3 digits for milliseconds
    return hr_str+':'+min_str+':'+sec_str+','+ms_str


def hex2String(hexstring):
    txt= bytes.fromhex(hexstring).decode("utf-16")    #convert the hex string to actual text
    txt= txt.rstrip("\x00")
    if ("\x00" in txt):                       #Find interrupting NULL byte
                firstnull= txt.find("\x00")           #get NULL's index
                txt=txt[0:firstnull]
    txt= txt.replace("\r","") #remove excess CR mark

    return txt

def srtWriter(srtfilename, out_str):
    with open(srtfilename, mode='w', encoding=out_encoding) as saveHandle:
        saveHandle.write(out_str)

def srtBuilder(infile):
    dtbl_StartFrame= dict() #table of Starting frames
    dtbl_EndFrame= dict() #table of Ending frames
    dtbl_Content= dict() #table of text
    offsetPattern=r"offset(\d+)"
    offsetrg= re.compile(offsetPattern, re.I)
    offsetmatch= offsetrg.search(infile)
    offsetFrame= 0
    if (offsetmatch):
        offsetFrame= int(offsetmatch.group(1))
        print("Found offset", offsetFrame)

    p= (r"start=(\d+)\n"
    r"end=(\d+)\n"
    r"layer=\d+\n"
    r"(group=\d+\n)*?"
    r"overlay=\d+\n"
    r"camera=\d+\n"
    r"\[\d+\.0\]\n"
    r"_name=(テキスト|Text)\n"
    r".+?"
    r"text=([0-9a-f]+?)\n")
    rg= re.compile(p, re.M|re.IGNORECASE|re.DOTALL)
    src= readEXO(infile)
    fLen= setFrameDuration(src)
    if (fLen==-1):
        print("Warning: missing frame rate info in file- {0}".format(infile))
        print("Assuming 30fps")
        fLen= 100/3
    m= rg.findall(src)
    if m: #if no text object, m would be NONE and throw error
        #0: st; 1: ed; 2: txt
        i=1 #dicts' key field
        for amatch in m:
            stTime= int(amatch[0])+offsetFrame
            edTime= int(amatch[1])+offsetFrame

            content= hex2String(amatch[len(amatch)-1])   #always the last capture group
            dtbl_StartFrame[i]= stTime                    #Add to Start Frame Tabel
            dtbl_EndFrame[i]= edTime                      #Add to End Frame Table
            dtbl_Content[i]= content                      #Add to text content Table
            i+=1 #increment key's value

        ordered_SFrame= collections.OrderedDict(sorted(dtbl_StartFrame.items(), key=lambda t:int(t[1]))) #sort by starting frame no.
        i=1 #srt item num.
        out_srt=""
        for pair in ordered_SFrame.items():
            key= pair[0]
            stStamp= getTimeStringFromFrame(pair[1], fLen)
            edStamp= getTimeStringFromFrame(dtbl_EndFrame[key], fLen)
            txt= dtbl_Content[key]
            #print(txt) #debug
            srtblock= srt_template.substitute(ID=i, Stime=stStamp, Etime=edStamp, Content=txt) #substitute the template srt
            out_srt+= srtblock
            i+=1

        srtWriter(infile[:-4]+".srt",out_srt) #default to original's file path, just rename the extension to .srt
        print("Processed {0} text objects from {1}".format(i-1, infile))
    else:
        print("No Text object found in {0}".format(infile))




def main():
    try:
        if len(sys.argv) > 1:
            filelist= sys.argv[1:]
            for afile in filelist:
                srtBuilder(afile)
                print(afile)
        else:
            print("No file to process")

    except:
        traceback.print_exc()


if __name__ == '__main__':
    main()
    dummy= input("Press any key")
