1import sys
2import os
3import wx
4from ui_editor import UIEditor
5from ui_options import UIOptions
6
7from constants import *
8from tools import Tools
9from demuxer import Demuxer
10from muxer import Muxer
11from finder import FindKateStreams
12from options import Options
13
14base_width=480
15base_height=240
16serial_width=(base_width/4)
17language_width=(base_width/4)
18category_width=(base_width/2)
19padding=8
20button_width=(base_width-padding*2)/3
21button_height=32
22list_height=96
23
24class UIMain(wx.Frame):
25  def __init__(self):
26    wx.Frame.__init__(self,None,wx.ID_ANY,kdj_name_version)
27
28    self.options=Options(wx.StandardPaths.Get().GetUserConfigDir())
29
30    try:
31      self.tools=Tools(wx.BeginBusyCursor,wx.EndBusyCursor)
32    except Exception,e:
33      wx.MessageBox('Failed to find necessary tools:\n'+str(e),'Error',style=wx.OK|wx.CENTRE|wx.ICON_ERROR)
34      sys.exit(1)
35
36    self.filename=None
37    self.demuxer=None
38
39    vbox=wx.BoxSizer(wx.VERTICAL)
40    self.SetSizer(vbox)
41
42    self.action_button=self.AddActionButton()
43    vbox.Add(self.action_button,0,wx.EXPAND|wx.ALL,border=padding)
44
45    self.list=self.AddList()
46    vbox.Add(self.list,1,wx.EXPAND|wx.ALL,border=padding)
47
48    bbox=wx.BoxSizer(wx.HORIZONTAL)
49    vbox.Add(bbox,0,wx.EXPAND|wx.ALL,border=padding)
50    self.options_button=self.AddOptionsButton()
51    bbox.Add(self.options_button,0,wx.EXPAND)
52    bbox.Add((padding,0),1,wx.EXPAND)
53    self.help_button=self.AddHelpButton()
54    bbox.Add(self.help_button,0,wx.EXPAND)
55    self.quit_button=self.AddQuitButton()
56    bbox.Add((padding,0),1,wx.EXPAND)
57    bbox.Add(self.quit_button,0,wx.EXPAND)
58
59    self.SetMinSize((base_width+2*padding,base_height))
60    self.Fit()
61    self.Show(True)
62
63  def SetupActionButton(self,button):
64    if self.filename==None:
65      button.SetLabel('Load Ogg stream')
66      button.Bind(wx.EVT_BUTTON,self.OnLoadButton,button)
67    elif self.demuxer!=None:
68      button.SetLabel('Remux file from parts')
69      button.Bind(wx.EVT_BUTTON,self.OnMuxButton,button)
70    else:
71      button.SetLabel('Demux file')
72      button.Bind(wx.EVT_BUTTON,self.OnDemuxButton,button)
73
74  def AddButton(self,text):
75    button=wx.Button(self,wx.ID_ANY,text)
76    return button
77
78  def AddLoadButton(self):
79    button=self.AddButton('Load file')
80    button.Bind(wx.EVT_BUTTON,self.OnLoadButton,button)
81    return button
82
83  def AddActionButton(self):
84    button=self.AddButton('')
85    self.SetupActionButton(button)
86    return button
87
88  def AddOptionsButton(self):
89    button=wx.Button(self,wx.ID_ANY,'Options',size=(button_width,button_height))
90    button.Bind(wx.EVT_BUTTON,self.OnOptionsButton,button)
91    return button
92
93  def AddHelpButton(self):
94    button=wx.Button(self,wx.ID_HELP,'Help',size=(button_width,button_height))
95    button.Bind(wx.EVT_BUTTON,self.OnHelpButton,button)
96    return button
97
98  def AddQuitButton(self):
99    button=wx.Button(self,wx.ID_EXIT,'Quit',size=(button_width,button_height))
100    button.Bind(wx.EVT_BUTTON,self.OnQuitButton,button)
101    return button
102
103  def OnListSizeChanged(self,event):
104    self.Refresh()
105    event.Skip()
106
107  def Edit(self,list_idx=-1):
108    wx.BeginBusyCursor()
109    dlg=UIEditor(self,self.tools,format=self.options.format)
110    try:
111      kate_streams=FindKateStreams(self.demuxer.GetDirectory())
112      if list_idx>=0:
113        idx=self.list.GetItemData(list_idx)
114        dlg.addStream(kate_streams[str(idx)]['filename'])
115      else:
116        for idx2 in kate_streams:
117          dlg.addStream(kate_streams[idx2]['filename'])
118      dlg.ShowModal()
119    except Exception,e:
120      print 'Exception: %s' % str(e)
121    dlg.Destroy()
122    wx.EndBusyCursor()
123
124  def OnListDoubleClicked(self,event):
125    idx=self.list.GetFirstSelected()
126    if idx>=0:
127      self.Edit(idx)
128    event.Skip()
129
130  def AddList(self):
131    list=wx.ListCtrl(self,style=wx.LC_REPORT)
132    list.ClearAll()
133    list.InsertColumn(0,'Serial')
134    list.InsertColumn(1,'Language')
135    list.InsertColumn(2,'Category')
136    list.SetColumnWidth(0,serial_width)
137    list.SetColumnWidth(1,language_width)
138    list.SetColumnWidth(2,category_width)
139    list.Bind(wx.EVT_SIZE,self.OnListSizeChanged)
140    list.Bind(wx.EVT_LEFT_DCLICK,self.OnListDoubleClicked)
141    return list
142
143  def CheckAndContinue(self):
144    if self.filename!=None and self.demuxer!=None:
145      ret=wx.MessageBox('Loaded file not remuxed - Quit anyway ?','Warning',parent=self,style=wx.YES|wx.NO_DEFAULT|wx.CENTRE|wx.ICON_QUESTION)
146      if ret!=wx.YES:
147        return False
148    return True
149
150  def SetFilename(self,dir,file):
151    filename=os.path.join(dir,file)
152    # TODO: some *quick* oggness checks
153    self.filename=filename
154    self.SetupActionButton(self.action_button)
155
156  def OnLoadButton(self,event):
157    if not self.CheckAndContinue():
158      return
159    extensions=['ogg','oga','ogv','ogx']
160    spec='Ogg streams|'
161    for ext in extensions:
162      spec+='*.'+ext+';'
163      spec+='*.'+ext.capitalize()+';'
164      spec+='*.'+ext.upper()+';'
165    spec+='|All files|*;*.*'
166    loader=wx.FileDialog(self,message='Load Ogg stream',wildcard=spec)
167    ret=loader.ShowModal()
168    if ret==wx.ID_OK:
169      filename=loader.GetFilename()
170      dirname=loader.GetDirectory()
171      self.SetFilename(dirname,filename)
172
173  def FillKateList(self,demuxer):
174    streams=FindKateStreams(demuxer.GetDirectory())
175    for idx in streams:
176      stream=streams[idx]
177      self.list.Append(['','',''])
178      list_idx=self.list.GetItemCount()-1
179      self.list.SetStringItem(list_idx,0,stream['serial'])
180      self.list.SetStringItem(list_idx,1,stream['language'])
181      self.list.SetStringItem(list_idx,2,stream['category'])
182      self.list.SetItemData(list_idx,int(idx))
183    return self.list.GetItemCount()>0
184
185  def OnDemuxButton(self,event):
186    try:
187      demuxer=Demuxer(self.tools,self.filename,self.options.format)
188    except Exception,e:
189      wx.MessageBox('Failed to demux file:\n'+str(e),'Error',parent=self,style=wx.OK|wx.CENTRE|wx.ICON_ERROR)
190      return
191
192    if not self.FillKateList(demuxer):
193      wx.MessageBox('No Kate streams found, or not an Ogg stream','Error',parent=self,style=wx.OK|wx.CENTRE|wx.ICON_ERROR)
194      self.filename=None
195      self.SetupActionButton(self.action_button)
196      return
197
198    self.demuxer=demuxer
199    self.SetupActionButton(self.action_button)
200    path=os.path.abspath(self.demuxer.GetDirectory())
201    wx.MessageBox(
202      'Demuxed files are in '+path+'.\n'+
203        'Double click on any entry to edit it.\n'+
204        'You may also edit them with your favorite text editor.',
205      'Edit files',
206      parent=self,
207      style=wx.OK|wx.CENTRE
208    )
209
210  def RemoveTemporaryFiles(self,directory):
211    streams=FindKateStreams(directory)
212    for idx in streams:
213      stream=streams[idx]
214      filename=stream['filename']
215      os.unlink(filename)
216      if os.access(filename+'.ogg',os.F_OK):
217        os.unlink(filename+'.ogg')
218    os.unlink(os.path.join(directory,'misc.ogg'))
219    os.rmdir(directory)
220
221  def OnMuxButton(self,event):
222    directory=self.demuxer.GetDirectory()
223    try:
224      if self.options.save_as_copy:
225        root,ext=os.path.splitext(self.filename)
226        remuxed_name=root+'.remuxed'+ext
227      else:
228        remuxed_name=self.filename
229      muxer=Muxer(self.tools,remuxed_name,directory,'kate')
230      while self.list.GetItemCount()>0: self.list.DeleteItem(0)
231      self.demuxer=None
232      self.SetupActionButton(self.action_button)
233      try:
234        self.RemoveTemporaryFiles(directory)
235      except:
236        wx.MessageBox('Failed to remove all temporary files from\n%s' % directory,'Error',parent=self,style=wx.OK|wx.CENTRE|wx.ICON_ERROR)
237    except Exception,e:
238      wx.MessageBox('Failed to remux file:\n'+str(e),'Error',parent=self,style=wx.OK|wx.CENTRE|wx.ICON_ERROR)
239
240  def OnOptionsButton(self,event):
241    dlg=UIOptions(self,self.options)
242#    dlg.CenterOnScreen()
243    dlg.ShowModal()
244    dlg.Destroy()
245
246  def OnHelpButton(self,event):
247    wx.MessageBox(
248      kdj_name+' is a remuxing program that allows extracting and decoding Kate tracks from an Ogg stream, '+
249      'and recreating that Ogg stream after the Kate streams have been altered.\n'+
250      '\n'+
251      kdj_name+' requires both the Kate tools (kateenc and katedec) and the oggz tools.'+
252      '\n\n'+
253      'Click \'Load Ogg stream\' to load an Ogg file from disk. All types of Ogg streams are supported '+
254      '(eg, audio, video, etc).\n'+
255      '\n'+
256      'Then, click \'Demux file\' to extract Kate streams, and decode them to a temporary place on disk. '+
257      'You may now edit the decoded Kate streams, either by double clicking on the one you wish to change, '+
258      'or with your text editor of choice.\n'+
259      '\n'+
260      'When done, click \'Remux file from parts\' to reconstruct the Ogg stream with the modified Kate streams. '+
261      'If you are notified of any error (eg, syntax errors in the modified Kate streams), then go back and '+
262      'correct any mistakes, and click \'Remux file from parts\' until no errors are reported.\n'
263      '\n'+
264      'You may now play the rebuilt Ogg stream in your audio/video player, and repeat the demux/remux two step '+
265      'process until you are happy with the resulting stream.\n'+
266      '',
267      kdj_name+' help',
268      parent=self,
269      style=wx.OK|wx.CENTRE
270    )
271
272  def OnQuitButton(self,event):
273    if not self.CheckAndContinue():
274      return
275    if self.demuxer!=None and self.options.remove_temporary_files:
276      directory=self.demuxer.GetDirectory()
277      try:
278        self.RemoveTemporaryFiles(directory)
279      except:
280        wx.MessageBox('Failed to remove all temporary files from\n%s' % directory,'Error',parent=self,style=wx.OK|wx.CENTRE|wx.ICON_ERROR)
281    sys.exit(0)
282
283