~ubuntu-branches/ubuntu/quantal/jokosher/quantal

« back to all changes in this revision

Viewing changes to Jokosher/Event.py

  • Committer: Bazaar Package Importer
  • Author(s): Luca Falavigna, Luca Falavigna, Piotr Ożarowski
  • Date: 2009-05-12 00:37:15 UTC
  • mfrom: (1.3.2 upstream)
  • Revision ID: james.westby@ubuntu.com-20090512003715-3hp2ycoqjlzwfnlv
Tags: 0.11.2-1
[ Luca Falavigna ]
* New upstream release (Closes: #517234).
  - Jokosher now appears under Sound & Video (Closes: #443788).
* New Maintainer (Closes: #523167).
* Add Python Applications Packaging Team to Uploaders.
* Add Vcs-* fields in source stanza.
* Adjust copyright informations:
  - Refresh upstream authors and copyright holders.
  - Link to /usr/share/common-licenses/GPL-2.
  - Adjust copyright holders for Debian packaging.
  - Replace (c) with ©.
* Apply changes from Ubuntu by Daniel Holbach (thanks!):
  - Drop scrollkeeper from Build-Depends.
  - Drop useless dependencies: python-alsaaudio, gstreamer0.10-gnomevfs,
    librsvg2-common, python-gnome2.
  - Bump gstreamer0.10-plugins-good requirement to >= 0.10.9.
  - Drop debian/jokosher.sh, useless.
  - Provide debian/watch file.
* Switch to debhelper 7.
* Install Jokosher module in private directory.
* Unpack egg files to let python-support handle them.
* Drop python-dev from Build-Depends, use python (>= 2.4) instead.
* Depend on python-gobject.
* Switch dependency from python-setuptools to python-pkg-resources
  because of package rename (Closes: #468728).
* debian/patches/10_update_mime_database.dpatch:
  - Refresh for new upstream release.
* debian/patches/20_LevelList_IOError.dpatch:
  - Fix IOError exception trying to add an audio file to a project.
* debian/patches/30_desktop_file.dpatch:
  - Adhere to Freedesktop.org standards by removing deprecated entries.
* debian/patches/50_CreateNewProject_return.dpatch:
  - Return class while creating a new project.
* Provide a simple man page for jokosher.
* Bump Standards-Version to 3.8.1:
  - Provide Homepage field in source stanza.
  - Provide debian/README.source to document dpatch usage.

[ Piotr Ożarowski ]
* Add 40_load_extensions_from_unpacked_eggs patch so that extensions in
  unzipped Eggs are recognized as well

Show diffs side-by-side

added added

removed removed

Lines of Context:
13
13
#-------------------------------------------------------------------------------
14
14
 
15
15
import xml.dom.minidom as xml
16
 
import os
 
16
import os, sys, os.path
17
17
import pygst
18
18
pygst.require("0.10")
19
 
import gst
20
 
from Monitored import Monitored
21
 
import Utils
22
 
import UndoSystem
 
19
import gst, gobject
 
20
import Utils, LevelsList
 
21
import UndoSystem, IncrementalSave
23
22
import Globals
24
23
import gettext
25
24
import urllib
 
25
import PlatformUtils
 
26
 
 
27
from elements.singledecodebin import SingleDecodeBin
26
28
_ = gettext.gettext
27
29
 
28
30
#=========================================================================
29
31
 
30
 
class Event(Monitored):
 
32
class Event(gobject.GObject):
31
33
        """
32
34
        This class handles maintaing the information for a single audio 
33
35
        event, normally, a fragment of a recorded file.
 
36
 
 
37
        Signals:
 
38
                "waveform" -- The waveform date for this event has changed.
 
39
                "position" -- The starting position of this event has changed.
 
40
                "length" -- The length of this event has changed.
 
41
                "corrupt" -- The audio file for this event is not playable. Two strings with detailed information are sent.
 
42
                "loading" -- Loading has started or completed.
 
43
 
34
44
        """
35
 
        
36
 
        """ State changed types (to be sent through the Monitored class) """
37
 
        WAVEFORM, MOVE, LENGTH, CORRUPT, LOADING = range(5)
38
 
        
 
45
        __gsignals__ = {
 
46
                "waveform"      : ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, () ),
 
47
                "position"      : ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, () ),
 
48
                "length"                : ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, () ),
 
49
                "corrupt"       : ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_STRING,)),
 
50
                "loading"       : ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, () ),
 
51
                "selected"      : ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, () )
 
52
        }
 
53
 
39
54
        """ The level sample interval in seconds """
40
 
        LEVEL_INTERVAL = 0.01
 
55
        LEVEL_INTERVAL = 0.1
 
56
        LEVELS_FILE_EXTENSION = ".leveldata"
41
57
        
42
58
        #_____________________________________________________________________
43
59
        
52
68
                        filelabel -- label to print in error messages.
53
69
                                                It can be different     from the file parameter.
