cinesync 0.9.6

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,95 @@
1
+ require 'dl'
2
+
3
+
4
+ class CineSync::UI::Win32SaveFileDialog
5
+ OFN_READONLY = 0x00000001
6
+ OFN_OVERWRITEPROMPT = 0x00000002
7
+ OFN_HIDEREADONLY = 0x00000004
8
+ OFN_NOCHANGEDIR = 0x00000008
9
+ OFN_SHOWHELP = 0x00000010
10
+ OFN_ENABLEHOOK = 0x00000020
11
+ OFN_ENABLETEMPLATE = 0x00000040
12
+ OFN_ENABLETEMPLATEHANDLE = 0x00000080
13
+ OFN_NOVALIDATE = 0x00000100
14
+ OFN_ALLOWMULTISELECT = 0x00000200
15
+ OFN_EXTENSIONDIFFERENT = 0x00000400
16
+ OFN_PATHMUSTEXIST = 0x00000800
17
+ OFN_FILEMUSTEXIST = 0x00001000
18
+ OFN_CREATEPROMPT = 0x00002000
19
+ OFN_SHAREAWARE = 0x00004000
20
+ OFN_NOREADONLYRETURN = 0x00008000
21
+ OFN_NOTESTFILECREATE = 0x00010000
22
+ OFN_NONETWORKBUTTON = 0x00020000
23
+ OFN_NOLONGNAMES = 0x00040000
24
+ OFN_EXPLORER = 0x00080000
25
+ OFN_NODEREFERENCELINKS = 0x00100000
26
+ OFN_LONGNAMES = 0x00200000
27
+ OFN_ENABLEINCLUDENOTIFY = 0x00400000
28
+ OFN_ENABLESIZING = 0x00800000
29
+ OFN_DONTADDTORECENT = 0x02000000
30
+ OFN_FORCESHOWHIDDEN = 0x10000000
31
+ OFN_EX_NOPLACESBAR = 0x00000001
32
+ PROTOTYPE = 'LIISSLLSLSLSSLHHSIPS'
33
+ MAX_PATH = 260
34
+
35
+ def initialize(opts = {})
36
+ @opts = {:title => "", :default_name => "", :extension => nil, :file_type => nil}.merge(opts)
37
+ @opts[:file_type] ||= "#{opts[:extension].upcase} file"
38
+
39
+ @ofn = DL.malloc(DL.sizeof(PROTOTYPE))
40
+ @ofn.struct!(PROTOTYPE,
41
+ :lStructSize,
42
+ :hwndOwner,
43
+ :hInstance,
44
+ :lpstrFilter,
45
+ :lpstrCustomFilter,
46
+ :nMaxCustFilter,
47
+ :nFilterIndex,
48
+ :lpstrFile,
49
+ :nMaxFile,
50
+ :lpstrFileTitle,
51
+ :nMaxFileTitle,
52
+ :lpstrInitialDir,
53
+ :lpstrTitle,
54
+ :Flags,
55
+ :nFileOffset,
56
+ :nFileExtension,
57
+ :lpstrDefExt,
58
+ :lCustData,
59
+ :lpfnHook,
60
+ :lpTemplateName)
61
+
62
+ @ofn[:lStructSize] = DL.sizeof(PROTOTYPE)
63
+ @ofn[:hwndOwner] = 0
64
+ @ofn[:lpstrFile] = DL.malloc(1024)
65
+ @ofn[:lpstrFile] = @opts[:default_name] + "\0"
66
+ @ofn[:nMaxFile] = MAX_PATH
67
+ if @opts[:extension]
68
+ ext = "*#{@opts[:extension]}"
69
+ @ofn[:lpstrFilter] = "#{@opts[:file_type]} (#{ext})\0#{ext}\0"
70
+ else
71
+ @ofn[:lpstrFilter] = "\0"
72
+ end
73
+ @ofn[:nFilterIndex] = 1
74
+ @ofn[:lpstrTitle] = @opts[:title] + "\0"
75
+ @ofn[:lpstrFileTitle] = "\0"
76
+ @ofn[:nMaxFileTitle] = 0
77
+ @ofn[:lpstrInitialDir] = "\0"
78
+ @ofn[:Flags] = OFN_OVERWRITEPROMPT
79
+
80
+ @@comdlg32 ||= DL.dlopen('comdlg32.dll')
81
+ @GetSaveFileName = @@comdlg32['GetSaveFileName','IP']
82
+ end
83
+
84
+ def execute!
85
+ if @GetSaveFileName.call(@ofn)[0]
86
+ @ofn[:lpstrFile].to_s
87
+ else
88
+ nil
89
+ end
90
+ end
91
+
92
+ def filename
93
+ @ofn[:lpstrFile].to_s
94
+ end
95
+ end
@@ -0,0 +1,68 @@
1
+ module CineSync
2
+ module UI
3
+ def self.open_url(url)
4
+ case RUBY_PLATFORM
5
+ when /darwin/
6
+ system('open', url)
7
+ when /mswin32|mingw32/
8
+ system('cmd', '/c', 'start', '', '/b', url) # Of course
9
+ end
10
+ end
11
+
12
+ def self.show_dialog(msg)
13
+ puts msg
14
+ case RUBY_PLATFORM
15
+ when /darwin/
16
+ mac_osax.display_alert("cineSync Script", :message => msg) if mac_osax
17
+ when /mswin32|mingw32/
18
+ require 'dl'
19
+ user32 = DL.dlopen('user32')
20
+ msgbox = user32['MessageBoxA', 'ILSSI']
21
+ msgbox.call(0, msg, "cineSync Script", 0x41000) # MB_TOPMOST | MB_SYSTEMMODAL
22
+ end
23
+ end
24
+
25
+ def self.prompt_to_save(prompt, filename)
26
+ default_path = File.expand_path("~/Desktop/") + filename
27
+ default_new_path = default_path unless File.exist?(default_path)
28
+
29
+ case RUBY_PLATFORM
30
+ when /darwin/
31
+ if mac_osax
32
+ begin
33
+ val = mac_osax.choose_file_name(:with_prompt => prompt, :default_name => filename)
34
+ String(val)
35
+ rescue Appscript::CommandError
36
+ nil
37
+ end
38
+ else
39
+ # Default to the Desktop
40
+ puts "Unable to create scripting object; using Desktop as save location."
41
+ default_new_path
42
+ end
43
+ when /mswin32|mingw32/
44
+ require 'cinesync/ui/win32_save_file_dialog'
45
+ opts = {:title => prompt}
46
+ opts[:default_name] = filename
47
+ opts[:extension] = File.extname(filename)
48
+ dlg = CineSync::UI::Win32SaveFileDialog.new(opts)
49
+ dlg.execute!
50
+ else
51
+ # Default to the Desktop
52
+ default_new_path
53
+ end
54
+ end
55
+
56
+
57
+ def self.mac_osax
58
+ begin
59
+ require 'appscript'
60
+ require 'osax'
61
+ require 'cinesync/ui/standard_additions' # Dumped terminology for 64-bit
62
+ @mac_osax ||= OSAX::ScriptingAddition.new("StandardAdditions", CineSync::UI::StandardAdditions).by_name("cineSync")
63
+ rescue
64
+ $stderr.puts "Unable to create scripting object. Check that rb-appscript gem is installed."
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,436 @@
1
+ require 'active_support'
2
+ require 'andand'
3
+ require 'builder'
4
+ require 'rexml/document'
5
+ require 'uri'
6
+
7
+
8
+ module CineSync
9
+ SessionV3Namespace = 'http://www.cinesync.com/ns/session/3.0'
10
+
11
+ class Session
12
+ # eSession = element session {
13
+ # attribute version { xsd:integer { minInclusive = "3" } } &
14
+ # attribute sessionFeatures { "standard" | "pro" } &
15
+ # aUserData? &
16
+ # eGroup* &
17
+ # eNotes? &
18
+ # eChat? &
19
+ # eMedia* }
20
+
21
+ def to_xml
22
+ fail "#{self.inspect}: Invalid" unless valid?
23
+
24
+ x = Builder::XmlMarkup.new(:indent => 4)
25
+ x.instruct!
26
+
27
+ attrs = {}
28
+ attrs['xmlns'] = SessionV3Namespace
29
+ # Always write as the version we know, not the version we loaded
30
+ attrs['version'] = SessionV3XMLFileVersion
31
+ attrs['sessionFeatures'] = session_features
32
+ attrs['userData'] = user_data unless user_data.empty?
33
+
34
+ x.session(attrs) {
35
+ groups.each {|g| x.group(g) }
36
+ x.notes(notes) unless notes.empty?
37
+ x << chat_elem.to_s if chat_elem
38
+ x << stereo_elem.to_s if stereo_elem
39
+ media.each {|m| m.to_xml(x) }
40
+ }
41
+ end
42
+
43
+
44
+ def self.load(str_or_io, silent = false)
45
+ doc = REXML::Document.new(str_or_io)
46
+
47
+ # Do a few checks (the user should have already confirmed that the
48
+ # document conforms to the schema, but we'll try to fail early in case)
49
+ fail 'Expected to find root <session> element' unless doc.root.name == 'session'
50
+ fail %Q[Root <session> element must have attribute xmlns="#{SessionV3Namespace}"] unless doc.root.attribute('xmlns').value == SessionV3Namespace
51
+
52
+ doc_version = doc.root.attribute('version').value.to_i
53
+ if doc_version > SessionV3XMLFileVersion
54
+ $stderr.puts("Warning: Loading session file with a newer version (#{doc_version}) than this library (#{SessionV3XMLFileVersion})") unless silent
55
+ end
56
+
57
+ elem = doc.root
58
+
59
+ returning self.new do |s|
60
+ s.instance_variable_set(:@file_version, doc_version)
61
+
62
+ s.user_data = elem.attribute('userData').andand.value || ''
63
+ s.groups = elem.get_elements('group').map {|g_elem| g_elem.text }
64
+ s.notes = elem.elements['notes'].andand.text || ''
65
+ s.chat_elem = elem.get_elements('chat').andand[0]
66
+ s.stereo_elem = elem.get_elements('stereo').andand[0]
67
+ s.media = elem.get_elements('media').map {|e| MediaBase.load(e) }
68
+ end
69
+ end
70
+ end
71
+
72
+
73
+ class MediaBase
74
+ # MediaBase =
75
+ # aUserData? &
76
+ # attribute active { tBool }? &
77
+ # attribute currentFrame { tFrameNumber }? &
78
+ # eGroup* &
79
+ # ePlayRange?
80
+
81
+ def to_xml(x)
82
+ fail "#{self.inspect}: Invalid" unless valid?
83
+
84
+ attrs = {}
85
+ attrs['userData'] = user_data unless user_data.empty?
86
+ attrs['active'] = active? if active?
87
+ attrs['currentFrame'] = current_frame if current_frame != 1
88
+
89
+ x.media(attrs) {
90
+ yield x
91
+ groups.each {|g| x.group(g) }
92
+ play_range.to_xml(x)
93
+ }
94
+ end
95
+
96
+
97
+ def self.load(elem)
98
+ klass = elem.elements['groupMovie'] ? GroupMovie : MediaFile
99
+ klass.load(elem)
100
+ end
101
+
102
+ protected
103
+ def self.common_load(elem, inst)
104
+ returning inst do |m|
105
+ m.user_data = elem.attribute('userData').andand.value || ''
106
+ m.active = elem.attribute('active').andand.value == 'true'
107
+ m.current_frame = elem.attribute('currentFrame').andand.value.andand.to_i || 1
108
+ m.groups = elem.get_elements('group').map {|g_elem| g_elem.text }
109
+
110
+ play_range_elem = elem.elements['playRange']
111
+ m.play_range = PlayRange.load(play_range_elem) if play_range_elem
112
+ end
113
+ end
114
+ end
115
+
116
+
117
+ class MediaFile < MediaBase
118
+ # eMedia |= element media {
119
+ # # Normal media file
120
+ # MediaBase &
121
+ # element name { xsd:string { minLength = "1" } } &
122
+ # element locators { eLocator+ } &
123
+ # eNotes? &
124
+ # eZoomState? &
125
+ # ePixelRatio? &
126
+ # eMask? &
127
+ # eColorGrading? &
128
+ # eFrameAnnotation* }
129
+
130
+ def to_xml(x)
131
+ super(x) do |x|
132
+ x.name(name)
133
+ locator.to_xml(x)
134
+ x.notes(notes) unless notes.empty?
135
+
136
+ zoom_state.to_xml(x)
137
+ pixel_ratio.to_xml(x)
138
+ mask.to_xml(x)
139
+ color_grading.to_xml(x)
140
+
141
+ annotations.values.each {|ann| ann.to_xml(x) }
142
+ end
143
+ end
144
+
145
+
146
+ def self.load(elem)
147
+ returning self.new do |m|
148
+ common_load(elem, m)
149
+
150
+ m.name = elem.elements['name'].text
151
+ m.locator = MediaLocator.load(elem.elements['locators'])
152
+ m.notes = elem.elements['notes'].andand.text || ''
153
+
154
+ elem.get_elements('annotation').each do |ann_elem|
155
+ FrameAnnotation.load(ann_elem, m)
156
+ end
157
+
158
+ # Load optional structures
159
+ %w(zoomState pixelRatio mask colorGrading).each do |camel_name|
160
+ set_sym = (camel_name + '=').underscore.to_sym
161
+ e = elem.elements[camel_name]
162
+ next if e.nil?
163
+
164
+ obj = ('CineSync::' + camel_name.camelize).constantize.load(e)
165
+ m.send(set_sym, obj)
166
+ end
167
+ end
168
+ end
169
+ end
170
+
171
+
172
+ class GroupMovie < MediaBase
173
+ # eMedia |= element media {
174
+ # # Group movie
175
+ # MediaBase &
176
+ # element groupMovie { eGroup } }
177
+
178
+ def to_xml(x)
179
+ super(x) do |x|
180
+ x.groupMovie {
181
+ x.group(group)
182
+ }
183
+ end
184
+ end
185
+
186
+
187
+ def self.load(elem)
188
+ group = elem.elements['groupMovie/group'].text
189
+ returning self.new(group) do |m|
190
+ common_load(elem, m)
191
+ end
192
+ end
193
+ end
194
+
195
+
196
+ class MediaLocator
197
+ # eLocator |= element path { tFilePath }
198
+ # eLocator |= element shortHash { tShortHash }
199
+ # eLocator |= element url { tURL }
200
+
201
+ def to_xml(x)
202
+ fail "#{self.inspect}: Invalid" unless valid?
203
+
204
+ x.locators {
205
+ x.path(path) if path
206
+ x.shortHash(short_hash) if short_hash
207
+ x.url(String(url)) if url
208
+ }
209
+ end
210
+
211
+
212
+ def self.load(elem)
213
+ returning self.new do |loc|
214
+ loc.path = elem.elements['path'].andand.text
215
+ loc.short_hash = elem.elements['shortHash'].andand.text
216
+ if elem.elements['url']
217
+ loc.url = URI::parse(elem.elements['url'].text)
218
+ end
219
+ end
220
+ end
221
+ end
222
+
223
+
224
+ class PlayRange
225
+ # ePlayRange = element playRange {
226
+ # element inFrame { attribute value { tFrameNumber } } &
227
+ # element outFrame { attribute value { tFrameNumber } } &
228
+ # element playOnlyRange { aBoolValue } }
229
+
230
+ def to_xml(x)
231
+ fail "#{self.inspect}: Invalid" unless valid?
232
+ return if default?
233
+
234
+ x.playRange {
235
+ x.inFrame(:value => in_frame)
236
+ x.outFrame(:value => out_frame)
237
+ x.playOnlyRange(:value => play_only_range?)
238
+ }
239
+ end
240
+
241
+
242
+ def self.load(elem)
243
+ returning self.new do |pr|
244
+ pr.in_frame = elem.elements['inFrame/attribute::value'].value.to_i
245
+ pr.out_frame = elem.elements['outFrame/attribute::value'].value.to_i
246
+ pr.play_only_range = elem.elements['playOnlyRange/attribute::value'].value == 'true'
247
+ end
248
+ end
249
+ end
250
+
251
+
252
+ class ZoomState
253
+ # eZoomState = element zoomState {
254
+ # element center { aXY } &
255
+ # eScaleFactor }
256
+
257
+ def to_xml(x)
258
+ fail "#{self.inspect}: Invalid" unless valid?
259
+ return if default?
260
+
261
+ x.zoomState {
262
+ x.center(:x => center[0], :y => center[1])
263
+ x.scaleFactor(:value => scale_factor)
264
+ }
265
+ end
266
+
267
+
268
+ def self.load(elem)
269
+ returning self.new do |zs|
270
+ x = elem.elements['center/attribute::x'].value.to_f
271
+ y = elem.elements['center/attribute::y'].value.to_f
272
+ zs.center = [x, y]
273
+ zs.scale_factor = elem.elements['scaleFactor/attribute::value'].value.to_f
274
+ end
275
+ end
276
+ end
277
+
278
+
279
+ class PixelRatio
280
+ # ePixelRatio = element pixelRatio {
281
+ # element source { aRatio } &
282
+ # element target { aRatio } }
283
+ # aRatio &= attribute width { tPositiveFloat }
284
+ # aRatio &= attribute height { tPositiveFloat }
285
+
286
+ def to_xml(x)
287
+ fail "#{self.inspect}: Invalid" unless valid?
288
+ return if default?
289
+
290
+ x.pixelRatio {
291
+ x.source(:width => source_width, :height => source_height)
292
+ x.target(:width => target_width, :height => target_height)
293
+ }
294
+ end
295
+
296
+
297
+ def self.load(elem)
298
+ returning self.new do |pr|
299
+ pr.source_width = elem.elements['source/attribute::width'].value.to_f
300
+ pr.source_height = elem.elements['source/attribute::height'].value.to_f
301
+ pr.target_width = elem.elements['target/attribute::width'].value.to_f
302
+ pr.target_height = elem.elements['target/attribute::height'].value.to_f
303
+ end
304
+ end
305
+ end
306
+
307
+
308
+ class Mask
309
+ # eMask = element mask {
310
+ # aAlpha &
311
+ # element center { aXY } &
312
+ # element ratio { aRatio } &
313
+ # eScaleFactor }
314
+
315
+ def to_xml(x)
316
+ fail "#{self.inspect}: Invalid" unless valid?
317
+ return if default?
318
+
319
+ x.mask(:alpha => alpha) {
320
+ x.center(:x => center[0], :y => center[1])
321
+ x.ratio(:width => width, :height => height)
322
+ x.scaleFactor(:value => scale_factor)
323
+ }
324
+ end
325
+
326
+
327
+ def self.load(elem)
328
+ returning self.new do |mask|
329
+ mask.alpha = elem.attribute('alpha').value.to_f
330
+ x = elem.elements['center/attribute::x'].value.to_f
331
+ y = elem.elements['center/attribute::y'].value.to_f
332
+ mask.center = [x, y]
333
+ mask.width = elem.elements['ratio/attribute::width'].value.to_f
334
+ mask.height = elem.elements['ratio/attribute::height'].value.to_f
335
+ mask.scale_factor = elem.elements['scaleFactor/attribute::value'].value.to_f
336
+ end
337
+ end
338
+ end
339
+
340
+
341
+ class ColorGrading
342
+ # eColorGrading = element colorGrading {
343
+ # element offset {
344
+ # attribute red { tColorOff } &
345
+ # attribute green { tColorOff } &
346
+ # attribute blue { tColorOff } }? &
347
+ #
348
+ # element brightness {
349
+ # attribute rgb { tColorExp } &
350
+ # attribute red { tColorExp } &
351
+ # attribute green { tColorExp } &
352
+ # attribute blue { tColorExp } }? &
353
+ #
354
+ # element saturation { attribute value { tColorExp } }? &
355
+ # element gamma { attribute value { tColorExp } }? &
356
+ # element contrast { attribute value { tColorExp } }? &
357
+ #
358
+ # element linearToLog { aBoolValue }? &
359
+ # element lutPath { attribute value { tFilePath } }? }
360
+
361
+ def to_xml(x)
362
+ fail "#{self.inspect}: Invalid" unless valid?
363
+ return if default?
364
+
365
+ x.colorGrading {
366
+ x.offset(:red => offset[:r], :green => offset[:g], :blue => offset[:b])
367
+ br = brightness
368
+ x.brightness(:rgb => br[:rgb], :red => br[:r], :green => br[:g], :blue => br[:b])
369
+ x.saturation(:value => saturation)
370
+ x.gamma(:value => gamma)
371
+ x.contrast(:value => contrast)
372
+ x.linearToLog(:value => linear_to_log?)
373
+ x.lutPath(:value => lut_path) if lut_path
374
+ }
375
+ end
376
+
377
+
378
+ def self.load(elem)
379
+ returning self.new do |cg|
380
+ offset_elem = elem.elements['offset']
381
+ if offset_elem
382
+ %w(red green blue).each do |attr|
383
+ cg.offset[attr] = offset_elem.attribute(attr).value.to_f
384
+ end
385
+ end
386
+
387
+ brightness_elem = elem.elements['brightness']
388
+ if brightness_elem
389
+ %w(rgb red green blue).each do |attr|
390
+ cg.brightness[attr] = brightness_elem.attribute(attr).value.to_f
391
+ end
392
+ end
393
+
394
+ # Everything is optional
395
+ (cg.saturation = elem.elements['saturation/attribute::value'].value.to_f) rescue :ignore
396
+ (cg.gamma = elem.elements['gamma/attribute::value'].value.to_f ) rescue :ignore
397
+ (cg.contrast = elem.elements['contrast/attribute::value'].value.to_f ) rescue :ignore
398
+
399
+ (cg.linear_to_log = elem.elements['linearToLog/attribute::value'].value == 'true') rescue :ignore
400
+ (cg.lut_path = elem.elements['lutPath/attribute::value']) rescue :ignore
401
+ end
402
+ end
403
+ end
404
+
405
+
406
+ class FrameAnnotation
407
+ # eFrameAnnotation = element annotation {
408
+ # attribute frame { tFrameNumber } &
409
+ # eNotes? &
410
+ # eObject* }
411
+ DrawingObjectElements = %w(line erase circle arrow text)
412
+
413
+ def to_xml(x)
414
+ fail "#{self.inspect}: Invalid" unless valid?
415
+ return if default?
416
+
417
+ attrs = {}
418
+ attrs['frame'] = frame
419
+
420
+ x.annotation(attrs) {
421
+ x.notes(notes) unless notes.empty?
422
+ drawing_objects.each {|obj| x << obj.to_s }
423
+ }
424
+ end
425
+
426
+
427
+ def self.load(elem, media)
428
+ returning media.annotations[elem.attribute('frame').value.to_i] do |ann|
429
+ ann.notes = elem.elements['notes'].andand.text || ann.notes
430
+ DrawingObjectElements.each do |obj_name|
431
+ ann.drawing_objects += elem.get_elements(obj_name)
432
+ end
433
+ end
434
+ end
435
+ end
436
+ end
@@ -0,0 +1,18 @@
1
+ module CineSync
2
+ class ZoomState
3
+ attr_accessor :center, :scale_factor
4
+
5
+ def initialize
6
+ @center = [0.5, 0.5]
7
+ @scale_factor = 1.0
8
+ end
9
+
10
+ def default?
11
+ center == [0.5, 0.5] and scale_factor == 1.0
12
+ end
13
+
14
+ def valid?
15
+ default? or (center.length == 2 and center.all? {|p| (0.0..1.0) === p } and scale_factor > 0.0) rescue false
16
+ end
17
+ end
18
+ end
data/lib/cinesync.rb ADDED
@@ -0,0 +1,46 @@
1
+ Dir["#{File.dirname(__FILE__)}/vendor/gems/**"].map do |dir|
2
+ $: << (File.directory?(lib = "#{dir}/lib") ? lib : dir)
3
+ end
4
+
5
+ require 'cinesync/session'
6
+ require 'cinesync/media_file'
7
+ require 'cinesync/play_range'
8
+ require 'cinesync/zoom_state'
9
+ require 'cinesync/pixel_ratio'
10
+ require 'cinesync/mask'
11
+ require 'cinesync/color_grading'
12
+ require 'cinesync/xml'
13
+ require 'cinesync/event_handler'
14
+ require 'cinesync/ui'
15
+ require 'digest'
16
+
17
+
18
+ module CineSync
19
+ SessionV3XMLFileVersion = 3
20
+ ShortHashSampleSize = 2048
21
+ AllFilesGroup = 'All Files'
22
+ OfflineKey = '_OFFLINE_'
23
+
24
+ # Utility functions
25
+ def self.short_hash(path)
26
+ File.open(path, 'rb') do |f|
27
+ size = f.stat.size
28
+ buf = ''
29
+
30
+ if size <= ShortHashSampleSize
31
+ buf << f.read(size)
32
+ buf << [].pack('x' * (ShortHashSampleSize - size))
33
+ else
34
+ buf << f.read(ShortHashSampleSize / 2)
35
+ f.seek(-ShortHashSampleSize / 2, IO::SEEK_END)
36
+ buf << f.read(ShortHashSampleSize / 2)
37
+ end
38
+ Digest::SHA1.hexdigest([size].pack('N').reverse + buf)
39
+ end
40
+ end
41
+
42
+ def self.event_handler
43
+ session = Session::load($stdin) rescue nil
44
+ yield CineSync::EventHandler.new(ARGV, session)
45
+ end
46
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
7
+ require 'cinesync'
8
+
9
+ class Test::Unit::TestCase
10
+ end