#Video Segmenter for adding comments to media files at user specified intervals

"""
TODO:
set video ratio so it doesnt stretch
"""

hotkeys = """
Hotkey list:

* Play/Pause     -   Spacebar
* Add Comment    -   TAB
* Load file      -   F1
* Recover        -   F12
* Quit program   -   Escape

"""

import wx, wx.media, os, csv, sys

#globals
backupInterval = 60000 #change autobackup time
data = []
currentComment = 0
currentTime = -1
tempTime = 0
exported = False
playing = False

#set directories
dir = sys.argv[0]
filename = os.path.basename(dir)
shortPath = dir[:(len(dir)-len(filename))]
dataPath = shortPath + "data/"

#make data folder if it doesnt exist
if not os.path.isdir(dataPath):
    os.makedirs(dataPath)

#class definition for the main window
class MainFrame(wx.Frame):
    def __init__(self, parent, id):
        global playing, backupInterval
        self.screen = wx.GetDisplaySize()
        self.playerSize = [self.screen[0]-40, self.screen[1]-200]
        wx.Frame.__init__(self,parent, id,title = "Media Player", size=(self.screen[0],self.screen[1]), style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER)
        self.Maximize(True)
        
        #create menubar
        menuBar = wx.MenuBar()
        fileMenu = wx.Menu()
        helpMenu = wx.Menu()
        recoverMenu = wx.Menu()
        ID_OPEN = wx.NewId()
        ID_SAVE = wx.NewId()
        ID_EXIT = wx.NewId()
        ID_RECOVER = wx.NewId()
        ID_HOTKEYS = wx.NewId()

        fileMenu.Append(ID_OPEN, "Open Video")
        fileMenu.Append(ID_SAVE, "Save Data")
        fileMenu.Append(ID_EXIT, "Exit")
        recoverMenu.Append(ID_RECOVER, "Recover Data")
        helpMenu.Append(ID_HOTKEYS, "Hotkeys")
        menuBar.Append(fileMenu, "File")
        menuBar.Append(recoverMenu, "Recover")
        menuBar.Append(helpMenu, "Help")
        self.SetMenuBar(menuBar)

        wx.EVT_MENU(self, ID_OPEN, self.loadFile)
        wx.EVT_MENU(self, ID_SAVE, self.exportData)
        wx.EVT_MENU(self, ID_EXIT, self.closeWindow)
        wx.EVT_MENU(self, ID_RECOVER, self.recover)
        wx.EVT_MENU(self, ID_HOTKEYS, self.hotkeys)

        #create buttons
        self.panel = wx.Panel(self)
        self.player = wx.media.MediaCtrl(self.panel, size=(self.playerSize[0], self.playerSize[1]-20))

        self.comment = wx.Button(self.panel,wx.ID_ANY,"Add Comment")
        self.comment.Bind(wx.EVT_BUTTON,self.openComment)

        self.play = wx.Button(self.panel,wx.ID_ANY,"Play/Pause")
        self.play.Bind(wx.EVT_LEFT_DOWN,self.playFile)

        self.stop = wx.Button(self.panel,wx.ID_ANY,"Stop")
        self.stop.Bind(wx.EVT_LEFT_DOWN,self.stopFile)

        #self.slider = wx.Slider(self.panel,wx.ID_ANY,size = (800,-1))
        #self.slider.Bind(wx.EVT_SLIDER,self.Seek)

        self.info_name = wx.StaticText(self.panel,wx.ID_ANY,"Name: ")
        self.info_length = wx.StaticText(self.panel,wx.ID_ANY,"Length: ")
        self.info_pos = wx.StaticText(self.panel,wx.ID_ANY,"Time: ")

        #bind keyboard events/hotkeys
        self.Bind(wx.EVT_CHAR_HOOK, self.onKeyDown)

        #bind exit button
        self.Bind(wx.EVT_CLOSE, self.closeWindow)
        
        #positioning of buttons
        sizer = wx.GridBagSizer(5,5)
        sizer.Add(self.player,(1,1),(1,5))
        #sizer.Add(self.slider,(2,1),(1,5))
        sizer.Add(self.play,(6,2))
        sizer.Add(self.stop,(6,3))
        sizer.Add(self.comment,(6,4))
        sizer.Add(self.info_name,(3,2),(1,4))
        sizer.Add(self.info_length,(4,2),(1,4))
        sizer.Add(self.info_pos,(5,2),(1,4))
        self.panel.SetSizerAndFit(sizer)
        self.Show()
        
        self.timer = wx.Timer(self)
        self.Bind(wx.EVT_TIMER, self.onTimer)
        self.timer.Start(100)

        #auto backup timer
        TIMER_ID = wx.NewId()
        self.backupTimer = wx.Timer(self.panel, TIMER_ID)
        self.backupTimer.Start(backupInterval)
        wx.EVT_TIMER(self.panel, TIMER_ID, self.autoBackup)

    def onTimer(self, event):
        global currentTime
        currentTime = self.player.Tell()
        if int(currentTime)/1000 == -1:
            pass
        else:
            self.info_pos.SetLabel("Time: %i seconds" % (int(currentTime)/1000))
        #self.slider.SetValue(currentTime)
       
    def Seek(self, event):
        self.player.Seek(self.slider.GetValue())
        
    def loadFile(self,event):
        msg = wx.FileDialog(self, message = "Open a media file",
                               style = wx.OPEN,
                               wildcard = "Media Files|*.wma;*.mp3;*.avi;*.mpg;*.mp4;*.mov")
        if msg.ShowModal() == wx.ID_OK:
            path = msg.GetPath()
            self.path = path
            if not self.player.Load(path):
                wx.MessageBox("Unable to load this file, it is in the wrong format")
            else:
                self.player.Play()
                
    def playFile(self, event):
        global playing
        if playing == False:
            playing = True
            self.player.Play()
            #self.slider.SetRange(0, self.player.Length())
            self.info_length.SetLabel('Length: %d seconds' % (self.player.Length()/1000))
            self.info_name.SetLabel("Name: %s" % (os.path.split(self.path)[1]))
        elif playing == True:
            playing = False
            self.player.Pause()
        
    def stopFile(self, event):
        global playing
        playing = False
        self.player.Stop()

    def closeWindow(self, event):
        global exported, data
        if exported == True:
            self.timer.Stop()
            self.backupTimer.Stop()
            self.Destroy()
            sys.exit()
        elif exported == False:
            if len(data) != 0:
                exitBox = wx.MessageDialog(None, "You have not exported your data. All data from the current session will be lost.\n\nAre you sure you want to Exit?", "Exit", wx.YES_NO)
                answer = exitBox.ShowModal()
                if answer == 5103:
                    self.timer.Stop()
                    self.backupTimer.Stop()
                    self.Destroy()
                    sys.exit()
                exitBox.Destroy()
            elif len(data) == 0:
                    self.timer.Stop()
                    self.backupTimer.Stop()
                    self.Destroy()
                    sys.exit()

    def openComment(self, event):
        global currentTime, tempTime
        tempTime = currentTime
        commentBox = Comment(parent=None, id=-1)

    def exportData(self, event):
        global data, exported, dataPath
        filenameEntry = wx.TextEntryDialog(None, "Please enter output filename (without the extension)", "Save data as...", "")
        if filenameEntry.ShowModal() == wx.ID_OK:
            pass
        filename = filenameEntry.GetValue()
        
        path = dataPath + filename + ".txt"
        if os.path.exists(path):
            exported = False
            wx.MessageBox('The file already exists. Please try again', 'Export Error', 
            wx.OK | wx.ICON_INFORMATION)
        elif not os.path.exists(path):
            exported = True
            with open(dataPath + filename + '.txt', 'w') as f:
                w = csv.writer(f, dialect = 'excel-tab')
                w.writerow(['Time (ms)', 'Choice', 'Comment'])
                w.writerows(data)

    def recover(self, event):
        global data, dataPath

        recoverBox = wx.MessageDialog(None, "Recovering clears and replaces all data from the current session.\n\nDo you want to continue?", "Recover?", wx.YES_NO)
        answer = recoverBox.ShowModal()
        if answer == 5103:
            with open(dataPath + "temp.txt", "r") as file:
                reader = csv.reader(file, dialect = 'excel-tab')
                index = 0
                for row in reader:
                    data.insert(index, row)
                    index += 1
            wx.MessageBox("Recovery complete", 'Recovery', wx.OK | wx.ICON_INFORMATION)
        recoverBox.Destroy()

    def hotkeys(self, event):
        hotkeyPage = Hotkeys(parent=None, id=-1)

    #hotkeys
    def onKeyDown(self, event):
        key = event.GetKeyCode()
        if key == wx.WXK_SPACE:
            self.playFile(event)
        elif key == wx.WXK_RETURN or key == wx.WXK_TAB:
            self.openComment(event)
        elif key == wx.WXK_F1:
            self.loadFile(event)
        elif key == wx.WXK_F12:
            self.recover(event)
        elif key == wx.WXK_ESCAPE:
            self.closeWindow(event)

    def autoBackup(self, event):
        global data, dataPath
        path = dataPath + "temp.txt"
        if len(data) != 0:
            with open(path, 'w') as file:
                w = csv.writer(file, dialect = 'excel-tab')
                for row in data:
                    w.writerow(row)

