video editor update

This commit is contained in:
rlx 2012-01-10 20:19:28 +05:30
parent ba9423462f
commit 88f31a5ae3
10 changed files with 245 additions and 65 deletions

View file

@ -627,10 +627,16 @@ OxArrayEditable
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
*/ */
.OxArrayEditable { .OxArrayEditable.OxArrayEditableInput {
//display: table-cell;
padding: 4px; padding: 4px;
} }
.OxArrayEditable.OxArrayEditableTextarea .OxEditableElement {
padding: 4px;
border-top: 1px solid rgb(128, 128, 128);
}
.OxArrayEditable.OxArrayEditableTextarea .OxEditableElement:first-child {
border-top: 0px
}
/* /*
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------

View file

@ -21,7 +21,7 @@ Ox.ArrayEditable = function(options, self) {
width: 256 width: 256
}) })
.options(options || {}) .options(options || {})
.addClass('OxArrayEditable') .addClass('OxArrayEditable OxArrayEditable' + Ox.toTitleCase(self.options.type))
.css({width: self.options.width - 8 + 'px'}) // 2 x 4 px padding .css({width: self.options.width - 8 + 'px'}) // 2 x 4 px padding
.bindEvent({ .bindEvent({
anyclick: anyclick, anyclick: anyclick,
@ -29,10 +29,10 @@ Ox.ArrayEditable = function(options, self) {
key_delete: deleteItem, key_delete: deleteItem,
key_enter: editItem, key_enter: editItem,
key_escape: selectNone, key_escape: selectNone,
key_down: selectLast, key_down: self.options.type == 'input' ? selectLast : selectNext,
key_left: selectPrevious, key_left: self.options.type == 'input' ? selectPrevious : selectFirst,
key_right: selectNext, key_right: self.options.type == 'input' ? selectNext : selectLast,
key_up: selectFirst key_up: self.options.type == 'input' ? selectFirst : selectPrevious
}); });
self.$items = []; self.$items = [];
@ -122,7 +122,7 @@ Ox.ArrayEditable = function(options, self) {
), ),
type: self.options.type, type: self.options.type,
value: item.value, value: item.value,
width: self.options.type == 'input' ? 0 : self.options.width width: self.options.type == 'input' ? 0 : self.options.width - 8
}) })
.addClass(item.id == self.options.selected ? 'OxSelected' : '') .addClass(item.id == self.options.selected ? 'OxSelected' : '')
.data({position: i}) .data({position: i})
@ -160,7 +160,7 @@ Ox.ArrayEditable = function(options, self) {
} }
that.find('.OxSelected').removeClass('OxSelected'); that.find('.OxSelected').removeClass('OxSelected');
self.selected > -1 && self.$items[self.selected].addClass('OxSelected'); self.selected > -1 && self.$items[self.selected].addClass('OxSelected');
that.triggerEvent('select', {id: self.options.selected}); triggerSelectEvent();
} }
function selectLast() { function selectLast() {
@ -197,6 +197,16 @@ Ox.ArrayEditable = function(options, self) {
} }
} }
function triggerSelectEvent() {
if (!self.triggered) {
that.triggerEvent('select', {id: self.options.selected});
self.triggered = true;
setTimeout(function() {
self.triggered = false;
}, 250);
}
};
self.setOption = function(key, value) { self.setOption = function(key, value) {
if (key == 'items') { if (key == 'items') {
renderItems(); renderItems();

View file

@ -87,7 +87,7 @@ Ox.Editable = function(options, self) {
//height = self.options.height || Ox.limit(self.$test.height() + 2, self.minHeight, self.maxHeight); //height = self.options.height || Ox.limit(self.$test.height() + 2, self.minHeight, self.maxHeight);
height = self.options.height || Math.max(self.$test.height() + 2, self.minHeight); height = self.options.height || Math.max(self.$test.height() + 2, self.minHeight);
width = self.options.width || Ox.limit(self.$test.width() + 2, self.minWidth, self.maxWidth); width = self.options.width || Ox.limit(self.$test.width() + 2, self.minWidth, self.maxWidth);
Ox.Log('Form', self.options.width, self.$test.width(), 'wxh', width, height) Ox.Log('Form', self.options.width, self.$test.html(), self.$test.width(), self.$test.height(), 'wxh', width, height)
if (self.options.type == 'input') { if (self.options.type == 'input') {
self.$input.options({ self.$input.options({
width: width width: width
@ -184,8 +184,14 @@ Ox.Editable = function(options, self) {
function formatTestValue() { function formatTestValue() {
return self.options.type == 'input' return self.options.type == 'input'
? self.options.value.replace(/ /g, ' ') ? self.options.value.replace(/ /g, ' ')
: Ox.encodeHTML(self.options.value || ' ')
//.replace(/ /g, ' ')
.replace(/\n$/, '\n ')
.replace(/\n/g, '<br/>')
/*
: Ox.parseHTML(self.options.value || '&nbsp;') : Ox.parseHTML(self.options.value || '&nbsp;')
.replace(/<br\/?>$/, '<br/>&nbsp;'); .replace(/(\n|<br\/?>)$/, '<br/>&nbsp;');
*/
} }
function formatValue() { function formatValue() {

View file

@ -1282,6 +1282,8 @@ Ox.List = function(options, self) {
} }
function triggerSelectEvent() { function triggerSelectEvent() {
// FIXME: the first select event should fire immediately
// see ArrayEditable
getSelectedIds(function(ids) { getSelectedIds(function(ids) {
self.options.selected = ids; self.options.selected = ids;
setTimeout(function() { setTimeout(function() {

View file

@ -85,14 +85,12 @@ Ox.AnnotationPanel = function(options, self) {
selected: self.options.selected, selected: self.options.selected,
sort: self.sort, sort: self.sort,
submitOnBlur: false, submitOnBlur: false,
width: self.options.width, width: self.options.type == 'text' ? self.options.width + 8 : self.options.width,
type: self.options.type == 'text' ? 'textarea' : 'input' type: self.options.type == 'text' ? 'textarea' : 'input'
}) })
.bindEvent({ .bindEvent({
add: function(data) { add: function(data) {
that.triggerEvent('add', { that.triggerEvent('add', {value: data.value || ''});
value: data.value || ''
});
}, },
blur: function() { blur: function() {
that.triggerEvent('blur'); that.triggerEvent('blur');
@ -196,6 +194,10 @@ Ox.AnnotationPanel = function(options, self) {
self.$annotations.blurItem(); self.$annotations.blurItem();
}; };
that.editItem = function(id) {
self.$annotations.editItem(id);
};
/*@ /*@
removeItems <f> removeItems removeItems <f> removeItems
@*/ @*/

View file

@ -31,6 +31,7 @@ Ox.BlockVideoTimeline = function(options, self) {
mousemove: mousemove mousemove: mousemove
}) })
.bindEvent({ .bindEvent({
doubleclick: doubleclick,
drag: function(data) { drag: function(data) {
mousedown(data); mousedown(data);
} }
@ -56,6 +57,7 @@ Ox.BlockVideoTimeline = function(options, self) {
'in': self.options['in'], 'in': self.options['in'],
out: self.options.out, out: self.options.out,
results: self.options.results, results: self.options.results,
state: self.options.state,
subtitles: self.options.showSubtitles ? self.options.subtitles : [], subtitles: self.options.showSubtitles ? self.options.subtitles : [],
timeline: self.options.getImageURL, timeline: self.options.getImageURL,
width: Math.round(self.options.duration), width: Math.round(self.options.duration),
@ -118,6 +120,22 @@ Ox.BlockVideoTimeline = function(options, self) {
.appendTo(self.$lines[i]); .appendTo(self.$lines[i]);
} }
function doubleclick(e) {
var position;
if ($(e.target).is('.OxInterface')) {
position = getPosition(e);
if (
self.options.state == 'selected'
&& position >= self.options['in']
&& position <= self.options.out
) {
that.triggerEvent('edit');
} else if (self.options.state != 'editing') {
that.triggerEvent('select');
}
}
}
function getLines() { function getLines() {
return Math.ceil(self.options.duration / self.options.width); return Math.ceil(self.options.duration / self.options.width);
} }
@ -138,6 +156,10 @@ Ox.BlockVideoTimeline = function(options, self) {
return subtitle; return subtitle;
} }
function getTooltip(e) {
}
function mousedown(e) { function mousedown(e) {
if ($(e.target).is('.OxInterface')) { if ($(e.target).is('.OxInterface')) {
self.options.position = getPosition(e); self.options.position = getPosition(e);

View file

@ -1,11 +1,17 @@
'use strict'; 'use strict';
/*@
Ox.SmallVideoTimeline <f> Small Video Timeline
@*/
Ox.SmallVideoTimeline = function(options, self) { Ox.SmallVideoTimeline = function(options, self) {
self = self || {}; self = self || {};
var that = Ox.Element({}, self) var that = Ox.Element({}, self)
.defaults({ .defaults({
_offset: 0, // hack for cases where all these position: absolute elements have to go into a float: left // _offset is a hack for cases where all these position: absolute
// elements have to go into a float: left
_offset: 0,
disabled: false, disabled: false,
duration: 0, duration: 0,
find: '', find: '',
@ -53,9 +59,6 @@ Ox.SmallVideoTimeline = function(options, self) {
width: self.interfaceWidth + 'px', width: self.interfaceWidth + 'px',
height: '20px', height: '20px',
}) })
.bind({
mousedown: mousedown
})
.bindEvent({ .bindEvent({
drag: function(data) { drag: function(data) {
mousedown(data); mousedown(data);
@ -63,7 +66,8 @@ Ox.SmallVideoTimeline = function(options, self) {
dragend: function(data) { dragend: function(data) {
self.triggered = false; self.triggered = false;
mousedown(data); mousedown(data);
} },
mousedown: mousedown
}) })
.appendTo(that); .appendTo(that);

View file

@ -151,7 +151,7 @@ Ox.SmallVideoTimelineImage = function(options, self) {
Ox.loop(left, right, function(x) { Ox.loop(left, right, function(x) {
Ox.loop(top, bottom, function(y) { Ox.loop(top, bottom, function(y) {
var alpha = self.options.type == 'editor' var alpha = self.options.type == 'editor'
&& (y == top || y == bottom - 1) ? 192 : 64, && (y == top || y == bottom - 1) ? 255 : 128,
color = rgb[[2, 3, 6].indexOf(x % 4 + y % 4) > -1 ? 0 : 1], color = rgb[[2, 3, 6].indexOf(x % 4 + y % 4) > -1 ? 0 : 1],
index = x * 4 + y * 4 * width; index = x * 4 + y * 4 * width;
data[index] = color[0]; data[index] = color[0];

View file

@ -69,10 +69,10 @@ Ox.VideoEditor = function(options, self) {
key_alt_shift_right: function() { key_alt_shift_right: function() {
}, },
key_backslash: function() { key_backslash: function() {
select('subtitle'); selectAnnotation();
}, },
key_closebracket: function() { key_closebracket: function() {
movePositionTo('subtitle', 1); movePositionTo('annotation', 1);
}, },
key_comma: function() { key_comma: function() {
movePositionTo('cut', -1); movePositionTo('cut', -1);
@ -83,6 +83,20 @@ Ox.VideoEditor = function(options, self) {
key_down: function() { key_down: function() {
movePositionBy(self.sizes.timeline[0].width); movePositionBy(self.sizes.timeline[0].width);
}, },
key_enter: function() {
if (self.editiing) {
submitAnnotation();
} else if (self.options.selected) {
editAnnotation();
}
},
key_escape: function() {
if (self.editing) {
blurAnnotation();
} else if (self.options.selected) {
deselectAnnotation();
}
},
key_f: function() { key_f: function() {
setTimeout(function() { setTimeout(function() {
self.$findInput.focusInput(true); self.$findInput.focusInput(true);
@ -101,7 +115,7 @@ Ox.VideoEditor = function(options, self) {
setPoint('out', self.options.position); setPoint('out', self.options.position);
}, },
key_openbracket: function() { key_openbracket: function() {
movePositionTo('subtitle', -1); movePositionTo('annotation', -1);
}, },
key_p: playInToOut, key_p: playInToOut,
key_right: function() { key_right: function() {
@ -141,7 +155,7 @@ Ox.VideoEditor = function(options, self) {
movePositionBy(-self.options.position); movePositionBy(-self.options.position);
}, },
key_slash: function() { key_slash: function() {
select('cut'); selectCut();
}, },
key_space: togglePaused, key_space: togglePaused,
key_up: function() { key_up: function() {
@ -149,6 +163,12 @@ Ox.VideoEditor = function(options, self) {
} }
}); });
Ox.loop(1, self.options.layers.length + 1, function(i) {
that.bindEvent('key_' + i, function() {
addAnnotation(self.options.layers[i - 1].id);
});
});
Ox.extend(self, { Ox.extend(self, {
$player: [], $player: [],
$timeline: [], $timeline: [],
@ -157,7 +177,7 @@ Ox.VideoEditor = function(options, self) {
margin: 8, margin: 8,
}); });
//Ox.print('VIDEO EDITOR OPTIONS', self.options) Ox.print('VIDEO EDITOR OPTIONS', self.options)
self.words = []; self.words = [];
Ox.forEach(Ox.count(Ox.words(self.options.subtitles.map(function(subtitle) { Ox.forEach(Ox.count(Ox.words(self.options.subtitles.map(function(subtitle) {
@ -306,8 +326,12 @@ Ox.VideoEditor = function(options, self) {
top: self.sizes.timeline[1].top + 'px', top: self.sizes.timeline[1].top + 'px',
}) })
.bindEvent({ .bindEvent({
edit: editAnnotation,
position: function(data) { position: function(data) {
setPosition(data.position); setPosition(data.position);
},
select: function() {
selectAnnotation();
} }
}) })
.appendTo(self.$editor); .appendTo(self.$editor);
@ -335,10 +359,7 @@ Ox.VideoEditor = function(options, self) {
) )
.bindEvent({ .bindEvent({
add: function(data) { add: function(data) {
data.layer = layer.id; addAnnotation(layer.id);
data['in'] = self.options['in'];
data.out = self.options.out;
that.triggerEvent('addannotation', data);
}, },
blur: function() { blur: function() {
//Ox.print('VIDEO EDITOR BLUR FOCUSED?', self.focused) //Ox.print('VIDEO EDITOR BLUR FOCUSED?', self.focused)
@ -346,13 +367,13 @@ Ox.VideoEditor = function(options, self) {
// ... // ...
} else { } else {
self.editing = false; self.editing = false;
setState(); setTimelineState();
this.blurItem(); this.blurItem();
} }
}, },
edit: function() { edit: function() {
self.editing = true; self.editing = true;
setState(); setTimelineState();
}, },
remove: function(data) { remove: function(data) {
that.triggerEvent('removeannotation', { that.triggerEvent('removeannotation', {
@ -372,7 +393,7 @@ Ox.VideoEditor = function(options, self) {
// ... // ...
} }
}, },
submit: editAnnotation submit: submitAnnotation
}) })
.appendTo(self.$annotations); .appendTo(self.$annotations);
}); });
@ -756,12 +777,31 @@ Ox.VideoEditor = function(options, self) {
submitFindInput(self.options.find, true); submitFindInput(self.options.find, true);
}, 0); }, 0);
function editAnnotation(data) { function addAnnotation(layer) {
that.triggerEvent('addannotation', {
'in': self.options['in'],
layer: layer,
out: self.options.out,
value: ''
});
}
function blurAnnotation() {
self.editing = false; self.editing = false;
setState(); setTimelineState();
data['in'] = self.options['in']; getPanel(self.options.selected).blurItem(self.options.selected);
data.out = self.options.out; }
that.triggerEvent('editannotation', data);
function deselectAnnotation() {
// FIXME: there is selectAnnotation({id: ''})
self.options.selected = '';
setTimelineState();
}
function editAnnotation() {
self.editing = true;
setTimelineState();
getPanel(self.options.selected).editItem(self.options.selected);
} }
function find(query) { function find(query) {
@ -778,21 +818,60 @@ Ox.VideoEditor = function(options, self) {
return results; return results;
} }
function getAnnotation() {
// Get annotation at current position
var annotations = [];
self.options.layers.forEach(function(layer) {
layer.items.forEach(function(item) {
if (
item['in'] <= self.options.position
&& item.out >= self.options.position
) {
annotations.push(item);
}
});
});
annotations.sort(function(a, b) {
var aValue = self.options.position - a['in'],
bValue = self.options.position - b['in'],
ret = 0;
if (aValue < bValue) {
ret = -1;
} else if (aValue > bValue) {
ret = 1;
} else if (a.duration < b.duration) {
ret = -1
} else if (a.duration > b.duration) {
ret = 1;
} else if (a.value < b.value) {
ret = -1
} else if (a.value > b.value) {
ret = 1;
}
return ret;
});
return annotations.length ? annotations[0] : {id: ''};
}
// fixme: why not goToNextPosition()? // fixme: why not goToNextPosition()?
function getNextPosition(type, direction) { function getNextPosition(type, direction) {
var found = false, var found = false,
position = 0, position = 0,
positions; positions;
if (type == 'cut') { if (type == 'annotation') {
positions = self.options.cuts; positions = Ox.sort(Ox.unique(Ox.flatten(
self.options.layers.map(function(layer) {
return layer.items.map(function(item) {
return item['in'];
});
})
)));
} else if (type == 'cut') {
positions = Ox.merge(0, self.options.cuts, self.options.duration);
} else if (type == 'result') { } else if (type == 'result') {
positions = self.results.map(function(v) { positions = self.results.map(function(v) {
return v['in']; return v['in'];
}); });
} else if (type == 'subtitle') {
positions = self.options.subtitles.map(function(v) {
return v['in'];
});
} }
direction == -1 && positions.reverse(); direction == -1 && positions.reverse();
Ox.forEach(positions, function(v) { Ox.forEach(positions, function(v) {
@ -809,23 +888,42 @@ Ox.VideoEditor = function(options, self) {
return position; return position;
} }
function getPanel(annotationId) {
var found = false, panel;
Ox.forEach(self.options.layers, function(layer, i) {
Ox.forEach(layer.items, function(item) {
if (item.id == annotationId) {
panel = self.$annotationPanel[i];
found = true;
return false;
}
});
return !found;
});
return panel;
}
/*
function getPoints(type) { function getPoints(type) {
var found = false, var found = false,
points, points,
positions = []; positions = [];
if (type == 'cut') { if (type == 'annotation') {
} else if (type == 'cut') {
positions = self.options.cuts; positions = self.options.cuts;
} else if (type == 'match') { } else if (type == 'result') {
// ... // ...
} else if (type == 'subtitle') { } else if (type == 'subtitle') {
// FIXME: remove? annotation?
self.options.subtitles.forEach(function(v, i) { self.options.subtitles.forEach(function(v, i) {
positions.push(v['in']); positions.push(v['in']);
positions.push(v.out); positions.push(v.out);
}); });
} }
positions.indexOf(0) == -1 && positions.unshift(0); positions.indexOf(0) == -1 && positions.unshift(0);
positions.indexOf(self.options.duration) == -1 && positions.indexOf(self.options.duration) == -1
positions.push(self.options.duration); && positions.push(self.options.duration);
Ox.forEach(positions, function(v, i) { Ox.forEach(positions, function(v, i) {
if (v > self.options.position) { if (v > self.options.position) {
points = [positions[i - 1], positions[i]]; points = [positions[i - 1], positions[i]];
@ -835,6 +933,7 @@ Ox.VideoEditor = function(options, self) {
}); });
return points; return points;
} }
*/
function getSizes(scrollbarIsVisible) { function getSizes(scrollbarIsVisible) {
var scrollbarWidth = Ox.UI.SCROLLBAR_SIZE, var scrollbarWidth = Ox.UI.SCROLLBAR_SIZE,
@ -932,12 +1031,8 @@ Ox.VideoEditor = function(options, self) {
function resizeEditor(data) { function resizeEditor(data) {
var width = data.size - 2 * margin + 100; var width = data.size - 2 * margin + 100;
resizeVideoPlayers(width); resizeVideoPlayers(width);
$timelineLarge.options({ $timelineLarge.options({width: width});
width: width $timelineSmall.options({width: width});
});
$timelineSmall.options({
width: width
});
} }
function resizePlayers() { function resizePlayers() {
@ -955,22 +1050,44 @@ Ox.VideoEditor = function(options, self) {
function selectAnnotation(data) { function selectAnnotation(data) {
//Ox.print('VE.sA') //Ox.print('VE.sA')
if (Ox.isUndefined(data)) {
data = getAnnotation();
}
self.editing = false; self.editing = false;
self.options.selected = data.id || ''; self.options.selected = data.id;
self.options.annotationsRange != 'position' && setPosition(data['in']); if (self.options.selected && self.options.annotationsRange != 'position') {
setState(); setPosition(data['in']);
that.triggerEvent('select', { }
id: self.options.selected
});
if (self.options.selected) { if (self.options.selected) {
setPoint('in', data['in']); setPoint('in', data['in']);
setPoint('out', data.out); setPoint('out', data.out);
Ox.print('???', JSON.stringify(self.options.selected), getPanel(self.options.selected))
getPanel(self.options.selected).options({selected: self.options.selected});
} }
setTimelineState();
that.triggerEvent('select', {
id: self.options.selected
});
} }
function select(type) { function selectCut() {
self.options.points = getPoints(type); var points = {
setPoints(); 'in': Ox.last(self.options.cuts),
out: self.options.duration
};
Ox.forEach(self.options.cuts, function(cut, i) {
if (cut > self.options.position) {
points = {
'in': i ? self.options.cuts[i - 1] : 0,
out: cut - 1 / self.options.fps
};
return false;
}
});
self.options.selected = '';
setTimelineState();
setPoint('in', points['in']);
setPoint('out', points.out);
} }
function setPoint(point, position, annotation) { function setPoint(point, position, annotation) {
@ -980,7 +1097,7 @@ Ox.VideoEditor = function(options, self) {
// should only happen through interaction // should only happen through interaction
if (self.options.selected && !self.editing) { if (self.options.selected && !self.editing) {
self.options.selected = ''; self.options.selected = '';
setState(); setTimelineState();
} }
*/ */
self.$player.forEach(function($player) { self.$player.forEach(function($player) {
@ -1057,7 +1174,7 @@ Ox.VideoEditor = function(options, self) {
}); });
} }
function setState() { function setTimelineState() {
self.$timeline[1].options({ self.$timeline[1].options({
state: self.editing ? 'editing' state: self.editing ? 'editing'
: self.options.selected ? 'selected' : self.options.selected ? 'selected'
@ -1066,6 +1183,14 @@ Ox.VideoEditor = function(options, self) {
//Ox.print('SET STATE', self.$timeline[1].options('state')) //Ox.print('SET STATE', self.$timeline[1].options('state'))
} }
function submitAnnotation(data) {
self.editing = false;
setTimelineState();
data['in'] = self.options['in'];
data.out = self.options.out;
that.triggerEvent('editannotation', data);
}
function submitFindInput(value, hasPressedEnter) { function submitFindInput(value, hasPressedEnter) {
self.options.find = value; self.options.find = value;
self.results = find(self.options.find); self.results = find(self.options.find);

View file

@ -747,6 +747,9 @@ Video
background: rgb(160, 224, 160); background: rgb(160, 224, 160);
box-shadow: 0 0 1px rgb(96, 96, 96); box-shadow: 0 0 1px rgb(96, 96, 96);
} }
.OxThemeClassic .OxAnnotationPanel .OxEditableElement textarea {
background: rgb(160, 224, 160);
}
/* /*
================================================================================ ================================================================================