1
# PiTiVi , Non-linear video editor
5
# Copyright (c) 2010, Brandon Lewis <brandon_lewis@berkeley.edu>
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.
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.
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.
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
38
class DuplicatePresetNameException(Exception):
39
"""Raised when an operation would result in a duplicated preset name."""
43
class PresetManager(object):
44
"""Abstract class for storing a list of presets.
46
Subclasses must provide a filename attribute.
48
@cvar filename: The name of the file where the presets will be stored.
51
@ivar cur_preset: The currently selected preset. Note that a preset has to
52
be selected before it can be changed.
54
@ivar ordered: A list holding (name -> preset_dict) tuples.
55
@type ordered: gtk.ListStore
56
@ivar presets: A (name -> preset_dict) map.
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
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
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))
81
for file in filepaths:
82
if file.endswith("json"):
83
self.loadSection(os.path.join(self.default_path, file))
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":
95
filepath = self.presets[name]["filepath"]
97
filename = name + ".json"
98
filepath = os.path.join(self.user_path, filename)
100
fout = open(filepath, "w")
101
self.saveSection(fout, name)
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
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":
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
124
def addPreset(self, name, values):
127
@param name: The name of the new preset.
129
@param values: The values of the new preset.
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))
138
def renamePreset(self, path, new_name):
139
"""Change the name of a preset.
141
@param path: The path in the model identifying the preset to be renamed.
143
@param new_name: The new name for the preset.
146
old_name = self.ordered[path][0]
147
assert old_name in self.presets
148
if old_name == new_name:
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)
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
168
def hasPreset(self, name):
170
return any(name == preset.lower() for preset in self.getPresetNames())
172
def getPresetNames(self):
173
return (row[0] for row in self.ordered)
176
"""Get the GtkModel used by the UI."""
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.
185
self.presets[self.cur_preset][name] = value
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)
191
def restorePreset(self, preset):
192
"""Select a preset and copy the values from the preset to the widgets.
194
@param preset: The name of the preset to be selected.
197
self._ignore_update_requests = True
199
self.cur_preset = None
201
elif not preset in self.presets:
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])
209
setter(self.presets['No preset'][field])
210
self._ignore_update_requests = False
212
def savePreset(self):
213
"""Update the preset values and write to disk"""
214
if self.cur_preset != "No preset":
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)
222
filepath = self.presets[self.cur_preset]["filepath"]
224
filepath = os.path.join(self.user_path, self.cur_preset + ".json")
226
fout = open(filepath, "w")
227
self.saveSection(fout, self.cur_preset)
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)
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()
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.
245
values = self.presets[self.cur_preset]
246
return any((values[field] != getter()
247
for field, (setter, getter) in self.widget_map.iteritems()))
249
def removePreset(self, name):
251
os.remove(self.presets[name]["filepath"]) # Deletes json file if exists
253
# Trying to remove a preset that has not actually been saved
257
self.presets.pop(name)
258
for i, row in enumerate(self.ordered):
262
if self.cur_preset == name:
263
self.cur_preset = None
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))
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":
275
full_path = self.presets[self.cur_preset]["filepath"]
276
(dir, name) = os.path.split(full_path)
278
# This is a newly created preset that has not yet been saved
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.
285
return self._isCurrentPresetChanged()
287
def isRemoveButtonSensitive(self):
288
"""Check if Remove buttons should be sensitive"""
289
if not self.cur_preset or self.cur_preset == "No preset":
292
full_path = self.presets[self.cur_preset]["filepath"]
293
(dir, name) = os.path.split(full_path)
295
# This is a newly created preset that has not yet been saved
296
# We cannot remove it since it does not exist
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.
307
class VideoPresetManager(PresetManager):
309
default_path = get_videopresets_dir()
310
user_path = os.path.join(xdg_data_home(), 'video_presets')
312
def loadSection(self, filepath):
313
parser = json.loads(open(filepath).read())
315
name = parser["name"]
316
width = parser["width"]
317
height = parser["height"]
319
framerate_num = parser["framerate-num"]
320
framerate_denom = parser["framerate-denom"]
321
framerate = gst.Fraction(framerate_num, framerate_denom)
323
par_num = parser["par-num"]
324
par_denom = parser["par-denom"]
325
par = gst.Fraction(par_num, par_denom)
327
self.addPreset(name, {
330
"frame-rate": framerate,
332
"filepath": filepath,
335
def saveSection(self, fout, section):
336
values = self.presets[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,
349
class AudioPresetManager(PresetManager):
351
default_path = get_audiopresets_dir()
352
user_path = os.path.join(xdg_data_home(), 'audio_presets')
354
def loadSection(self, filepath):
355
parser = json.loads(open(filepath).read())
357
name = parser["name"]
359
channels = parser["channels"]
360
depth = parser["depth"]
361
sample_rate = parser["sample-rate"]
363
self.addPreset(name, {
364
"channels": channels,
366
"sample-rate": sample_rate,
367
"filepath": filepath,
370
def saveSection(self, fout, section):
371
values = self.presets[section]
374
"channels": values["channels"],
375
"depth": int(values["depth"]),
376
"sample-rate": int(values["sample-rate"]),
381
class RenderPresetManager(PresetManager):
383
default_path = get_renderpresets_dir()
384
user_path = os.path.join(xdg_data_home(), 'render_presets')
386
def loadSection(self, filepath):
387
parser = json.loads(open(filepath).read())
389
name = parser["name"]
390
container = parser["container"]
391
acodec = parser["acodec"]
392
vcodec = parser["vcodec"]
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()]):
401
width = parser["width"]
402
height = parser["height"]
407
framerate_num = parser["framerate-num"]
408
framerate_denom = parser["framerate-denom"]
409
framerate = gst.Fraction(framerate_num, framerate_denom)
411
channels = parser["channels"]
412
depth = parser["depth"]
413
sample_rate = parser["sample-rate"]
415
self.addPreset(name, {
416
"container": container,
421
"frame-rate": framerate,
422
"channels": channels,
424
"sample-rate": sample_rate,
425
"filepath": filepath,
428
def saveSection(self, fout, section):
429
values = self.presets[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"]),