~timo-jyrinki/ubuntu/trusty/pitivi/backport_utopic_fixes

« back to all changes in this revision

Viewing changes to pitivi/ui/preset.py

  • Committer: Package Import Robot
  • Author(s): Sebastian Dröge
  • Date: 2014-04-05 15:28:16 UTC
  • mfrom: (6.1.13 sid)
  • Revision ID: package-import@ubuntu.com-20140405152816-6lijoax4cngiz5j5
Tags: 0.93-3
* debian/control:
  + Depend on python-gi (>= 3.10), older versions do not work
    with pitivi (Closes: #732813).
  + Add missing dependency on gir1.2-clutter-gst-2.0 (Closes: #743692).
  + Add suggests on gir1.2-notify-0.7 and gir1.2-gnomedesktop-3.0.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# PiTiVi , Non-linear video editor
2
 
#
3
 
#       pitivi/ui/preset.py
4
 
#
5
 
# Copyright (c) 2010, Brandon Lewis <brandon_lewis@berkeley.edu>
6
 
#
7
 
# This program is free software; you can redistribute it and/or
8
 
# modify it under the terms of the GNU Lesser General Public
9
 
# License as published by the Free Software Foundation; either
10
 
# version 2.1 of the License, or (at your option) any later version.
11
 
#
12
 
# This program is distributed in the hope that it will be useful,
13
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15
 
# Lesser General Public License for more details.
16
 
#
17
 
# You should have received a copy of the GNU Lesser General Public
18
 
# License along with this program; if not, write to the
19
 
# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
20
 
# Boston, MA 02110-1301, USA.
21
 
 
22
 
import ConfigParser
23
 
import os.path
24
 
 
25
 
import gst
26
 
import gtk
27
 
 
28
 
from pitivi.encode import available_muxers, available_video_encoders, \
29
 
     available_audio_encoders
30
 
from pitivi.settings import xdg_data_home
31
 
from pitivi.utils import isWritable
32
 
from pitivi.configure import get_data_dir, get_renderpresets_dir, \
33
 
        get_audiopresets_dir, get_videopresets_dir
34
 
import json
35
 
import os
36
 
 
37
 
 
38
 
class DuplicatePresetNameException(Exception):
39
 
    """Raised when an operation would result in a duplicated preset name."""
40
 
    pass
41
 
 
42
 
 
43
 
class PresetManager(object):
44
 
    """Abstract class for storing a list of presets.
45
 
 
46
 
    Subclasses must provide a filename attribute.
47
 
 
48
 
    @cvar filename: The name of the file where the presets will be stored.
49
 
    @type filename: str
50
 
 
51
 
    @ivar cur_preset: The currently selected preset. Note that a preset has to
52
 
        be selected before it can be changed.
53
 
    @type cur_preset: str
54
 
    @ivar ordered: A list holding (name -> preset_dict) tuples.
55
 
    @type ordered: gtk.ListStore
56
 
    @ivar presets: A (name -> preset_dict) map.
57
 
    @type presets: dict
58
 
    @ivar widget_map: A (propname -> (setter_func, getter_func)) map.
59
 
        These two functions are used when showing or saving a preset.
60
 
    @type widget_map: dict
61
 
    """
62
 
 
63
 
    def __init__(self):
64
 
        self.presets = {}
65
 
        self.widget_map = {}
66
 
        self.ordered = gtk.ListStore(str, object)
67
 
        self.cur_preset = None
68
 
        # Whether to ignore the updateValue calls.
69
 
        self._ignore_update_requests = False
70
 
 
71
 
    def loadAll(self):
72
 
        filepaths = []
73
 
        try:
74
 
            for uri in os.listdir(self.default_path):
75
 
                filepaths.append(os.path.join(self.default_path, uri))
76
 
            for uri in os.listdir(self.user_path):
77
 
                filepaths.append(os.path.join(self.user_path, uri))
78
 
        except Exception:
79
 
            pass
80
 
 
81
 
        for file in filepaths:
82
 
            if file.endswith("json"):
83
 
                self.loadSection(os.path.join(self.default_path, file))
84
 
 
85
 
    def saveAll(self):
86
 
        """Write changes to disk for all presets"""
87
 
        if os.path.isfile(self.user_path):
88
 
            # We used to save presets as a single file instead of a directory
89
 
            os.remove(self.user_path)
90
 
        if not os.path.exists(self.user_path):
91
 
            os.makedirs(self.user_path)
92
 
        for name, properties in self.ordered:
93
 
            if not name == "No preset":
94
 
                try:
95
 
                    filepath = self.presets[name]["filepath"]
96
 
                except KeyError:
97
 
                    filename = name + ".json"
98
 
                    filepath = os.path.join(self.user_path, filename)
99
 
                try:
100
 
                    fout = open(filepath, "w")
101
 
                    self.saveSection(fout, name)
102
 
                except IOError:
103
 
                    # FIXME: this can happen in two cases: a permissions error,
104
 
                    # or an invalid filename (ex: gibberish). In the latter case
105
 
                    # we should log an error message or show an infobar, and
106
 
                    # the test suite should verify this
107
 
                    pass
108
 
 
109
 
    def _convertSectionNameToPresetName(self, section):
110
 
        # A section name for a ConfigParser can have any name except "default"!
111
 
        assert section != "default"
112
 
        if section.rstrip("_").lower() == "default":
113
 
            return section[:-1]
114
 
        else:
115
 
            return section
116
 
 
117
 
    def _convertPresetNameToSectionName(self, preset):
118
 
        if preset.rstrip("_").lower() == "default":
119
 
            # We add an _ to allow the user to have a preset named "default".
120
 
            return "%s_" % preset
121
 
        else:
122
 
            return preset
123
 
 
124
 
    def addPreset(self, name, values):
125
 
        """Add a new preset.
126
 
 
127
 
        @param name: The name of the new preset.
128
 
        @type name: str
129
 
        @param values: The values of the new preset.
130
 
        @type values: dict
131
 
        """
132
 
        if self.hasPreset(name):
133
 
            raise DuplicatePresetNameException(name)
134
 
        self.presets[name] = values
135
 
        # Note: This generates a "row-inserted" signal in the model.
136
 
        self.ordered.append((name, values))
137
 
 
138
 
    def renamePreset(self, path, new_name):
139
 
        """Change the name of a preset.
140
 
 
141
 
        @param path: The path in the model identifying the preset to be renamed.
142
 
        @type path: str
143
 
        @param new_name: The new name for the preset.
144
 
        @type new_name: str
145
 
        """
146
 
        old_name = self.ordered[path][0]
147
 
        assert old_name in self.presets
148
 
        if old_name == new_name:
149
 
            # Nothing to do.
150
 
            return
151
 
        if (not old_name.lower() == new_name.lower() and
152
 
            self.hasPreset(new_name)):
153
 
            raise DuplicatePresetNameException()
154
 
        self.ordered[path][0] = new_name
155
 
        self.presets[new_name] = self.presets[old_name]
156
 
        if "filepath" in self.presets[old_name]:
157
 
            # If the previous preset had already been saved,
158
 
            # delete the file and pop it from the list
159
 
            self.removePreset(old_name)
160
 
        else:
161
 
            # We're renaming an unsaved preset, so just pop it from the list
162
 
            self.presets.pop(old_name)
163
 
        new_filepath = os.path.join(self.user_path, new_name + ".json")
164
 
        self.presets[new_name]["filepath"] = new_filepath
165
 
        self.cur_preset = new_name
166
 
        self.savePreset()
167
 
 
168
 
    def hasPreset(self, name):
169
 
        name = name.lower()
170
 
        return any(name == preset.lower() for preset in self.getPresetNames())
171
 
 
172
 
    def getPresetNames(self):
173
 
        return (row[0] for row in self.ordered)
174
 
 
175
 
    def getModel(self):
176
 
        """Get the GtkModel used by the UI."""
177
 
        return self.ordered
178
 
 
179
 
    def updateValue(self, name, value):
180
 
        """Update a value in the current preset, if any."""
181
 
        if self._ignore_update_requests:
182
 
            # This is caused by restorePreset, nothing to do.
183
 
            return
184
 
        if self.cur_preset:
185
 
            self.presets[self.cur_preset][name] = value
186
 
 
187
 
    def bindWidget(self, propname, setter_func, getter_func):
188
 
        """Link the specified functions to the specified preset property."""
189
 
        self.widget_map[propname] = (setter_func, getter_func)
190
 
 
191
 
    def restorePreset(self, preset):
192
 
        """Select a preset and copy the values from the preset to the widgets.
193
 
 
194
 
        @param preset: The name of the preset to be selected.
195
 
        @type preset: str
196
 
        """
197
 
        self._ignore_update_requests = True
198
 
        if preset is None:
199
 
            self.cur_preset = None
200
 
            return
201
 
        elif not preset in self.presets:
202
 
            return
203
 
        values = self.presets[preset]
204
 
        self.cur_preset = preset
205
 
        for field, (setter, getter) in self.widget_map.iteritems():
206
 
            if values[field] != 0:
207
 
                setter(values[field])
208
 
            else:
209
 
                setter(self.presets['No preset'][field])
210
 
        self._ignore_update_requests = False
211
 
 
212
 
    def savePreset(self):
213
 
        """Update the preset values and write to disk"""
214
 
        if self.cur_preset != "No preset":
215
 
            self._updatePreset()
216
 
            if os.path.isfile(self.user_path):
217
 
                # We used to save presets as a single file instead of a directory
218
 
                os.remove(self.user_path)
219
 
            if not os.path.exists(self.user_path):
220
 
                os.makedirs(self.user_path)
221
 
            try:
222
 
                filepath = self.presets[self.cur_preset]["filepath"]
223
 
            except KeyError:
224
 
                filepath = os.path.join(self.user_path, self.cur_preset + ".json")
225
 
            try:
226
 
                fout = open(filepath, "w")
227
 
                self.saveSection(fout, self.cur_preset)
228
 
            except IOError:
229
 
                # TODO: show an error infobar... but this should never happen,
230
 
                # because the UI is supposed to disable the Save button
231
 
                # ...unless the user types an invalid filename (ex: gibberish)
232
 
                pass
233
 
 
234
 
    def _updatePreset(self):
235
 
        """Copy the values from the widgets to the preset."""
236
 
        values = self.presets[self.cur_preset]
237
 
        for field, (setter, getter) in self.widget_map.iteritems():
238
 
            values[field] = getter()
239
 
 
240
 
    def _isCurrentPresetChanged(self):
241
 
        """Return whether the widgets values differ from those of the preset."""
242
 
        if not self.cur_preset:
243
 
            # There is no preset selected, nothing to do.
244
 
            return False
245
 
        values = self.presets[self.cur_preset]
246
 
        return any((values[field] != getter()
247
 
                    for field, (setter, getter) in self.widget_map.iteritems()))
248
 
 
249
 
    def removePreset(self, name):
250
 
        try:
251
 
            os.remove(self.presets[name]["filepath"])  # Deletes json file if exists
252
 
        except KeyError:
253
 
            # Trying to remove a preset that has not actually been saved
254
 
            return
255
 
        except Exception:
256
 
            raise
257
 
        self.presets.pop(name)
258
 
        for i, row in enumerate(self.ordered):
259
 
            if row[0] == name:
260
 
                del self.ordered[i]
261
 
                break
262
 
        if self.cur_preset == name:
263
 
            self.cur_preset = None
264
 
 
265
 
    def prependPreset(self, name, values):
266
 
        self.presets[name] = values
267
 
        # Note: This generates a "row-inserted" signal in the model.
268
 
        self.ordered.prepend((name, values))
269
 
 
270
 
    def isSaveButtonSensitive(self):
271
 
        """Check if the Save button should be sensitive"""
272
 
        if not self.cur_preset or self.cur_preset == "No preset":
273
 
            return False
274
 
        try:
275
 
            full_path = self.presets[self.cur_preset]["filepath"]
276
 
            (dir, name) = os.path.split(full_path)
277
 
        except KeyError:
278
 
            # This is a newly created preset that has not yet been saved
279
 
            return True
280
 
        if dir == self.default_path or not isWritable(full_path):
281
 
            # default_path is the system-wide directory where the default
282
 
            # presets are installed; they are not expected to be editable.
283
 
            return False
284
 
        else:
285
 
            return self._isCurrentPresetChanged()
286
 
 
287
 
    def isRemoveButtonSensitive(self):
288
 
        """Check if Remove buttons should be sensitive"""
289
 
        if not self.cur_preset or self.cur_preset == "No preset":
290
 
            return False
291
 
        try:
292
 
            full_path = self.presets[self.cur_preset]["filepath"]
293
 
            (dir, name) = os.path.split(full_path)
294
 
        except KeyError:
295
 
            # This is a newly created preset that has not yet been saved
296
 
            # We cannot remove it since it does not exist
297
 
            return False
298
 
        if dir == self.default_path or not isWritable(full_path):
299
 
            # default_path is the system-wide directory where the default
300
 
            # presets are installed; they are not expected to be editable.
301
 
            return False
302
 
        else:
303
 
            return True
304
 
        return False
305
 
 
306
 
 
307
 
class VideoPresetManager(PresetManager):
308
 
 
309
 
    default_path = get_videopresets_dir()
310
 
    user_path = os.path.join(xdg_data_home(), 'video_presets')
311
 
 
312
 
    def loadSection(self, filepath):
313
 
        parser = json.loads(open(filepath).read())
314
 
 
315
 
        name = parser["name"]
316
 
        width = parser["width"]
317
 
        height = parser["height"]
318
 
 
319
 
        framerate_num = parser["framerate-num"]
320
 
        framerate_denom = parser["framerate-denom"]
321
 
        framerate = gst.Fraction(framerate_num, framerate_denom)
322
 
 
323
 
        par_num = parser["par-num"]
324
 
        par_denom = parser["par-denom"]
325
 
        par = gst.Fraction(par_num, par_denom)
326
 
 
327
 
        self.addPreset(name, {
328
 
            "width": width,
329
 
            "height": height,
330
 
            "frame-rate": framerate,
331
 
            "par": par,
332
 
            "filepath": filepath,
333
 
        })
334
 
 
335
 
    def saveSection(self, fout, section):
336
 
        values = self.presets[section]
337
 
        data = json.dumps({
338
 
            "name": section,
339
 
            "width": int(values["width"]),
340
 
            "height": int(values["height"]),
341
 
            "framerate-num": values["frame-rate"].num,
342
 
            "framerate-denom": values["frame-rate"].denom,
343
 
            "par-num": values["par"].num,
344
 
            "par-denom": values["par"].denom,
345
 
        }, indent=4)
346
 
        fout.write(data)
347
 
 
348
 
 
349
 
class AudioPresetManager(PresetManager):
350
 
 
351
 
    default_path = get_audiopresets_dir()
352
 
    user_path = os.path.join(xdg_data_home(), 'audio_presets')
353
 
 
354
 
    def loadSection(self, filepath):
355
 
        parser = json.loads(open(filepath).read())
356
 
 
357
 
        name = parser["name"]
358
 
 
359
 
        channels = parser["channels"]
360
 
        depth = parser["depth"]
361
 
        sample_rate = parser["sample-rate"]
362
 
 
363
 
        self.addPreset(name, {
364
 
            "channels": channels,
365
 
            "depth": depth,
366
 
            "sample-rate": sample_rate,
367
 
            "filepath": filepath,
368
 
        })
369
 
 
370
 
    def saveSection(self, fout, section):
371
 
        values = self.presets[section]
372
 
        data = json.dumps({
373
 
            "name": section,
374
 
            "channels": values["channels"],
375
 
            "depth": int(values["depth"]),
376
 
            "sample-rate": int(values["sample-rate"]),
377
 
        }, indent=4)
378
 
        fout.write(data)
379
 
 
380
 
 
381
 
class RenderPresetManager(PresetManager):
382
 
 
383
 
    default_path = get_renderpresets_dir()
384
 
    user_path = os.path.join(xdg_data_home(), 'render_presets')
385
 
 
386
 
    def loadSection(self, filepath):
387
 
        parser = json.loads(open(filepath).read())
388
 
 
389
 
        name = parser["name"]
390
 
        container = parser["container"]
391
 
        acodec = parser["acodec"]
392
 
        vcodec = parser["vcodec"]
393
 
 
394
 
        if (acodec not in [fact.get_name() for fact in available_audio_encoders()] or
395
 
            vcodec not in [fact.get_name() for fact in available_video_encoders()] or
396
 
            container not in [fact.get_name() for fact in available_muxers()]):
397
 
 
398
 
            return
399
 
 
400
 
        try:
401
 
            width = parser["width"]
402
 
            height = parser["height"]
403
 
        except:
404
 
            width = 0
405
 
            height = 0
406
 
 
407
 
        framerate_num = parser["framerate-num"]
408
 
        framerate_denom = parser["framerate-denom"]
409
 
        framerate = gst.Fraction(framerate_num, framerate_denom)
410
 
 
411
 
        channels = parser["channels"]
412
 
        depth = parser["depth"]
413
 
        sample_rate = parser["sample-rate"]
414
 
 
415
 
        self.addPreset(name, {
416
 
            "container": container,
417
 
            "acodec": acodec,
418
 
            "vcodec": vcodec,
419
 
            "width": width,
420
 
            "height": height,
421
 
            "frame-rate": framerate,
422
 
            "channels": channels,
423
 
            "depth": depth,
424
 
            "sample-rate": sample_rate,
425
 
            "filepath": filepath,
426
 
        })
427
 
 
428
 
    def saveSection(self, fout, section):
429
 
        values = self.presets[section]
430
 
        data = json.dumps({
431
 
            "name": section,
432
 
            "container": str(values["container"]),
433
 
            "acodec": str(values["acodec"]),
434
 
            "vcodec": str(values["vcodec"]),
435
 
            "width": int(values["width"]),
436
 
            "height": int(values["height"]),
437
 
            "framerate-num": values["frame-rate"].num,
438
 
            "framerate-denom": values["frame-rate"].denom,
439
 
            "channels": values["channels"],
440
 
            "depth": int(values["depth"]),
441
 
            "sample-rate": int(values["sample-rate"]),
442
 
        }, indent=4)
443
 
        fout.write(data)