diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 454c5a3..0000000 Binary files a/.DS_Store and /dev/null differ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d2bb9b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,27 @@ +.sass-cache +styles/*.css + +# OS generated files # +###################### +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +Icon? +ehthumbs.db +Thumbs.db + +# FRAMEWORKS # +.zfproject.xml + +# IDE # +###################### +.idea/ +netbeans/ +nbproject/ +.komodoproject/ +*.iml + +#Node JS# +node_modules/ diff --git a/.idea/.name b/.idea/.name deleted file mode 100644 index b6df638..0000000 --- a/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -projecttouch \ No newline at end of file diff --git a/.idea/cssxfire.xml b/.idea/cssxfire.xml deleted file mode 100644 index f4acc38..0000000 --- a/.idea/cssxfire.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/.idea/encodings.xml b/.idea/encodings.xml deleted file mode 100644 index e206d70..0000000 --- a/.idea/encodings.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 1162f43..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 2c138f7..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/.idea/projecttouch.iml b/.idea/projecttouch.iml deleted file mode 100644 index 6b8184f..0000000 --- a/.idea/projecttouch.iml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/.idea/scopes/scope_settings.xml b/.idea/scopes/scope_settings.xml deleted file mode 100644 index 922003b..0000000 --- a/.idea/scopes/scope_settings.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index c80f219..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/.idea/workspace.xml b/.idea/workspace.xml deleted file mode 100644 index 065b9a7..0000000 --- a/.idea/workspace.xml +++ /dev/null @@ -1,452 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1372409636242 - 1372409636242 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/1clip.mp4 b/1clip.mp4 deleted file mode 100644 index 80aec94..0000000 Binary files a/1clip.mp4 and /dev/null differ diff --git a/app/.DS_Store b/app/.DS_Store deleted file mode 100644 index f7bc4d4..0000000 Binary files a/app/.DS_Store and /dev/null differ diff --git a/app/.txt b/app/.txt deleted file mode 100644 index e69de29..0000000 diff --git a/app/collections/library.js b/app/collections/library.js index 29b0d99..671fedd 100644 --- a/app/collections/library.js +++ b/app/collections/library.js @@ -1,33 +1,39 @@ -/** - * Microsoft Video Editor - * - * @namespace collections - * @name media - * @author Thierry M.P. Kleist - * @date: 4/24/13 - */ -/*global views, console, $, $$, TweenLite, TweenMax, TimelineLite, TimelineMax, Ease, Linear, Power0, Power1, Power2, Power3, Power4, Quad, Cubic, Quart, Strong, Back, Bounce, Circ, Elastic, Expo, Sine, SlowMo */ - -define(['backbone', - 'underscore', - 'app/models/media'], function (Backbone, _, Media) { +/* Microsoft Video Editor + * @author: T.M.P. Kleist / Code D'azur + * ============================================================================== */ + +/*global views, console, $, define */ + +define(['app/models/library'], function (Model) { 'use strict'; return Backbone.Collection.extend({ - model: Media, - + model: Model, + + initialize: function () { + this.on('layer', function (model) { + + window.App.timeline.collection.add({ + type: model.get('type'), + media: model + }); + + + }); + }, + add: function (model) { var exist = 0; - _.each(this.models, function (m) { + _.each(this.models, function (m) { if (model.get('file').name === m.get('file').name && model.get('file').size === m.get('file').size && model.get('file').type === m.get('file').type) { exist += 1; -// this.trigger('add', m); + // this.trigger('add', m); } }, this); - - if(exist === 0) { + + if (exist === 0) { Backbone.Collection.prototype.add.apply(this, arguments); } } diff --git a/app/collections/timeline.js b/app/collections/timeline.js index d2a5d2c..a574541 100644 --- a/app/collections/timeline.js +++ b/app/collections/timeline.js @@ -1,37 +1,59 @@ -/** - * Microsoft Video Editor - * - * @namespace collections - * @name media - * @author Thierry M.P. Kleist - * @date: 4/24/13 - */ -/*global views, console, $, $$, TweenLite, TweenMax, TimelineLite, TimelineMax, Ease, Linear, Power0, Power1, Power2, Power3, Power4, Quad, Cubic, Quart, Strong, Back, Bounce, Circ, Elastic, Expo, Sine, SlowMo */ - -define(['backbone', 'underscore', 'app/models/clip'], function (Backbone, _, Model) { +/* Microsoft Video Editor + * @author: T.M.P. Kleist / Code D'azur + * ============================================================================== */ + +/*global views, console, $, define */ + +define(['app/models/layer', 'app/filters'], function (Model, Filter) { 'use strict'; return Backbone.Collection.extend({ model: Model, + totalFrames: 5000, - initialize: function () { + getActive: function () { - }, + var targetModel = [], + position = 99999; - getActive: function () { + _.each(this.where({type:'video'}), function (model) { - var targetModel; + if (model.get('status') === 'playing' || model.get('status') === 'seeking') { - _.each(this.models, function (model) { - if (model.get('status') === 'playing') { - targetModel = model; - } + targetModel.push(model); + + } + }, this); return targetModel; + }, + + getFilter: function (frame) { + var effects, len, x, trim, offset, endEffect, startEffect; + effects = this.where({type: 'effect'}); + len = effects.length; + if (len === 0) { + return null; + } + for (x = 0; x < len; x = x += 1) { + + trim = effects[x].get('trim'); + offset = effects[x].get('offset'); + endEffect = effects[x].get('frames') + offset - trim.end; + startEffect = trim.start + offset; + if (frame >= startEffect && frame <= endEffect) { + return Filter[effects[x].get('name')]; + } + if (x === (len - 1)) { + return null; + } + + } + return null; } }); diff --git a/app/controllers/timeline.js b/app/controllers/timeline.js index 59cbb38..b51fb10 100644 --- a/app/controllers/timeline.js +++ b/app/controllers/timeline.js @@ -1,35 +1,34 @@ -/** - * Microsoft Video Editor - * - * @namespace controllers - * @name timeline - * @author Thierry M.P. Kleist - * @date: 4/24/13 - */ -/*global views, console, $, $$, TweenLite, TweenMax, TimelineLite, TimelineMax, Ease, Linear, Power0, Power1, Power2, Power3, Power4, Quad, Cubic, Quart, Strong, Back, Bounce, Circ, Elastic, Expo, Sine, SlowMo */ - -define(['backbone', 'underscore'], function (Backbone, _) { +/* Microsoft Video Editor + * @author: T.M.P. Kleist / Code D'azur + * ============================================================================== */ - 'use strict'; +/*global views, console, $, define */ - var Timeline = function (collection) { +define(['app/collections/timeline', 'app/filters'], function (Collection, Filter) { - this.initialize(collection); + 'use strict'; + var Timeline = function () { + this.initialize(); }; Timeline.prototype = { - totalTime: 1000, - seek: 0, + _frame: 0, playing: false, - initialize: function (collection) { + initialize: function () { this.layers = []; this.videos = []; this.filter = null; - this.collection = collection; + this.collection = new Collection(); + + this.collection.on('trim:start', function (model) { + window.App.player.setSource([model]); + }, this); + + this.collection.on('change:trim', this.seek, this); _.bindAll(this, 'addEventListeners', 'changeFilter', 'play', 'stop', 'frame'); @@ -37,28 +36,23 @@ define(['backbone', 'underscore'], function (Backbone, _) { }, addEventListeners: function () { - - // this.collection.initTime(); - this.collection.on('play', this.play); - this.collection.on('stop', this.stop); - // App.Views.interface.effects.on('change', this.changeFilter); - + document.querySelector('header').addEventListener('click', this.play); }, changeFilter: function (filter) { switch (filter) { - case 'gray': - this.filter = Filters.grayscale; - break; - case 'thres': - this.filter = Filters.threshold; - break; - case 'pixel': - this.filter = Filters.pixelize; - break; - default: - this.filter = null; + case 'gray': + this.filter = Filters.grayscale; + break; + case 'thres': + this.filter = Filters.threshold; + break; + case 'pixel': + this.filter = Filters.pixelize; + break; + default: + this.filter = null; } }, @@ -66,40 +60,64 @@ define(['backbone', 'underscore'], function (Backbone, _) { play: function () { if (this.playing) { + this.playing = false; + + window.clearInterval(this.timer); + this.collection.trigger('pause'); + + this.trigger('pause', 'pause'); + return; } + this.trigger('play', 'play'); this.playing = true; this.timer = window.setInterval(this.frame, 40); }, - /* - * Syncs all the layers and sends the current layer to the Player API - */ + + /* Scrubs/Seek to frame + * ---------------------------------------------------------------------- */ + + seek: function (frame) { + if (this.playing) { + this.stop(); + } + this._frame = !isNaN(frame) ? frame : this._frame; + this.collection.trigger('seek', this._frame, this.collection.totalFrames); + window.App.player.setSource(this.collection.getActive()); + window.App.filter = window.App.timeline.collection.getFilter(this._frame); + }, + + + /* Syncs all the layers and sends the current layer to the Player API + * ---------------------------------------------------------------------- */ frame: function () { - this.collection.trigger('frame-sync', this.seek); + this.collection.trigger('frame-sync', this._frame, this.collection.totalFrames); window.App.player.setSource(this.collection.getActive()); + //window.App.player.setSource([this.collection.models[0], this.collection.models[1]]); - if (this.seek === this.totalTime) { - this.stopTimeline(); + if (this._frame === this.collection.totalFrames) { + this.stop(); } else { - this.seek += 1; + this._frame += 1; } - + window.App.filter = window.App.timeline.collection.getFilter(this._frame); }, stop: function () { this.playing = false; + this.trigger('stop', 'stop'); window.clearInterval(this.timer); window.App.player.setSource(); - this.seek = 0; - this.collection.trigger('kill'); + this._frame = 0; + this.collection.trigger('kill', 0); } diff --git a/app/filters.js b/app/filters.js index 76817f2..4856d58 100644 --- a/app/filters.js +++ b/app/filters.js @@ -1,75 +1,180 @@ -/** - * Project Touch - * - * @date: 6/18/13 - */ +/* Microsoft Video Editor + * @author: T.M.P. Kleist / Code D'azur + * ============================================================================== */ -/*global define, window, document, $, requirejs, require */ +/*global views, console, $, define */ define([], function () { - - 'use strict'; - + return { - threshold: function (pixels) { - var threshold = 80, - d = pixels.data; + + darken: function (pixels) { + + var d = pixels.data, + r, + g, + b, + v; + for (var i = 0; i < d.length; i += 4) { - var r = d[i]; - var g = d[i + 1]; - var b = d[i + 2]; - var v = (0.2126 * r + 0.7152 * g + 0.0722 * b >= threshold) ? 255 : 0; - d[i] = d[i + 1] = d[i + 2] = v + d[i] -= 30 + d[i + 1] -= 30 + d[i + 2] -= 30 } return pixels; }, - + grayscale: function (pixels) { - var source = pixels.data, - data32 = new Uint32Array(source.buffer), - r = 0, - g = 0, - b = 0, - v = 0; - - for (var y = 0; y < 360; ++y) { - for (var x = 0; x < 720; ++x) { - var b = (data32[y * 720 + x] >> 16) & 0xff; - var g = (data32[y * 720 + x] >> 8) & 0xff; - var r = (data32[y * 720 + x] >> 0) & 0xff; - - var v = 0.2126 * r + 0.7152 * g + 0.0722 * b; - - data32[y * 720 + x] = - (255 << 24) | - (v << 16) | - (v << 8) | - v; - } + + var d = pixels.data, + r, + g, + b, + v; + + for (var i = 0; i < d.length; i += 4) { + r = d[i]; + g = d[i + 1]; + b = d[i + 2]; + v = 0.2126 * r + 0.7152 * g + 0.0722 * b; + d[i] = d[i + 1] = d[i + 2] = v } - return pixels; }, + + overlight: function (pixels) { + + var d = pixels.data, + r, + g, + b, + v; + + for (var i = 0; i < d.length; i += 4) { - hipster: function (pixels) { - var ar = App.R, - ag = App.G, - ab = App.B, - data32 = new Uint32Array(pixels.data.buffer), // use Uint32Array directly for faster manipulation - ii, - tt = data32.length, + r = d[i] + (d[i] > 1 ? 50 : 0); + g = d[i + 1] + (d[i + 1] > 1 ? 50 : 0); + b = d[i + 2]; + v = 0.2126 * r + 0.7152 * g + 0.0722 * b; + d[i] = d[i + 1] = d[i + 2] = v > 255 ? 255 : v + } + return pixels; + }, + + underlight: function (pixels) { + + var d = pixels.data, + r, + g, + b, + v; + + for (var i = 0; i < d.length; i += 4) { + r = d[i] - 10; + g = d[i + 1] - 40; + b = d[i + 2]; + v = 0.2126 * r + 0.7152 * g + 0.0722 * b; + d[i] = d[i + 1] = d[i + 2] = v + } + return pixels; + }, + + sepia: function (pixels) { + + var d = pixels.data, + r, + g, + b, + v; + + for (var i = 0; i < d.length; i += 4) { + + r = d[i] - 20; + g = d[i + 1] - 20; + b = d[i + 2] - 20; + v = 0.2126 * r + 0.7152 * g + 0.0722 * b; + d[i] = v + 40 + d[i + 1] = v + 25 + d[i + 2] = v + } + return pixels; + }, + + hippie: function (pixels) { + + var d = pixels.data, + r, + g, b, + v; + + for (var i = 0; i < d.length; i += 4) { + d[i] -= 10; + d[i + 1] -= 35; + } + return pixels; + }, + + icequeen: function (pixels) { + + var d = pixels.data, + r, g, - r; + b, + v; + + for (var i = 0; i < d.length; i += 4) { + d[i] -= 5; + d[i + 1] -= 5; + d[i + 2] += 30; + } + return pixels; + }, - for (ii = 0; ii < tt; ii+=1) { - b = ((data32[ii] >> 16) & 0xff) * App.B; - g = ((data32[ii] >> 8) & 0xff) * App.G; - r = ((data32[ii] >> 0) & 0xff) * App.R; - data32[ii] = (255 << 24) | ((b > 255 ? 255 : b) << 16) | ((g > 255 ? 255 : g) << 8) | (r > 255 ? 255 : r); - } +// hipster: function (pixels) { +// +// var ar = App.R, +// ag = App.G, +// ab = App.B, +// data32 = new Uint32Array(pixels.data.buffer), // use Uint32Array directly for faster manipulation +// ii, +// tt = data32.length, +// b, +// g, +// r; +// +// for (ii = 0; ii < tt; ii+=1) { +// b = ((data32[ii] >> 16) & 0xff) * App.B; +// g = ((data32[ii] >> 8) & 0xff) * App.G; +// r = ((data32[ii] >> 0) & 0xff) * App.R; +// +// // data32[ii] = (255 << 24) | +// ((b > 255 ? 255 : b) << 16) | +// ((g > 255 ? 255 : g) << 8) | +// (r > 255 ? 255 : r); +// } +// +// return pixels; +// }, + threshold: function (pixels) { + var threshold = 80, + d = pixels.data, + r, + g, + b, + v; + + for (var i = 0; i < d.length; i += 4) { + + r = d[i]; + g = d[i + 1]; + b = d[i + 2]; + v = (0.2126 * r + 0.7152 * g + 0.0722 * b >= threshold) ? 255 : 0; + d[i] = d[i + 1] = d[i + 2] = v; + + } return pixels; }, @@ -116,7 +221,5 @@ define([], function () { } return pixels; } - }; - }); diff --git a/app/models/clip.js b/app/models/clip.js deleted file mode 100644 index c09572e..0000000 --- a/app/models/clip.js +++ /dev/null @@ -1,150 +0,0 @@ -/** - * Microsoft Video Editor - * - * @namespace models - * @name media - * @author Thierry M.P. Kleist - * @date: 4/24/13 - */ -/*global views, console, $, $$, TweenLite, TweenMax, TimelineLite, TimelineMax, Ease, Linear, Power0, Power1, Power2, Power3, Power4, Quad, Cubic, Quart, Strong, Back, Bounce, Circ, Elastic, Expo, Sine, SlowMo */ - -define(['backbone', 'underscore'], function (Backbone, _) { - - 'use strict'; - - return Backbone.Model.extend({ - - defaults: { - "media": null, - "video": null, - "type": "video", - "frames": 0, - "status": "notready", - "start": 0, - "offset-start": 0, - "offset-end": 0, - "scale": "stretch-to-fit", - "position": 0 - }, - - initialize: function () { - - if (this.get("type") === "video") { - this.collection.on('frame-sync', this.sync, this); - this.collection.on('kill', this.kill, this); - this.initializeVideo(); - } - - }, - - - setOffset: function (offset) { - - this.set('offset-start', parseInt(offset.start * this.get('frames'))); - this.set('offset-end', parseInt(offset.end * this.get('frames'))); - - this.trigger('offset'); - this.syncTime(); - - }, - - initializeVideo: function () { - - var self = this; - - this.video = document.createElement('video'); - this.video.addEventListener('loadedmetadata', function () { - self.set('frames', parseInt(self.video.duration * 1000 / 40)); - self.set('status', 'idle'); - }); - this.video.muted = true; - this.video.src = this.get('media') - .get('blob'); - this.video.addEventListener('ended', this.ended, false); - this.set('status', 'idle'); - return true; - - }, - - play: function () { - - if (this.get('status') === 'idle') { - this.video.play(); - this.set('status', 'playing'); - } else if (this.get('status') === 'playing') { - log('video ' + this.get('name') + ' is already playing', this.get('status')); - } else { - log('video ' + this.get('name') + ' is not ready', this.get('status')); - } - - }, - - stop: function () { - - this.video.pause(); - // this.video.currentTime = parseInt(this.get('offset-start')) / 25; - this.set('status', 'idle'); - - }, - - syncTime: function () { - - var frame, - start = this.get('start'), - offset = this.get('offset-start'); - - if (start < 0) { - start = Math.abs(start); - frame = (start > offset) ? start : offset; - } else { - frame = offset; - } - - this.video.currentTime = frame / 25; - - }, - - kill: function () { - this.stop(); - this.syncTime(); - }, - - sync: function (frame) { - - var start = this.get('start'), - offset = this.get('offset-start'), - end = this.get('offset-end'), - frames = this.get('frames'), - status = this.get('status'); - - if (frame >= (start + offset) && frame < (start + frames - end) && status !== 'playing') { - log('PLAY'); - this.play(); - } else if (frame === (start + frames - end)) { - this.stop(); - } - - }, - - seek: function (frame) { - - if (!isNaN(offset)) { - this.video.currentTime = (frame * this.get('frames')) / 25; - } - - }, - - setPosition: function (frame) { - - this.set('start', frame); - this.syncTime(); - - }, - - getStart: function () { - return this.get('start') + this.get('offset-start'); - } - - }); - -}); diff --git a/app/models/layer.js b/app/models/layer.js new file mode 100644 index 0000000..ea4c7d9 --- /dev/null +++ b/app/models/layer.js @@ -0,0 +1,244 @@ +/* Microsoft Video Editor + * @author: T.M.P. Kleist / Code D'azur + * ============================================================================== */ + +/*global views, console, $, define */ + +define([], function () { + + 'use strict'; + + return Backbone.Model.extend({ + + defaults: { + "media": null, + "type": "video", + "name": null, + "rotation": 0, + "frames": 0, + "status": "notready", + "offset": 0, + "volume": 0, + "trim": { + start: 0, + end: 0 + }, + "scale": 0.5, + "position": 0 + }, + + initialize: function() { + + if (this.collection) { + this.set('position', this.collection.length) + } else { + this.set('position', window.App.timeline.collection.length) + } + + if (this.get("type") === "video") { + this.on('trim:preview', this.setFrame, this); + this.collection.on('seek', this.seek, this); + this.collection.on('frame-sync', this.sync, this); + this.collection.on('kill', this.kill, this); + this.collection.on('pause', this.pause, this); + this.on('change:volume', this.setVolume, this); + this.on('change:trim', function() { + this.set('status', 'idle'); + this.syncFrame(); + window.App.timeline.stop(); + }, this); + this.initializeVideo(); + } + + if (this.get("type") === "audio") { + this.on('change:trim', function() { + this.set('status', 'idle'); + this.syncFrame(); + window.App.timeline.stop(); + }, this); + this.collection.on('pause', this.pause, this); + this.collection.on('kill', this.kill, this); + this.collection.on('frame-sync', this.sync, this); + this.collection.on('seek', this.seek, this); + this.initializeVideo(); + this.video.volume = 1; + } + + }, + + + /* Initializes the video element + * ---------------------------------------------------------------------- */ + + initializeVideo: function() { + + var self = this; + + this.video = document.createElement('video'); + this.video.addEventListener('loadedmetadata', function() { + self.set('frames', parseInt(self.video.duration * 1000 / 40)); + self.set('status', 'idle'); + }); + this.video.volume = 0; + this.video.src = this.get('media') + .get('blob'); + this.video.addEventListener('ended', this.ended, false); + this.set('status', 'idle'); + + this.vv = document.createElement('div'); + this.vv.style.width = "600px"; + this.vv.style.height = "300px"; + this.vv.appendChild(this.video); + + this.video.setAttribute('style', '-webkit-transform: rotate(20deg);'); + return true; + + }, + + + /* Sets the volume for the video + * ---------------------------------------------------------------------- */ + + setVolume: function() { + if (this.video) { + this.video.volume = this.get('volume'); + } + }, + + + /* Plays the video + * ---------------------------------------------------------------------- */ + + play: function() { + + if (this.get('status') === 'idle' || this.get('status') === 'seeking') { + this.video.play(); + this.set('status', 'playing'); + } else if (this.get('status') === 'playing') { + log('video ' + this.get('name') + ' is already playing', this.get('status')); + } else { + log('video ' + this.get('name') + ' is not ready', this.get('status')); + } + + }, + + + /* Pauses the video + * ---------------------------------------------------------------------- */ + + pause: function () { + this.video.pause(); + this.set('status', 'idle'); + }, + + + /* Stops the video + * ---------------------------------------------------------------------- */ + + stop: function() { + this.video.pause(); + this.video.currentTime = parseInt(this.get('trim') + .start) / 25; + this.set('status', 'idle'); + }, + + + /* this will set all the correct trim positions for the video/audio + * ---------------------------------------------------------------------- */ + + syncFrame: function() { + + var frame, + offset = this.get('offset'), + frames = this.get('frames'), + trim = this.get('trim'); + if (offset < 0) { + offset = Math.abs(offset); + frame = (offset > trim.start) ? offset : trim.start; + + } else { + frame = trim.start; + } + + if (App.timeline._frame < (this.get('offset') + frames - trim.end) && App.timeline._frame >= (trim.start + this.get('offset'))) { + + frame = App.timeline._frame - this.get('offset') + + } + + this.video.currentTime = frame / 25; + + }, + + + /* Checks if the video/audio needs to be played + * ---------------------------------------------------------------------- */ + + sync: function(frame) { + + var offset = this.get('offset'), + trim = this.get('trim'), + frames = this.get('frames'), + status = this.get('status'); + + if (frame >= (trim.start + offset) && frame < (offset + frames - trim.end) && status !== 'playing') { + this.play(); + } else if (frame === (offset + frames - trim.end)) { + this.stop(); + } + + }, + + + /* Check if the video is in frame and sets the time + * ---------------------------------------------------------------------- */ + + seek: function(frame) { + + var offset = this.get('offset'), + trim = this.get('trim'), + frames = this.get('frames'); + + if (frame >= (trim.start + offset) && frame < (trim.start + frames - trim.end)) { + if (this.get('status') === 'playing') { + this.video.pause(); + } + + this.setFrame(frame); + this.set('status', 'seeking'); + + } else { + this.set('status', 'idle'); + } + }, + + + /* Sets the current time to the correct frame + * ---------------------------------------------------------------------- */ + + setFrame: function(frame) { + this.video.currentTime = frame / 25; + }, + + + /* kills the video + * ---------------------------------------------------------------------- */ + + kill: function() { + log('kill') + this.stop(); + this.syncFrame(); + }, + + + /* Returns the start frame + * ---------------------------------------------------------------------- */ + + getStart: function() { + return this.get('offset') + this.get('trim') + .start; + } + + }); + +}); diff --git a/app/models/library.js b/app/models/library.js new file mode 100644 index 0000000..8081e1e --- /dev/null +++ b/app/models/library.js @@ -0,0 +1,65 @@ +/* Microsoft Video Editor + * @author: T.M.P. Kleist / Code D'azur + * ============================================================================== */ + +/*global views, console, $, define */ + +define([], function () { + + 'use strict'; + + return Backbone.Model.extend({ + + defaults: { + "blob": null, + "file": null + }, + + initialize: function () { + this.set('blob', window.URL.createObjectURL(this.get('file'))); + + if (this.get('file').type.indexOf('audio') === -1) { + this.getThumb(); + this.set('type', 'video'); + } else { + this.set('type', 'audio'); + } + + }, + + + /* Gets the base64 thumb of the video element + * ---------------------------------------------------------------------- */ + + getThumb: function () { + + var self = this, + canvas = document.createElement('canvas'), + video = document.createElement('video'), + ctx = canvas.getContext('2d'); + + canvas.width = 260; + canvas.height = 140; + + video.addEventListener('canplaythrough', function () { + _.delay(function () { + ctx.drawImage(video, 0, 0, video.videoWidth, video.videoHeight, 0, 0, 263, 140); + self.set('thumb', canvas.toDataURL('image/jpeg')); + }, 1000); + }); + + video.src = this.get('blob'); + }, + + + /* Clears the ObjectURL + * ---------------------------------------------------------------------- */ + + clear: function () { + window.URL.revokeObjectURL(this.get('blob')); + Backbone.Model.prototype.clear.apply(this, arguments); + } + + }); + +}); diff --git a/app/models/media.js b/app/models/media.js deleted file mode 100644 index a3e0fd2..0000000 --- a/app/models/media.js +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Microsoft Video Editor - * - * @namespace models - * @name media - * @author Thierry M.P. Kleist - * @date: 4/24/13 - */ -/*global views, console, $, $$, TweenLite, TweenMax, TimelineLite, TimelineMax, Ease, Linear, Power0, Power1, Power2, Power3, Power4, Quad, Cubic, Quart, Strong, Back, Bounce, Circ, Elastic, Expo, Sine, SlowMo */ - -define(['backbone', 'underscore'], function (Backbone, _) { - - 'use strict'; - - return Backbone.Model.extend({ - - defaults: { - "blob": null, - "file": null - }, - - initialize: function () { - this.set('blob', window.URL.createObjectURL(this.get('file'))); - this.getThumb(); - }, - - getThumb: function () { - - var self = this, - canvas = document.createElement('canvas'), - video = document.createElement('video'), - ctx = canvas.getContext('2d'); - - canvas.width = 260; - canvas.height = 140; - - video.addEventListener('canplaythrough', function () { - - ctx.drawImage(video, 0, 0, video.videoWidth, video.videoHeight, 0, 0, 263, 140); - self.set('thumb', canvas.toDataURL('image/jpeg')); - - }); - - video.src = this.get('blob'); - }, - - clear: function () { - window.URL.revokeObjectURL(this.get('blob')); - Backbone.Model.prototype.clear.apply(this, arguments); - } - - }); - -}); diff --git a/app/project-touch.js b/app/project-touch.js index aea2975..0945630 100644 --- a/app/project-touch.js +++ b/app/project-touch.js @@ -1,156 +1,85 @@ -/** - * Project Touch - * - * @date: 6/18/13 - */ - -/*global define, window, document, $, requirejs, require, console */ - -define(['backbone', 'app/views/player', 'app/filters', 'app/collections/library', 'app/collections/timeline', 'app/controllers/timeline', 'app/models/media', 'app/views/library/list'], function (Backbone, Player, Filters, Library, Timeline, TimelineController, Media, LibView) { +/* Microsoft Video Editor + * @author: T.M.P. Kleist / Code D'azur + * ============================================================================== */ + +/*global views, console, $, define */ + +Number.prototype.toMMSS = function () { + var sec_num = parseInt(this, 10); // don't forget the second parm + var hours = Math.floor(sec_num / 3600); + var minutes = Math.floor((sec_num - (hours * 3600)) / 60); + var seconds = sec_num - (hours * 3600) - (minutes * 60); + + if (hours < 10) {hours = "0"+hours;} + if (minutes < 10) {minutes = "0"+minutes;} + if (seconds < 10) {seconds = "0"+seconds;} + var time = minutes+':'+seconds; + return time; +} + +define(['app/views/player', + 'app/filters', + 'app/utils', + 'app/views/ui/library', + 'app/views/ui/effects', + 'app/views/ui/edit', + 'app/controllers/timeline', + 'app/views/ui/timeline', + 'app/views/composition'], function () { 'use strict'; return Backbone.View.extend({ filter: null, - debug: true, - - events: { - "click button": "handler" - }, - - R: 1, - G: 1, - B: 1, - Sat: 100, - C: 0, initialize: function () { - - var self = this; - - window.log = function () { - if (self.debug) { - console.log.apply(console, arguments); - } - } - - this.library = new Library(); + + var self = this, + Timeline = require('app/controllers/timeline'), + Player = require('app/views/player'), + Filter = require('app/filters'), + Utils = require('app/utils'), + Composition = require('app/views/composition'); + + this.views = {}; this.player = new Player(); this.timeline = new Timeline(); - this.timelineController = new TimelineController(this.timeline); - - this.library.on('add', function (model) { - log('model', this.library.models.length, model) - }, this); + this.composition = new Composition(); + this.utils = Utils; + + this.R = 1; + this.G = 1; + this.B = 1.3; - //setInterval(this.getRGB, 600); }, render: function () { + var Library = require('app/views/ui/library'), + Effects = require('app/views/ui/effects'), + Edit = require('app/views/ui/edit'), + Timeline = require('app/views/ui/timeline'); + + this.views.library = new Library({ + position: 'left' + }); + + this.views.timeline = new Timeline(); + + this.views.edit = new Edit({ + position: 'left', + collection: this.views.timeline.collection + }); + + this.views.effects = new Effects({ + position: 'right', + collection: this.views.timeline.collection + }); + + + this.composition.el.appendChild(this.player.render().el); + $('#player').height($('#player').width() / 1.7778); - _.bindAll(this, 'handleFileSelect'); - - this.el.appendChild(this.player.render() - .el); - - this.form = document.createElement('form'); - this.input = document.createElement('input'); - this.input.setAttribute('type', 'file'); - this.input.setAttribute('name', 'file-upload'); - this.input.setAttribute('style', 'position: absolute; display: block; height: 10%; width: 10%;'); - - this.form.appendChild(this.input); - this.el.insertBefore(this.form, this.el.firstChild); - - this.el.addEventListener('dragenter', this.dragEscape, false); - this.el.addEventListener('dragexit', this.dragEscape, false); - - this.el.addEventListener('dragover', this.handleDragOver, false); - this.el.addEventListener('drop', this.handleFileSelect, false); - - this.input.addEventListener('change', this.handleFileSelect, false); - - this.libView = new LibView(); - this.el.appendChild(this.libView.render() - .el); - this.library.on('add', this.libView.add); - - }, - - handleFileSelect: function (evt) { - - var files; - - if (evt.type === 'change') { - files = evt.target.files; - } else { - files = evt.dataTransfer.files; - evt.preventDefault(); - evt.stopPropagation(); - } - - _.each(files, function (file) { - - if (file.type !== "video/mp4") { - console.warn('file must be mp4'); - } else { - this.library.add(new Media({ - file: file - })); - } - }, this); - - }, - - handler: function (e) { - - var className = e.currentTarget.getAttribute('class'); - - switch (className) { - case "play": - this.timelineController.play(); - e.currentTarget.setAttribute('class', 'pause'); - break; - case "pause": - - e.currentTarget.setAttribute('class', 'play'); - break; - case "stop": - if (document.querySelector('.pause')) { - this.timelineController.stop(); - document.querySelector('.pause') - .setAttribute('class', 'play'); - } - break; - - case "grayscale": - case "threshold": - case "pixelize": - case "red": - case "green": - case "contrast": - case "hipster": - if (this.filter === Filters[className]) { - console.log('disabling filter', className); - this.filter = null; - } else { - console.log('enabling filter', className); - this.filter = Filters[className]; - } - break; - - } - }, - - getRGB: function () { - - (Math.floor(Math.random() * 11)) / 10 - - App.R = 0;//(Math.floor(Math.random() * 11)) / 10; - App.G = 0;//(Math.floor(Math.random() * 11)) / 10; - App.B = 0;//(Math.floor(Math.random() * 11)) / 10; - App.C = 0;//(parseFloat(document.getElementById('c').value)); } }); diff --git a/app/utils.js b/app/utils.js index 974fe6d..2ede841 100644 --- a/app/utils.js +++ b/app/utils.js @@ -1,10 +1,8 @@ -/** - * Project Touch - * - * @date: 6/18/13 - */ +/* Microsoft Video Editor + * @author: T.M.P. Kleist / Code D'azur + * ============================================================================== */ -/*global define, window, document, $, requirejs, require */ +/*global views, console, $, define */ define([], function () { @@ -20,6 +18,31 @@ define([], function () { ctx.drawImage(video, 0, 0, video.videoWidth, video.videoHeight, 0, 0, 263, 140); return canvas.toDataURL('image/jpeg'); + }, + + captureAsCanvas: function (video, options, handle) { + + // Create canvas and call handle function + var callback = function () { + // Create canvas + var canvas = document.createElement('canvas'); + canvas.width = options.width; + canvas.height = options.height; + // Get context and draw screen on it + canvas.getContext('2d').drawImage(video, 0, 0, options.width, options.height); + // Call handle function (because of event) + handle.call(this, canvas); + } + // If we have time in options + if (options.time && !isNaN(parseInt(options.time))) { + // Seek to any other time + video.currentTime = options.time; + // Wait for seeked event + $(video).bind('seeked', callback); + return; + } + // Otherwise callback with video context - just for compatibility with calling in the seeked event + return callback.apply(video); } }; diff --git a/app/views/composition.js b/app/views/composition.js new file mode 100644 index 0000000..4aa79c8 --- /dev/null +++ b/app/views/composition.js @@ -0,0 +1,96 @@ +/* Microsoft Video Editor + * @author: T.M.P. Kleist / Code D'azur + * ============================================================================== */ + +/*global views, console, $, define */ + +define([], function () { + + 'use strict'; + + return Backbone.View.extend({ + + id: 'composition', + el: '#composition', + + initialize: function () { + _.bindAll(this, 'togglePanels', 'hidePanels', 'showPanels'); + + this.title = this.$('h2'); + this.hammertime = Hammer(this.el); + this.hammertime.on('doubletap', this.togglePanels); + + $(window).resize(function(){ + $('#player').height($('#player').width() / 1.7778); + }); + + $('#player').height($('#player').width() / 1.7778); + + }, + + togglePanels: function () { + if (this.$el.css('left') === '310px') { + this.hidePanels(); + } else { + this.showPanels(); + } + }, + + hidePanels: function () { + this.title.animate({ + 'margin-left': '40px' + }); + var klass = this; + $('#edit').slideUp(); + $('#library').slideUp(); + $('footer').animate({ + bottom: '-400px' + }); + $('#effects').slideUp({ + complete: function () { + klass.$el.animate({ + 'left': '15%', + 'width': '70%', + 'height': '100%' + }, function () { + $('#player').animate({ + 'height': $('#player').width() / 1.7778 + }); + }); + $('footer').css('display', 'none'); + } + }); + }, + + showPanels: function () { + this.title.animate({ + 'margin-left': '20px' + }); + + var newWidth = (window.innerWidth - 620) + 'px'; + + + + this.$el.animate({ + 'left': '310px', + 'right': '310px', + 'width': newWidth, + 'height': '80%' + }, function () { + $('footer').animate({ + bottom: '0px' + }).css('display', 'block'); + if (window.App.views.edit.active) { + $('#edit').slideDown(); + } else { + $('#library').slideDown(); + } + $('#effects').slideDown(); + $('#player').animate({ + 'height': $('#player').width() / 1.7778 + }); + }); + } + + }); +}); diff --git a/app/views/library/list.js b/app/views/library/list.js deleted file mode 100644 index 032f31b..0000000 --- a/app/views/library/list.js +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Project Touch - * - * @date: 6/18/13 - */ - -/*global define, window, document, $, requirejs, require */ - -define(['backbone', 'underscore', 'app/views/library/media'], function (Backbone, _, Media) { - - 'use strict'; - - return Backbone.View.extend({ - - tagName: 'ul', - id: 'library', - - events: { - 'click li': 'addToTimeline' - }, - - initialize: function () { - _.bindAll(this, 'add', 'addToTimeline'); - }, - - add: function (model) { - - var item = new Media({model:model}); - this.el.appendChild(item.render().el); - this.delegateEvents(); - - }, - - addToTimeline: function (e) { - - var id = e.currentTarget.getAttribute('data-id'); - - window.App.timeline.add({ - media: window.App.library.get(id) - }); - - } - - }); - -}); diff --git a/app/views/library/media.js b/app/views/library/media.js deleted file mode 100644 index c2c87d8..0000000 --- a/app/views/library/media.js +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Project Touch - * - * @date: 6/18/13 - */ - -/*global define, window, document, $, requirejs, require */ - -define(['backbone', 'underscore'], function (Backbone, _) { - - 'use strict'; - - return Backbone.View.extend({ - - tagName: 'li', - - initialize: function () { - _.bindAll(this, 'addThumb'); - this.options.model.on('change:thumb', this.addThumb, this); - }, - - render: function () { - - var title = document.createElement('div'); - - this.el.setAttribute('data-id', this.options.model.cid); - title.innerHTML = this.options.model.get('file') - .name; - this.el.appendChild(title); - return this; - - }, - - addThumb: function () { - var img = new Image(); - this.el.appendChild(img); - img.src = this.options.model.get('thumb'); - } - - }); - -}); diff --git a/app/views/panel.js b/app/views/panel.js new file mode 100644 index 0000000..f59cd23 --- /dev/null +++ b/app/views/panel.js @@ -0,0 +1,44 @@ +/* Microsoft Video Editor + * @author: T.M.P. Kleist / Code D'azur + * ============================================================================== */ + +/*global views, console, $, define */ + +define([], function() { + + return Backbone.View.extend({ + + tagName: 'section', + className: 'ui-panel', + + initialize: function() { + if (this.el) { + this.ul = this.el.querySelector('ul'); + } + }, + + render: function() { + + switch (this.options.position) { + case "left": + case "right": + this.el.classList.add(this.options.position); + break; + default: + this.el.classList.add("left"); + } + + var title = document.createElement('h2'); + title.innerHTML = this.options.title; + this.el.appendChild(title); + + this.ul = document.createElement('ul'); + this.el.appendChild(this.ul); + + return this; + + } + + }); + +}); diff --git a/app/views/player.js b/app/views/player.js index b73a51a..60ba047 100644 --- a/app/views/player.js +++ b/app/views/player.js @@ -1,12 +1,10 @@ -/** - * Project Touch - * - * @date: 6/18/13 - */ +/* Microsoft Video Editor + * @author: T.M.P. Kleist / Code D'azur + * ============================================================================== */ -/*global define, window, document, $, requirejs, require */ +/*global views, console, $, define */ -define(['backbone', 'underscore'], function (Backbone, _) { +define([], function () { 'use strict'; @@ -28,96 +26,118 @@ define(['backbone', 'underscore'], function (Backbone, _) { this.source = null; this.effect = false; - this.width = 1280; - this.height = 720; - - }, - - setSource: function (model) { - - if (model !== undefined && model !== null) { - this.source = model.video; - } else { - this.source = null; - this.context.clearRect(0, 0, this.width, this.height); - } + this.width = 640; + this.height = 360; }, - setEffect: function (effect) { - - }, - setOverLay: function (overlay) { - - }, - - filterImage: function (filter, data, var_args) { - var args = [data]; - - _.each(arguments, function (arg) { - args.push(arg); - }); - - return filter.apply(App, args); - }, - render: function () { this.effectCanvas = document.createElement('canvas'); this.effectContext = this.effectCanvas.getContext('2d'); - this.effectCanvas.width = this.width; this.effectCanvas.height = this.height; this.el.setAttribute('id', 'player'); - this.el.width = this.width; this.el.height = this.height; this.context = this.el.getContext('2d'); - this.renderLoop(); - this.el.addEventListener('click', this.toggleEffect, false); - this.el.style.position = "absolute"; - this.el.style.top = "50%"; - this.el.style.left = "50%"; - this.el.style.margin = '-' + (this.height / 2) + 'px 0 0 -' + (this.width / 2) + 'px'; - return this; }, - toggleEffect: function () { - this.effect = this.effect === false; + + /* Sets the source of the player + * ---------------------------------------------------------------------- */ + + setSource: function (model) { + + if (model !== undefined && model !== null) { + this.source = model; + } else { + this.source = null; + this.context.clearRect(0, 0, this.width, this.height); + } + }, - renderLoop: function () { - if (this.source !== null && this.source !== undefined) { + setEffect: function (effect) { }, + setOverLay: function (overlay) { }, - var width, + + /* Filters an image with a specific filter + * ---------------------------------------------------------------------- */ + + filterImage: function (filter, data, var_args) { + var args = [data]; + + _.each(arguments, function (arg) { + args.push(arg); + }); + + return filter.apply(App, args); + }, + + + /* toggles the effect + * ---------------------------------------------------------------------- */ + + toggleEffect: function () { + this.effect = this.effect === false; + + }, + + + /* The function that's being triggered every frame per video + * ---------------------------------------------------------------------- */ + + rendering: function (source, opacity) { + var width, height, posX, posY; + if (source !== undefined && source.video !== undefined) { + width = source.video.videoWidth * source.get('scale'); + height = source.video.videoHeight * source.get('scale'); + + posX = -(width / 2); + posY = -(height / 2); + + this.context.save(); + this.context.globalAlpha = this.source.length > 1 ? 1 / this.source.length : 1; + this.context.translate(this.width / 2, this.height / 2); + this.context.rotate(source.get('rotation') * Math.PI / 180); + this.context.drawImage(source.video, 0, 0, source.video.videoWidth, source.video.videoHeight, posX, posY, width, height); + this.context.restore(); + } + }, + - width = this.width; - height = this.height; - posX = 0; - posY = 0; - - if (window.App.filter !== null) { + /* The render loop that's being triggered every frame + * ---------------------------------------------------------------------- */ + + renderLoop: function () { - var pixelData; + if (this.source !== null && this.source !== undefined) { - this.effectContext.drawImage(this.source, 0, 0, this.source.videoWidth, this.source.videoHeight, posX, posY, width, height); - pixelData = this.filterImage(App.filter, this.effectContext.getImageData(0, 0, this.width, this.height)); - this.context.putImageData(pixelData, 0, 0); + var width, + height, + posX, + posY; + if (this.source.length > 1) { + _.each(this.source, this.rendering, this); } else { - this.context.drawImage(this.source, 0, 0, this.source.videoWidth, this.source.videoHeight, posX, posY, width, height); + this.rendering(this.source[0]); + } + if (window.App.filter !== null) { + this.context.putImageData(this.filterImage(App.filter, this.context.getImageData(0, 0, this.width, this.height)), 0, 0); } } diff --git a/app/views/ui/edit-level.js b/app/views/ui/edit-level.js new file mode 100644 index 0000000..687f3c8 --- /dev/null +++ b/app/views/ui/edit-level.js @@ -0,0 +1,132 @@ +/* Microsoft Video Editor + * @author: T.M.P. Kleist / Code D'azur + * ============================================================================== */ + +/*global views, console, $, define */ + +define([], function () { + + 'use strict'; + + return Backbone.View.extend({ + tagName: 'div', + className: 'level', + previousLevel: null, + level: 1, + + initialize: function () { + _.bindAll(this, 'mouseDownRight', 'trim', 'setLevel'); + this._trim = { + start: 0, + end: 0 + }; + this.levelWidth = 235; + this.scrubberOffset = 22; + this.level = 1; + this.prevLevel = 1; + this.holder = this.$('.holder')[0]; + this.render(); + }, + + render: function () { + this.right = this.$('.right')[0]; + this.hammertimeR = Hammer(this.right); + this.hammertimeR.on("dragstart", this.mouseDownRight); + this.hammertimeR.on("drag", this.trim); + this.setLevel(this.level); + this.prevLevel = this.level; + return this; + + }, + + mouseDownRight: function () { + window.App.dragging = true; + this.paddingRightStart = parseInt(this.holder.style.paddingRight); + }, + + trim: function (e) { + var paddingRight, deltaX, level; + //How far been dragged + deltaX = e.gesture.deltaX; + paddingRight = -deltaX + this.paddingRightStart; + paddingRight = paddingRight < 0 ? 0 : paddingRight; + paddingRight = paddingRight > this.levelWidth ? this.levelWidth : paddingRight; + this.setPositions(paddingRight); + var difference = this.levelWidth - paddingRight; + + if (this.options.type === 'rotation') { + level = difference / (this.levelWidth / 360); + level = level - 180; + } else { + level = difference / this.levelWidth; + } + + this.level = level + + //DEVELOPMENT: Put this back in when finished. + this.model.set(this.options.type, level); + + }, + + setLevel: function (level) { + + var paddingRight; + + if (this.options.type === 'rotation') { + if (this.prevLevel === 180 && level < 170) { + return; + } + if (this.prevLevel === -180 && level > -170) { + return; + } + this.level = level; + paddingRight = ((360 - (level + 180)) / 360) * this.levelWidth; + } else if (this.options.type === 'scale') { + //scale is 0 -10 + var scale = level; + + var step = 0.05; + + + + if (scale > 1) { + this.level = this.prevLevel + (step * scale); + } else { + this.level = this.prevLevel - step * scale; + } + if (this.level > 1) { + this.level = 1; + } + if (this.level < 0) { + this.level = 0; + } + + paddingRight = (1 - this.level) * this.levelWidth; + if (paddingRight > this.levelWidth) { + paddingRight = this.levelWidth; + } + if (paddingRight < 0) { + paddingRight = 0; + } + } + this.setPositions(paddingRight); + + + if(this.model) { + this.model.set(this.options.type, this.level); + } + + + }, + + getLevel: function () { + return this.level; + }, + + setPositions: function (paddingRight) { + this.$('.right').css('right', paddingRight - this.scrubberOffset); + this.holder.style.paddingRight = paddingRight + 'px'; + } + }); + +}); diff --git a/app/views/ui/edit.js b/app/views/ui/edit.js new file mode 100644 index 0000000..bbc5fb0 --- /dev/null +++ b/app/views/ui/edit.js @@ -0,0 +1,121 @@ +/* Microsoft Video Editor + * @author: T.M.P. Kleist / Code D'azur + * ============================================================================== */ + +/*global views, console, $, define */ + +define(['app/views/panel', + 'app/views/ui/edit-level'], function (Panel, LevelView) { + + 'use strict'; + + return Panel.extend({ + + id: 'edit', + el: '#edit', + active: false, + levelWidth: null, + usablePixels: null, + scaleStartPaddingRight: null, + rotationStartPaddingRight: null, + currentRotation: null, + + events: { + "click #btnCloseEdit": "closeEdit" + }, + + initialize: function () { + Panel.prototype.initialize.call(this); + this.options.collection.on("open", this.open, this); + var self = this; + + _.bindAll(this, 'transform', 'transformstart'); + + this.scaleRight = $('#level-scale .right'); + this.scaleHolder = $('#level-scale .holder'); + + this.rotationRight = $('#level-rotation .right'); + this.rotationHolder = $('#level-rotation .holder'); + + this.scaleView = new LevelView({el: '#level-scale', type: 'scale'}); + this.rotationView = new LevelView({el: '#level-rotation', type: 'rotation'}); + this.volumeView = new LevelView({el: '#level-volume', type: 'volume'}); + this.hammertime = Hammer(window.App.composition.el); + this.hammertime.on('transformstart', function (e) { + self.transformstart(e); + }); + this.hammertime.on('transform', this.transform); + this.hammertime.on('transformend', this.transform); + + }, + + + render: function () { + Panel.prototype.render.call(this); + return this; + }, + + transformstart: function (event) { + + this.rotationView.prevLevel = this.rotationView.level + this.scaleView.prevLevel = this.scaleView.level + + }, + + transform: function (e) { + if (!this.active) { + return; + } + + var newDegree = this.convertDegree(e.gesture.rotation); + this.rotationView.setLevel(newDegree / 5); + this.scaleView.setLevel(e.gesture.scale); + + }, + + convertDegree: function (degree) { + //Convert -270 to 90 and compare to current level + if (degree < -180) { + degree = 360 + degree; + } else if (degree > 180) { + degree = -360 + degree; + } + degree = degree + this.rotationView.getLevel(); + if (degree > 180) { + degree = 180; + } + if (degree < -180) { + degree = -180; + } + return degree; + }, + + /* this will be triggered when a layer is selected + * ---------------------------------------------------------------------- */ + + open: function (model) { + this.active = true; + this.scaleView.model = model; + this.rotationView.model = model; + this.volumeView.model = model; + + this.scaleView.setLevel(model.get('scale')); + this.rotationView.setLevel(model.get('rotation')); + this.volumeView.setLevel(model.get('volume')); + $('#library').slideUp(500, function () { + $('#edit').slideDown(500); + }); + }, + + + closeEdit: function () { + this.active = false; + $('#edit').slideUp(500, function () { + $('#library').slideDown(500); + $('.media').removeClass('selected'); + }); + } + + }); + +}); diff --git a/app/views/ui/effects.js b/app/views/ui/effects.js new file mode 100644 index 0000000..b049082 --- /dev/null +++ b/app/views/ui/effects.js @@ -0,0 +1,95 @@ +/* Microsoft Video Editor + * @author: T.M.P. Kleist / Code D'azur + * ============================================================================== */ + +/*global views, console, $, define */ + +define(['app/views/panel', 'app/filters', 'app/models/layer'], function (Panel, Filter, Layer) { + + return Panel.extend({ + + id: 'effects', + el: '#effects', + + events: { + "click ul a": "addEffect" + }, + + initialize: function () { + Panel.prototype.initialize.call(this); + + _.each(Filter, function (f, id) { + this.$el.find('ul').append('
  • ' + id + '
  • '); + }, this); + + this.options.collection.on('add open', this.reloadEffects, this); + }, + + + /* Reloads the effect preview for the current video model + * ---------------------------------------------------------------------- */ + + reloadEffects: function (model) { + + if (model.get('type') === "video") { + + var self = this, + video = document.createElement('video'); + + video.addEventListener('loadedmetadata', function () { + window.App.utils.captureAsCanvas(video, { + width: 280, + height: 155, + time: parseInt(video.duration / 2) + }, function (canvas) { + + var ctx = canvas.getContext('2d'), + src, + pixels, + newPixels, + newCanvas, + ctx2; + + $('#effects li').each(function (index, li) { + newCanvas = document.createElement('canvas'); + ctx2 = newCanvas.getContext('2d'); + pixels = ctx.getImageData(0, 0, 280, 155); + newPixels = App.player.filterImage(Filter[li.getAttribute('data-effect')], pixels); + ctx2.putImageData(newPixels, 0, 0); + src = newCanvas.toDataURL(); + li.style.backgroundImage = 'url(' + src + ')'; + }); + }) + }, false); + + video.src = model.get('media').get('blob'); + + } + + }, + + + /* Adds the effect to the timeline + * ---------------------------------------------------------------------- */ + + addEffect: function (e) { + e.preventDefault(); + e.currentTarget.offsetParent.className = 'on'; + + window.App.timeline.collection.add(new Layer({ + type: "effect", + name: e.currentTarget.getAttribute('href'), + frames: 5000 + })); + + }, + + + render: function () { + Panel.prototype.render.call(this); + return this; + } + + }); + +}); diff --git a/app/views/ui/library-item.js b/app/views/ui/library-item.js new file mode 100644 index 0000000..20f5eef --- /dev/null +++ b/app/views/ui/library-item.js @@ -0,0 +1,67 @@ +/* Microsoft Video Editor + * @author: T.M.P. Kleist / Code D'azur + * ============================================================================== */ + +/*global views, console, $, define */ + +define(["app/views/player", "app/filters"], function (Player, Filter) { + + 'use strict'; + + return Backbone.View.extend({ + + tagName: 'li', + className: 'library-item', + template: _.template('\ +

    <%= filename %>

    \ + \ + '), + + + events: { + 'click .add': 'add', + 'click .edit': 'edit' + }, + + + initialize: function () { + _.bindAll(this, 'addThumb'); + + log('new library item:',this.options.model.get('file').name); + this.options.model.on('change:thumb', this.addThumb, this); + }, + + + render: function () { + + var templateResult = this.template({ + filename: this.options.model.get('file').name.substr(0,this.options.model.get('file').name.indexOf('.')) + }); + + this.$el.addClass(this.options.model.get('type')); + this.$el.attr('data-id', this.options.model.cid); + this.$el.html(templateResult); + + return this; + }, + + + add: function () { + this.options.model.collection.trigger('layer', this.options.model); + }, + + + edit: function () { + $('#library').slideUp(500, function(){ + $('#edit').slideDown(500); + }); + }, + + + addThumb: function () { + this.el.style.backgroundImage = 'url(' + this.options.model.get('thumb') + ')'; + } + + }); + +}); diff --git a/app/views/ui/library.js b/app/views/ui/library.js new file mode 100644 index 0000000..4d1aba3 --- /dev/null +++ b/app/views/ui/library.js @@ -0,0 +1,97 @@ +/* Microsoft Video Editor + * @author: T.M.P. Kleist / Code D'azur + * ============================================================================== */ + +/*global views, console, $, define */ + +define(['app/views/panel', + 'app/views/ui/library-item', + 'app/collections/library', + 'app/models/library'], function (Panel) { + + return Panel.extend({ + + id: 'library', + el: '#library', + + events: { + 'change input': 'handleFileSelect', + 'click .file-selector': 'handleFileSelect' + }, + + + initialize: function () { + Panel.prototype.initialize.call(this); + var Collection = require('app/collections/library'); + this.collection = new Collection(); + this.collection.on('add', this.add, this); + + _.bindAll(this, 'handleFileSelect', 'add'); + }, + + + render: function () { + Panel.prototype.render.call(this); + + var li = document.createElement('li'), + form = document.createElement('form'), + input = document.createElement('input'); + + li.classList.add('file-selector'); + input.setAttribute('type', 'file'); + input.setAttribute('name', 'file-upload'); + + form.appendChild(input); + li.appendChild(form); + + this.ul.appendChild(li); + this.delegateEvents(); + + return this; + }, + + + handleFileSelect: function (evt) { + + var files, + Model = require('app/models/library'); + + if (evt.type === 'change') { + files = evt.target.files; + } else if (evt.dataTransfer) { + files = evt.dataTransfer.files; + evt.preventDefault(); + evt.stopPropagation(); + } else { + console.warn('no files found in the event', evt); + } + + _.each(files, function (file) { + + this.collection.add(new Model({ + file: file + })); + + }, this); + + }, + + + add: function (model) { + var LibraryItem = require('app/views/ui/library-item'), + item = new LibraryItem({ + model: model + }); + + if (this.ul.getElementsByTagName("li").length > 1) { + this.ul.insertBefore(item.render().el, this.ul.getElementsByTagName("li")[1]); + } else { + this.ul.appendChild(item.render().el); + } + + this.delegateEvents(); + } + + }); + +}); diff --git a/app/views/ui/timeclip.js b/app/views/ui/timeclip.js deleted file mode 100644 index 6802f83..0000000 --- a/app/views/ui/timeclip.js +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Project Touch - * - * @date: 6/18/13 - */ - -/*global define, window, document, $, requirejs, require */ - -define(['backbone', 'underscore'], function (Backbone, _) { - - 'use strict'; - - return Backbone.View.extend({ - - tagName: 'li', - - initialize: function () { - - _.bindAll(this, 'addThumb'); - this.options.model.on('change:thumb', this.addThumb, this); - - }, - - render: function () { - - var title = document.createElement('div'); - - this.el.setAttribute('data-id', this.options.model.cid); - title.innerHTML = this.options.model.get('file') - .name; - this.el.appendChild(title); - return this; - - } - - }); - -}); diff --git a/app/views/ui/timeline-layer-slide.js b/app/views/ui/timeline-layer-slide.js new file mode 100644 index 0000000..ee0a15e --- /dev/null +++ b/app/views/ui/timeline-layer-slide.js @@ -0,0 +1,200 @@ +/* Microsoft Video Editor + * @author: T.M.P. Kleist / Code D'azur + * ============================================================================== */ + +/*global views, console, $, define */ + +define([], function () { + + 'use strict'; + + return Backbone.View.extend({ + + tageName: 'div', + className: 'media', + resource: null, + video: null, + + initialize: function () { + + _.bindAll(this, + 'mouseDownLeft', + 'mouseDownRight', + 'trim', + 'endTrim', + 'resize'); + + this._trim = { + start: 0, + end: 0 + }; + + this.moving = false; + + }, + + render: function () { + + _.bindAll(this, 'select') + + this.el.style.left = '0px'; + + this.left = document.createElement('div'); + this.left.setAttribute('class', 'left'); + + this.middle = document.createElement('div'); + this.middle.setAttribute('class', 'middle'); + this.middle.style.backgroundColor = this.options.color; + + this.right = document.createElement('div'); + this.right.setAttribute('class', 'right'); + + this.el.appendChild(this.left); + this.el.appendChild(this.middle); + this.el.appendChild(this.right); + + this.hammertime = Hammer(this.el); + this.hammertime.on("doubletap", this.select); + + this.hammertimeL = Hammer(this.left); + this.hammertimeL.on("dragstart", this.mouseDownLeft); + this.hammertimeL.on("drag", this.trim); + + this.hammertimeR = Hammer(this.right); + this.hammertimeR.on("dragstart", this.mouseDownRight); + this.hammertimeR.on("drag", this.trim); + + this.options.model.collection.on('open', this.changeState, this); + + return this; + + }, + + select: function (e) { + if(this.options.model.get('type') === 'video') { + this.options.model.trigger('open', this.options.model); + } + + }, + + + /* Changes the selected state of the layer + * ---------------------------------------------------------------------- */ + + changeState: function (model) { + if (this.options.model === model) { + this.$el.addClass('selected'); + } else { + this.$el.removeClass('selected'); + } + }, + + + /* Trim left start + * ---------------------------------------------------------------------- */ + + mouseDownLeft: function () { + + window.App.dragging = true; + this.dragStartPos = parseInt(this.el.style.paddingLeft); + this.moving = 'left'; + this.options.model.trigger('trim:start', this.options.model); + }, + + + /* Trim right start + * ---------------------------------------------------------------------- */ + + mouseDownRight: function () { + + window.App.dragging = true; + this.dragStartPos = parseInt(this.el.style.paddingRight); + this.moving = 'right'; + this.options.model.trigger('trim:start', this.options.model); + }, + + + /* Trimming + * ---------------------------------------------------------------------- */ + + trim: function (e) { + + var left, + offset, + margin; + + if (this.moving === 'left') { + + left = e.gesture.deltaX + this.el.parentNode.parentNode.parentNode.scrollLeft; + + offset = parseInt(this.el.style.left) + 0; + margin = this.dragStartPos + left; + + if (margin < 0) { + margin = 0; + } + + this.el.style.paddingLeft = margin + 'px'; + this.left.style.left = margin + 'px'; + + this._trim.start = parseInt((margin / parseInt(this.el.style.width)) * this.options.model.get('frames')); + + } + + if (this.moving === 'right') { + + left = e.gesture.deltaX + this.el.parentNode.parentNode.parentNode.scrollLeft; + offset = parseInt(this.el.style.left) + 0; + margin = -this.dragStartPos + left; + + if (margin > 0) { + margin = 0; + } + + margin = Math.abs(margin); + + this.el.style.paddingRight = margin + 'px'; + this.right.style.right = margin + 'px'; + + this._trim.end = parseInt((margin / parseInt(this.el.style.width)) * this.options.model.get('frames')); + + margin = parseInt(this.el.style.width) - margin; + } + + if (this.options.model.get('type') === 'video') { + this.options.model.trigger('trim:preview', parseInt((margin / parseInt(this.el.style.width)) * this.options.model.get('frames'))); + } + }, + + + /* End of trimming + * ---------------------------------------------------------------------- */ + + endTrim: function () { + + if (this.moving) { + window.App.dragging = false; + window.removeEventListener('mousemove', this.trim, true); + + this.moving = false; + this.options.model.set('trim', {start:this._trim.start, end:this._trim.end}); + } + + }, + + resize: function () { + + var trim = this.options.model.get('trim'), + frames = this.options.model.get('frames'); + + this.el.style.paddingLeft = ((trim.start / frames) * parseInt(this.el.style.width)) + 'px'; + this.left.style.left = ((trim.start / frames) * parseInt(this.el.style.width)) + 'px'; + + this.el.style.paddingRight = ((trim.end / frames) * parseInt(this.el.style.width)) + 'px'; + this.right.style.right = ((trim.end / frames) * parseInt(this.el.style.width)) + 'px'; + + } + + }); + +}); \ No newline at end of file diff --git a/app/views/ui/timeline-layer.js b/app/views/ui/timeline-layer.js new file mode 100644 index 0000000..2cfea00 --- /dev/null +++ b/app/views/ui/timeline-layer.js @@ -0,0 +1,151 @@ +/* Microsoft Video Editor + * @author: T.M.P. Kleist / Code D'azur + * ============================================================================== */ + +/*global views, console, $, define */ + +define(['app/views/ui/timeline-layer-slide'], function (Slide) { + + 'use strict'; + + return Backbone.View.extend({ + + tagName: 'li', + + events: { + "click button": "clone" + }, + + template: _.template("\ +
    \ +
    \ + "), + + + initialize: function () { + + _.bindAll(this, 'resize', 'trimEnd', 'trimStart', 'trim', 'resize', 'clone'); + + this.moving = false; + this.startMove = 0; + + this.options.model.on('destroy', this.remove, this); + + if (this.options.model.get('frames') === 0) { + log('check frames later'); + this.options.model.on("change:frames", this.resize, this); + } + }, + + + render: function () { + + this.media = new Slide({ + color: "#00ffff", + model: this.options.model + }); + + this.$el.html(this.template()); + this.el.querySelector('.layer').appendChild(this.media.render().el); + + if (this.model.get('type') !== 'video') { + this.$('.layer').addClass(this.model.get('type').replace('/','-')); + } + + this.hammertime = Hammer(this.media.el); + + this.hammertime.on("dragstart", this.trimStart); + this.hammertime.on("drag", this.trim); + this.hammertime.on("dragend", this.trimEnd); + + window.addEventListener('resize', this.resize, false); + + return this; + + }, + + + /* Clones the selected layer + * ---------------------------------------------------------------------- */ + + clone: function () { + this.options.model.collection.add(this.options.model.toJSON()); + + }, + + + /* Start of the drag/trim + * ---------------------------------------------------------------------- */ + + trimStart: function (e) { + + if (e.target.getAttribute('class') === 'left' || e.target.getAttribute('class') === 'right') { + return; + } + + window.App.dragging = true; + + this.startMove = e.gesture.deltaX - parseInt(this.media.el.style.left); + this.moving = true; + + }, + + + /* Dragging the media + * ---------------------------------------------------------------------- */ + + trim: function (e) { + + if (this.moving) { + this.media.el.style.left = (e.gesture.deltaX - this.startMove) + 'px'; + } + + }, + + + /* The End of the media trim move + * ---------------------------------------------------------------------- */ + + trimEnd: function () { + + this.media.endTrim(); + + if (this.moving) { + window.App.dragging = false; + + var frame = (parseInt(this.media.el.style.left) / this.$('.layer').width()) * this.options.model.collection.totalFrames; + this.options.model.set('offset', Math.round(frame)); + if (this.model.get('type') === 'video') { + this.options.model.syncFrame(); + } + + window.removeEventListener('mousemove', this.moveMedia, true); + this.moving = false; + } + + }, + + + resize: function () { + + var percentage = this.options.model.get('frames') / this.options.model.collection.totalFrames; + + this.media.el.style.width = parseInt(percentage * this.$('.layer').width()) + 'px'; + this.media.resize(); + + if (this.options.model.get('offset') < 0) { + this.media.el.style.left = '-' + ((Math.abs(this.options.model.get('offset')) / this.options.model.collection.totalFrames) * this.$('.layer').width()) + 'px'; + } else { + this.media.el.style.left = ((this.options.model.get('offset') / this.options.model.collection.totalFrames) * this.$('.layer').width()) + 'px'; + } + + }, + + + remove: function () { + Backbone.View.prototype.remove.call(this); + } + + }); + +}); diff --git a/app/views/ui/timeline.js b/app/views/ui/timeline.js new file mode 100644 index 0000000..34ca1bc --- /dev/null +++ b/app/views/ui/timeline.js @@ -0,0 +1,285 @@ +/* Microsoft Video Editor + * @author: T.M.P. Kleist / Code D'azur + * ============================================================================== */ + +/*global views, console, $, define */ + +define(['app/views/ui/timeline-layer', 'app/views/ui/effects'], function (Layer) { + + 'use strict'; + + return Backbone.View.extend({ + + tagName: 'footer', + el: 'body > footer', + currentZoom: 100, + layers: [], + scrollstartScrubber: 0, + dragProp: '', + + events: { + "click .zoom": "zoom", + 'click button': 'clickHandler' + }, + + initialize: function () { + + _.bindAll(this, 'pinch', 'dragstart', 'drag', 'dragstartScrubber', 'dragScrubber', 'dragend'); + + this.time = this.el.querySelector('#time .line'); + this.collection = window.App.timeline.collection; + window.App.timeline.on('play pause stop', this.setState, this); + this.collection.on('add', this.add, this); + this.collection.on('remove', this.remove, this); + this.collection.on('frame-sync seek kill', this.progress, this); + + var spans = this.el.querySelectorAll('.time span'), + devidedFrames = this.collection.totalFrames / 10; + + + /* Dividing the spans with + * time + * ------------------------- */ + + _.each(spans, function (span, id) { + if (id !== 0) span.innerHTML = '
    ' + (devidedFrames * id / 25).toMMSS() + '
    '; + }); + + /* Hammer for Scrub + * ------------------------- */ + + this.hammerscrubber = Hammer(document.getElementById('scrubber')); + this.hammerscrubber.on('dragstart', this.dragstartScrubber); + this.hammerscrubber.on('drag', this.dragScrubber); + + + /* Hammer for Time + * ------------------------- */ + + this.hammertime = Hammer(this.el); + this.hammertime.on('pinchin', this.pinch); + this.hammertime.on('pinchout', this.pinch); + this.hammertime.on('dragstart', this.dragstart); + this.hammertime.on('dragend', this.dragend); + this.hammertime.on('drag', this.drag); + + }, + + + /* Handler for all the click events + * ---------------------------------------------------------------------- */ + + clickHandler: function (e) { + + switch (e.currentTarget.getAttribute('class')) { + case 'play': + window.App.timeline.play(); + break; + + case 'pause': + window.App.timeline.play(); + break; + + case 'stop': + window.App.timeline.stop(); + break; + + case 'options': + $(e.currentTarget.parentElement).toggleClass('active'); + break; + + case 'delete': + var parent = e.currentTarget.parentElement.parentElement; + this.collection.get(parent.getAttribute('data-cid')).destroy(); + $(parent).remove(); + break; + + case 'duplicate': + var cid = e.currentTarget.parentElement.parentElement.getAttribute('data-cid'); + this.collection.add(this.collection.get(cid).toJSON()); + break; + } + + }, + + + /* Sets the play and pause state of the button + * ---------------------------------------------------------------------- */ + + setState: function (event) { + switch (event) { + case 'play': + document.getElementById('play-pause').setAttribute('class', 'pause'); + break; + case 'pause': + case 'stop': + document.getElementById('play-pause').setAttribute('class', 'play'); + break; + + } + }, + + + /* Adds the layer to the view + * ---------------------------------------------------------------------- */ + + add: function (model) { + + var title = "", + layer = new Layer({ + model: model + }), + template = _.template("\ + <%= title %>\ + \ +
    \ + \ + \ +
    \ + "); + + if (model.get('type') === 'video') { + title = model.get('media').get('file').name; + title = title.substring(0, title.length - 4) + } else { + title = model.get('name'); + } + + this.el.querySelector('.layers').appendChild(layer.render().el); + + var el = document.createElement('li'); + el.setAttribute('data-cid', model.cid); + el.innerHTML = template({ + title: title + }); + + this.el.querySelector('.labels').appendChild(el); + this.layers.push(layer); + if (model.get('frames') !== 0) { + layer.resize(); + } + }, + + + /* Removes the layer from the view + * ---------------------------------------------------------------------- */ + + remove: function (model) { + this.$('*[data-cid="' + model.cid + '"]').remove(); + }, + + + /* Pinch zoom effect on the timeline to zoom in and out + * ---------------------------------------------------------------------- */ + + pinch: function (event) { + var layers = this.el.querySelector('.layers'), + scale = event.gesture.scale; + + this.currentZoom = (scale < 1 ? 1 : scale) * 100; + layers.style.width = this.currentZoom + "%"; + + _.each(this.layers, function (layer) { + layer.resize(); + }); + }, + + + /* Time Drag start + * ---------------------------------------------------------------------- */ + + dragstart: function (event) { + if (this.dragProp === ''){ + this.dragProp = 'time'; + this.scrollstart = this.el.querySelector('.layer-holder').scrollLeft; + } + }, + + + /* Time Drag + * ---------------------------------------------------------------------- */ + + drag: function (event) { + if (!window.App.dragging && this.dragProp === 'time') { + this.el.querySelector('.layer-holder').scrollLeft = this.scrollstart + -(event.gesture.deltaX); + } + }, + + + /* Drag end + * ---------------------------------------------------------------------- */ + + dragend: function (event) { + this.dragProp = ''; + }, + + + /* Scrubber drag start + * ---------------------------------------------------------------------- */ + + dragstartScrubber: function (event) { + this.dragProp = 'scrub'; + this.startFrameScrub = window.App.timeline._frame; + }, + + + /* Scrubber drag + * ---------------------------------------------------------------------- */ + + dragScrubber: function (event) { + + var frameScrub = parseInt((Math.abs(event.gesture.deltaX) / $('#layers').width()) * window.App.timeline.collection.totalFrames); + + switch (event.gesture.direction) { + case "left": + frameScrub = this.startFrameScrub - frameScrub; + break; + case "right": + frameScrub = this.startFrameScrub + frameScrub; + break; + default: + return; + } + + window.App.timeline.seek(frameScrub); + + }, + + + /* The zoom function of the timeline + * ---------------------------------------------------------------------- */ + + zoom: function (event) { + + var layers = this.el.querySelector('.layers'), + current = layers.style.width; + + switch (event.currentTarget.getAttribute('class').replace('zoom ', '')) { + case "in": + this.currentZoom += 25; + break; + case "out": + this.currentZoom = this.currentZoom <= 100 ? 100 : this.currentZoom - 10; + break; + } + + layers.style.width = this.currentZoom + "%"; + + _.each(this.layers, function (layer) { + layer.resize(); + }); + + }, + + progress: function (frame) { + var current = frame / 25, + total = this.collection.totalFrames / 25; + + this.el.querySelector('#scrubber').innerHTML = current.toMMSS(); + this.el.querySelector('#scrubber').style.left = ((current / total) * 100) + "%"; + + } + + }); + +}); diff --git a/application.js b/application.js index 16959f2..360fe52 100644 --- a/application.js +++ b/application.js @@ -1,10 +1,8 @@ -/** - * Project Touch - * - * @date: 6/18/13 - */ +/* Microsoft Video Editor + * @author: T.M.P. Kleist / Code D'azur + * ============================================================================== */ -/*global define, window, document, $, requirejs, require */ +/*global views, console, $, define */ requirejs.config({ waitSeconds: 1, @@ -25,24 +23,29 @@ requirejs.config({ exports: "Backbone" }, "stats": { - exports: "Stats" + exports: "Stats" + }, + "jquery-ui": { + exports: "$", + deps: ['jquery'] } } }); -require(['app/project-touch', 'hammer', 'stats'], function (PT) { +//Enforce loading global libraries first +require(['backbone', 'underscore', 'hammer', 'stats'], function (PT) { + + require(['app/project-touch'], function (PT) { + + 'use strict'; + + window.log = Function.prototype.bind.call(console.log, console); - 'use strict'; - -// var stats = new Stats(); -// -// stats.domElement.style.position = 'absolute'; -// stats.domElement.style.left = '0px'; -// stats.domElement.style.bottom = '0px'; -// -// document.body.appendChild( stats.domElement ); + window.App = new PT({ + el: document.querySelector('.video') + }); + window.App.render(); - window.App = new PT({el: document.querySelector('.video')}); - window.App.render(); + }); }); diff --git a/audio.mp3 b/audio.mp3 new file mode 100644 index 0000000..d38b6f4 Binary files /dev/null and b/audio.mp3 differ diff --git a/clip.mp4 b/clip-nature.mp4 old mode 100755 new mode 100644 similarity index 100% rename from clip.mp4 rename to clip-nature.mp4 diff --git a/clip-street.mp4 b/clip-street.mp4 new file mode 100644 index 0000000..2841300 Binary files /dev/null and b/clip-street.mp4 differ diff --git a/config.rb b/config.rb new file mode 100644 index 0000000..8d9fb5f --- /dev/null +++ b/config.rb @@ -0,0 +1,9 @@ +require 'compass-colors' + +project_type = "stand_alone" +http_path = "/" +css_dir = "styles" +sass_dir = "styles" +images_path = "images" +javascripts_dir = "app" +output_style = "compact" \ No newline at end of file diff --git a/images/grayscale.png b/images/grayscale.png deleted file mode 100644 index 9fbd396..0000000 Binary files a/images/grayscale.png and /dev/null differ diff --git a/images/interface-se494e7670e.png b/images/interface-se494e7670e.png new file mode 100644 index 0000000..1dd3279 Binary files /dev/null and b/images/interface-se494e7670e.png differ diff --git a/images/stop.png b/images/interface/button-add.png similarity index 73% rename from images/stop.png rename to images/interface/button-add.png index 35bc9f5..6de6def 100644 Binary files a/images/stop.png and b/images/interface/button-add.png differ diff --git a/images/pause.png b/images/interface/button-delete.png similarity index 73% rename from images/pause.png rename to images/interface/button-delete.png index f6207e5..c92d487 100644 Binary files a/images/pause.png and b/images/interface/button-delete.png differ diff --git a/images/pixelize.png b/images/interface/button-duplicate.png similarity index 73% rename from images/pixelize.png rename to images/interface/button-duplicate.png index e99f8b8..2ba6681 100644 Binary files a/images/pixelize.png and b/images/interface/button-duplicate.png differ diff --git a/images/threshold.png b/images/interface/button-maximize.png similarity index 72% rename from images/threshold.png rename to images/interface/button-maximize.png index 2b89b11..63612ca 100644 Binary files a/images/threshold.png and b/images/interface/button-maximize.png differ diff --git a/images/interface/button-menu-active.png b/images/interface/button-menu-active.png new file mode 100644 index 0000000..47a1d89 Binary files /dev/null and b/images/interface/button-menu-active.png differ diff --git a/images/interface/button-menu.png b/images/interface/button-menu.png new file mode 100644 index 0000000..cd6f22d Binary files /dev/null and b/images/interface/button-menu.png differ diff --git a/images/interface/button-pause.png b/images/interface/button-pause.png new file mode 100644 index 0000000..0514e81 Binary files /dev/null and b/images/interface/button-pause.png differ diff --git a/images/interface/button-play.png b/images/interface/button-play.png new file mode 100644 index 0000000..ab9a384 Binary files /dev/null and b/images/interface/button-play.png differ diff --git a/images/interface/button-stop.png b/images/interface/button-stop.png new file mode 100644 index 0000000..fb4b4e0 Binary files /dev/null and b/images/interface/button-stop.png differ diff --git a/images/interface/button-zoom-in.png b/images/interface/button-zoom-in.png new file mode 100644 index 0000000..f746e37 Binary files /dev/null and b/images/interface/button-zoom-in.png differ diff --git a/images/interface/button-zoom-out.png b/images/interface/button-zoom-out.png new file mode 100644 index 0000000..9aa1ada Binary files /dev/null and b/images/interface/button-zoom-out.png differ diff --git a/images/interface/drag-left.png b/images/interface/drag-left.png new file mode 100644 index 0000000..f8edebd Binary files /dev/null and b/images/interface/drag-left.png differ diff --git a/images/interface/drag-right.png b/images/interface/drag-right.png new file mode 100644 index 0000000..22022a8 Binary files /dev/null and b/images/interface/drag-right.png differ diff --git a/images/interface/icon-effect-off.png b/images/interface/icon-effect-off.png new file mode 100644 index 0000000..d2f58cd Binary files /dev/null and b/images/interface/icon-effect-off.png differ diff --git a/images/interface/icon-effect-on.png b/images/interface/icon-effect-on.png new file mode 100644 index 0000000..79ef99b Binary files /dev/null and b/images/interface/icon-effect-on.png differ diff --git a/images/interface/icon-note.png b/images/interface/icon-note.png new file mode 100644 index 0000000..857fb2d Binary files /dev/null and b/images/interface/icon-note.png differ diff --git a/images/interface/icon-play.png b/images/interface/icon-play.png new file mode 100644 index 0000000..d314146 Binary files /dev/null and b/images/interface/icon-play.png differ diff --git a/images/interface/icon-sound.png b/images/interface/icon-sound.png new file mode 100644 index 0000000..99ff142 Binary files /dev/null and b/images/interface/icon-sound.png differ diff --git a/images/interface2x-s83bd2fe9ce.png b/images/interface2x-s83bd2fe9ce.png new file mode 100644 index 0000000..6855ec8 Binary files /dev/null and b/images/interface2x-s83bd2fe9ce.png differ diff --git a/images/interface2x/button-add.png b/images/interface2x/button-add.png new file mode 100644 index 0000000..7c8cec6 Binary files /dev/null and b/images/interface2x/button-add.png differ diff --git a/images/interface2x/button-delete.png b/images/interface2x/button-delete.png new file mode 100644 index 0000000..d168f77 Binary files /dev/null and b/images/interface2x/button-delete.png differ diff --git a/images/interface2x/button-duplicate.png b/images/interface2x/button-duplicate.png new file mode 100644 index 0000000..c3c3913 Binary files /dev/null and b/images/interface2x/button-duplicate.png differ diff --git a/images/interface2x/button-maximize.png b/images/interface2x/button-maximize.png new file mode 100644 index 0000000..a8fedbc Binary files /dev/null and b/images/interface2x/button-maximize.png differ diff --git a/images/interface2x/button-menu-active.png b/images/interface2x/button-menu-active.png new file mode 100644 index 0000000..224c4db Binary files /dev/null and b/images/interface2x/button-menu-active.png differ diff --git a/images/interface2x/button-menu.png b/images/interface2x/button-menu.png new file mode 100644 index 0000000..9f5d13c Binary files /dev/null and b/images/interface2x/button-menu.png differ diff --git a/images/interface2x/button-pause.png b/images/interface2x/button-pause.png new file mode 100644 index 0000000..edb4808 Binary files /dev/null and b/images/interface2x/button-pause.png differ diff --git a/images/interface2x/button-play.png b/images/interface2x/button-play.png new file mode 100644 index 0000000..8601bdc Binary files /dev/null and b/images/interface2x/button-play.png differ diff --git a/images/interface2x/button-stop.png b/images/interface2x/button-stop.png new file mode 100644 index 0000000..8c736ee Binary files /dev/null and b/images/interface2x/button-stop.png differ diff --git a/images/interface2x/button-zoom-in.png b/images/interface2x/button-zoom-in.png new file mode 100644 index 0000000..e09c682 Binary files /dev/null and b/images/interface2x/button-zoom-in.png differ diff --git a/images/interface2x/button-zoom-out.png b/images/interface2x/button-zoom-out.png new file mode 100644 index 0000000..658b3de Binary files /dev/null and b/images/interface2x/button-zoom-out.png differ diff --git a/images/interface2x/drag-left.png b/images/interface2x/drag-left.png new file mode 100644 index 0000000..0a0f00f Binary files /dev/null and b/images/interface2x/drag-left.png differ diff --git a/images/interface2x/drag-right.png b/images/interface2x/drag-right.png new file mode 100644 index 0000000..28d8711 Binary files /dev/null and b/images/interface2x/drag-right.png differ diff --git a/images/interface2x/icon-effect-off.png b/images/interface2x/icon-effect-off.png new file mode 100644 index 0000000..99c899e Binary files /dev/null and b/images/interface2x/icon-effect-off.png differ diff --git a/images/interface2x/icon-effect-on.png b/images/interface2x/icon-effect-on.png new file mode 100644 index 0000000..afe86e1 Binary files /dev/null and b/images/interface2x/icon-effect-on.png differ diff --git a/images/interface2x/icon-note.png b/images/interface2x/icon-note.png new file mode 100644 index 0000000..3e3911a Binary files /dev/null and b/images/interface2x/icon-note.png differ diff --git a/images/interface2x/icon-play.png b/images/interface2x/icon-play.png new file mode 100644 index 0000000..3c6f282 Binary files /dev/null and b/images/interface2x/icon-play.png differ diff --git a/images/interface2x/icon-sound.png b/images/interface2x/icon-sound.png new file mode 100644 index 0000000..e275e67 Binary files /dev/null and b/images/interface2x/icon-sound.png differ diff --git a/images/play.png b/images/play.png deleted file mode 100644 index 2112b87..0000000 Binary files a/images/play.png and /dev/null differ diff --git a/index.html b/index.html index 2129062..5c5fef2 100644 --- a/index.html +++ b/index.html @@ -2,65 +2,103 @@ + Project Touch - +
    - +

    Project Touch

    - - + +
    +

    My Content

    +
      +
    • +
      + + +
      +
    • +
    +
    + +
    +

    Edit Video

    + + +

    Scale

    + +
    +
    +
    +
    +
    +
    +

    Rotation

    + +
    +
    +
    +
    +
    +
    +

    Audio level

    + +
    +
    +
    +
    +
    +
    +
    + + + +
    +

    My Composition

    + + +
    + +

    My Effects

      +
      diff --git a/readme.md b/readme.md index aa51e1f..8bdc369 100644 --- a/readme.md +++ b/readme.md @@ -3,6 +3,8 @@ ProjectTouch **Community built HTML 5 video editor.** +![Editor](http://staging.codedazur.nl/projecttouch/pt.jpg) + There are three tough challenges in developing the web-based video editor for Touch: - UX and touch input - Video loading, sequencing and manipulation @@ -14,4 +16,17 @@ Videos you can use for testing: - [Nature](https://www.theinternetoftouch.com/videos/nature.zip) - [Trailer](https://www.theinternetoftouch.com/videos/trailer.zip) -Check out the [ProjectTouch microsite](http://www.theinternetoftouch.com) for updates. \ No newline at end of file +Check out the [ProjectTouch microsite](http://www.theinternetoftouch.com) for updates. + + +Editing installation +-------------------- + + git clone git@github.com:projecttouch/projecttouch.git + gem install compass + gem install compass-colors + +commit and push your changes +do a pull request and state your changes + + diff --git a/styles/_mixin.sass b/styles/_mixin.sass new file mode 100644 index 0000000..a7239cb --- /dev/null +++ b/styles/_mixin.sass @@ -0,0 +1,97 @@ +@import compass/css3/background-size +@import compass/utilities/sprites/base + +=getSprite($sprites, $sprites-retina, $name, $center: false) + @if $center == true + background: sprite-url($sprites) sprite-position($sprites, $name) repeat-x + @else + background: sprite-url($sprites) sprite-position($sprites, $name) no-repeat + @media (-webkit-min-device-pixel-ratio: 1.5), (min--moz-device-pixel-ratio: 1.5), (-o-min-device-pixel-ratio: 3 / 2), (min-device-pixel-ratio: 1.5) + & + $xpos: round(nth(sprite-position($sprites-retina, $name), 1) / 2) + $ypos: round(nth(sprite-position($sprites-retina, $name), 2) / 2) + @if $center == true + background: sprite-url($sprites-retina) $xpos $ypos repeat-x + +background-size(ceil(image-width(sprite-path($sprites-retina)) / 2), ceil(image-height(sprite-path($sprites-retina)) / 2)) + @else + background: sprite-url($sprites-retina) $xpos $ypos no-repeat + +background-size(ceil(image-width(sprite-path($sprites-retina)) / 2), auto) + +// Returns just the sprite positions + +=getSpritePositions($sprites, $sprites-retina, $name, $right: false) + @if $right == true + background-position: right nth(sprite-position($sprites, $name), 2) + @else + background-position: sprite-position($sprites, $name) + @media (-webkit-min-device-pixel-ratio: 2), (-o-min-device-pixel-ratio: 3 / 2), (min-device-pixel-ratio: 2) + $xpos: round(nth(sprite-position($sprites-retina, $name), 1) / 2) + $ypos: round(nth(sprite-position($sprites-retina, $name), 2) / 2) + @if $right == true + background-position: right $ypos + @else + background-position: $xpos $ypos + +// image mixin +=imageBefore($sprites, $sprites-retina, $name, $top, $left) + &:before + content: " " + display: block + position: absolute + top: #{$top}px + left: #{$left}px + +getSprite($sprites, $sprites-retina, $name) + +sprite-dimensions($sprites, $name) + +=imageAfter($sprites, $sprites-retina, $name, $top, $right) + &:after + content: " " + display: block + position: absolute + top: #{$top}px + right: #{$right}px + +getSprite($sprites, $sprites-retina, $name) + +sprite-dimensions($sprites, $name) + +// Button mixin + +@function -returnLeftSpriteName($name, $state: "Normal") + @return $name + "Left" + $state + +@function -returnCenterSpriteName($name, $state: "Normal") + @return $name + "Center" + $state + +@function -returnRightSpriteName($name, $state: "Normal") + @return $name + "Right" + $state + +=button($sprites-sides, $sprites-sides-retina, $sprites-center, $sprites-center-retina, $sprite-name) + display: inline-block + position: relative + height: image-height(sprite-file($sprites-center, -returnCenterSpriteName($sprite-name))) + margin: 0 image-width(sprite-file($sprites-sides, -returnRightSpriteName($sprite-name))) 0 image-width(sprite-file($sprites-sides, -returnLeftSpriteName($sprite-name))) + +getSprite($sprites-center, $sprites-center-retina, -returnCenterSpriteName($sprite-name), true) + &:before, &:after + content: " " + display: block + position: absolute + top: 0 + &:before + left: -#{image-width(sprite-file($sprites-sides, -returnLeftSpriteName($sprite-name)))} + +getSprite($sprites-sides, $sprites-sides-retina, -returnLeftSpriteName($sprite-name)) + +sprite-dimensions($sprites-sides, -returnLeftSpriteName($sprite-name)) + &:after + right: -#{image-width(sprite-file($sprites-sides, -returnRightSpriteName($sprite-name)))} + +getSprite($sprites-sides, $sprites-sides-retina, -returnRightSpriteName($sprite-name)) + +sprite-dimensions($sprites-sides, -returnRightSpriteName($sprite-name)) + &:hover + +getSpritePositions($sprites-center, $sprites-center-retina, -returnCenterSpriteName($sprite-name, "Hover")) + &:before + +getSpritePositions($sprites-sides, $sprites-sides-retina, -returnLeftSpriteName($sprite-name, "Hover")) + &:after + +getSpritePositions($sprites-sides, $sprites-sides-retina, -returnRightSpriteName($sprite-name, "Hover")) + &:active + +getSpritePositions($sprites-center, $sprites-center-retina, -returnCenterSpriteName($sprite-name, "Down")) + &:before + +getSpritePositions($sprites-sides, $sprites-sides-retina, -returnLeftSpriteName($sprite-name, "Down")) + &:after + +getSpritePositions($sprites-sides, $sprites-sides-retina, -returnRightSpriteName($sprite-name, "Down")) diff --git a/styles/stylesheet.sass b/styles/stylesheet.sass new file mode 100644 index 0000000..7816c2e --- /dev/null +++ b/styles/stylesheet.sass @@ -0,0 +1,123 @@ +/* Stylesheet + * @author: T.M.P. Kleist / Code D'azur + * ============================================================================== */ + +@import url(http://fonts.googleapis.com/css?family=Open+Sans:300) +@import compass/reset +@import compass/css3/images +@import mixin +@import compass/css3/transition +@import compass/utilities/text/replacement +@import compass/css3/opacity + + +/* Variables + * ---------------------------------------------------------------------- */ + +$interface-sprite: sprite-map("interface/*.png", $layout: smart) +$interface-sprite2x: sprite-map("interface2x/*.png", $layout: smart) + +$headerHeight: 50px +$footerHeight: 125px + +$sideMargin: 20px +$panelMargin: 10px +$panelHeightPct: 80% +$panelWidth: 280px + +$titleBlue: #0CF + +$sideMargin: 20px +$panelMargin: 10px + +$libraryItemHeight: 155px + + +/* Base + * ---------------------------------------------------------------------- */ + +* + -webkit-box-sizing: border-box + -moz-box-sizing: border-box + -o-box-sizing: border-box + box-sizing: border-box + + -webkit-touch-callout: none + -webkit-user-select: none + -khtml-user-select: none + -moz-user-select: moz-none + -ms-user-select: none + user-select: none + + +html + height: 100% + -ms-content-zooming: none + +body + height: 100% + background: #1d1d1d + color: #fff + font-family: 'Open Sans', helvetica, verdana, sans-serif + font-weight: 300 + -ms-content-zooming: none + + #hiddenVideo + display: none + +header + position: absolute + top: 0 + width: 100% + background: #333333 + min-height: $headerHeight + +h1 + color: $titleBlue + font-size: 30px + line-height: 50px + vertical-align: middle + text-indent: 40px + +h2 + font-size: 30px + margin: 10px 0 10px 20px + +button + border: 0 + background: 0 + margin: 0 + padding: 0 + cursor: pointer + outline: none + +transition(opacity 100ms ease-in-out) + &:hover + opacity: 0.6 + &:active + +transition(opacity 0ms ease-in-out) + opacity: 0.2 + +div.video + position: absolute + top: $headerHeight + width: 100% + height: $panelHeightPct + + +/* Imports + * ---------------------------------------------------------------------- */ + +@import ui/footer +@import ui/panel +@import ui/effects +@import ui/edit +@import ui/library +@import ui/composition +@import ui/player +@import ui/scrollbars + + + + + + diff --git a/styles/ui/_composition.sass b/styles/ui/_composition.sass new file mode 100644 index 0000000..0869085 --- /dev/null +++ b/styles/ui/_composition.sass @@ -0,0 +1,18 @@ +#composition + position: absolute + left: $panelWidth + $sideMargin + $panelMargin + right: $panelWidth + $sideMargin + $panelMargin + height: $panelHeightPct + z-index: 99999999 + + &.fullwidth + h2 + margin-left: 40px + + .controls + z-index: 1 + position: absolute + vertical-position: bottom + top: 500px + left: 20px + \ No newline at end of file diff --git a/styles/ui/_edit.sass b/styles/ui/_edit.sass new file mode 100644 index 0000000..7d83e4d --- /dev/null +++ b/styles/ui/_edit.sass @@ -0,0 +1,69 @@ +$levelWidth: 235px + + +#edit + display: none + h2 + margin-bottom: 20px + float: left + + #btnCloseEdit + color: #fff + float: right + margin-top: 24px + + h3 + font-size: 20px + margin: 10px 0 10px 20px + clear: both + + div.level + position: relative + display: block + width: $levelWidth + height: 30px + padding-top: 5px + + .right + -ms-touch-action: none + display: block + position: absolute + right: $levelWidth -22px + top: 0 + height: 30px + width: 2px + cursor: w-resize + z-index: 99 + padding-left: 20px + border-right: 2px solid white + + &:after + content: '' + position: absolute + display: block + width: 22px + height: 100% + right: -22px + top: 0 + + .holder + + position: relative + height: 20px + overflow: hidden + width: $levelWidth + padding-right: $levelWidth + margin-bottom: 34px + margin-left: 20px + + +background(linear-gradient(top, #262526, #323132)) + + .middle + -ms-touch-action: none + display: block + position: relative + height: 20px + width: 100% + z-index: 80 + +background(linear-gradient(top, #199DCA, #127EA2)) + diff --git a/styles/ui/_effects.sass b/styles/ui/_effects.sass new file mode 100644 index 0000000..2eb1fb5 --- /dev/null +++ b/styles/ui/_effects.sass @@ -0,0 +1,37 @@ +#effects.ui-panel + li + background-color: #000 + + a + display: block + padding-top: 68px + height: 155px + text-align: center + color: #1D1D1D + text-decoration: none + text-transform: capitalize + font-size: 27px + width: 100% + position: absolute + z-index: 10 + + img + position: absolute + width: 100% + height: 100% + border: none + z-index: 8 + + span + position: absolute + display: block + right: 10px + top: 10px + width: 20px + height: 20px + z-index: 9 + + &.off span + +getSprite($interface-sprite, $interface-sprite2x, icon-effect-off) + &.on span + +getSprite($interface-sprite, $interface-sprite2x, icon-effect-on) \ No newline at end of file diff --git a/styles/ui/_footer.sass b/styles/ui/_footer.sass new file mode 100644 index 0000000..961b4e7 --- /dev/null +++ b/styles/ui/_footer.sass @@ -0,0 +1,274 @@ +$experimental-support-for-svg: true +footer + background: #333333 + min-height: 20% + position: absolute + bottom: 0 + width: 100% + padding: 0 30px 30px + padding-bottom: 20px + color: #999999 + font-size: 14px + z-index: 9999999 + + h2 + position: absolute + font-size: 20px + top: 9px + + .zoom + position: absolute + top: 13px + width: 34px + height: 34px + + &.in + left: 136px + +imageAfter($interface-sprite, $interface-sprite2x, button-zoom-in,8,8) + &.out + left: 160px + +imageAfter($interface-sprite, $interface-sprite2x, button-zoom-out,8,8) + + .controls + position: absolute + top: 21px + left: 40px + + button + width: 20px + height: 30px + position: relative + + &.play + +imageAfter($interface-sprite, $interface-sprite2x, button-play, 0,0) + + &.stop + +imageAfter($interface-sprite, $interface-sprite2x, button-stop, 0,0) + + &.pause + +imageAfter($interface-sprite, $interface-sprite2x, button-pause, 0,0) + + + #scrubber + position: absolute + height: 100% + z-index: 999 + margin: 0 0 0 -25px + width: 50px + text-align: center + padding: 3px + color: white + top: 0 + + cursor: -webkit-grab + #cursor: -webkit-grabbing + + &:after + content: '' + position: absolute + height: 100% + border: 1px solid #FFFFFF + top: 20px + left: 50% + + .labels + width: 170px + height: 50px + position: absolute + top: 50px + li + height: 20px + position: relative + margin-top: 10px + background: #2C2B2C + color: white + line-height: 20px + padding: 0 0 0 20px + + &>button + position: absolute + right: 5px + top: 0 + width: 20px + height: 20px + +imageAfter($interface-sprite, $interface-sprite2x, button-menu-active, 5, 0) + + &>div + display: none + position: absolute + width: 50px + height: 20px + z-index: 9999 + top: 0 + right: -50px + background: #2C2B2C + + button + display: inline-block + width: 20px + height: 20px + position: relative + + &.delete + +imageAfter($interface-sprite, $interface-sprite2x, button-delete, 3, 0) + + &.duplicate + +imageAfter($interface-sprite, $interface-sprite2x, button-duplicate, 3, 0) + + &.active>div + display: block + + + &:first-child + margin: 0 + + .layers + position: relative + width: 100% + + .layer-holder + position: relative + margin: 0 0 0 170px + padding: 20px 0 0 + overflow: hidden + + + ol + li + background-color: #2d2c2d + height: 20px + position: relative + margin-top: 10px + + &:first-child + font-size: 0 + margin-top: 0px + cursor: move + + span + width: 10% + display: inline-block + height: 100% + border-left: 1px solid #424142 + position: relative + color: #858585 + +background( linear-gradient(left, #2C2B2D, #2A282A) ) + + &:first-child + border: 0 + + div + position: absolute + font-size: 12px + top: -15px + left: -16px + + + .layer + position: absolute + right: 0 + top: 0 + left: 0 + height: 100% + font-size: 11px + height: 20px + background: #282628 + overflow: hidden + + &>button + position: absolute + left: 0 + width: 20px + height: 20px + background: white + &.effect + .media + .middle + +background(linear-gradient(top, #4cffa7, #3dcd87)) + + &.audio + .media + .middle + +background(linear-gradient(top, #EEEEEE, #C1C1C1)) + + .media + position: absolute + height: 100% + cursor: move + overflow: hidden + + +background( linear-gradient(top, #1F1F1F, #191919) ) + + + .left + -ms-touch-action: none + display: block + position: absolute + left: 0 + top: 0 + height: 26px + width: 26px + cursor: e-resize + z-index: 99 + +imageBefore($interface-sprite, $interface-sprite2x, drag-left, 2, 2) + + .middle + -ms-touch-action: none + display: block + position: relative + height: 26px + width: 100% + z-index: 80 + +background( linear-gradient(top, #199DCA, #127EA2) ) + + + .right + -ms-touch-action: none + display: block + position: absolute + right: 0 + top: 0 + height: 26px + width: 26px + cursor: w-resize + z-index: 99 + +imageAfter($interface-sprite, $interface-sprite2x, drag-right, 2, 2) + + + &.selected + // +background( linear-gradient(top, darken(#191919, 15), darken(#1F1F1F, 15)) ) + + .middle + +background( linear-gradient(top, darken(#127EA2, 15), darken(#199DCA, 15)) ) + + .layer span + float: right + margin-right: 6px + + .layer .part + position: absolute + height: 100% + +background( linear-gradient(top, #199DCA, #127EA2) ) + + +footer .part .left, footer .part .right + position: absolute + width: 40px + height: 40px + background: rgba(255, 255, 255, 0.3) + top: -10px + +footer .part .left + left: -10px + +footer .part .right + right: -10px + +footer .layer .part + background: -moz-layerar-gradient(top, #4cffa7 0%, #36d486 100%) + background: -webkit-gradient(layerar, left top, left bottom, color-stop(0%, #4cffa7), color-stop(100%, #36d486)) + background: -webkit-layerar-gradient(top, #4cffa7 0%, #36d486 100%) + background: -o-layerar-gradient(top, #4cffa7 0%, #36d486 100%) + background: -ms-layerar-gradient(top, #4cffa7 0%, #36d486 100%) + background: layerar-gradient(to bottom, #4cffa7 0%, #36d486 100%) + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#4cffa7', endColorstr='#36d486',GradientType=0 ) diff --git a/styles/ui/_library.sass b/styles/ui/_library.sass new file mode 100644 index 0000000..82bc3ba --- /dev/null +++ b/styles/ui/_library.sass @@ -0,0 +1,80 @@ +#library + //display: none + .file-selector + width: $panelWidth + +imageBefore($interface-sprite, $interface-sprite2x, button-add, 47, 110) + + form + cursor: pointer + +opacity(0) + width: $panelWidth + height: 100% + overflow: hidden + + input + +opacity(0) + width: $panelWidth * 2 + height: 100% + position: absolute + right: 0 + &::-ms-browse + width: 300px + + label + cursor: pointer + opacity: 0 + width: 280px + height: 100% + position: absolute + display: block + top: 0 + + &:hover + background-color: $titleBlue + + .library-item + width: $panelWidth + height: $libraryItemHeight + background: rgba(0, 0, 0, 0.5) no-repeat center center + background-size: cover + + > * + position: absolute + + h3 + background: rgba(0, 0, 0, 0.5) + width: 100% + padding: 5px + line-height: 1.5em + text-indent: 5px + + button + color: black + height: 34px + width: 34px + bottom: 10px + right: 10px + position: absolute + +imageAfter($interface-sprite, $interface-sprite2x, button-zoom-in, 8, 8) + +hide-text() + + img, video + width: 100% + height: auto + + &:hover + h3 + opacity: 1 + + &.audio + height: 95px + + +imageAfter($interface-sprite, $interface-sprite2x, icon-sound, 16, 16) + + &:after + background-color: white + h3 + opacity: 1 + padding: 5px 50px 5px 5px + background: none + diff --git a/styles/ui/_panel.sass b/styles/ui/_panel.sass new file mode 100644 index 0000000..74874a5 --- /dev/null +++ b/styles/ui/_panel.sass @@ -0,0 +1,33 @@ +.ui-panel + + width: $panelWidth + position: absolute + top: 0 + bottom: $headerHeight + + &.left + left: $sideMargin + + &.right + right: $sideMargin + + ul + width: 100% + position: absolute + top: 50px + bottom: 0 + overflow: hidden + overflow-y: auto + + li + position: relative + width: 100% + height: $libraryItemHeight + margin: 10px 0 0 + + &:first-child + margin: 0 + + + + \ No newline at end of file diff --git a/styles/ui/_player.sass b/styles/ui/_player.sass new file mode 100644 index 0000000..7b2793d --- /dev/null +++ b/styles/ui/_player.sass @@ -0,0 +1,3 @@ +#player + width: 100% + pointer-events: none \ No newline at end of file diff --git a/styles/ui/_scrollbars.sass b/styles/ui/_scrollbars.sass new file mode 100644 index 0000000..b957371 --- /dev/null +++ b/styles/ui/_scrollbars.sass @@ -0,0 +1,63 @@ +/* WEBKIT + * ---------------------------------------------------------------------- */ + +::-webkit-scrollbar + width: 10px + height: 6px + +::-webkit-scrollbar-button + background-color: #666 + +::-webkit-scrollbar-track + background-color: #999 + +::-webkit-scrollbar-track-piece + background-color: #666 + +::-webkit-scrollbar-thumb + height: 50px + background-color: $titleBlue + border-radius: 3px + +::-webkit-scrollbar-corner + background-color: #999 + +::-webkit-resizer + background-color: #666 + +/* IE + * ---------------------------------------------------------------------- */ + +body + scrollbar-base-color: $titleBlue + scrollbar-3dlight-color: #666 + scrollbar-highlight-color: #666 + scrollbar-track-color: #666 + scrollbar-arrow-color: #666 + scrollbar-shadow-color: #666 + scrollbar-dark-shadow-color: #1D1D1D + +/* MOZ + * ---------------------------------------------------------------------- */ + +@-moz-document url-prefix(http://),url-prefix(https://) + scrollbar + -moz-appearance: none !important + background: rgb(255, 255, 0) !important + thumb, scrollbarbutton + -moz-appearance: none !important + background-color: $titleBlue !important + thumb:hover + -moz-appearance: none !important + background-color: rgb(255, 0, 0) !important + scrollbarbutton + &:hover + -moz-appearance: none !important + background-color: rgb(255, 0, 0) !important + display: none !important + scrollbar[orient="vertical"] + min-width: 10px !important + + + + \ No newline at end of file diff --git a/stylesheet.css b/stylesheet.css deleted file mode 100644 index 428a23d..0000000 --- a/stylesheet.css +++ /dev/null @@ -1,265 +0,0 @@ -@import url(http://fonts.googleapis.com/css?family=Open+Sans:300); - -@CHARSET "UTF-8"; -html, body, div, span, applet, object, iframe, -h1, h2, h3, h4, h5, h6, p, blockquote, pre, -a, abbr, acronym, address, big, cite, code, -del, dfn, em, img, ins, kbd, q, s, samp, -small, strike, strong, sub, sup, tt, var, -b, u, i, center, -dl, dt, dd, ol, ul, li, -fieldset, form, label, legend, -table, caption, tbody, tfoot, thead, tr, th, td, -article, aside, canvas, details, embed, -figure, figcaption, footer, header, hgroup, -menu, nav, output, ruby, section, summary, -time, mark, audio, video { - margin: 0; - padding: 0; - border: 0; - font: inherit; - font-size: 100%; - vertical-align: baseline; -} - -html { - line-height: 1; -} - -ol, ul { - list-style: none; -} - -table { - border-collapse: collapse; - border-spacing: 0; -} - -caption, th, td { - text-align: left; - font-weight: normal; - vertical-align: middle; -} - -q, blockquote { - quotes: none; -} - -q:before, q:after, blockquote:before, blockquote:after { - content: ""; - content: none; -} - -a img { - border: none; -} - -article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section, summary { - display: block; -} - -* { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - -o-box-sizing: border-box; - box-sizing: border-box; -} - -html { - height: 100%; -} - -body { - height: 100%; - background: #1d1d1d; - color: #fff; - font-family: 'Open Sans'; - font-weight: 300; -} - -header { - background: #333333; - height: 50px; -} - -footer { - background: #333333; - min-height: 50px; - position: absolute; - bottom: 0; - width: 100%; - padding: 30px; - padding-bottom: 20px; - color: #999999; - font-size: 14px; -} - - footer div:first-child { - background: none; - } - - footer div:first-child .line { - background: none; - background-color: #2d2c2d; - padding-left: 6px; - } - - footer .bar { - background-color: #2d2c2d; - padding: 3px 30px; - position: relative; - margin-bottom: 10px; - } - - footer .bar .line { - position: absolute; - right: 0; - top: 0; - width: 90%; - height: 100%; - font-size: 11px; - line-height: 20px; - background: #282628; - } - - footer .bar .line span { - float: right; - margin-right: 6px; - } - - footer .bar .line .part { - position: absolute; - height: 100%; - background: #24687d; /* Old browsers */ - background: -moz-linear-gradient(top, #24687d 0%, #144454 100%); /* FF3.6+ */ - background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#24687d), color-stop(100%,#144454)); /* Chrome,Safari4+ */ - background: -webkit-linear-gradient(top, #24687d 0%,#144454 100%); /* Chrome10+,Safari5.1+ */ - background: -o-linear-gradient(top, #24687d 0%,#144454 100%); /* Opera 11.10+ */ - background: -ms-linear-gradient(top, #24687d 0%,#144454 100%); /* IE10+ */ - background: linear-gradient(to bottom, #24687d 0%,#144454 100%); /* W3C */ - filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#24687d', endColorstr='#144454',GradientType=0 ); /* IE6-9 */ - } - - footer .bar.effects .line .part { - background: -moz-linear-gradient(top, #4cffa7 0%, #36d486 100%); /* FF3.6+ */ - background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#4cffa7), color-stop(100%,#36d486)); /* Chrome,Safari4+ */ - background: -webkit-linear-gradient(top, #4cffa7 0%,#36d486 100%); /* Chrome10+,Safari5.1+ */ - background: -o-linear-gradient(top, #4cffa7 0%,#36d486 100%); /* Opera 11.10+ */ - background: -ms-linear-gradient(top, #4cffa7 0%,#36d486 100%); /* IE10+ */ - background: linear-gradient(to bottom, #4cffa7 0%,#36d486 100%); /* W3C */ - filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#4cffa7', endColorstr='#36d486',GradientType=0 ); /* IE6-9 */ - } - -#player { - -} - -button { - width: 40px; - height: 40px; - border: 0; - background: center center no-repeat; - cursor: pointer; -} - -button:hover { - opacity: 0.8; -} - -#library { - position: absolute; - top: 20px; - left: 20px; -} - -#library li { - background: white; - color: black; - font-size: 20px; - padding: 10px; - position: relative; - text-transform: uppercase; - cursor: pointer; - width: 280px; - height: 160px; - margin: 5px 0; -} - -#library li>div { - margin: 56px 0; - width: 100%; - text-align: center; - color: white; -} - -#library li>img { - position: absolute; - top: 10px; -} - -#library li:first-child { - -} - -button.play { - background-image: url(images/play.png); - margin-top: 200px; -} - -button.pause { - background-image: url(images/pause.png); - margin-top: 200px; -} - -button.stop { - background-image: url(images/stop.png); - margin-top: 200px; -} - -button.pixelize { - background-image: url(images/pixelize.png); -} - -button.threshold { - background-image: url(images/threshold.png); -} - -button.grayscale { - background-image: url(images/grayscale.png); -} - -button.red { - background-color: red; -} - -button.green { - background-color: green; -} - -button.contrast { - background-color: blue; -} - -button.hipster { - background-color: yellowgreen; -} - -#rgb { - position: absolute; - z-index: 99; - right: 0; - display: none; -} - -#rgb input { - width: 300px; -} - -#fps { - position: absolute; - bottom: 10px; - left: 10px; -} - - - diff --git a/usa.mp4 b/usa.mp4 new file mode 100644 index 0000000..0afb4ab Binary files /dev/null and b/usa.mp4 differ