54
70
                """
55
 
                Monitored.__init__(self)
 
71
                gobject.GObject.__init__(self)
56
72
                
 
73
                self.id = instrument.project.GenerateUniqueID(id)  #check is id is already taken, then set it.
57
74
                self.start = 0.0                        # Time in seconds at which the event begins
58
75
                self.duration = 0.0                     # Duration in seconds of the event
59
76
                # The file this event should play (without escaped characters)
60
77
                # If you need characters escaped, please do self.file.replace(" ", "\ ") 
61
78
                # but **do not** assign it to this variable.
62
79
                self.file = file
63
 
 
 
80
                if self.file and os.path.isabs(self.file) and \
 
81
                      PlatformUtils.samefile(instrument.project.audio_path, os.path.dirname(self.file)):
 
82
                        # If the file is in the audio dir, just include the filename, not the absolute path
 
83
                        Globals.debug("Event() given absolute file, should be relative:", self.file)
 
84
                        self.file = os.path.basename(self.file)
 
85
                
 
86
                # levels_file is a filename only, no directory information for levels here.
 
87
                basename = os.path.basename(self.file or "Unknown")
 
88
                self.levels_file = "%s_%d%s" % (basename, self.id, self.LEVELS_FILE_EXTENSION)
 
89
                
64
90
                # the label is the filename to print in error messages
65
91
                # if it differs from the real filename (i.e its been copied into the project)
66
92
                if filelabel != None:
72
98
                self.name = "New Event"         # Name of this event
73
99
                
74
100
                self.selection  = [0, 0]        # List start and end of selection (for fades, etc) measured in seconds 
75
 
                self.levels = []                        # Array of audio levels to be drawn for this event
 
101
                self.levels_list = LevelsList.LevelsList()      # LevelsList class containing array of audio levels to be drawn for this event
76
102
                
77
 
                self.id = instrument.project.GenerateUniqueID(id)  #check is id is already taken, then set it.
78
103
                self.instrument = instrument    # The parent instrument
79
 
                self.filesrc = None                     # The gstreamer gnlfilesource object.
 
104
                self.gnlsrc = None                      # The gstreamer gnlsource object.
 
105
                self.single_decode_bin = None           # The gstreamer file decoder element.
80
106
                
81
107
                self.offset = 0.0                       # Offset through the file in seconds
82
108
                self.isLoading = False          # True if the event is currently loading level data
99
125
                # The list *must* be ordered by time-in-seconds, so when you update it from
100
126
                # the dictionary using dict.items(), be sure to sort it again.
101
127
                self.audioFadePoints = []
102
 
                #Just like self.levels except with all the levels scaled according to the
 
128
                #Just like self.levels_list except with all the levels scaled according to the
103
129
                #points in self.audioFadePoints.
104
 
                self.fadeLevels = []
 
130
                self.fadeLevels = LevelsList.LevelsList()
105
131
 
106
132
        #_____________________________________________________________________
107
133
        
 
134
        def GetFilename(self):
 
135
                return os.path.basename(self.file)
 
136
        
 
137
        #_____________________________________________________________________
 
138
        
 
139
        def GetAbsFile(self):
 
140
                if os.path.isabs(self.file):
 
141
                        return self.file
 
142
                else:
 
143
                        return os.path.join(self.instrument.project.audio_path, self.file)
 
144
        
 
145
        #_____________________________________________________________________
 
146
        
 
147
        def GetAbsLevelsFile(self):
 
148
                return os.path.join(self.instrument.project.levels_path, self.levels_file)
 
149
        
 
150
        #_____________________________________________________________________
 
151
        
108
152
        def CreateFilesource(self):     
109
153
                """
110
154
                Creates a new GStreamer file source with an unique id.
112
156
                properties.
113
157
                """
114
158
                Globals.debug("create file source")
115
 
                if not self.filesrc:
116
 
                        self.filesrc = gst.element_factory_make("gnlfilesource", "Event_%d"%self.id)
117
 
                if not self.filesrc in list(self.instrument.composition.elements()):
118
 
                        self.instrument.composition.add(self.filesrc)
 
159
                if not self.gnlsrc:
 
160
                        self.gnlsrc = gst.element_factory_make("gnlsource", "Event_%d"%self.id)
 
161
                if not self.gnlsrc in list(self.instrument.composition.elements()):
 
162
                        self.instrument.composition.add(self.gnlsrc)
119
163
                
120
164
                self.SetProperties()
 
165
 
 
166
        #_____________________________________________________________________
 
167
 
 
168
        def DestroyFilesource(self):
 
169
                """
 
170
                Removes the Gstreamer file source from the instrument's composition.
 
171
                """
 
172
                if self.gnlsrc in list(self.instrument.composition.elements()):
 
173
                        self.instrument.composition.remove(self.gnlsrc)
121
174
                
122
175
        #_____________________________________________________________________
123
176
                
126
179
                Sets basic Event properties like location, start, duration, etc.
127
180
                """
128
181
                if self.file:
 
182
                        if self.single_decode_bin:
 
183
                                self.gnlsrc.remove(self.single_decode_bin)
 
184
 
 
185
                        Globals.debug("creating SingleDecodeBin")
 
186
                        caps = gst.caps_from_string("audio/x-raw-int;audio/x-raw-float")
 
187
                        f = PlatformUtils.pathname2url(self.GetAbsFile())
 
188
                        Globals.debug("file uri is:", f)
 
189
                        self.single_decode_bin = SingleDecodeBin(caps=caps, uri=f)
 
190
                        self.gnlsrc.add(self.single_decode_bin)
129
191
                        Globals.debug("setting event properties:")
