vid-skim 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
+