#class definition for comment box
class Comment(wx.Frame):
    def __init__(self, parent, id):
        global current
        #position and size comment box
        commentBoxSize = [frame.screen[0]/4, frame.screen[1]/3]
        commentBoxPos = [(frame.screen[0]/4)*3, frame.screen[1]/4]
        
        wx.Frame.__init__(self, wx.GetApp().TopWindow, id, title="Add Comment", pos=(commentBoxPos[0], commentBoxPos[1]), size=(commentBoxSize[0], commentBoxSize[1]), style = wx.CAPTION)

        commentPanel = wx.Panel(self)
        #move mouse cursor
        self.WarpPointer(150, 100)
        #add form elements
        self.input = wx.TextCtrl(commentPanel, style = wx.TE_MULTILINE, value = "")
        self.save = wx.Button(commentPanel, label="Save")
        self.cancel = wx.Button(commentPanel, label="Cancel")
        self.time = wx.StaticText(commentPanel, label="Time: %s seconds" % str(int(currentTime)/1000))
        self.radioList = ["Structured", "Unstructured", "Neither"]
        self.radio = wx.RadioBox(commentPanel, label="Was this section...", choices=self.radioList, style = wx.RA_HORIZONTAL)

        self.Bind(wx.EVT_BUTTON, self.closeComment, self.cancel)
        self.Bind(wx.EVT_BUTTON, self.saveComment, self.save)
        self.input.Bind(wx.EVT_CHAR, self.onChar)
        #position elements in the window
        sizer = wx.GridBagSizer(7, 7)
        sizer.Add(self.time, (0, 2))
        sizer.Add(self.radio, (1, 0), (1, 3))
        sizer.Add(self.input, (2, 0), (3, 3), flag=wx.EXPAND)
        sizer.Add(self.save, (5, 0))
        sizer.Add(self.cancel, (5, 1))

        sizer.AddGrowableCol(1)
        sizer.AddGrowableRow(2)
        commentPanel.SetSizerAndFit(sizer)

        self.Show()

    def onChar(self, event):
        #disallow enter key
        key = event.GetKeyCode()

        if key in (13, 27):
            return False
        else:
            event.Skip()
            return

    def saveComment(self, event):
        global currentComment, data, tempTime
        commentText = self.input.GetValue()
        radioValue = self.radio.GetSelection()
        data.insert(currentComment, [str(tempTime), str(self.radioList[radioValue]), str(commentText)])
        currentComment += 1
        frame.autoBackup(event)
        self.Close()

    def closeComment(self, event):
        self.Close()

#class definition for hotkey info window
class Hotkeys(wx.Frame):
    def __init__(self, parent, id):
        global hotkeys
        #position and size comment box
        hotkeyBoxSize = [frame.screen[0]/5, frame.screen[1]/4]
        hotkeyBoxPos = [frame.screen[0]/3, frame.screen[1]/3]
        
        wx.Frame.__init__(self, wx.GetApp().TopWindow, id, title="Hotkeys", pos=(hotkeyBoxPos[0], hotkeyBoxPos[1]), size=(hotkeyBoxSize[0], hotkeyBoxSize[1]))
        hotkeyPanel = wx.Panel(self)

        wx.StaticText(hotkeyPanel, -1, hotkeys, (10, 10), (hotkeyBoxSize[0],-1))

        self.Bind(wx.EVT_CHAR_HOOK, self.close)

        self.Show()

    def close(self, event):
        key = event.GetKeyCode()
        if key == wx.WXK_ESCAPE or key == 27:
            self.Close()

if __name__=='__main__':    
    app = wx.App(False)
    frame = MainFrame(parent=None, id=-1)
    app.MainLoop()
