vid-skim 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,26 @@
1
+ require 'rubygems'
2
+ require 'json'
3
+ gem 'vid-skim'
4
+ gem 'edl'
5
+
6
+ class EdlParser < VidSkim::Parser
7
+ # Build a VidSkim collection of files from an EDL file.
8
+ def load(file)
9
+ list = EDL::Parser.new(fps=30).parse(File.open(file))
10
+ @transcript = VidSkim::Transcript.new({})
11
+ @transcript.title = File.basename(file)
12
+ @transcript.divisions["Transcript"] = VidSkim::Transcript::Division.new("Transcript")
13
+
14
+ list.each do |evt|
15
+ entry = VidSkim::Transcript::Entry.new
16
+ entry.title = evt.clip_name
17
+ clip_start = evt.rec_start_tc
18
+ clip_end = evt.rec_end_tc
19
+ entry.range = ["#{clip_start.hours}:#{clip_start.minutes}:#{clip_start.seconds}",
20
+ "#{clip_end.hours}:#{clip_end.minutes}:#{clip_end.seconds}"]
21
+ @transcript.divisions["Transcript"].entries << entry
22
+ end
23
+ end
24
+
25
+
26
+ end
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ gem "vid-skim"
3
+ gem "json"
4
+
5
+ class JsonParser < VidSkim::Parser
6
+ # Build an expanded collection of files from a VidSkim JSON file.
7
+ def load(file)
8
+ @transcript = VidSkim::Transcript.find(file)
9
+ end
10
+ end
@@ -0,0 +1,319 @@
1
+ /*
2
+ ###### Preloader
3
+ */
4
+ $.preloadImages = function(){
5
+ $.each(arguments, function (){
6
+ $("<img>").attr("src", this);
7
+ });
8
+ };
9
+
10
+ /*
11
+ ###### Transcript
12
+ */
13
+ function defineTranscript($) {
14
+ var transcript = function (timeline_id, trans_id, extra_id, nav_id, tab_id, rail_id, viewer_id, location) { /* TK params should be hasherized */
15
+ this.current_texts = [];
16
+ this.timeline_id = timeline_id;
17
+ this.trans_id = trans_id;
18
+ this.extra_id = extra_id;
19
+ this.rail_id = rail_id;
20
+ this.nav_id = nav_id;
21
+ this.tab_id = tab_id;
22
+ this.viewer_id = viewer_id;
23
+ this.max = 0;
24
+ this.hashSeconds = parseInt(location.hash.replace("#", ''), 10) || 0;
25
+ this.baseURL = location.href.replace(location.hash, '');
26
+ this.initted = false;
27
+ };
28
+ transcript.prototype = {
29
+ init: function(){
30
+ },
31
+
32
+ // central dispatch for data setting and view switching
33
+ setData: function(raw_data, key) {
34
+ this.raw_data = raw_data || this.raw_data;
35
+ var temp = {};
36
+ temp = this.raw_data.divisions[key];
37
+ this.mode = key;
38
+ this.parsed_data = temp;
39
+ transcript = this;
40
+ $(this.tab_id + " a.tab-" + key).addClass('active');
41
+ $(this.rail_id + " div").hide();
42
+ $(this.rail_id + " div." + this.parameterize(key)).show();
43
+ $(this.rail_id + " div." + this.parameterize(key)).children().show();
44
+
45
+ if (this.initted){ /* for tabs */
46
+ this.buildTimeline();
47
+ } else {
48
+ $.each(this.raw_data.divisions, function(){
49
+ $.each(this.entries, function() {
50
+
51
+ this.range[0] = transcript.timeCodeParse(this.range[0]);
52
+ this.range[1] = transcript.timeCodeParse(this.range[1]);
53
+ });
54
+ });
55
+ }
56
+ },
57
+
58
+ //called after we know the length of the video
59
+ setBounds: function(max) {
60
+ if(this.max === 0){
61
+ this.max = max;
62
+ this.initTimeLine();
63
+ this.draw(0);
64
+ }
65
+ },
66
+
67
+ //convienence method and hook for possible support of other players
68
+ setPlayer: function(player) {
69
+ this.player = player;
70
+ },
71
+
72
+ //handles all construction of extra html elements, as well as initializing mouse listeners
73
+ initTimeLine: function(){
74
+ /* scrubber and timeline */
75
+ var scrubber_selector = this.timeline_id + " div.scrubber";
76
+ var timeline = this.timeline_id;
77
+
78
+ var max = this.max;
79
+ var transcript = this;
80
+ $(scrubber_selector).hide();
81
+ $(this.timeline_id).mouseover(function(){$(scrubber_selector).show();});
82
+ $(this.timeline_id).mousemove(function(e) {
83
+ var x_real = e.clientX - $(timeline).offset().left;
84
+ var titles = transcript.lookup(Math.floor(x_real*max/$(timeline).width()));
85
+ if (titles[0]) {
86
+ $(scrubber_selector).html(titles[0].title);
87
+ } else {
88
+ $(scrubber_selector).html('');
89
+ }
90
+ if (x_real < $(timeline).width()){
91
+ $(scrubber_selector).css({'left': x_real});
92
+ }
93
+ });
94
+
95
+ $(this.timeline_id).click(function(e){
96
+ var x_real = e.clientX - $(timeline).offset().left;
97
+ var seconds = Math.floor(x_real*max/$(timeline).width());
98
+ seconds = transcript.lookup(seconds).length ?
99
+ transcript.lookup(seconds)[0].range[0] : seconds;
100
+ transcript.seek(seconds);
101
+ });
102
+
103
+ $(this.timeline_id).mouseleave(function(){
104
+ $(scrubber_selector).hide();
105
+ });
106
+
107
+ /*navigation*/
108
+ $.each(["Next", "Previous"], function() {
109
+ $(transcript.nav_id + " a." + this.toLowerCase()).click(function (e){
110
+ transcript[$(this).attr('class')]();
111
+ return false;
112
+ });
113
+ });
114
+
115
+ $("a.index").click(function (e){
116
+ transcript.seek(parseInt($(this).attr('href').replace('#', ''), 10));
117
+ return false;
118
+ });
119
+ $.each(this.raw_data.divisions, function(i, val){
120
+ $(transcript.tab_id + ' a.tab-' + i).
121
+ prepend("<span class=\"color\" style=\"border-color:" +
122
+ val.color + "; background-color:" +
123
+ val.color + "\">&nbsp;</span>&nbsp;");
124
+ });
125
+ $(this.tab_id + ' a').click(function (e){
126
+ if(transcript.current_tab !== $(this).attr('class').replace("tab-", '')){
127
+ transcript.current_tab = $(this).attr('class').replace("tab-", '');
128
+ $(transcript.tab_id + " a").removeClass('active');
129
+ transcript.setData(null, $(this).attr('class').replace("tab-", ''));
130
+ }
131
+ return false;
132
+ });
133
+
134
+ /*height calculations*/
135
+ var height = $(this.rail_id).height()-45;
136
+ $(this.viewer_id).css({'height':height+'px'});
137
+ $(this.trans_id).css({'height':height-60+'px'});
138
+
139
+
140
+ this.buildTimeline();
141
+ },
142
+ // refreshes the timeline each time setData is called.
143
+ buildTimeline: function (){
144
+ /* boxes */
145
+ var timeline = this.timeline_id;
146
+ var max = this.max;
147
+ var transcript = this;
148
+ $(this.timeline_id + " div.time_box").remove();
149
+
150
+ $.each(this.parsed_data.entries, function() {
151
+ $(timeline).append('<div class="' + transcript.parameterize(this.title) + ' time_box" style="left:'+ Math.floor(this.range[0]*$(timeline).width()/max) + 'px; width:' + (Math.floor(this.range[1]*$(timeline).width()/max) - Math.floor(this.range[0]*$(timeline).width()/max)) + 'px">&nbsp;</span>');
152
+
153
+ });
154
+
155
+ $(timeline + " .time_box").css({
156
+ 'background-color': this.color
157
+ });
158
+ this.highlightSection();
159
+
160
+ $(timeline + " .time_box").hover(function(){
161
+ transcript.highlightSection();
162
+ $(this).css({'background-color':transcript.parsed_data.hover});
163
+ }, function(){
164
+ $(this).css({'background-color':transcript.parsed_data.color});
165
+ transcript.highlightSection();
166
+ });
167
+ },
168
+
169
+
170
+ //highlights the current section on the timeline
171
+ highlightSection: function(){
172
+ $(this.timeline_id + ' .time_box').css({
173
+ 'background-color':
174
+ this.parsed_data.color
175
+ });
176
+ if(this.current_texts[0]) {
177
+
178
+ $(this.timeline_id + ' .'+
179
+ this.parameterize(this.current_texts[0].title)).css({
180
+ 'background-color':
181
+ this.parsed_data.hover
182
+ });
183
+ }
184
+ },
185
+
186
+ //convenience method for working with hash keys
187
+ parameterize: function (str) {
188
+ return str.toLowerCase().replace(/[^a-z0-9\-_\+]+/ig, '');
189
+ },
190
+
191
+ testTexts: function(texts){
192
+ var test = !texts || !texts[0] || !this.current_texts || !this.current_texts[0] ||
193
+ texts[0].range[0] !==
194
+ this.current_texts[0].range[0];
195
+ return test;
196
+ },
197
+ //rerenders the current view as needed
198
+ draw: function(time){
199
+ if(this.initted){
200
+ var texts = this.lookup(time);
201
+ if(this.testTexts(texts)){ /* transcript */
202
+ this.current_texts = texts;
203
+ this.highlightSection();
204
+
205
+ var trans_all = "";
206
+
207
+ if(this.current_texts[0]){
208
+ /* index highlighting */
209
+ $('a.index').removeClass('active');
210
+ $('a.index.' + this.parameterize(this.current_texts[0].title))
211
+ .addClass('active');
212
+
213
+
214
+ /* transcript building */
215
+ $.each(this.current_texts, function(){
216
+ trans_all = trans_all + '<div class="' + this.css_class + '">' + this.transcript + "</div>";
217
+
218
+ });
219
+
220
+ $(this.trans_id).html(trans_all);
221
+ } else {
222
+
223
+ $(this.trans_id).html('');
224
+ }
225
+
226
+ if(this.current_texts[0] && Math.floor(this.current_texts[0].range[0]) > 0){
227
+ var new_seconds = Math.floor(this.current_texts[0].range[0]);
228
+ if(new_seconds !== this.hashSeconds){
229
+ this.hashSeconds = Math.floor(this.current_texts[0].range[0]);
230
+ window.location.href = trans.baseURL + "#" + this.hashSeconds;
231
+ }
232
+ }
233
+
234
+ }
235
+ $(this.timeline_id + " div.position").css({'left':this.player.getCurrentTime()*$(this.timeline_id).width()/this.max});
236
+ }
237
+ },
238
+
239
+ //convenience caller to seekToIndex
240
+ next: function(){
241
+ this.seekToIndex(1);
242
+ },
243
+ //convenience caller to seekToIndex
244
+ previous: function(){
245
+ this.seekToIndex(0);
246
+ },
247
+
248
+ seekToIndex: function(offset) {
249
+ var time = this.player.getCurrentTime();
250
+ var entries = this.lookup(time, true);
251
+ if(entries[0][offset]){
252
+ this.seek(entries[0][offset].range[0]);
253
+ }
254
+ },
255
+ //the brains of the operation, a binary search that can handle arbitrary ranges of data, returns the next and previous items if asked nicely
256
+ lookup: function(time, nearest){
257
+ var ret = [];
258
+ nearest = nearest || false;
259
+
260
+ var start = 0;
261
+ var end = this.parsed_data.entries.length;
262
+ while (start < end) {
263
+ var mid = Math.floor((start+end)/2);
264
+ if (this.parsed_data.entries[mid].range[1] < time) {
265
+ start = mid+1;
266
+ } else {
267
+ end = mid;
268
+ }
269
+ }
270
+ if (nearest){
271
+ var before = this.parsed_data.entries[start-1];
272
+ var after = this.parsed_data.entries[start+1];
273
+ var found = this.parsed_data.entries[start];
274
+ var first = this.parsed_data.entries[0];
275
+ var last = this.parsed_data.entries[this.parsed_data.entries.length-1];
276
+
277
+ if(this.parsed_data.entries[start] &&
278
+ this.parsed_data.entries[start].range[0] <= time &&
279
+ this.parsed_data.entries[start].range[1] >= time){ // case 1: in a clip
280
+ ret.push([before, after]);
281
+ } else if(start === 0) { // case 3: before any clips
282
+ ret.push([first, first]);
283
+ } else if(this.parsed_data.entries[start] &&
284
+ this.parsed_data.entries[start-1].range[1] < time &&
285
+ this.parsed_data.entries[start].range[0] > time) { // case 2: in between clips
286
+ ret.push([before, found]);
287
+ } else if(start >= end){ // case 4: after any clips
288
+ ret.push([last,last]);
289
+ }
290
+ } else {
291
+ if (this.parsed_data.entries[start] &&
292
+ this.parsed_data.entries[start].range[0] <= time &&
293
+ this.parsed_data.entries[start].range[1] >= time) {
294
+ var find = this.parsed_data.entries[start];
295
+ find.css_class = this.mode;
296
+ ret.push(find);
297
+ }
298
+ }
299
+
300
+ return ret;
301
+ },
302
+ //parsing into integers from "hh:mm:ss" format
303
+ timeCodeParse: function(timeCode){
304
+ var times = timeCode.split(':');
305
+ var hours = parseInt(times[0], 10);
306
+ var minutes = parseInt(times[1], 10);
307
+ var seconds = parseInt(times[2], 10);
308
+ return hours*60*60 + minutes*60 + seconds;
309
+ },
310
+
311
+ //wrapper function
312
+ seek: function(time) {
313
+ this.player.seekTo(time+1, true);
314
+ }
315
+ };
316
+ return transcript;
317
+ }
318
+ // all that for this.
319
+ var Transcript = defineTranscript($);
@@ -0,0 +1,167 @@
1
+ #vid-skim, #vid-skim div, #vid-skim span, #vid-skim p,
2
+ #vid-skim a, #vid-skim strong {
3
+ margin: 0;
4
+ padding: 0;
5
+ border: 0;
6
+ outline: 0;
7
+ font-size: 100%;
8
+ vertical-align: baseline;
9
+ background: transparent;
10
+ font-size: 11px;
11
+ font-family: Helvetica, Arial, Sans-serif;
12
+ }
13
+
14
+ #vid-skim p{
15
+ font-size: 12px;
16
+ margin-bottom: 12px;
17
+ }
18
+
19
+ #vid-skim a:link, #vid-skim a:visited {
20
+ background-color:transparent;
21
+ color:#143D8D;
22
+ text-decoration:none;
23
+ }
24
+
25
+ #vid-skim div.viewer {
26
+ padding: 20px;
27
+ border: 5px solid #e4e4e4;
28
+ width: 775px;
29
+ height: 360px;
30
+ overflow: hidden;
31
+ margin: 0;
32
+ margin-bottom: 10px;
33
+ position: relative;
34
+ float:left;
35
+ }
36
+ #vid-skim div.timeline {
37
+ width:82%;
38
+ height:32px;
39
+ background-color:#DCDED2;
40
+ position:relative;
41
+ margin-bottom: 1em;
42
+ }
43
+
44
+ #vid-skim div.timeline div.scrubber {
45
+ color: #222;
46
+ font-family: sans-serif;
47
+ padding:.2em .1em .1em .1em;
48
+ font-size: 12px;
49
+ width: 300px;
50
+ position:absolute;
51
+ top:-16px;
52
+ left: 0px;
53
+ display:none;
54
+ z-index:200;
55
+ }
56
+ #vid-skim div.timeline div.position {
57
+ border-left:3px solid #fff;
58
+ height:100%;
59
+ width:2px;
60
+ position:absolute;
61
+ top:0;
62
+ left:0;
63
+ z-index:100
64
+ }
65
+ #vid-skim div.timeline div.time_box{
66
+ height:100%;
67
+ position:absolute;
68
+ display:block;
69
+ top:0;
70
+ left:0;
71
+ }
72
+ #vid-skim div.vid-nav{
73
+ width: 18%;
74
+ height:32px;
75
+ margin-bottom: 5px;
76
+ position: absolute;
77
+ top:20px;
78
+ right:20px;
79
+ }
80
+ #vid-skim div.vid-nav a {
81
+ width: 65px;
82
+ height: 32px;
83
+ text-indent: -9999px;
84
+ display: block;
85
+ float: right;
86
+ padding: 3px 0 0 3px;
87
+ }
88
+
89
+ #vid-skim div.vid-nav a.previous{
90
+ background: transparent url('../images/prev.jpg') no-repeat;
91
+ }
92
+ #vid-skim div.vid-nav a.next{
93
+ background: transparent url('../images/next.jpg') no-repeat;
94
+ }
95
+
96
+ #vid-skim div.vid-nav a.previous:hover{
97
+ background: transparent url('../images/prev-hover.jpg') no-repeat;
98
+ }
99
+ #vid-skim div.vid-nav a.next:hover{
100
+ background: transparent url('../images/next-hover.jpg') no-repeat;
101
+ }
102
+
103
+ #vid-skim div.trans{
104
+ float: right;
105
+ width: 375px;
106
+ height: 300px;
107
+ margin: 0 5px 5px 5px;
108
+ padding: 5px;
109
+ overflow: auto;
110
+ }
111
+
112
+
113
+ #vid-skim div.player_container {
114
+ float:left;
115
+ padding-top:5px;
116
+ }
117
+ #vid-skim div.rail {
118
+ float: left;
119
+ width: 143px;
120
+ }
121
+ #vid-skim div.rail a, #vid-skim div.rail strong, #vid-skim div.vid-tabs a {
122
+ font-size:11px;
123
+ display: block;
124
+ font-weight: bold;
125
+ padding: .5em;
126
+ line-height: 1.1em;
127
+ }
128
+ #vid-skim div.rail div a:hover, #vid-skim div.rail div a.active, #vid-skim div.vid-tabs div a:hover, #vid-skim div.vid-tabs div a.active {
129
+ background-color: #e4e4e4;
130
+ }
131
+
132
+ #vid-skim div.vid-tabs div{
133
+ display: inline;
134
+ margin: none;
135
+ }
136
+
137
+ #vid-skim div.vid-tabs div a {
138
+ display: inline;
139
+ padding-bottom: none;
140
+ border: 2px solid #e4e4e4;
141
+ padding: 5px;
142
+ }
143
+ #vid-skim div.vid-tabs div{
144
+ margin-bottom: 3px;
145
+ }
146
+ #vid-skim div.vid-tabs .color{
147
+ font-size: 1px;
148
+ border: 6px solid #000;
149
+ margin-right: .3em;
150
+ padding: .1em;
151
+ position:relative;
152
+ bottom: 4px;
153
+ }
154
+ #vid-skim div.rail p {
155
+ margin-bottom: 0;
156
+ padding-top:0;
157
+ padding-bottom: 0;
158
+ }
159
+
160
+ #vid-skim .clearfix:after {
161
+ content: ".";
162
+ display: block;
163
+ height: 0;
164
+ clear: both;
165
+ visibility: hidden;
166
+ }
167
+