130
 
                        propsDict = {"location" : self.file,
 
192
                        propsDict = {
 
193
                                        "caps" : caps,
131
194
                                        "start" : long(self.start * gst.SECOND),
132
195
                                        "duration" : long(self.duration * gst.SECOND),
133
196
                                        "media-start" : long(self.offset * gst.SECOND),
136
199
                                        }
137
200
                                        
138
201
                        for prop, value in propsDict.iteritems():
139
 
                                self.filesrc.set_property(prop, value)
 
202
                                self.gnlsrc.set_property(prop, value)
140
203
                                Globals.debug("\t", prop, "=", value)
141
204
 
142
205
        #_____________________________________________________________________
163
226
                ev.appendChild(params)
164
227
                
165
228
                items = ["start", "duration", "isSelected", 
166
 
                                  "name", "offset", "file", "isLoading", "isRecording"
 
229
                                  "name", "offset", "file", "filelabel", "levels_file",
 
230
                                  "isLoading", "isRecording"
167
231
                                ]
168
232
                                
169
233
                #Since we are saving the path to the project file, don't delete it on exit
170
 
                if self.file in self.instrument.project.deleteOnCloseAudioFiles:
171
 
                        self.instrument.project.deleteOnCloseAudioFiles.remove(self.file)
172
 
                
173
 
                self.temp = self.file
174
 
                if os.path.samefile(self.instrument.path, os.path.dirname(self.file)):
175
 
                        # If the file is in the audio dir, just include the filename, not the absolute path
176
 
                        self.file = os.path.basename(self.file)
177
 
                
178
 
                Utils.StoreParametersToXML(self, doc, params, items)
179
 
                
180
 
                # Put self.file back to its absolute path
181
 
                self.file = self.temp
182
 
                
 
234
                if self.GetAbsFile() in self.instrument.project.deleteOnCloseAudioFiles:
 
235
                        self.instrument.project.deleteOnCloseAudioFiles.remove(self.GetAbsFile())
 
236
                
 
237
                Utils.StoreParametersToXML(self, doc, params, items)            
183
238
                
184
239
                xmlPoints = doc.createElement("FadePoints")
185
240
                ev.appendChild(xmlPoints)
186
241
                Utils.StoreDictionaryToXML(doc, xmlPoints, self.__fadePointsDict, "FadePoint")
187
242
                
188
 
                if self.levels:
189
 
                        levelsXML = doc.createElement("Levels")
190
 
                        ev.appendChild(levelsXML)
191
 
                        stringList = map(str, self.levels)
192
 
                        levelsXML.setAttribute("value", ",".join(stringList))
 
243
                if self.levels_list:
 
244
                        self.levels_list.tofile(self.GetAbsLevelsFile())
 
245
                if self.GetAbsLevelsFile() in self.instrument.project.deleteOnCloseAudioFiles:
 
246
                        self.instrument.project.deleteOnCloseAudioFiles.remove(self.GetAbsLevelsFile())
193
247
                
194
248
        #_____________________________________________________________________
195
249
                
222
276
        
223
277
        #_____________________________________________________________________
224
278
        
225
 
        @UndoSystem.UndoCommand("Move", "start", "temp")
226
 
        def Move(self, frm, to):
 
279
        @UndoSystem.UndoCommand("Move", "temp")
 
280
        def Move(self, to, frm=None):
227
281
                """
228
282
                Moves this Event in time.
229
283
 
230
284
                Parameters:
 
285
                        to -- the time the Event's moving to.
231
286
                        frm -- the time the Event's moving from.
232
 
                        to -- the time the Event's moving to.
233
287
                """
234
 
                self.temp = frm
 
288
                if frm is None:
 
289
                        self.temp = self.start
 
290
                else:
 
291
                        self.temp = frm
235
292
                self.start = to
236
293
                self.SetProperties()
237
 
        
238
 
        #_____________________________________________________________________
239
 
        
240
 
        def Split(self, split_point, id=-1):
241
 
                """
242
 
                Dummy function kept for compatibility with 0.2 project files.
 
294
                self.emit("position")
 
295
        
 
296
        #_____________________________________________________________________
 
297
        
 
298
        def _Compat09_Move(self, frm, to, _undoAction_):
 
299
                """
 
300
                Moves this Event in time.
 
301
                
 
302
                Considerations:
 
303
                        A compatibility method for undo functions from 
 
304
                        0.1, 0.2 and 0.9 project files. It should not be called
 
305
                        explicitly by anyone.
 
306
                
 
307
                Parameters:
 
308
                        frm -- the time the Event's moving from.
 
309
                        to -- the time the Event's moving to.
 
310
                """
 
311
                self.Move(to, frm, _undoAction_=_undoAction_)
 
312
        
 
313
        #_____________________________________________________________________
 
314
        
 
315
        def _Compat02_Split(self, split_point, id=-1):
 
316
                """
 
317
                Function kept for compatibility with 0.2 project files.
243
318
                Parameters are the same as SplitEvent().
244
319
                """
245
320
                self.SplitEvent(split_point)
246
321
        
247
322
        #_____________________________________________________________________
248
323
        
249
 
        def Join(self, joinEventID):
 
324
        def _Compat02_Join(self, joinEventID):
250
325
                """
251
 
                Dummy function kept for compatibility with 0.2 project files.
 
326
                Function kept for compatibility with 0.2 project files.
252
327
                Parameters are the same as JoinEvent().
253
328
                """
254
329
                self.JoinEvent(joinEventID)
255
330
        
256
331
        #_____________________________________________________________________
257
332
        
 
333
        def CopySelection(self, eventID=-1):
 
334
                """
 
335
                Only for use with a 2-point selection.
 
336
                Essentially performs a 'fake split' and returns a new event
 
337
                which would be the result of splitting an event at the 2 points.
 
338
                
 
339
                This is used when the user shift-drags an event to create a selection,
 
340
                then chooses 'copy' from the context menu. The new event can be placed
 
341
                wherever the user wishes by right-clicking and choosing 'paste'.
 
342
                """
 
343
                if eventID >= 0:
 
344
                        e = [x for x in self.instrument.graveyard if x.id == eventID][0]
 
345
                        self.instrument.graveyard.remove(e)
 
346
                else:
 
347
                        e = Event(self.instrument, self.file)
 
348
                e.name = self.name
 
349
                
 
350
                dur = self.selection[1] - self.selection[0]
 
351
                
 
352
                e.start = self.start + self.selection[0]
 
353
                e.offset = self.selection[0] #+self.offset
 
354
                e.duration = dur
 
355
                
 
356
                dictLeft = {}
 
357
                dictRight = {}
 
358
                for key, value in self.__fadePointsDict.iteritems():
 
359
                        if key < self.selection[0]:
 
360
                                dictLeft[key] = value
 
361
                        if key > self.selection[0]:
 
362
                                dictRight[key - self.selection[0]] = value
 
363
                #in case there is a fade passing through the split point, recreate half of it on either side
 
364
                splitFadeLevel = self.GetFadeLevelAtPoint(self.selection[0])
 
365
                dictLeft[self.selection[0]] = splitFadeLevel
 
366
                dictRight[0.0] = splitFadeLevel
 
367
                
 
368
                millis = int(self.selection[0] * 1000)
 
369
                e.levels_list = self.levels_list.slice_by_endtime(millis)
 
370
                e.__fadePointsDict = dictRight
 
371
                        
 
372
                e.__UpdateAudioFadePoints()
 
373
                e.SetProperties()
 
374
                self.instrument.events.append(e)
 
375
                e.emit("length")
 
376
                e.emit("position")
 
377
                
 
378
                return e
 
379
                
 
380
 
 
381
        
258
382
        @UndoSystem.UndoCommand("JoinEvent", "temp", "temp2")
259
383
        def SplitEvent(self, split_point, cutRightSide=True, eventID=-1):
260
384
                """
302
426
                        e.duration = dur - split_point
303
427
                        self.duration = split_point
304
428
                        
305
 
                        nl = int(len(self.levels) * (split_point / dur))
306
 
                        e.levels = self.levels[nl:]
307
 
                        self.levels = self.levels[:nl]
 
429
                        millis = int(split_point * 1000)
 
430
                        e.levels_list = self.levels_list.slice_by_endtime(millis)
 
431
                        self.levels_list = self.levels_list.slice_by_endtime(0, millis)
308
432
                        
309
433
                        self.__fadePointsDict = dictLeft
310
434
                        e.__fadePointsDict = dictRight
317
441
                        self.offset = self.offset + split_point
318
442
                        self.duration = dur - split_point
319
443
                        
320
 
                        nl = int(len(self.levels) * (split_point / dur))
321
 
                        e.levels = self.levels[:nl]
322
 
                        self.levels = self.levels[nl:]
 
444
                        millis = int(split_point * 1000)
 
445
                        e.levels_list = self.levels_list.slice_by_endtime(0, millis)
 
446
                        self.levels_list = self.levels_list.slice_by_endtime(millis)
323
447
                        
324
448
                        self.__fadePointsDict = dictRight
325
449
                        e.__fadePointsDict = dictLeft
329
453
                e.SetProperties()
330
454
                self.instrument.events.append(e)
331
455
                self.SetProperties()
332
 
                self.StateChanged(self.LENGTH)
333
 
                self.StateChanged(self.MOVE)
 
456
                self.emit("length")
 
457
                self.emit("position")
 
458
                self.instrument.emit("event::added", e)
334
459
                
335
460
                #undo parameters
336
461
                self.temp = e.id
362
487
                        if self.__fadePointsDict.has_key(self.duration):
363
488
                                del self.__fadePointsDict[self.duration]
364
489
                        
 
490
                        old_duration = int(self.duration * 1000)
365
491
                        self.duration += joinEvent.duration
366
 
                        self.levels.extend(joinEvent.levels)
 
492
                        self.levels_list.extend(old_duration, joinEvent.levels_list)
367
493
                        #update the fade point list after the level, and duration because it depends on them
368
494
                        self.__UpdateAudioFadePoints()
369
495
                else:
371
497
                
372
498
                        self.start = joinEvent.start
373
499
                        self.offset = joinEvent.offset
 
500
                        
 
501
                        old_duration = int(self.duration * 1000)
374
502
                        self.duration += joinEvent.duration
375
 
                        self.levels = joinEvent.levels + self.levels
 
503
                        self.levels_list = LevelsList.add(old_duration, joinEvent.levels_list, self.levels_list)
376
504
                        
377
505
                        newDict = joinEvent.__fadePointsDict.copy()
378
506
                        for key, value in self.__fadePointsDict.iteritems():
383
511
                        self.__fadePointsDict = newDict
384
512
                        self.__UpdateAudioFadePoints()
385
513
                        
 
514
                #create an undo action that is not attached to the project so that
 
515
                # the following delete will not be undone (it will be re-split not resurrected)
 
516
                nullAction = UndoSystem.AtomicUndoAction()
386
517
                # Now that they're joined, move delete the rightEvent
387
 
                if joinEvent in self.instrument.events:
388
 
                        self.instrument.events.remove(joinEvent)
389
 
                if not joinEvent in self.instrument.graveyard:
390
 
                        self.instrument.graveyard.append(joinEvent)
 
518
                joinEvent.Delete(_undoAction_=nullAction)
391
519
                
392
 
                self.StateChanged(self.LENGTH)
393
 
                self.StateChanged(self.MOVE)
 
520
                self.emit("length")
 
521
                self.emit("position")
394
522
                
395
523
                self.temp2 = joinToRight
396
524
                self.temp3 = joinEvent.id
427
555
                        self.instrument.DeleteEvent(rightSplit.id, _undoAction_=undoAction)
428
556
                
429
557
                self.SetProperties()
430
 
                self.StateChanged(self.LENGTH)
 
558
                self.emit("length")
431
559
                
432
560
        #_____________________________________________________________________
433
561
        
434
 
        def UndoTrim(self, leftID, rightID):
 
562
        def _Compat02_UndoTrim(self, leftID, rightID):
435
563
                """
436
564
                Resurrects two pieces from the graveyard and joins them to
437
565
                either side of this Event.
479
607
                        self.instrument.ResurrectEvent(self.id)
480
608
                        
481
609
        #_____________________________________________________________________
 
610
 
 
611
        def install_plugin_cb(self, result):
 
612
                self._installing_plugins = False
 
613
                if result == gst.pbutils.INSTALL_PLUGINS_SUCCESS:
 
614
                        gst.update_registry()
 
615
                        self.GenerateWaveform()
 
616
                        return
 
617
 
 
618
                # FIXME: send a better error
 
619
                msg = "failed to install plugins: %s" % result
 
620
                self.emit("corrupt", msg)
 
621
 
 
622
        #_____________________________________________________________________
482
623
        
483
624
        def bus_message(self, bus, message):
484
625
                """
498
639
                        return False
499
640
 
500
641
                st = message.structure
501
 
                if st:
502
 
                        if st.get_name() == "level":
503
 
                                newLevel = self.__CalculateAudioLevel(st["peak"])
504
 
                                self.levels.append(newLevel)
505
 
                                
506
 
                                end = st["endtime"] / float(gst.SECOND)
507
 
                                self.loadingLength = int(end)
508
 
                                
509
 
                                # Only send events every second processed to reduce GUI load
510
 
                                if self.loadingLength != self.lastEnd:
511
 
                                        self.lastEnd = self.loadingLength 
512
 
                                        self.StateChanged(self.LENGTH) # tell the GUI
 
642
                if not st:
 
643
                        return False
 
644
                
 
645
                if st.get_name().startswith('missing-'):
 
646
                        self.loadingPipeline.set_state(gst.STATE_NULL)
 
647
                        Utils.HandleGstPbutilsMissingMessage(message, self.install_plugin_cb)
 
648
 
 
649
                elif st.get_name() == "level":
 
650
                        self.__AppendLevelToList(st)
 
651
                        
 
652
                        #Truncate so it updates once per second
 
653
                        self.loadingLength = st["endtime"] / gst.SECOND
 
654
                        
 
655
                        # Only send events every second processed to reduce GUI load
 
656
                        if self.loadingLength != self.lastEnd:
 
657
                                self.lastEnd = self.loadingLength 
 
658
                                self.emit("length") # tell the GUI
513
659
                return True
514
660
                
515
661
        #_____________________________________________________________________
541
687
                                else:
542
688
                                        self.duration = self.loadingLength
543
689
                        
 
690
                        if self.levels_list:
 
691
                                final_endtime = self.levels_list[-1][0]
 
692
                                if final_endtime > int(self.duration * 1000):
 
693
                                        Globals.debug("Event %d: duration (%f) is less than last level endtime (%d)."
 
694
                                                      % (self.id, self.duration, final_endtime))
 
695
                                        self.duration = final_endtime / 1000.0
 
696
                                        self.SetProperties()
 
697
                                        Globals.debug("\tduration has been increased to", self.duration)
 
698
                        
544
699
                        if length and (self.offset > 0 or self.duration != length):
545
 
                                dt = int(self.duration * len(self.levels) / length)
546
 
                                start = int(self.offset * len(self.levels) / length)
547
 
                                self.levels = self.levels[start:start+dt]
548
 
                        
 
700
                                starttime = int(self.offset * 1000)
 
701
                                stoptime = int((self.offset + self.duration) * 1000)
 
702
                                self.levels_list = self.levels_list.slice_by_endtime(starttime, stoptime)
 
703
                                
549
704
                        # We're done with the bin so release it
550
705
                        self.StopGenerateWaveform()
551
706
                        
552
707
                        # Signal to interested objects that we've changed
553
 
                        self.StateChanged(self.WAVEFORM)
 
708
                        self.emit("waveform")
554
709
                        return False
555
710
                        
556
711
        #_____________________________________________________________________
566
721
                # state has changed
567
722
                try:
568
723
                        time = self.loadingPipeline.query_duration(gst.FORMAT_TIME)
569
 
                        if self.duration == 0 and time[0] != 0:
 
724
                        if self.duration == 0 and time[0] > 0:
570
725
                                self.duration = float(time[0] / float(gst.SECOND))
571
726
                                
572
727
                                #update position with proper duration
573
728
                                self.MoveButDoNotOverlap(self.start)
574
729
                                self.SetProperties()
575
 
                                self.StateChanged(self.MOVE)
576
 
                                self.StateChanged(self.LENGTH)
 
730
                                self.emit("length")
 
731
                                self.emit("position")
577
732
                except:
578
733
                        # no size available yet
579
734
                        pass
590
745
                """
591
746
                error, debug = message.parse_error()
592
747
                
 
748
                Globals.debug("Event Bus Error Message:")
 
749
                Globals.debug("\tCode:", error.code)
 
750
                Globals.debug("\tDomain:", error.domain)
 
751
                Globals.debug("\tMessage:", error.message)
 
752
                
593
753
                Globals.debug("Event bus error:", str(error), str(debug))
594
 
                self.StateChanged(self.CORRUPT, str(error), str(debug))
 
754
                self.emit("corrupt", "%s\n%s" % (error, debug))
595
755
        
596
756
        #_____________________________________________________________________
597
757
        
603
763
                        bus -- GStreamer bus sending the message.
604
764
                        message -- GStreamer message.
605
765
                """
606
 
                Globals.debug("recieved group of tags")
 
766
                Globals.debug("received group of tags")
607
767
                st = message.structure
608
768
                
609
769
                title, artist = None, None
624
784
                """
625
785
                Renders the level information for the GUI.
626
786
                """
627
 
                pipe = """filesrc name=src location=%s ! decodebin ! audioconvert ! level interval=%d message=true ! fakesink""" 
628
 
                pipe = pipe % (self.file.replace(" ", "\ "), self.LEVEL_INTERVAL * gst.SECOND)
 
787
                pipe = """filesrc name=src ! decodebin ! audioconvert ! level message=true name=level_element ! fakesink"""
629
788
                self.loadingPipeline = gst.parse_launch(pipe)
 
789
                
 
790
                filesrc = self.loadingPipeline.get_by_name("src")
 
791
                level = self.loadingPipeline.get_by_name("level_element")
 
792
                
 
793
                filesrc.set_property("location", self.GetAbsFile())
 
794
                level.set_property("interval", int(self.LEVEL_INTERVAL * gst.SECOND))
630
795
 
631
796
                self.bus = self.loadingPipeline.get_bus()
632
797
                self.bus.add_signal_watch()
636
801
                self.bus.connect("message::eos", self.bus_eos)
637
802
                self.bus.connect("message::error", self.bus_error)
638
803
 
639
 
                self.levels = []
 
804
                self.levels_list = LevelsList.LevelsList()
640
805
                self.isLoading = True
641
 
                self.StateChanged(self.LOADING)
 
806
                self.emit("loading")
642
807
 
643
808
                self.loadingPipeline.set_state(gst.STATE_PLAYING)
644
809
 
649
814
                Copies the audio file to the new file location and reads the levels
650
815
                at the same time.
651
816
                """
652
 
                if not gst.element_make_from_uri(gst.URI_SRC, uri):
 
817
                
 
818
                urisrc = gst.element_make_from_uri(gst.URI_SRC, uri)
 
819
                if not urisrc:
653
820
                        #This means that here is no gstreamer src element on the system that can handle this URI type.
654
821
                        return False
655
822
                
656
 
                pipe = """%s ! tee name=mytee mytee. ! queue ! filesink location=%s """ +\
657
 
                """mytee. ! queue ! decodebin ! audioconvert ! level interval=%d message=true ! fakesink""" 
658
 
                pipe = pipe % (urllib.quote(uri,":/"), self.file.replace(" ", "\ "), self.LEVEL_INTERVAL * gst.SECOND)
 
823
                pipe = """tee name=mytee mytee. ! queue ! filesink name=sink """ +\
 
824
                       """mytee. ! queue ! decodebin ! audioconvert ! level name=level_element message=true ! fakesink""" 
659
825
                self.loadingPipeline = gst.parse_launch(pipe)
 
826
                
 
827
                tee = self.loadingPipeline.get_by_name("mytee")
 
828
                filesink = self.loadingPipeline.get_by_name("sink")
 
829
                level = self.loadingPipeline.get_by_name("level_element")
 
830
                
 
831
                self.loadingPipeline.add(urisrc)
 
832
                urisrc.link(tee)
 
833
                
 
834
                filesink.set_property("location", self.GetAbsFile())
 
835
                level.set_property("interval", int(self.LEVEL_INTERVAL * gst.SECOND))
660
836
 
661
837
                self.bus = self.loadingPipeline.get_bus()
662
838
                self.bus.add_signal_watch()
666
842
                self.bus.connect("message::eos", self.bus_eos)
667
843
                self.bus.connect("message::error", self.bus_error)
668
844
 
669
 
                self.levels = []
 
845
                self.levels_list = LevelsList.LevelsList()
670
846
                self.isLoading = True
671
 
                self.StateChanged(self.LOADING)
 
847
                self.emit("loading")
672
848
 
673
849
                self.loadingPipeline.set_state(gst.STATE_PLAYING)
674
850
                
690
866
                if self.loadingPipeline:
691
867
                        self.loadingPipeline.set_state(gst.STATE_NULL)
692
868
                        
 
869
                        if finishedLoading and self.levels_list:
 
870
                                self.levels_list.tofile(self.GetAbsLevelsFile())
 
871
                                del_on_close_list = self.instrument.project.deleteOnCloseAudioFiles
 
872
                                # this event might not be in the project file yet
 
873
                                # if so, levels_file should be deleted when audio file is deleted on exit
 
874
                                if self.GetAbsFile() in del_on_close_list:
 
875
                                        del_on_close_list.append(self.GetAbsLevelsFile())
 
876
                                        
 
877
                                inc = IncrementalSave.CompleteLoading(self.id, self.duration, self.levels_file)
 
878
                                self.instrument.project.SaveIncrementalAction(inc)
 
879
                        
693
880
                        if self.isDownloading:
694
881
                                # If we are currently downloading, we can't restart later, 
695
882
                                # so cancel regardless of the finishedLoading boolean's value.
698
885
                        else:
699
886
                                self.isLoading = not finishedLoading
700
887
                        
 
888
                        
701
889
                        self.loadingPipeline = None
702
890
                        self.loadingLength = 0
703
 
                        self.StateChanged(self.LOADING)
 
891
                        self.emit("loading")
704
892
        
705
893
        #_____________________________________________________________________
706
894
 
723
911
                
724
912
                st = message.structure
725
913
                if st and message.src.get_name() == "recordlevel":
726
 
                        newLevel = self.__CalculateAudioLevel(st["peak"])
727
 
                        self.levels.append(newLevel)
 
914
                        self.__AppendLevelToList(st)
728
915
                        
729
 
                        end = st["endtime"] / float(gst.SECOND)
 
916
                        end = st["endtime"] / float(gst.SECOND)  #convert to float representing seconds 
730
917
                        #Round to one decimal place so it updates 10 times per second
731
918
                        self.loadingLength = round(end, 1)
732
919
                        
733
920
                        # Only send events every second processed to reduce GUI load
734
921
                        if self.loadingLength != self.lastEnd:
735
922
                                self.lastEnd = self.loadingLength 
736
 
                                self.StateChanged(self.LENGTH) # tell the GUI
 
923
                                self.emit("length") # tell the GUI
737
924
                return True
738
925
                
739
926
        #_____________________________________________________________________
740
927
        
741
 
        def __CalculateAudioLevel(self, channelLevels):
742
 
                """
743
 
                Calculates an average for all channel levels.
744
 
                
745
 
                Parameters:
746
 
                        channelLevels -- list of levels from each channel.
747
 
                        
748
 
                Returns:
749
 
                        an average level, also taking into account negative infinity numbers,
750
 
                        which will be discarded in the average.
751
 
                """
752
 
                negInf = float("-inf")
753
 
                peaktotal = 0
754
 
                peakcount = 0
755
 
                for peak in channelLevels:
756
 
                        #don't add -inf values cause 500 + -inf is still -inf
757
 
                        if peak != negInf:
758
 
                                peaktotal += peak
759
 
                                peakcount += 1
760
 
                #avoid a divide by zero here
761
 
                if peakcount > 0:
762
 
                        peaktotal /= peakcount
763
 
                #it must be put back to -inf if nothing has been added to it, so that the DbToFloat conversion will work
764
 
                elif peakcount == 0:
765
 
                        peaktotal = negInf
766
 
                
767
 
                #convert to 0...1 float, and return
768
 
                return Utils.DbToFloat(peaktotal)
 
928
        def __AppendLevelToList(self, structure):
 
929
                (end, peaks) = Utils.CalculateAudioLevelFromStructure(structure)
 
930
                
 
931
                # the last level may be sent twice. If timestamp is the same, ignore it.
 
932
                if self.levels_list and self.levels_list[-1][0] == end:
 
933
                        return
 
934
                
 
935
                # work around GStreamer bug where stream time will be -1 (indicating error)
 
936
                # and then cast to guint64 which results in the maximum 64-bit integer value.
 
937
                # In this case stream-time and endtime are bogus values, but duration is still correct.
 
938
                stream_time = structure["stream-time"]
 
939
                if stream_time == ((2**64) - 1):
 
940
                        delta = int(structure["duration"] / Utils.NANO_TO_MILLI_DIVISOR)
 
941
                        self.levels_list.append_time_delta(delta, peaks)
 
942
                else:
 
943
                        self.levels_list.append(end, peaks)
769
944
                
770
945
        #_____________________________________________________________________
771
946
        
778
953
                                        True = the Event has been selected.
779
954
                                        False = the Event has been deselected.
780
955
                """
781
 
                # No need to call StateChanged when there is no change in selection state
 
956
                # No need to emit a signal when there is no change in selection state
782
957
                if self.isSelected is not sel:
783
958
                        self.isSelected = sel
784
 
                        self.StateChanged()
 
959
                        self.emit("selected")
785
960
        
786
961
        #_____________________________________________________________________
787
962
        
958
1133
                                self.audioFadePoints.append(last)
959
1134
                        
960
1135
                self.__UpdateFadeLevels()
961
 
                self.StateChanged(self.WAVEFORM)
 
1136
                self.emit("waveform")
962
1137
        
963
1138
        #_____________________________________________________________________
964
1139
        
969
1144
                list is a cache of faded levels as they will be shown on the screen
970
1145
                so that we don't have to calculate them everytime we draw.
971
1146
                """
972
 
                if not self.audioFadePoints:
 
1147
                if not self.audioFadePoints or len(self.audioFadePoints) < 2:
 
1148
                        Globals.debug("Event", self.id, ": no fade points to use")
973
1149
                        #there are no fade points for us to use
974
1150
                        return
975
1151
                        
976
 
                fadePercents = []
977
 
                oneSecondInLevels = len(self.levels) / self.duration
978
 
                
979
 
                previousFade = self.audioFadePoints[0]
980
 
                for fade in self.audioFadePoints[1:]:
981
 
                        #calculate the number of levels that should be in the list between the two points
982
 
                        levelsInThisSection = int(round((fade[0] - previousFade[0]) * oneSecondInLevels))
983
 
                        if fade[1] == previousFade[1]:
984
 
                                # not actually a fade, just two points at the same volume
985
 
                                fadePercents.extend([ fade[1] ] * levelsInThisSection)
 
1152
                #fadePercents = []
 
1153
                #oneSecondInLevels = len(self.levels) / self.duration
 
1154
                
 
1155
                self.fadeLevels = LevelsList.LevelsList()
 
1156
                
 
1157
                iterFadePoints = iter(self.audioFadePoints)
 
1158
                firstFadeTime, firstFadeValue = iterFadePoints.next()
 
1159
                firstFadeTime = int(firstFadeTime * 1000)       #convert to milliseconds
 
1160
                secondFadeTime, secondFadeValue = iterFadePoints.next()
 
1161
                secondFadeTime = int(secondFadeTime * 1000)     #convert to milliseconds
 
1162
                # if less than one percent difference, assume they are the same
 
1163
                sameValues = abs(firstFadeValue - secondFadeValue) < 0.01
 
1164
                
 
1165
                if not sameValues:
 
1166
                        slope = (secondFadeValue - firstFadeValue) / (secondFadeTime - firstFadeTime)
 
1167
                
 
1168
                for endtime, peak in self.levels_list:
 
1169
                        # check if we have moved into the next fade point pair
 
1170
                        # don't care about 1 millisecond difference, its rounding error
 
1171
                        if endtime > (secondFadeTime + 1):  
 
1172
                                firstFadeTime = secondFadeTime
 
1173
                                firstFadeValue = secondFadeValue
 
1174
                                try:
 
1175
                                        secondFadeTime, secondFadeValue = iterFadePoints.next()
 
1176
                                except StopIteration:
 
1177
                                        Globals.debug("Event %d: endtime (%d) is after last fade point (%d,%d)"
 
1178
                                                      % (self.id, endtime, secondFadeTime, secondFadeValue))
 
1179
                                
 
1180
                                secondFadeTime = int(secondFadeTime * 1000)     #convert to milliseconds
 
1181
                                
 
1182
                                # if less than one percent difference, assume they are the same
 
1183
                                sameValues = abs(firstFadeValue - secondFadeValue) < 0.01
 
1184
                                if not sameValues:
 
1185
                                        # the fade line is not flat, so calculate the slope of it
 
1186
                                        slope = (secondFadeValue - firstFadeValue) / (secondFadeTime - firstFadeTime)
 
1187
                        
 
1188
                        if sameValues:
 
1189
                                #no fade here, the same volume continues across
 
1190
                                self.fadeLevels.append(endtime, [int(peak * firstFadeValue)])
986
1191
                        else:
987
 
                                step = (fade[1] - previousFade[1]) / levelsInThisSection
988
 
                                floatList = Utils.floatRange(previousFade[1], fade[1], step)
989
 
                                #make sure the list of levels does not exceed the calculated length
990
 
                                floatList = floatList[:levelsInThisSection]
991
 
                                fadePercents.extend(floatList)
992
 
                        previousFade = fade
993
 
                
994
 
                if len(fadePercents) != len(self.levels):
995
 
                        #make sure its not longer than the levels list
996
 
                        fadePercents = fadePercents[:len(self.levels)]
997
 
                        #make sure its not shorter than the levels list
998
 
                        #by copying the last level over again
999
 
                        lastLevel = fadePercents[-1]
1000
 
                        while len(fadePercents) < len(self.levels):
1001
 
                                fadePercents.append(lastLevel)
 
1192
                                rel_time = endtime - firstFadeTime
 
1193
                                peak_delta = slope * rel_time
 
1194
                                new_fade_value = firstFadeValue + peak_delta
 
1195
                                assert new_fade_value <= 1.0            # a fade cannot be more than 100%
 
1196
                                peak = int(peak * new_fade_value)
1002
1197
                                
1003
 
                self.fadeLevels = []
1004
 
                for i in range(len(self.levels)):
1005
 
                        self.fadeLevels.append(fadePercents[i] * self.levels[i])
1006
 
                
 
1198
                                self.fadeLevels.append(endtime, [peak])
1007
1199
        #_____________________________________________________________________
1008
1200
        
1009
1201
        def GetFadeLevels(self):
1018
1210
                """
1019
1211
                # no fades registered
1020
1212
                if not self.audioFadePoints:
1021
 
                        return self.levels[:]
 
1213
                        return self.levels_list
1022
1214
                        
1023
 
                if len(self.fadeLevels) != len(self.levels):
 
1215
                if len(self.fadeLevels) != len(self.levels_list):
1024
1216
                        self.__UpdateFadeLevels()
 
1217
                        assert len(self.fadeLevels) == len(self.levels_list)
1025
1218
                        
1026
1219
                return self.fadeLevels
1027
1220