home *** CD-ROM | disk | FTP | other *** search
/ PC Professionell 2006 June / PCpro_2006_06.ISO / files / freeware / openvip.exe / {app} / MainFrame.py < prev    next >
Encoding:
Python Source  |  2003-06-18  |  35.7 KB  |  794 lines

  1. #
  2. # This file is part of OpenVIP (http://openvip.sourceforge.net)
  3. #
  4. # Copyright (C) 2002-2003
  5. # Michal Dvorak, Jiri Sedlar, Antonin Slavik, Vaclav Slavik, Jozef Smizansky
  6. #
  7. # This program is licensed under GNU General Public License version 2;
  8. # see file COPYING in the top level directory for details.
  9. #
  10. # $Id: MainFrame.py,v 1.66 2003/06/18 16:55:08 vaclavslavik Exp $
  11. #
  12. # The main application window
  13. #
  14. from wxPython.wx import *
  15. from wxPython.html import *
  16. from wxPython.htmlhelp import *
  17.  
  18. import TimelineWidget
  19. from ObjectPanel import ObjectPanel
  20. from PreviewFrame import PreviewFrame
  21. from OutputSettingsDialog import OutputSettingsDialog
  22. from AboutDialog import AboutDialog
  23. import globals, logging, model, notify, openvip, render, worker
  24. import copy
  25. from conv2 import getBestAudioFormat, getBestVideoFormat
  26.  
  27. def create(parent):
  28.     return mainFrame(parent)
  29.  
  30. [wxID_MAINFRAME, wxID_MAINFRAMESPLITTER, wxID_MAINFRAMETOOLBAR, 
  31. ] = map(lambda _init_ctrls: wxNewId(), range(3))
  32.  
  33. [wxID_MAINFRAMETOOLBARIMPORT, wxID_TOOLBARUNDO, wxID_TOOLBARREDO] = map(lambda init_toolbar: wxNewId(), range(3))
  34.  
  35. [wxID_FILEMENUNEWTMLN, wxID_MAINFRAMEFILEMENUEXIT, wxID_FILEMENULOADTMLN, wxID_FILEMENUSAVETMLN, wxID_FILEMENUSAVETMLNAS, wxID_FILEMENURENDER, wxID_EDITMENUUNDO, wxID_EDITMENUREDO, wxID_EDITMENUIMPORT, wxID_VIEWMENUFAST, wxID_VIEWMENUONE, wxID_VIEWMENUTHUMB, wxID_VIEWMENUPREV, wxID_EDITMENUREMOVE, wxID_EDITMENUINSTRANS] = map(lambda _init_coll_filemenu_Items: wxNewId(), range(15))
  36.  
  37. wxID_CUTMODEL = wxNewId()
  38. wxID_ZOOMBOX = wxNewId()
  39. wxID_ZOOMPLUS = wxNewId()
  40. wxID_ZOOMMINUS = wxNewId()
  41. wxID_ZOOMSLIDER = wxNewId()
  42. wxID_PREVFRAME = wxNewId()
  43. wxID_SHOW_HELP = wxNewId()
  44. wxID_SHOW_ABOUT = wxNewId()
  45. wxID_ZOOMMIN = wxNewId()
  46. wxID_ZOOMMAX = wxNewId()
  47. wxID_ZOOMIN = wxNewId()
  48. wxID_ZOOMOUT = wxNewId()
  49.  
  50. ZOOM_LEFT = 0
  51. ZOOM_RIGHT = 1000
  52. ZOOM_STEP = 50
  53.  
  54. class mainFrame(wxFrame):
  55.     """This is the main application window. It displays menu, toolbar,
  56.        timeline widget and object panel. It responds to events from
  57.        menu and toolbar."""
  58.     def init_menuBar(self, parent):
  59.         parent.Append(self.filemenu, title='&File')
  60.         parent.Append(self.editmenu, title='&Edit')
  61.         parent.Append(self.viewmenu, title='&View')
  62.  
  63.         help = wxMenu()
  64.         help.Append(wxID_SHOW_HELP, '&Contents')
  65.         help.AppendSeparator()
  66.         help.Append(wxID_SHOW_ABOUT, '&About ...')
  67.         parent.Append(help, title='&Help')
  68.         EVT_MENU(self, wxID_SHOW_HELP, self.OnHelp)
  69.         EVT_MENU(self, wxID_SHOW_ABOUT, self.OnAbout)
  70.  
  71.     def init_filemenu_items(self, parent):
  72.         parent.Append(wxID_FILEMENUNEWTMLN,"&New timeline\tCtrl-N")
  73.         parent.Append(wxID_FILEMENULOADTMLN,"&Open timeline ...\tCtrl-O")
  74.         self.savetimeline_item=wxMenuItem(parent, wxID_FILEMENUSAVETMLN,"&Save timeline\tCtrl-S")
  75.         parent.AppendItem(self.savetimeline_item)
  76.         self.savetimelineas_item=wxMenuItem(parent, wxID_FILEMENUSAVETMLNAS,"Save timeline &as ...")
  77.         parent.AppendItem(self.savetimelineas_item)
  78.         self.render_item=wxMenuItem(parent, wxID_FILEMENURENDER,"&Render ...")
  79.         parent.AppendItem(self.render_item)
  80.         parent.AppendSeparator()
  81.         parent.Append(helpString='', id=wxID_MAINFRAMEFILEMENUEXIT,
  82.               item='E&xit', kind=wxITEM_NORMAL)
  83.         EVT_MENU(self, wxID_FILEMENUNEWTMLN, self.OnNewTimeline)
  84.         EVT_MENU(self, wxID_FILEMENULOADTMLN, self.OnLoadTimeline)
  85.         EVT_MENU(self, wxID_FILEMENUSAVETMLN, self.OnSaveTimeline)
  86.         EVT_MENU(self, wxID_FILEMENUSAVETMLNAS, self.OnSaveTimelineAs)
  87.         EVT_MENU(self, wxID_FILEMENURENDER, self.OnRender)
  88.         EVT_MENU(self, wxID_MAINFRAMEFILEMENUEXIT, self.OnExit)
  89.  
  90.     def init_editmenu_items(self, parent):
  91.         self.undo_item=wxMenuItem(parent, wxID_EDITMENUUNDO,"&Undo\tCtrl-Z")
  92.         parent.AppendItem(self.undo_item)
  93.         self.redo_item=wxMenuItem(parent, wxID_EDITMENUREDO,"&Redo\tCtrl-Y")
  94.         parent.AppendItem(self.redo_item)
  95.         self.remove_item=wxMenuItem(parent, wxID_EDITMENUREMOVE,"R&emove selected objects\tCtrl-X")
  96.         parent.AppendItem(self.remove_item)
  97.         parent.AppendSeparator()
  98.         self.importclip_item=wxMenuItem(parent, wxID_EDITMENUIMPORT,"&Insert clip ...")
  99.         parent.AppendItem(self.importclip_item)
  100.         self.inserttrans_item=wxMenuItem(parent, wxID_EDITMENUINSTRANS,"Insert &transition ...")
  101.         parent.AppendItem(self.inserttrans_item)
  102.         EVT_MENU(self, wxID_EDITMENUUNDO, self.OnUndo)
  103.         EVT_MENU(self, wxID_EDITMENUREDO, self.OnRedo)
  104.         EVT_MENU(self, wxID_EDITMENUIMPORT, self.OnImport)
  105.         EVT_MENU(self, wxID_EDITMENUREMOVE, self.OnRemove)
  106.         EVT_MENU(self, wxID_EDITMENUINSTRANS, self.OnInsertTrans)
  107.  
  108.     def init_viewmenu_items(self, parent):
  109.         self.onethumb_item=wxMenuItem(parent, wxID_VIEWMENUONE,"&Info mode\tCtrl-1", kind = wxITEM_RADIO)
  110.         parent.AppendItem(self.onethumb_item)
  111.         self.allthumb_item=wxMenuItem(parent, wxID_VIEWMENUTHUMB,"Film&strip mode\tCtrl-2", kind = wxITEM_RADIO)
  112.         parent.AppendItem(self.allthumb_item)
  113.         self.fasttmln_item=wxMenuItem(parent, wxID_VIEWMENUFAST,"&Fast mode\tCtrl-3", kind = wxITEM_RADIO)
  114.         parent.AppendItem(self.fasttmln_item)
  115.         parent.AppendSeparator()
  116.         parent.Append(wxID_ZOOMMIN, 'Minimal Zoom')
  117.         parent.Append(wxID_ZOOMOUT, 'Zoom &Out')
  118.         parent.Append(wxID_ZOOMIN, 'Zoom &In')
  119.         parent.Append(wxID_ZOOMMAX, 'Maximal Zoom')
  120.         parent.AppendSeparator()
  121.         self.prevwindow_item = wxMenuItem(parent, wxID_VIEWMENUPREV, "&Preview window\tCtrl-P", kind = wxITEM_CHECK)
  122.         parent.AppendItem(self.prevwindow_item)        
  123.         EVT_MENU(self, wxID_VIEWMENUFAST, self.OnViewFast)
  124.         EVT_MENU(self, wxID_VIEWMENUONE, self.OnViewOne)
  125.         EVT_MENU(self, wxID_VIEWMENUTHUMB, self.OnViewThumb)
  126.         EVT_MENU(self, wxID_VIEWMENUPREV, self.OnPreviewFrame)
  127.         mode = wxConfigBase_Get().ReadInt('/TimelineWidget/thumb_mode',
  128.                                           TimelineWidget.MODE_ONE_THUMBNAIL)
  129.         if mode == TimelineWidget.MODE_FAST:
  130.             self.fasttmln_item.Check(True)
  131.         elif mode == TimelineWidget.MODE_ONE_THUMBNAIL:
  132.             self.onethumb_item.Check(True)
  133.         else:
  134.             self.allthumb_item.Check(True)
  135.  
  136.     def init_toolbar(self, parent):
  137.         parent.AddTool(bitmap=wxBitmap('bitmaps/open.png', wxBITMAP_TYPE_PNG),
  138.               id=wxID_MAINFRAMETOOLBARIMPORT, isToggle=False, longHelpString='',
  139.               pushedBitmap=wxNullBitmap, shortHelpString='Import clip')
  140.         parent.AddTool(bitmap=wxBitmap('bitmaps/undo.png', wxBITMAP_TYPE_PNG),
  141.               id=wxID_TOOLBARUNDO, isToggle=False, longHelpString='',
  142.               pushedBitmap=wxNullBitmap, shortHelpString='Undo')
  143.         parent.AddTool(bitmap=wxBitmap('bitmaps/redo.png', wxBITMAP_TYPE_PNG),
  144.               id=wxID_TOOLBARREDO, isToggle=False, longHelpString='',
  145.               pushedBitmap=wxNullBitmap, shortHelpString='Redo')
  146.  
  147.         parent.AddSeparator()
  148.         
  149.         parent.AddTool(bitmap=wxBitmap('bitmaps/cut.png', wxBITMAP_TYPE_PNG),
  150.               id=wxID_CUTMODEL, isToggle=True, longHelpString='',
  151.               pushedBitmap=wxNullBitmap, shortHelpString='Cut object')
  152.         
  153.         parent.AddSeparator()
  154.         
  155.         parent.AddTool(bitmap=wxBitmap('bitmaps/minus.png', wxBITMAP_TYPE_PNG),
  156.               id=wxID_ZOOMMINUS, isToggle=False, longHelpString='',
  157.               pushedBitmap=wxNullBitmap, shortHelpString='Zoom Out')
  158.  
  159.         self.ZoomSlider = wxSlider(parent, wxID_ZOOMSLIDER,
  160.                                    value=(ZOOM_RIGHT - ZOOM_LEFT) / 2, minValue=ZOOM_LEFT, maxValue=ZOOM_RIGHT,
  161.                                    size=wxSize(80,-1),
  162.                                    style=wxSL_HORIZONTAL|wxDOUBLE_BORDER)
  163.         parent.AddControl(self.ZoomSlider)
  164.         self.ZoomSlider.lastvalue = self.ZoomSlider.GetValue()
  165.  
  166.         #arbitrary values, will be recalculated anyway
  167.         self.ZoomSlider.minzoom = 10
  168.         self.ZoomSlider.maxzoom = 10 + ZOOM_RIGHT - ZOOM_LEFT
  169.         self.ZoomSlider.lastThumbHeight = -1
  170.         self.ZoomSlider.lastTimelineWidth = -1
  171.         self.ZoomSlider.initialized = False
  172.         
  173.         parent.AddTool(bitmap=wxBitmap('bitmaps/plus.png', wxBITMAP_TYPE_PNG),
  174.               id=wxID_ZOOMPLUS, isToggle=False, longHelpString='',
  175.               pushedBitmap=wxNullBitmap, shortHelpString='Zoom In')
  176.         
  177.         parent.SetToolSeparation(20)
  178.         
  179.         EVT_TOOL(self, wxID_ZOOMPLUS, self.OnZoom) 
  180.         EVT_TOOL(self, wxID_ZOOMMINUS, self.OnZoom) 
  181.         EVT_TOOL(self, wxID_ZOOMIN, self.OnZoom) 
  182.         EVT_TOOL(self, wxID_ZOOMOUT, self.OnZoom) 
  183.         EVT_TOOL(self, wxID_ZOOMMIN, self.OnZoom) 
  184.         EVT_TOOL(self, wxID_ZOOMMAX, self.OnZoom) 
  185.         EVT_SCROLL(self.ZoomSlider, self.OnZoom) 
  186.         EVT_TOOL(self, wxID_TOOLBARUNDO, self.OnUndo) 
  187.         EVT_TOOL(self, wxID_TOOLBARREDO, self.OnRedo) 
  188.         EVT_TOOL(self, wxID_MAINFRAMETOOLBARIMPORT, self.OnImport) 
  189.         EVT_TOOL(self, wxID_CUTMODEL, self.OnCut)
  190.         
  191.         parent.Realize()
  192.  
  193.     def _init_utils(self):
  194.         self.menuBar = wxMenuBar()
  195.  
  196.         self.filemenu = wxMenu(title='')
  197.         self.init_filemenu_items(self.filemenu)
  198.         self.editmenu = wxMenu(title='')
  199.         self.init_editmenu_items(self.editmenu)
  200.         self.viewmenu = wxMenu(title='')
  201.         self.init_viewmenu_items(self.viewmenu)
  202.  
  203.         self.init_menuBar(self.menuBar)
  204.  
  205.     def _init_ctrls(self, prnt):
  206.         # generated method, don't edit
  207.         cfg = wxConfigBase_Get()
  208.         size = wxSize(cfg.ReadInt('/MainFrame/width', 800),
  209.                       cfg.ReadInt('/MainFrame/height', 600))
  210.         wxFrame.__init__(self, id=wxID_MAINFRAME, name='mainFrame', parent=prnt,
  211.               size=size, style=wxDEFAULT_FRAME_STYLE, title='OpenVIP Editor')
  212.         self._init_utils()
  213.         self.SetMenuBar(self.menuBar)
  214.  
  215.         self.splitter = wxSplitterWindow(id=wxID_MAINFRAMESPLITTER,
  216.               name='splitter', parent=self, point=wxPoint(0, 0),
  217.               size=wxSize(739, 485), style=wxSP_3D)
  218.         self.splitter.SetMinimumPaneSize(50)
  219.  
  220.         self.toolbar = wxToolBar(id=wxID_MAINFRAMETOOLBAR, name='toolbar',
  221.               parent=self, style=wxTB_HORIZONTAL | wxNO_BORDER )
  222.         self.init_toolbar(self.toolbar)
  223.  
  224.     def __init__(self, parent):
  225.         self.help = None
  226.         self._init_ctrls(parent)
  227.         self.SetToolBar(self.toolbar)
  228.         self.objectPanel = ObjectPanel(self.splitter, -1, wxDefaultPosition,
  229.                                        wxDefaultSize, wxTAB_TRAVERSAL,
  230.                                        "objectPanel")
  231.         self.previewframe = PreviewFrame(self, wxID_PREVFRAME, "")
  232.         EVT_CLOSE(self.previewframe, self.OnClosePrevFrame)
  233.         self.timeline = TimelineWidget.TimelineWidget(self.splitter, -1,
  234.                                    wxDefaultSize, objectPanel=self.objectPanel, previewframe=self.previewframe)
  235.         self.splitter.SplitVertically(self.timeline, self.objectPanel,
  236.                                       self.splitter.GetSize()[0]*3/4)
  237.         self.current_filename = None
  238.         self.history = []
  239.         self.outdialog = OutputSettingsDialog(self, -1, "")
  240.         self.outdialog.viewer.SetValue(wxConfigBase_Get().Read('/MainFrame/viewer', ''))
  241.         self.EnableItems(False)
  242.         self.importclip_item.Enable(True)
  243.         self.toolbar.EnableTool(wxID_MAINFRAMETOOLBARIMPORT,True)
  244.         EVT_CLOSE(self, self.OnClose)
  245.         render.EVT_RENDERING_DONE(self, self.OnRenderFinished)
  246.  
  247.     def EnableItems(self, enable):
  248.         """Toggles on or off all widgets that cannot be used without
  249.            a timeline."""
  250.         self.savetimeline_item.Enable(enable)
  251.         self.savetimelineas_item.Enable(enable)
  252.         self.render_item.Enable(enable)
  253.         self.undo_item.Enable(enable)
  254.         self.redo_item.Enable(enable)
  255.         self.importclip_item.Enable(enable)
  256.         self.remove_item.Enable(enable)
  257.         self.inserttrans_item.Enable(enable)
  258.         self.onethumb_item.Enable(enable)
  259.         self.allthumb_item.Enable(enable)
  260.         self.fasttmln_item.Enable(enable)
  261.         self.prevwindow_item.Enable(enable)
  262.         self.viewmenu.Enable(wxID_ZOOMIN,enable);
  263.         self.viewmenu.Enable(wxID_ZOOMOUT,enable);
  264.         self.viewmenu.Enable(wxID_ZOOMMIN,enable);
  265.         self.viewmenu.Enable(wxID_ZOOMMAX,enable);
  266.         self.toolbar.EnableTool(wxID_MAINFRAMETOOLBARIMPORT,enable)
  267.         self.toolbar.EnableTool(wxID_TOOLBARUNDO,enable)
  268.         self.toolbar.EnableTool(wxID_TOOLBARREDO,enable)
  269.         self.toolbar.EnableTool(wxID_CUTMODEL,enable)
  270.         self.toolbar.EnableTool(wxID_ZOOMMINUS,enable)
  271.         self.toolbar.EnableTool(wxID_ZOOMPLUS,enable)
  272.         self.ZoomSlider.Enable(enable)
  273.  
  274.     def OnClose(self, event):
  275.         """Saves main window size and external viewer path on exit."""
  276.         self.previewframe.WaitUntilNotUsed()
  277.         cfg = wxConfigBase_Get()
  278.         size = self.GetSize()
  279.         cfg.WriteInt('/MainFrame/width', size.x)
  280.         cfg.WriteInt('/MainFrame/height', size.y)
  281.         cfg.Write('/MainFrame/viewer', self.outdialog.viewer.GetValue())
  282.         event.Skip()
  283.  
  284.     def RecalcZoomValue(self):
  285.         """When minimal or maximal setting of zoom change, this function tries to set the
  286.            slider to such a position that the absolute zoom value doesn't change."""
  287.         self.ZoomSlider.lastTimelineWidth = self.timeline.GetClientWidth()
  288.         if self.timeline.model.length() == 0:
  289.             #timeline is empty, set default zoom next time a user adds something
  290.             self.ZoomSlider.initialized = False
  291.             newminz = 10
  292.             self.ZoomSlider.SetValue(ZOOM_LEFT);
  293.         else:
  294.             newminz = float(self.timeline.GetClientWidth()) / self.timeline.model.length()
  295.         newmaxz = newminz
  296.         for o in self.timeline.model.objects:
  297.             if (o.track)[0] == 'V' and o.track != "VFx":
  298.                 aspect = -1
  299.                 fps = -1
  300.                 try:
  301.                     fi = globals.get_file_info(o.src_spec["filename"])
  302.                     aspect = (fi.video_streams[0]).aspect
  303.                     fps = (fi.video_streams[0]).fps
  304.                 except openvip.Error:
  305.                     wxLogWarning("Could not get information about file '%s'." % o.src_spec)
  306.                 tmp = aspect * fps * self.timeline.GetThumbnailHeight()
  307.                 if tmp > newmaxz:
  308.                     newmaxz = tmp
  309.                     
  310.         if newmaxz == newminz:
  311.             #there are probably no video tracks - just put something into the new maximum zoom setting
  312.             newmaxz = newminz * 10
  313.  
  314.         if not self.ZoomSlider.initialized:
  315.             #the timeline was empty before the current function call, so we will set default zoom
  316.             self.ZoomSlider.minzoom = newminz
  317.             self.ZoomSlider.maxzoom = newmaxz
  318.             self.ZoomSlider.SetValue(ZOOM_LEFT);
  319.             self.CheckZoomButtons()
  320.             return
  321.             
  322.         maxz = self.ZoomSlider.maxzoom
  323.         minz = self.ZoomSlider.minzoom
  324.         v = self.ZoomSlider.GetValue()
  325.         lastv = self.ZoomSlider.lastvalue
  326.         self.ZoomSlider.SetValue( ((v - ZOOM_LEFT) * (maxz - minz) + (minz - newminz) * (ZOOM_RIGHT - ZOOM_LEFT)) / (newmaxz - newminz) + ZOOM_LEFT )
  327.         self.ZoomSlider.lastvalue = ((lastv - ZOOM_LEFT) * (maxz - minz) + (minz - newminz) * (ZOOM_RIGHT - ZOOM_LEFT)) / (newmaxz - newminz) + ZOOM_LEFT
  328.         self.ZoomSlider.minzoom = newminz
  329.         self.ZoomSlider.maxzoom = newmaxz
  330.         if newminz < minz and self.ZoomSlider.GetValue() == ZOOM_LEFT:
  331.             self.ZoomSlider.SetValue(ZOOM_LEFT + 1)
  332.             self.ZoomSlider.lastvalue = ZOOM_LEFT + 1
  333.         if newmaxz > maxz and self.ZoomSlider.GetValue() == ZOOM_RIGHT:
  334.             self.ZoomSlider.SetValue(ZOOM_RIGHT - 1)
  335.             self.ZoomSlider.lastvalue = ZOOM_RIGHT - 1
  336.         self.CheckZoomButtons()
  337.  
  338.  
  339.     def UpdateZoom(self):
  340.         """This function actually changes the zoom of timeline."""
  341.         if self.timeline.GetClientWidth() != self.ZoomSlider.lastTimelineWidth:
  342.             self.RecalcZoomValue()
  343.         if self.timeline.model == None: return
  344.         if self.timeline.model.length() != 0:
  345.             self.ZoomSlider.initialized = True
  346.         th = self.timeline.GetThumbnailHeight()
  347.         if self.ZoomSlider.lastThumbHeight < 0: 
  348.             self.ZoomSlider.lastThumbHeight = th
  349.         elif th != self.ZoomSlider.lastThumbHeight:
  350.             self.ZoomSlider.maxzoom = float(self.ZoomSlider.maxzoom) * th / self.ZoomSlider.lastThumbHeight
  351.             self.ZoomSlider.lastThumbHeight = th
  352.         self.timeline.SetZoom( float(self.ZoomSlider.GetValue() - ZOOM_LEFT) / (ZOOM_RIGHT - ZOOM_LEFT) * (self.ZoomSlider.maxzoom - self.ZoomSlider.minzoom) + self.ZoomSlider.minzoom )
  353.         self.CheckZoomButtons()
  354.  
  355.     def OnZoom(self, event):
  356.         """This function only does basic event handling."""
  357.         if self.timeline.GetClientWidth() != self.ZoomSlider.lastTimelineWidth:
  358.             self.RecalcZoomValue()
  359.         max = self.ZoomSlider.GetMax()
  360.         min = self.ZoomSlider.GetMin()
  361.         tmp = self.ZoomSlider.GetValue()
  362.         if (event.GetId() == wxID_ZOOMPLUS or event.GetId() == wxID_ZOOMIN):
  363.             tmp = self.ZoomSlider.GetValue() + ZOOM_STEP
  364.             if tmp > max : tmp = max
  365.         elif (event.GetId() == wxID_ZOOMMINUS or event.GetId() == wxID_ZOOMOUT):
  366.             tmp = self.ZoomSlider.GetValue() - ZOOM_STEP
  367.             if tmp < min : tmp = min
  368.         elif (event.GetId() == wxID_ZOOMMIN):
  369.             tmp = ZOOM_LEFT
  370.         elif (event.GetId() == wxID_ZOOMMAX):
  371.             tmp = ZOOM_RIGHT
  372.         if (tmp != self.ZoomSlider.lastvalue):
  373.             self.ZoomSlider.SetValue( tmp )
  374.             self.ZoomSlider.lastvalue = tmp
  375.             self.UpdateZoom()
  376.         
  377.     def CheckZoomButtons(self):
  378.         """If zoom is maximal/minimal, disable the corresponding button."""
  379.         if self.ZoomSlider.GetValue() == self.ZoomSlider.GetMin():
  380.             self.toolbar.EnableTool(wxID_ZOOMMINUS,False)
  381.         else:
  382.             self.toolbar.EnableTool(wxID_ZOOMMINUS,True)
  383.         if self.ZoomSlider.GetValue() == self.ZoomSlider.GetMax():
  384.             self.toolbar.EnableTool(wxID_ZOOMPLUS,False)
  385.         else:
  386.             self.toolbar.EnableTool(wxID_ZOOMPLUS,True)
  387.         self.ZoomSlider.Refresh()
  388.         
  389.     def OnCut(self, event):
  390.         if self.timeline.model == None: return
  391.         if event.IsChecked():
  392.             self.timeline.Cut()
  393.         else:
  394.             self.timeline.CancelCut()
  395.     
  396.     def OnNewTimeline(self, event):
  397.         """Creates a new timeline and clears the history of changes."""
  398.         self.timeline.SetModel(model.Timeline())
  399.         self.history_index = 0
  400.         self.history = [copy.deepcopy(self.timeline.model)]
  401.         notify.listen(self.timeline.model, self.OnChangeTimeline)
  402.         self.EnableItems(True)
  403.         self.RecalcZoomValue()
  404.         self.UpdateZoom()
  405.         
  406.     def OnLoadTimeline(self, event):
  407.         """Displays fileselector for loading timeline from a file,
  408.         loads it and sets a default zoom."""
  409.         ret = wxFileSelector('Load timeline', wildcard='OpenVIP timeline (*.timeline)|*.timeline',
  410.                              flags=wxOPEN|wxFILE_MUST_EXIST)
  411.         if ret == '': return
  412.         self.OpenFile(ret)
  413.         self.RecalcZoomValue()
  414.         self.UpdateZoom()
  415.  
  416.     def OnSaveTimeline(self, event):
  417.         """Saves timeline to a file."""
  418.         if self.current_filename!=None:
  419.             self.timeline.model.save(self.current_filename)
  420.         else:
  421.             self.OnSaveTimelineAs(event)
  422.  
  423.     def OnSaveTimelineAs(self, event):
  424.         """Saves timeline to a file, always asks for a new name."""
  425.         ret = wxFileSelector('Save timeline', wildcard='OpenVIP timeline (*.timeline)|*.timeline',
  426.                              flags=wxSAVE|wxOVERWRITE_PROMPT)
  427.         if ret == '': return
  428.         self.timeline.model.save(ret)
  429.         self.current_filename = ret
  430.  
  431.     def OnUndo(self, event):
  432.         """Reverts the last change. The history_index always points to
  433.            a copy of the current model in the history list."""
  434.         if self.history_index>0:
  435.             self.history_index-=1
  436.             notify.unlisten(self.timeline.model, self.OnChangeTimeline)
  437.             self.timeline.SetModel(copy.deepcopy(self.history[self.history_index]))
  438.             notify.listen(self.timeline.model, self.OnChangeTimeline)
  439.  
  440.     def OnRedo(self, event):
  441.         """Step forward in history list."""
  442.         if self.history_index<len(self.history)-1:
  443.             self.history_index+=1
  444.             notify.unlisten(self.timeline.model, self.OnChangeTimeline)
  445.             self.timeline.SetModel(copy.deepcopy(self.history[self.history_index]))
  446.             notify.listen(self.timeline.model, self.OnChangeTimeline)
  447.  
  448.     def OnChangeTimeline(self):
  449.         """Timeline changed - update the history and preview frame."""
  450.         self.history=self.history[:self.history_index+1]
  451.         self.history.append(copy.deepcopy(self.timeline.model))
  452.         if len(self.history)>50:
  453.             self.history.pop(0)
  454.         self.history_index=len(self.history)-1
  455.         self.RecalcZoomValue()
  456.         if not self.ZoomSlider.initialized:
  457.             self.UpdateZoom()
  458.         self.previewframe.SetModel(self.timeline.model) 
  459.  
  460.     def OnExit(self, event):
  461.         self.previewframe.WaitUntilNotUsed()
  462.         self.Close()
  463.     
  464.     def OnRenderFinished(self, event):
  465.         """Execute external viewer after rendering finished."""
  466.         if self.use_viewer:
  467.             wxExecute(self.outdialog.viewer.GetValue()+" "+self.outdialog.output_filename.GetValue(),wxEXEC_ASYNC)
  468.         self.use_viewer = False;
  469.  
  470.     def OnRender(self, event):
  471.         """Shows the render dialog and renders the output file."""
  472.         timesel=self.timeline.GetTimeSelection()
  473.         self.outdialog.render_radio.EnableItem(1, timesel!=None)
  474.         vf = getBestVideoFormat(self.timeline.model,self.timeline.model.length(),self.timeline.model.beginning())
  475.         if vf!=None:
  476.             self.outdialog.video_width.SetValue(vf.width)
  477.             self.outdialog.video_height.SetValue(vf.height)
  478.             self.outdialog.video_fps.SetValue(vf.fps)
  479.         af = getBestAudioFormat(self.timeline.model,self.timeline.model.length(),self.timeline.model.beginning())
  480.         if af!=None:
  481.             self.outdialog.audio_samplerate.SetValue(str(af.sample_rate))
  482.             self.outdialog.channels_choice.SetSelection(af.channels-1)
  483.         if (self.outdialog.ShowModal() == wxID_OK):
  484.             if self.outdialog.HasVideo():
  485.                 vf = model.VideoFormat(self.outdialog.video_width.GetValue(),self.outdialog.video_height.GetValue(),self.outdialog.video_fps.GetValue())
  486.             else:
  487.                 vf = None
  488.             if self.outdialog.HasAudio():
  489.                 if self.outdialog.channels_choice.GetStringSelection()=='mono':
  490.                     channels=1
  491.                 else:
  492.                     channels=2
  493.                 af = model.AudioFormat(int(self.outdialog.audio_samplerate.GetValue()),channels)
  494.             else:
  495.                 af = None
  496.             out_params=self.outdialog.GetParams()
  497.             if af!=None or vf!=None:
  498.                 if self.outdialog.render_radio.GetSelection()==1:
  499.                     time_from=timesel[0]
  500.                     time_to=timesel[1]
  501.                     if time_from<self.timeline.model.beginning():
  502.                         time_from=self.timeline.model.beginning()
  503.                     if time_to>self.timeline.model.beginning()+self.timeline.model.length():
  504.                         time_from=self.timeline.model.beginning()+self.timeline.model.length()
  505.                 else:
  506.                     time_from=time_to=None
  507.                 net=self.timeline.model.tonetworkxml(self.outdialog.output_filename.GetValue(),vf,af,out_params=out_params,time_from=time_from,time_to=time_to)
  508.                 self.use_viewer=self.outdialog.use_viewer.IsChecked() 
  509.                 render.renderFromString(net, self, cancellable=True,
  510.                                         background=False)
  511.     
  512.     def OpenFile(self, filename):
  513.         """Loads timeline from file."""
  514.         try:
  515.             m = model.load(filename)
  516.         except model.Error, err:
  517.             wxLogError(err.msg)
  518.             wxLogError("File '%s' is not valid OpenVIP file." % filename)
  519.             return False
  520.         self.timeline.SetModel(m)
  521.         self.current_filename = filename
  522.         self.history_index = 0
  523.         self.history = [copy.deepcopy(self.timeline.model)]
  524.         notify.listen(self.timeline.model, self.OnChangeTimeline)
  525.         self.EnableItems(True)
  526.         return True
  527.  
  528.     def OnViewFast(self, event):
  529.         """Enables the Fast timeline mode."""
  530.         self.timeline.SetMode(TimelineWidget.MODE_FAST)
  531.  
  532.     def OnViewOne(self, event):
  533.         """Enables the Info timeline mode."""
  534.         self.timeline.SetMode(TimelineWidget.MODE_ONE_THUMBNAIL)
  535.  
  536.     def OnViewThumb(self, event):
  537.         """Enables the Filmstrip timeline mode."""
  538.         self.timeline.SetMode(TimelineWidget.MODE_THUMBNAILS)
  539.  
  540.     def OnPreviewFrame(self, event):
  541.         """Shows or hides the preview frame (from menu)."""
  542.         if self.previewframe.IsShown()==False:
  543.             self.previewframe.Show()
  544.             self.previewframe.SetModel(self.timeline.model)
  545.         else:
  546.             self.previewframe.Show(False)
  547.  
  548.     def OnClosePrevFrame(self, event):
  549.         """Hides the preview frame (close button event)."""        
  550.         self.previewframe.Show(False)
  551.         self.prevwindow_item.Check(False)
  552.         self.previewframe.WaitUntilNotUsed()
  553.  
  554.     def OnImport(self, event):
  555.         """Imports clip from a file, i.e. creates objects on timeline which
  556.            correspond to audio and video streams (if any)."""
  557.         filename = wxFileSelector('Import clip', 
  558.                                     wildcard='All supported filetypes|*.avi;*.avs;*.mpg;*.m2v;*.wav;*.mp3;*.ogg;*.png;*.jpg|AVI Clip (*.avi)|*.avi;*.avs|MPEG Clip (*.mpg, *.m2v)|*.mpg;*.m2v|Audio files|*.wav;*.mp3;*.ogg|Images (*.png, *.jpg)|*.png;*.jpg|All files|*',
  559.                                     flags=wxOPEN)
  560.         
  561.         if filename == "": return
  562.         try:
  563.             fi = globals.get_file_info(filename)
  564.         except openvip.Error:
  565.             wxLogError("File '%s' is not valid multimedia file." % filename)
  566.             return
  567.         
  568.         gr = model.Group()
  569.         gr.id = model.getUniqueID(self.timeline.model, "group")
  570.         countv = 0
  571.         for v in fi.video_streams:
  572.             countv += 1
  573.             obj = model.Object()
  574.             obj.time_from = 0
  575.             obj.time_to = v.length / v.fps
  576.             obj.src_from = obj.src_to = 0
  577.             obj.track = "V" #temporary - remember type of the object
  578.             obj.src_spec["filename"] = fi.filename
  579.             obj.src_channel = "video" + str(countv - 1)
  580.             gr.objects.append(obj)
  581.         
  582.         counta = 0
  583.         for a in fi.audio_streams:
  584.             counta += 1
  585.             obj = model.Object()
  586.             obj.time_from = 0
  587.             obj.time_to = 1.0 * a.length / a.sample_rate
  588.             obj.src_from = obj.src_to = 0
  589.             obj.track = "A" #temporary - remember type of the object
  590.             obj.src_spec["filename"] = fi.filename
  591.             obj.src_channel = "audio" + str(counta - 1)
  592.             gr.objects.append(obj)
  593.         
  594.         if countv + counta == 0:
  595.             wxLogError("No video or audio streams found in file '%s'" % filename)
  596.             return
  597.         
  598.         ic = ImportClass(self.timeline, gr, self)
  599.         self.timeline.AddObject(ic)
  600.  
  601.     def OnInsertTrans(self, event):
  602.         """Inserts transition using the callback mechanism.""" 
  603.         ic = InsertTransitionCallback(self.timeline, self)
  604.         self.timeline.AddObject(ic)
  605.  
  606.     def OnRemove(self, event):
  607.         """Removes selected objects from timeline."""
  608.         for o in self.timeline.selection:
  609.             if isinstance(o, model.Object):
  610.                 self.timeline.model.objects.remove(o)
  611.             elif isinstance(o, model.Transition):
  612.                 self.timeline.model.transitions.remove(o)
  613.             for g in self.timeline.model.groups:
  614.                 if g.objects.count(o)>0:
  615.                     g.objects.remove(o)
  616.                 if len(g.objects)==0:
  617.                     self.timeline.model.groups.remove(g)
  618.         notify.notify(self.timeline.model)
  619.  
  620.     def OnHelp(self, event):
  621.         """Displays the help window."""
  622.         if self.help == None:
  623.             self.help = wxHtmlHelpController()
  624.             self.help.AddBook('doc/htmlhelp.hhp')
  625.         self.help.DisplayContents()
  626.  
  627.     def OnAbout(self, event):
  628.         """Displays the About dialog box."""
  629.         self.about = AboutDialog(self, -1, "")
  630.         self.about.ShowModal()
  631.  
  632. class InsertTransitionCallback:
  633.         """Callback class for inserting new transitions
  634.            (see TimelineWidget.py)."""
  635.         def __init__(self, tl, mainframe):
  636.             self.timeline = tl
  637.             self.mainframe = mainframe
  638.             lib = globals.core
  639.             self.video_transitions = lib.enum_plugins(openvip.PLUGIN_VIDEO_TRANSITION)
  640.             self.audio_transitions = lib.enum_plugins(openvip.PLUGIN_AUDIO_TRANSITION)
  641.  
  642.         def veto(self,time,track):
  643.             # Is it an audio or video transition track?
  644.             if track == 'VFx':
  645.                 track1='VA'
  646.                 track2='VB'
  647.             elif track == 'AFx':
  648.                 track1='AA'
  649.                 track2='AB'
  650.             else:
  651.                 return True
  652.                 
  653.             # Find the two objects for transition
  654.             object_a=object_b=None
  655.             for o in self.timeline.model.objects:
  656.                 if o.track==track1 and o.time_from<time<o.time_to:
  657.                     object_a=o
  658.                 elif o.track==track2 and o.time_from<time<o.time_to:
  659.                     object_b=o
  660.             if object_a==None or object_b==None:
  661.                 return True
  662.  
  663.             # Find the interval that the transition should occupy
  664.             if object_a.time_from<=object_b.time_from<time<object_a.time_to<=object_b.time_to:
  665.                 self.time_from = object_b.time_from
  666.                 self.time_to = object_a.time_to
  667.                 self.direction = 'AB'
  668.             elif object_b.time_from<=object_a.time_from<time<object_b.time_to<=object_a.time_to:
  669.                 self.time_from = object_a.time_from
  670.                 self.time_to = object_b.time_to
  671.                 self.direction = 'BA'
  672.             elif object_b.time_from<=object_a.time_from<time<object_a.time_to<=object_b.time_to:
  673.                 self.time_from = object_a.time_from
  674.                 self.time_to = object_a.time_to
  675.                 self.direction = 'AB'
  676.             elif object_a.time_from<=object_b.time_from<time<object_b.time_to<=object_a.time_to:
  677.                 self.time_from = object_b.time_from
  678.                 self.time_to = object_b.time_to
  679.                 self.direction = 'AB'
  680.             else:
  681.                 return True
  682.             
  683.             # Check whether the interval is free
  684.             for o in self.timeline.model.transitions:
  685.                 if o.track == track:
  686.                     if self.time_from<o.time_from<self.time_to: return True
  687.                     if self.time_from<o.time_to<self.time_to: return True
  688.                     if o.time_from<=self.time_from<=self.time_to<=o.time_to: return True
  689.             return False
  690.  
  691.         def add(self,time,track):
  692.             if track=='VFx':
  693.                 msg='Available video transitions:'
  694.                 list=self.video_transitions
  695.             else:
  696.                 msg='Available audio transitions:'
  697.                 list=self.audio_transitions
  698.             t_dialog = wxSingleChoiceDialog(self.mainframe, msg, "Select transition ...", [p.description for p in list])
  699.             if (t_dialog.ShowModal() == wxID_OK) and (t_dialog.GetSelection()>=0):
  700.                 t = model.Transition()
  701.                 t.id = model.getUniqueID(self.timeline.model, "trans")
  702.                 t.name = list[t_dialog.GetSelection()].name
  703.                 t.track = track
  704.                 t.time_from=self.time_from
  705.                 t.time_to=self.time_to
  706.                 t.direction=self.direction
  707.                 self.timeline.model.transitions.append(t)
  708.                 notify.notify(self.timeline.model)
  709.  
  710. class ImportClass:
  711.         """Callback class for inserting new objects
  712.            (see TimelineWidget.py)."""
  713.         def __init__(self, tl, gr, main):
  714.             self.timeline = tl
  715.             self.gr = gr
  716.             self.main = main
  717.         def isFree(self, time_from, time_to, track, checkboth):
  718.             # is the given interval on the given track unoccupied?
  719.             # if checkboth is true, isFree checks both audio and video tracks of given index
  720.             if checkboth:
  721.                 # track_opp will contain name of corresponding track of opposite type
  722.                 track_opp = track
  723.                 if track[0] == "V": track_opp = "A" + track[1]
  724.                 else: track_opp = "V" + track[1]
  725.             for o in self.timeline.model.objects:
  726.                 if (o.track == track) or (checkboth and (o.track == track_opp)):
  727.                     if o.time_from >= time_from and o.time_from <= time_to: return False
  728.                     if o.time_to >= time_from and o.time_to <= time_to: return False
  729.                     if o.time_to < time_from and o.time_to > time_to: return False
  730.             return True
  731.         
  732.         def freeTrack(self, time_from, time_to, firsttrack):
  733.             # returns first track with given interval unoccupied (starting at firsttrack)
  734.             base = firsttrack[0]
  735.             idx = firsttrack[1]
  736.             while not self.isFree(time_from, time_to, base + idx, False):
  737.                 idx = nextIdx(idx)
  738.                 if idx == '': return ''
  739.             return base + idx
  740.             
  741.         def add(self,time,track):
  742.             idxV = track[1] # in case of multiple added objects we iterate through available tracks
  743.             idxA = track[1] # if there are more objects of the same type (audio/video), we put each
  744.                             # of them to next available track
  745.             for o in self.gr.objects:
  746.                 if o.track == 'V':
  747.                     tr = self.freeTrack(time, time + o.time_to, o.track + idxV)
  748.                     if tr == '':
  749.                         LogError("Not enough empty video tracks!")
  750.                     else:
  751.                         idxV = tr[1]
  752.                         idxV = nextIdx(idxV)
  753.                 else:
  754.                     tr = self.freeTrack(time, time + o.time_to, o.track + idxA)
  755.                     if tr == '':
  756.                         LogError("Not enough empty audio tracks!")
  757.                     else:
  758.                         idxA = tr[1]
  759.                         idxA = nextIdx(idxA)
  760.                 if tr != '':
  761.                     o.track = tr
  762.                     o.time_from = time
  763.                     o.time_to += time
  764.                     o.id = model.getUniqueID(self.timeline.model, "object")
  765.                     # print("pridan objekt na stopu " + tr + ", " + str(o.time_from) + " - " + str(o.time_to) + ", id = " + str(o.id) + "\n")
  766.                     self.timeline.model.objects.append(o)
  767.                 else: self.gr.objects.remove(o)
  768.             self.timeline.model.groups.append(self.gr)
  769.             notify.notify(self.timeline.model)
  770.             self.timeline.CreateThumbnails()
  771.         
  772.         def veto(self,time,track):
  773.             # Decide if the object can be placed somewhere here
  774.             if track[0] != self.gr.objects[0].track: return True
  775.             if track == 'VFx' or track == 'AFx': return True
  776.             checkboth = audiovideo(self.gr)
  777.             return not self.isFree(time, time + self.gr.objects[0].time_to, track, checkboth)
  778.             
  779. def nextIdx(idx):
  780.                 """Returns next track index (or "" if idx == 4)."""
  781.                 if idx == 'B' : idx = '0'
  782.                 else: idx = chr(ord(idx) + 1)
  783.                 if (idx == '5'): #maximum trackumber is 4
  784.                     return ''
  785.                 else: return idx
  786.  
  787. def audiovideo(group):
  788.     """Checks whether both audio and video tracks are present."""
  789.     audio = video = False
  790.     for o in group.objects:
  791.         if o.track[0] == "V": video = True
  792.         if o.track[0] == "A": audio = True
  793.     return audio and video
  794.