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

« back to all changes in this revision

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