sensible-cinema 0.26.0 → 0.26.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. data/.gitmodules +3 -0
  2. data/README +9 -6
  3. data/TODO +91 -41
  4. data/VERSION +1 -1
  5. data/bin/sensible-cinema +13 -1506
  6. data/bin/sensible-cinema-cli +156 -151
  7. data/change_log_with_feature_list.txt +17 -2
  8. data/developer_how_to_contribute_to_the_project.txt +3 -2
  9. data/documentation/upconversion.txt +4 -0
  10. data/history_and_related_works_list.txt +1 -0
  11. data/{play_with_inserted_scene.bat → legal/play_with_inserted_scene.bat} +0 -0
  12. data/{play_with_overlay.bat → legal/play_with_overlay.bat} +0 -0
  13. data/legal2 +2 -0
  14. data/lib/count_down_timer_jruby_swing.rb +3 -4
  15. data/lib/edl_parser.rb +345 -339
  16. data/lib/eight_three.rb +6 -1
  17. data/lib/gui/sensible-cinema-base.rb +671 -0
  18. data/lib/gui/sensible-cinema-create.rb +304 -0
  19. data/lib/{sensible-cinema-dependencies.rb → gui/sensible-cinema-dependencies.rb} +0 -0
  20. data/lib/gui/sensible-cinema-normal.rb +349 -0
  21. data/lib/gui/sensible-cinema-side-by-side.rb +27 -0
  22. data/lib/gui/sensible-cinema-upconvert.rb +254 -0
  23. data/lib/screen_tracker.rb +1 -0
  24. data/spec/edl_parser.spec.rb +6 -0
  25. data/spec/notes +107 -16
  26. data/spec/sensible_cinema_gui.spec.rb +63 -58
  27. data/todo.inventionzy.txt +12 -0
  28. data/todo.propaganda +1 -2
  29. data/todo.upconvert +3 -1
  30. data/upconvert_netflix/record_screen/record.bat +2 -0
  31. data/upconvert_netflix/record_screen/recording/1.png +0 -0
  32. data/upconvert_netflix/record_screen/recording/10.png +0 -0
  33. data/upconvert_netflix/record_screen/recording/2.png +0 -0
  34. data/upconvert_netflix/record_screen/recording/3.png +0 -0
  35. data/upconvert_netflix/record_screen/recording/4.png +0 -0
  36. data/upconvert_netflix/record_screen/recording/5.png +0 -0
  37. data/upconvert_netflix/record_screen/recording/6.png +0 -0
  38. data/upconvert_netflix/record_screen/recording/7.png +0 -0
  39. data/upconvert_netflix/record_screen/recording/8.png +0 -0
  40. data/upconvert_netflix/record_screen/recording/9.png +0 -0
  41. data/upconvert_netflix/record_screen/recording/d.png +0 -0
  42. data/www/content_editor.html +2 -2
  43. data/zamples/edit_decision_lists/dvds/edls_being_edited/cars_disney.txt +5 -7
  44. data/zamples/edit_decision_lists/dvds/innerspace.txt +77 -0
  45. data/zamples/edit_decision_lists/netflix_instant/avatar-last-air-bender-movie.txt +12 -0
  46. data/zamples/edit_decision_lists/netflix_instant/avatar-last-airbender-series.txt +1 -0
  47. data/zamples/players/netflix/netflix_firefox_non_maximized.txt +1 -1
  48. metadata +28 -11
  49. data/lib/file_chooser.rb +0 -46
  50. data/lib/swing_helpers.rb +0 -142
  51. data/www/monkey.png +0 -0
@@ -1,154 +1,159 @@
1
- #!/usr/bin/ruby
2
- =begin
3
- Copyright 2010, Roger Pack
4
- This file is part of Sensible Cinema.
5
-
6
- Sensible Cinema is free software: you can redistribute it and/or modify
7
- it under the terms of the GNU General Public License as published by
8
- the Free Software Foundation, either version 3 of the License, or
9
- (at your option) any later version.
10
-
11
- Sensible Cinema is distributed in the hope that it will be useful,
12
- but WITHOUT ANY WARRANTY; without even the implied warranty of
13
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
- GNU General Public License for more details.
15
-
16
- You should have received a copy of the GNU General Public License
17
- along with Sensible Cinema. If not, see <http://www.gnu.org/licenses/>.
18
- =end
19
-
20
- require File.dirname(__FILE__) + "/../lib/add_any_bundled_gems_to_load_path.rb"
21
- require 'sane'
22
-
23
- for file in ['overlayer', 'keyboard_input', 'screen_tracker', 'mouse', 'file_chooser', 'ocr', 'vlc_programmer', 'edl_parser', 'auto_window_finder']
24
- require_relative '../lib/' + file
25
- end
26
-
27
- def go_sc(args)
28
-
29
- $VERBOSE = 1 if args.delete('-v')
30
- $DEBUG = 1 if args.delete('-d')
31
- if args.delete('--clear-cache')
32
- OCR.clear_cache!
33
- puts 'cleared cache'
34
- end
35
-
36
- $stderr.puts 'warning: currently windows only for certain parts currently' unless ENV['OS'] == 'Windows_NT'
37
-
38
- if args.detect{|arg| arg == '-h' || arg == '--help'}
39
-
40
- puts <<-END
41
-
42
- syntax: [player_description.yml] [delete_list.txt|test] [-t, -v, --clear-cache] (or just nothing at all--it will prompt for all things it needs)
43
-
1
+ #!/usr/bin/ruby
2
+ =begin
3
+ Copyright 2010, Roger Pack
4
+ This file is part of Sensible Cinema.
5
+
6
+ Sensible Cinema is free software: you can redistribute it and/or modify
7
+ it under the terms of the GNU General Public License as published by
8
+ the Free Software Foundation, either version 3 of the License, or
9
+ (at your option) any later version.
10
+
11
+ Sensible Cinema is distributed in the hope that it will be useful,
12
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ GNU General Public License for more details.
15
+
16
+ You should have received a copy of the GNU General Public License
17
+ along with Sensible Cinema. If not, see <http://www.gnu.org/licenses/>.
18
+ =end
19
+
20
+ require File.dirname(__FILE__) + "/../lib/add_any_bundled_gems_to_load_path.rb"
21
+ require 'sane'
22
+
23
+ for file in ['overlayer', 'keyboard_input', 'screen_tracker', 'mouse', 'ocr', 'vlc_programmer', 'edl_parser', 'auto_window_finder', 'swing_helpers']
24
+ require_relative '../lib/' + file
25
+ end
26
+
27
+
28
+ def choose_file title, dir
29
+ SwingHelpers::FileDialog.new_previously_existing_file_selector_and_go title, dir
30
+ end
31
+
32
+ def go_sc(args)
33
+
34
+ $VERBOSE = 1 if args.delete('-v')
35
+ $DEBUG = 1 if args.delete('-d')
36
+ if args.delete('--clear-cache')
37
+ OCR.clear_cache!
38
+ puts 'cleared cache'
39
+ end
40
+
41
+ $stderr.puts 'warning: currently windows only for certain parts currently' unless ENV['OS'] == 'Windows_NT'
42
+
43
+ if args.detect{|arg| arg == '-h' || arg == '--help'}
44
+
45
+ puts <<-END
46
+
47
+ syntax: [player_description.yml] [delete_list.txt|test] [-t, -v, --clear-cache] (or just nothing at all--it will prompt for all things it needs)
48
+
44
49
  If you specify "test" for the edit list, it will pause 2s, take a single snapshot of the selected player, then exit.
45
50
  Useful for debugging your screen capture of the current player.
46
- You can also specify -v or -t if you want to enable more verbose (chatty) output.
47
-
48
- END
49
-
50
- for file in Dir[__DIR__ + '../zamples/mute*/*']
51
- puts "\n", "Example file:", file + "\n\n", File.read(file)
52
- end
53
-
54
- exit 1
55
- end
56
-
57
- players_root_dir = __DIR__ + "/../zamples/players"
58
- # allow for command line filenames
59
- player_description = args.shift
60
- unless player_description
61
- player_description = AutoWindowFinder.search_for_player_and_url_match(players_root_dir)
62
- if player_description
63
- p 'auto selected player ' + player_description
64
- else
65
- player_description = ''
66
- end
67
- end
68
-
69
- if !File.exist?(player_description)
70
- puts 'Please Select Computer Player'
71
- player_description = FileChooser.choose_file(" SELECT COMPUTER PLAYER", players_root_dir)
72
- raise unless player_description
73
- end
74
-
75
- edit_decision_list = args.shift.to_s
76
-
77
- if edit_decision_list == 'test'
78
- overlay = nil
79
- p 'got test...just doing screen dump'
80
- $VERBOSE=true # adds some extra output
81
- elsif File.exist? edit_decision_list
82
- # accept it from the command line
83
- else
84
- # assume they want an online player, right? LODO player can tell us...
85
- auto_found = AutoWindowFinder.search_for_single_url_match
86
- if auto_found
87
- p 'auto-discovered open window for player x EDL, using it ' + auto_found
88
- edit_decision_list = auto_found
89
- else
90
- puts 'Select Edit Decision List to use'
91
- edit_decision_list = FileChooser.choose_file(" SELECT EDIT DECISION LIST", __DIR__ + "/../zamples/edit_decision_lists")
92
- end
93
- settings = EdlParser.parse_file auto_found
94
-
95
- if !edit_decision_list
96
- puts "error: have to specify a scene descriptions file\n or specify \"test\" on the command line if you just want to snapshot your player"
97
- exit 1
98
- end
99
-
100
- puts 'Selected scene descriptions file ' + File.basename(edit_decision_list) + "\n\t(full path: #{edit_decision_list})"
101
- Blanker.startup
102
- # todo start it late as it has an annoying startup blip
103
- end
104
- overlay = OverLayer.new(edit_decision_list) if edit_decision_list
105
-
106
- if File.exist? player_description.to_s
107
- puts 'Selected player ' + File.basename(player_description) + "\n\t(full path: #{player_description})"
108
- # this one doesn't use any updates, so just pass in file contents, not filename
109
- screen_tracker = ScreenTracker.new_from_yaml File.binread(player_description), overlay
110
- does_not_need_mouse_jerk = YAML.load_file(player_description)["does_not_need_mouse_movement"]
111
- unless does_not_need_mouse_jerk
112
- p 'yes using mouse jitter' if $VERBOSE or $DEBUG
113
- Mouse.jitter_forever_in_own_thread # when this ends you know a snapshot was taken...
114
- else
115
- p 'not using mouse jitter' if $VERBOSE or $DEBUG
116
- end
117
-
118
- # exit early if we just wanted a screen dump...a little kludgey...
119
- unless overlay
120
- puts 'warning--only doing screen dump in t-minus 2s...'
121
- sleep 2
122
- puts 'snap!'
123
- screen_tracker.dump_bmps
124
- exit 1
125
- end
126
- screen_tracker.process_forever_in_thread
127
- else
128
- puts 'warning--not using any screen tracking...'
129
- end
130
-
131
- OCR.unserialize_cache_from_disk # do this every time so we don't delete it if they don't have one...
132
-
133
- p 'moving mouse to align it for muting down 10'
134
- Mouse.move_mouse_relative 0, 10 # LODO
135
- puts "Opening the curtains... (please play in your other video player now)"
136
- overlay.start_thread true
137
- key_input = KeyboardInput.new overlay
138
- key_input.start_thread # status thread
139
- at_exit {
140
- Blanker.shutdown # lodo move this and the 'q' key to within overlayer
141
- OCR.serialize_cache_to_disk
142
- }
143
- begin
144
- key_input.handle_keystrokes_forever # blocking...
145
- ensure
146
- puts "syntax from command line: \"#{player_description}\" \"#{edit_decision_list}\""
147
- end
148
-
149
- end
150
-
151
- if __FILE__ == $0
152
- puts 'Welcome to Sensible Cinema...'
153
- go_sc ARGV
51
+ You can also specify -v or -t if you want to enable more verbose (chatty) output.
52
+
53
+ END
54
+
55
+ for file in Dir[__DIR__ + '../zamples/mute*/*']
56
+ puts "\n", "Example file:", file + "\n\n", File.read(file)
57
+ end
58
+
59
+ exit 1
60
+ end
61
+
62
+ players_root_dir = __DIR__ + "/../zamples/players"
63
+ # allow for command line filenames
64
+ player_description = args.shift
65
+ unless player_description
66
+ player_description = AutoWindowFinder.search_for_player_and_url_match(players_root_dir)
67
+ if player_description
68
+ p 'auto selected player ' + player_description
69
+ else
70
+ player_description = ''
71
+ end
72
+ end
73
+
74
+ if !File.exist?(player_description)
75
+ puts 'Please Select Computer Player'
76
+ player_description = choose_file(" SELECT COMPUTER PLAYER", players_root_dir)
77
+ raise unless player_description
78
+ end
79
+
80
+ edit_decision_list = args.shift.to_s
81
+
82
+ if edit_decision_list == 'test'
83
+ overlay = nil
84
+ p 'got test...just doing screen dump'
85
+ $VERBOSE=true # adds some extra output
86
+ elsif File.exist? edit_decision_list
87
+ # accept it from the command line
88
+ else
89
+ # assume they want an online player, right? LODO player can tell us...
90
+ auto_found = AutoWindowFinder.search_for_single_url_match
91
+ if auto_found
92
+ p 'auto-discovered open window for player x EDL, using it ' + auto_found
93
+ edit_decision_list = auto_found
94
+ else
95
+ puts 'Select Edit Decision List to use'
96
+ edit_decision_list = choose_file(" SELECT EDIT DECISION LIST", __DIR__ + "/../zamples/edit_decision_lists")
97
+ end
98
+ settings = EdlParser.parse_file edit_decision_list
99
+
100
+ if !edit_decision_list
101
+ puts "error: have to specify a scene descriptions file\n or specify \"test\" on the command line if you just want to snapshot your player"
102
+ exit 1
103
+ end
104
+
105
+ puts 'Selected scene descriptions file ' + File.basename(edit_decision_list) + "\n\t(full path: #{edit_decision_list})"
106
+ Blanker.startup
107
+ # todo start it late as it has an annoying startup blip
108
+ end
109
+ overlay = OverLayer.new(edit_decision_list) if edit_decision_list
110
+
111
+ if File.exist? player_description.to_s
112
+ puts 'Selected player ' + File.basename(player_description) + "\n\t(full path: #{player_description})"
113
+ # this one doesn't use any updates, so just pass in file contents, not filename
114
+ screen_tracker = ScreenTracker.new_from_yaml File.binread(player_description), overlay
115
+ does_not_need_mouse_jerk = YAML.load_file(player_description)["does_not_need_mouse_movement"]
116
+ unless does_not_need_mouse_jerk
117
+ p 'yes using mouse jitter' if $VERBOSE or $DEBUG
118
+ Mouse.jitter_forever_in_own_thread # when this ends you know a snapshot was taken...
119
+ else
120
+ p 'not using mouse jitter' if $VERBOSE or $DEBUG
121
+ end
122
+
123
+ # exit early if we just wanted a screen dump...a little kludgey...
124
+ unless overlay
125
+ puts 'warning--only doing screen dump in t-minus 2s...'
126
+ sleep 2
127
+ puts 'snap!'
128
+ screen_tracker.dump_bmps
129
+ exit 1
130
+ end
131
+ screen_tracker.process_forever_in_thread
132
+ else
133
+ puts 'warning--not using any screen tracking...'
134
+ end
135
+
136
+ OCR.unserialize_cache_from_disk # do this every time so we don't delete it if they don't have one...
137
+
138
+ p 'moving mouse to align it for muting down 10'
139
+ Mouse.move_mouse_relative 0, 10 # LODO
140
+ puts "Opening the curtains... (please play in your other video player now)"
141
+ overlay.start_thread true
142
+ key_input = KeyboardInput.new overlay
143
+ key_input.start_thread # status thread
144
+ at_exit {
145
+ Blanker.shutdown # lodo move this and the 'q' key to within overlayer
146
+ OCR.serialize_cache_to_disk
147
+ }
148
+ begin
149
+ key_input.handle_keystrokes_forever # blocking...
150
+ ensure
151
+ puts "syntax from command line: \"#{player_description}\" \"#{edit_decision_list}\""
152
+ end
153
+
154
+ end
155
+
156
+ if __FILE__ == $0
157
+ puts 'Welcome to Sensible Cinema...'
158
+ go_sc ARGV
154
159
  end
@@ -1,6 +1,18 @@
1
+ == 0.26.1 ==
2
+
3
+ Cleaned up the upconvert interface, added a way for easier comparison of different upconversion settings (automated-ly-zy).
4
+
5
+ Note that we also use a simplified (for editing) EDL file format for easier human consumption,
6
+ including use of timestamps.
7
+
8
+ Note also that you can screen capture a playing video using my other project to project it "edited" to some device.
9
+ Also you can capture the audio and "manipulate it" using my other project.
10
+
11
+ Add some EDL's
12
+
1
13
  == 0.26.0 ==
2
14
 
3
- It can now parse out (screen scraping), at runtime, EDL information from a user editable online 3rd party wiki
15
+ It can now parse out (screen scraping from web page), at runtime, EDL information from a user editable online 3rd party wiki
4
16
  (imdb parent guide), including property timestamp conversions and making it easy to read, etc., and doing all the various
5
17
  sections to an appropriate category.
6
18
  So it binds the DVD's unique ID with its imdb database.
@@ -18,6 +30,8 @@ Including software blu-ray players etc.
18
30
  Note that it already demo'ed being able to have "user customizable" volume level for muted sections
19
31
  (i.e. not mute it all the way, if desired)
20
32
 
33
+ Also included "leveling" (if above x level, then allow or the like) demo'ed.
34
+
21
35
  Note also that it can be used by hooking to a projector or from computer -> tv so we have this unofficially
22
36
  in the living room or across several devices or the like.
23
37
 
@@ -85,7 +99,8 @@ We leverage all sorts of nifty things like bringing windows to the top, internal
85
99
  good stuff.
86
100
 
87
101
  Since we integrate with VLC, we already have edited blu-ray playback implicitly, by playing them back
88
- through slysoft hd-dvd.
102
+ through slysoft hd-dvd. Also the python "movie content editor" has EDL support for VLC natively, so that means
103
+ blu-ray explicit EDL playback support.
89
104
 
90
105
  Note also that I recently showed demos of how an edited player can jump around/skip to scenes within a movie,
91
106
  not just playback linear "skipping" content.
@@ -2,10 +2,9 @@ A few notes for developers:
2
2
 
3
3
  You can test the DVD portion of the program out by mounting the DVD of "big buck bunny"
4
4
  http://rogerdpack.t28.net/sensible-cinema/releases/dvd_isos has it and a few other creative commons ISO's for use.
5
- On windows daemon tools helps there, or "magic ISO" or the like.
5
+ On windows daemon tools helps there, or "magic ISO" or the like, may be helpful to avoid having to burn disks.
6
6
 
7
7
  Or you could alternatively burn a copy of big buck bunny to DVD, then use that to test it against (a slightly more cross platform approach).
8
- Or http://rogerdpack.t28.net/sensible-cinema/releases/dvd_isos has an extra copy of the file and you can just use the "file based" options.
9
8
 
10
9
  Then rip it and use the output.
11
10
 
@@ -27,6 +26,8 @@ The tests are in the spec directory, though some of them are out of date as deve
27
26
 
28
27
  Note: you transfer (c) of materials to sensible cinema when you submit patches/contributions, of course, so don't submit something you don't want published :)
29
28
 
29
+ Also you'll need to run git submodule init && git submodule update (possibly modify .gitsubmodules to be the open one, or ask me to)
30
+
30
31
  To run it via command line:
31
32
  $ jruby bin/sensible-cinema --help # tells you all available options
32
33
 
@@ -0,0 +1,4 @@
1
+ With upconversion, if your monitor is 1024 pixels wide and you upconvert to 2048 pixels then display it on 1024, it looks better.
2
+ Sensible Cinema sets the default default resolution to be 2x your screen resolution, which is better for high end cpu's.
3
+ If you have a low powered system or laptop you may need to change it (drag the slider) lower to avoid it displaying only a black or white screen, as your system can't handle it,
4
+ so adjust accordingly if you have a lesser system. Also changing this setting can make it use more or less CPU overall.
@@ -98,4 +98,5 @@ AviSynth can script dvd playback http://forum.doom9.org/archive/index.php/t-1095
98
98
  ps3 media server has edl support: https://github.com/chocolateboy/PMS-EDL maybe would work with DVD's dunno
99
99
  Anydvd HD's "magic file replacement" for DVD's/blu-ray's
100
100
  RiffTrax allows for an audio "overlay" to a DVD playback, have their own player to coordinate the two.
101
+ "fan subs" are edited subtitles, displayed overlaid on the original track/video
101
102
  Avatar DVD was released with an audio track that was "euphemized" of the profanity.
data/legal2 CHANGED
@@ -1 +1,3 @@
1
1
  http://en.wikipedia.org/wiki/DVD_Decrypter mentions fair use a bit, though in context of a different program.
2
+
3
+ Also the footnotes to fanedit.org have some related and applicable discussion of legal issues
@@ -1,9 +1,9 @@
1
1
  require 'rubygems'
2
2
  require 'sane'
3
- require_relative '../lib/swing_helpers'
4
-
5
- module SensibleSwing
3
+ require_relative 'jruby-swing-helpers/swing_helpers'
6
4
 
5
+ include SwingHelpers
6
+
7
7
  class MainWindow < JFrame
8
8
 
9
9
  def show_blocking_message_dialog(message, title = message.split("\n")[0], style= JOptionPane::INFORMATION_MESSAGE)
@@ -53,4 +53,3 @@ class MainWindow < JFrame
53
53
 
54
54
  MainWindow.new.show
55
55
 
56
- end
data/lib/edl_parser.rb CHANGED
@@ -1,339 +1,345 @@
1
- =begin
2
- Copyright 2010, Roger Pack
3
- This file is part of Sensible Cinema.
4
-
5
- Sensible Cinema is free software: you can redistribute it and/or modify
6
- it under the terms of the GNU General Public License as published by
7
- the Free Software Foundation, either version 3 of the License, or
8
- (at your option) any later version.
9
-
10
- Sensible Cinema is distributed in the hope that it will be useful,
11
- but WITHOUT ANY WARRANTY; without even the implied warranty of
12
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
- GNU General Public License for more details.
14
-
15
- You should have received a copy of the GNU General Public License
16
- along with Sensible Cinema. If not, see <http://www.gnu.org/licenses/>.
17
- =end
18
- require 'sane'
19
- class EdlParser
20
-
21
- EDL_DIR = File.expand_path(__DIR__ + "/../zamples/edit_decision_lists/dvds")
22
-
23
- if File::ALT_SEPARATOR
24
- EDL_DIR.gsub! File::SEPARATOR, File::ALT_SEPARATOR # to_filename...
25
- end
26
-
27
- # returns {"mutes" => [["00:00", "00:00", string1, string2, ...], ...], "blank_outs" -> [...], "url" => ...}
28
- def self.parse_file filename, expand = true
29
- output = parse_string File.read(filename), filename, []
30
- if relative = output["take_from_relative_file"]
31
- new_filename = File.dirname(filename) + '/' + relative
32
- new_input = parse_file new_filename
33
- output.merge! new_input
34
- end
35
- require_relative 'sensible-cinema-dependencies'
36
-
37
- if expand
38
-
39
- if output["from_url"] # replacement
40
- downloaded = SensibleSwing::MainWindow.download_to_string(output["from_url"])
41
- output = parse_string downloaded # full replacement
42
- end
43
-
44
- if imdb_id = output["imdb_id"]
45
- require_relative 'convert_thirty_fps'
46
- url = "http://www.imdb.com/title/#{imdb_id}/parentalguide"
47
- all = SensibleSwing::MainWindow.download_to_string(url)
48
-
49
- header, violence_word, violence_section, profanity_word, profanity_section, alcohol_word, alcohol_section, frightening_word, frightening_section =
50
- sections = all.split(/<span>(Violence|Profanity|Alcohol|Frightening)/)
51
- header = sections.shift
52
- all ={}
53
- while(!sections.empty?) # my klugey to_hash method
54
- word_type = sections.shift
55
- settings = sections.shift
56
- assert word_type.in? ['Violence', 'Profanity', 'Alcohol', 'Frightening']
57
- all[word_type] = settings
58
- end
59
- # blank_outs or mutes for each...
60
- # TODO make -> optional
61
- split_into_timestamps = /([\d:]+(?:\.\d+|))\W*-&gt;\W*([\d:]+(?:\.\d+|))([^\d\n]+)/
62
- for type, settings in all
63
- settings.scan(split_into_timestamps) do |begin_ts, end_ts, description|
64
- puts "parsing from wiki imdb entry violence: #{begin_ts} #{end_ts} #{description} #{type}"
65
- start_seconds = translate_string_to_seconds begin_ts
66
- end_seconds = translate_string_to_seconds end_ts
67
- # convert from 30 to 29.97 fps ... we presume ...
68
- start_seconds = ConvertThirtyFps.from_twenty_nine_nine_seven start_seconds
69
- start_seconds = ("%.02f" % start_seconds).to_f # round
70
- start_seconds = translate_time_to_human_readable start_seconds, true
71
- end_seconds = ConvertThirtyFps.from_twenty_nine_nine_seven end_seconds
72
- end_seconds = ("%.02f" % end_seconds).to_f # round
73
- end_seconds = translate_time_to_human_readable end_seconds, true
74
- p end_seconds
75
- if type == 'Profanity'
76
- output['mutes'] << [start_seconds, end_seconds]
77
- else
78
- output['blank_outs'] << [start_seconds, end_seconds]
79
- end
80
- end
81
- end
82
-
83
- end
84
- end
85
-
86
- output
87
- end
88
-
89
- private
90
-
91
- def self.download full_url, to_here
92
- require 'open-uri'
93
- writeOut = open(to_here, "wb")
94
- writeOut.write(open(full_url).read)
95
- writeOut.close
96
- end
97
-
98
- # better eye-ball these before letting people run them, eh? TODO
99
- # but I couldn't think of any other way to parse the files tho
100
- def self.parse_string string, filename, ok_categories_array = []
101
- string = '{' + string + "\n}"
102
- if filename
103
- raw = eval(string, binding, filename)
104
- else
105
- raw = eval string
106
- end
107
-
108
- raise SyntaxError.new("maybe missing quotation marks?" + string) if raw.keys.contain?(nil)
109
-
110
- # mutes and blank_outs need to be special parsed into arrays...
111
- mutes = raw["mutes"] || []
112
- blanks = raw["blank_outs"] || []
113
- raw["mutes"] = convert_to_timestamp_arrays(mutes, ok_categories_array)
114
- raw["blank_outs"] = convert_to_timestamp_arrays(blanks, ok_categories_array)
115
- raw
116
- end
117
-
118
- # converts "blanks" => ["00:00:00", "00", "reason", "01", "01", "02", "02"] into sane arrays, also filters based on category, though disabled for production
119
- def self.convert_to_timestamp_arrays array, ok_categories_array
120
- out = []
121
- while(single_element = extract_entry!(array))
122
- # assume that it (could be, at least) start_time, end_time, category, number
123
- category = single_element[-2]
124
- category_number = single_element[-1]
125
- include = true
126
- if ok_categories_array.index([category, category_number])
127
- include = false
128
- elsif ok_categories_array.index([category])
129
- include = false
130
- elsif ok_categories_array.detect{|cat, setting| setting.is_a? Fixnum}
131
- for cat, setting in ok_categories_array
132
- if cat == category && setting.is_a?(Fixnum)
133
- # check for a number?
134
- if category_number.to_i.to_s == category_number
135
- as_number = category_number.to_i
136
- if as_number < setting
137
- include = false
138
- end
139
- end
140
- end
141
- end
142
-
143
- end
144
- out << single_element if include
145
- end
146
- out
147
- end
148
-
149
- #TimeStamp = /(^\d+:\d\d[\d:\.]*$|\d+)/ # this one also allows for 4444 [?] and also weirdness like "don't kill the nice butterfly 2!" ...
150
- TimeStamp = /^\d+:\d\d[\d:\.]*$/
151
- # starts with a digit, has at least one colon followed by two digits,then some combo of digits and colons and periods...
152
-
153
- def self.extract_entry! from_this
154
- return nil if from_this.length == 0
155
- # two digits, then whatever else you see, that's not a digit...
156
- out = from_this.shift(2)
157
- out.each{|d|
158
- unless d =~ TimeStamp
159
- raise SyntaxError.new('non timestamp? ' + d)
160
- end
161
- }
162
- while(from_this[0] && from_this[0] !~ TimeStamp)
163
- out << from_this.shift
164
- end
165
- out
166
- end
167
-
168
- def self.get_secs timestamp_string_begin, timestamp_string_end, add_begin, add_end, splits
169
- answers = []
170
- unless timestamp_string_begin
171
- raise 'non begin'
172
- end
173
- unless timestamp_string_end
174
- raise 'non end'
175
- end
176
- for type, offset, multiplier in [[timestamp_string_begin, add_begin, -1], [timestamp_string_end, add_end, 1]]
177
- original_secs = translate_string_to_seconds(type) + offset
178
- # now if splits is 900 and we'are at 909, then we're just 9
179
- closest_split_idx = splits.reverse.index{|t| t < original_secs}
180
- if closest_split_idx
181
- closest_split = splits.reverse[closest_split_idx]
182
- # add some extra seconds onto these if they're "past" a split, too
183
- original_secs = original_secs - closest_split + multiplier * (splits.length - closest_split_idx)
184
- original_secs = [0, original_secs].max # no negatives allowed :)
185
- end
186
- answers << original_secs
187
- end
188
- answers
189
- end
190
-
191
- public
192
-
193
- # called later, from external
194
- # divides up mutes and blanks so that they don't overlap, preferring blanks over mutes
195
- # returns it like [[start,end,type], [s,e,t]...] type like either :blank and :mute
196
- # [[70.0, 73.0, :blank], [378.0, 379.1, :mute]]
197
- def self.convert_incoming_to_split_sectors incoming, add_this_to_all_ends = 0, subtract_this_from_beginnings = 0, splits = []
198
- raise if subtract_this_from_beginnings < 0
199
- raise if add_this_to_all_ends < 0
200
- if splits != []
201
- # allow it to do all the double checks we later skip, just in case :)
202
- self.convert_incoming_to_split_sectors incoming
203
- end
204
- mutes = incoming["mutes"] || {}
205
- blanks = incoming["blank_outs"] || {}
206
- mutes = mutes.map{|k, v| get_secs(k, v, -subtract_this_from_beginnings, add_this_to_all_ends, splits) + [:mute]}
207
- blanks = blanks.map{|k, v| get_secs(k, v, -subtract_this_from_beginnings, add_this_to_all_ends, splits) + [:blank]}
208
- combined = (mutes+blanks).sort
209
-
210
- # detect overlap...
211
- previous = nil
212
- combined.each_with_index{|current, idx|
213
- s,e,t = current
214
- if e < s
215
- raise SyntaxError.new("detected an end before a start: #{e} < #{s}") if e < s unless splits.length > 0
216
- end
217
- if previous
218
- ps, pe, pt = previous
219
- if (s < pe)
220
- raise SyntaxError.new("detected an overlap #{[s,e,t].join(' ')} #{previous.join(' ')}") unless splits.length > 0
221
- # our start might be within the previous' in which case its their start, with (greater of our, their ending)
222
- preferred_end = [e,pe].max
223
- preferred_type = [t,pt].detect{|t| t == :blank} || :mute # prefer blank to mute
224
- combined[idx-1] = [ps, preferred_end, preferred_type]
225
- combined[idx] = nil # allow it to be culled later
226
- end
227
-
228
- end
229
- previous = current
230
- }
231
- combined.compact
232
- end
233
-
234
- # its reverse: translate_time_to_human_readable
235
- def self.translate_string_to_seconds s
236
- # might actually already be a float, or int, depending on the yaml
237
- # int for 8 => 9 and also for 1:09 => 1:10
238
- if s.is_a? Numeric
239
- return s.to_f
240
- end
241
-
242
- # s is like 1:01:02.0
243
- total = 0.0
244
- seconds = nil
245
- begin
246
- seconds = s.split(":")[-1]
247
- rescue Exception => e
248
- p 'failed!', s
249
- raise e
250
- end
251
- total += seconds.to_f
252
- minutes = s.split(":")[-2] || "0"
253
- total += 60 * minutes.to_i
254
- hours = s.split(":")[-3] || "0"
255
- total += 60* 60 * hours.to_i
256
- total
257
- end
258
-
259
- # its reverse: translate_string_to_seconds
260
- def self.translate_time_to_human_readable seconds, force_hour_stamp = false
261
- # 3600 => "1:00:00"
262
- out = ''
263
- hours = seconds.to_i / 3600
264
- if hours > 0 || force_hour_stamp
265
- out << "%d" % hours
266
- out << ":"
267
- end
268
- seconds = seconds - hours*3600
269
- minutes = seconds.to_i / 60
270
- out << "%02d" % minutes
271
- seconds = seconds - minutes * 60
272
- out << ":"
273
-
274
- # avoid an ugly .0 at the end
275
- if seconds == seconds.to_i
276
- out << "%02d" % seconds
277
- else
278
- out << "%06.3f" % seconds # man that is tricky...
279
- end
280
- end
281
-
282
- def self.all_edl_files_parsed use_all_not_just_dvds
283
- dir = EDL_DIR
284
- dir += "/.." if use_all_not_just_dvds
285
- Dir[dir + '/**/*.txt'].map{|filename|
286
- begin
287
- parsed = parse_file(filename)
288
- [filename, parsed]
289
- rescue SyntaxError => e
290
- # ignore poorly formed edit lists for the auto choose phase...
291
- p 'warning, unable to parse a file:' + filename + " " + e.to_s
292
- nil
293
- end
294
- }.compact
295
- end
296
-
297
- # returns single matching filename
298
- def self.find_single_edit_list_matching use_all = false
299
- matching = all_edl_files_parsed(use_all).map{|filename, parsed|
300
- yield(parsed) ? filename : nil
301
- }.compact
302
- if matching.length == 1
303
- file = matching[0]
304
- p "selecting the one only matching EDL: #{file}"
305
- file
306
- elsif matching.length > 1
307
- p "found multiple matches for media? #{matching.inspect}"
308
- nil
309
- else
310
- nil
311
- end
312
- end
313
-
314
- def self.single_edit_list_matches_dvd dvd_id
315
- return nil unless dvd_id
316
- find_single_edit_list_matching {|parsed|
317
- parsed["disk_unique_id"] == dvd_id
318
- }
319
- end
320
-
321
-
322
- end
323
-
324
- # == 1.8.7 1.9 Symbol compat
325
-
326
- class Symbol
327
- # Standard in ruby 1.9. See official documentation[http://ruby-doc.org/core-1.9/classes/Symbol.html]
328
- def <=>(with)
329
- return nil unless with.is_a? Symbol
330
- to_s <=> with.to_s
331
- end unless method_defined? :"<=>"
332
- end
333
-
334
- if $0 == __FILE__
335
- p 'syntax: filename'
336
- require 'rubygems'
337
- require 'sane'
338
- p EdlParser.parse_file(*ARGV)
339
- end
1
+ =begin
2
+ Copyright 2010, Roger Pack
3
+ This file is part of Sensible Cinema.
4
+
5
+ Sensible Cinema is free software: you can redistribute it and/or modify
6
+ it under the terms of the GNU General Public License as published by
7
+ the Free Software Foundation, either version 3 of the License, or
8
+ (at your option) any later version.
9
+
10
+ Sensible Cinema is distributed in the hope that it will be useful,
11
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ GNU General Public License for more details.
14
+
15
+ You should have received a copy of the GNU General Public License
16
+ along with Sensible Cinema. If not, see <http://www.gnu.org/licenses/>.
17
+ =end
18
+ require 'sane'
19
+ class EdlParser
20
+
21
+ EDL_DIR = File.expand_path(__DIR__ + "/../zamples/edit_decision_lists/dvds")
22
+
23
+ if File::ALT_SEPARATOR
24
+ EDL_DIR.gsub! File::SEPARATOR, File::ALT_SEPARATOR # to_filename...
25
+ end
26
+
27
+ # returns {"mutes" => [["00:00", "00:00", string1, string2, ...], ...], "blank_outs" -> [...], "url" => ...}
28
+ def self.parse_file filename, expand = true
29
+ output = parse_string File.read(filename), filename, []
30
+ # now respect a few options
31
+ if relative = output["take_from_relative_file"]
32
+ new_filename = File.dirname(filename) + '/' + relative
33
+ new_input = parse_file new_filename
34
+ output.merge! new_input
35
+ end
36
+
37
+ require_relative 'gui/sensible-cinema-dependencies' # for download method...
38
+
39
+ if expand
40
+
41
+ if output["from_url"] # replacement
42
+ downloaded = SensibleSwing::MainWindow.download_to_string(output["from_url"])
43
+ output = parse_string downloaded # full replacement
44
+ end
45
+
46
+ if imdb_id = output["imdb_id"]
47
+ parse_imdb output, imdb_id
48
+ end
49
+ end
50
+ output
51
+ end
52
+
53
+ def self.parse_imdb output, imdb_id
54
+ require_relative 'convert_thirty_fps'
55
+ url = "http://www.imdb.com/title/#{imdb_id}/parentalguide"
56
+ all = SensibleSwing::MainWindow.download_to_string(url)
57
+
58
+ header, violence_word, violence_section, profanity_word, profanity_section, alcohol_word, alcohol_section, frightening_word, frightening_section =
59
+ sections = all.split(/<span>(Violence|Profanity|Alcohol|Frightening)/)
60
+ header = sections.shift
61
+ all ={}
62
+ while(!sections.empty?) # my klugey to_hash method
63
+ word_type = sections.shift
64
+ settings = sections.shift
65
+ assert word_type.in? ['Violence', 'Profanity', 'Alcohol', 'Frightening']
66
+ all[word_type] = settings
67
+ end
68
+ # blank_outs or mutes for each...
69
+ # TODO make the -> optional
70
+ split_into_timestamps = /([\d:]+(?:\.\d+|))\W*-&gt;\W*([\d:]+(?:\.\d+|))([^\d\n]+)/
71
+ for type, settings in all
72
+ settings.scan(split_into_timestamps) do |begin_ts, end_ts, description|
73
+ puts "parsing from wiki imdb entry violence: #{begin_ts} #{end_ts} #{description} #{type}"
74
+ start_seconds = translate_string_to_seconds begin_ts
75
+ end_seconds = translate_string_to_seconds end_ts
76
+ # convert from 30 to 29.97 fps ... we presume ...
77
+ start_seconds = ConvertThirtyFps.from_twenty_nine_nine_seven start_seconds
78
+ start_seconds = ("%.02f" % start_seconds).to_f # round
79
+ start_seconds = translate_time_to_human_readable start_seconds, true
80
+ end_seconds = ConvertThirtyFps.from_twenty_nine_nine_seven end_seconds
81
+ end_seconds = ("%.02f" % end_seconds).to_f # round
82
+ end_seconds = translate_time_to_human_readable end_seconds, true
83
+ p end_seconds
84
+ if type == 'Profanity'
85
+ output['mutes'] << [start_seconds, end_seconds]
86
+ else
87
+ output['blank_outs'] << [start_seconds, end_seconds]
88
+ end
89
+ end
90
+ end
91
+ end
92
+
93
+ private
94
+
95
+ def self.download full_url, to_here
96
+ require 'open-uri'
97
+ writeOut = open(to_here, "wb")
98
+ writeOut.write(open(full_url).read)
99
+ writeOut.close
100
+ end
101
+
102
+ # better eye-ball these before letting people run them, eh? TODO
103
+ # but I couldn't think of any other way to parse the files tho
104
+ def self.parse_string string, filename, ok_categories_array = []
105
+ string = '{' + string + "\n}"
106
+ if filename
107
+ raw = eval(string, binding, filename)
108
+ else
109
+ raw = eval string
110
+ end
111
+
112
+ raise SyntaxError.new("maybe missing quotation marks?" + string) if raw.keys.contain?(nil)
113
+
114
+ # mutes and blank_outs need to be special parsed into arrays...
115
+ mutes = raw["mutes"] || []
116
+ blanks = raw["blank_outs"] || []
117
+ raw["mutes"] = convert_to_timestamp_arrays(mutes, ok_categories_array)
118
+ raw["blank_outs"] = convert_to_timestamp_arrays(blanks, ok_categories_array)
119
+ raw
120
+ end
121
+
122
+ # converts "blanks" => ["00:00:00", "00", "reason", "01", "01", "02", "02"] into sane arrays, also filters based on category, though disabled for production
123
+ def self.convert_to_timestamp_arrays array, ok_categories_array
124
+ out = []
125
+ while(single_element = extract_entry!(array))
126
+ # assume that it (could be, at least) start_time, end_time, category, number
127
+ category = single_element[-2]
128
+ category_number = single_element[-1]
129
+ include = true
130
+ if ok_categories_array.index([category, category_number])
131
+ include = false
132
+ elsif ok_categories_array.index([category])
133
+ include = false
134
+ elsif ok_categories_array.detect{|cat, setting| setting.is_a? Fixnum}
135
+ for cat, setting in ok_categories_array
136
+ if cat == category && setting.is_a?(Fixnum)
137
+ # check for a number for filtering out based on level
138
+ if category_number.to_i.to_s == category_number
139
+ as_number = category_number.to_i
140
+ if as_number < setting
141
+ include = false
142
+ end
143
+ end
144
+ end
145
+ end
146
+
147
+ end
148
+ out << single_element if include
149
+ end
150
+ out
151
+ end
152
+
153
+ #TimeStamp = /(^\d+:\d\d[\d:\.]*$|\d+)/ # this one also allows for 4444 [?] and also weirdness like "don't kill the nice butterfly 2!" ...
154
+ TimeStamp = /(^\d+:\d\d[\d:\.]*|\d+\.\d+)$/ # allow 00:00:00 00:00:00.0 1222.4
155
+ # disallow 1905 too but in the code
156
+ # starts with a digit, has at least one colon followed by two digits,then some combo of digits and colons and periods...
157
+
158
+ def self.extract_entry! from_this
159
+ return nil if from_this.length == 0
160
+ # two digits, then whatever else you see, that's not a digit...
161
+ out = from_this.shift(2)
162
+ out.each{|d|
163
+ unless d =~ TimeStamp
164
+ raise SyntaxError.new('non timestamp? ' + d)
165
+ end
166
+ }
167
+ while(from_this[0] && from_this[0] !~ TimeStamp)
168
+ raise SyntaxError.new('straight digits not allowed use 1000.0 instead') if from_this[0] =~ /^\d+$/
169
+ out << from_this.shift
170
+ end
171
+ out
172
+ end
173
+
174
+ def self.get_secs timestamp_string_begin, timestamp_string_end, add_begin, add_end, splits
175
+ answers = []
176
+ unless timestamp_string_begin
177
+ raise 'non begin'
178
+ end
179
+ unless timestamp_string_end
180
+ raise 'non end'
181
+ end
182
+ for type, offset, multiplier in [[timestamp_string_begin, add_begin, -1], [timestamp_string_end, add_end, 1]]
183
+ original_secs = translate_string_to_seconds(type) + offset
184
+ # now if splits is 900 and we'are at 909, then we're just 9
185
+ closest_split_idx = splits.reverse.index{|t| t < original_secs}
186
+ if closest_split_idx
187
+ closest_split = splits.reverse[closest_split_idx]
188
+ # add some extra seconds onto these if they're "past" a split, too
189
+ original_secs = original_secs - closest_split + multiplier * (splits.length - closest_split_idx)
190
+ original_secs = [0, original_secs].max # no negatives allowed :)
191
+ end
192
+ answers << original_secs
193
+ end
194
+ answers
195
+ end
196
+
197
+ public
198
+
199
+ # called later, from external
200
+ # divides up mutes and blanks so that they don't overlap, preferring blanks over mutes
201
+ # returns it like [[start,end,type], [s,e,t]...] type like either :blank and :mute
202
+ # [[70.0, 73.0, :blank], [378.0, 379.1, :mute]]
203
+ def self.convert_incoming_to_split_sectors incoming, add_this_to_all_ends = 0, subtract_this_from_beginnings = 0, splits = []
204
+ raise if subtract_this_from_beginnings < 0
205
+ raise if add_this_to_all_ends < 0
206
+ if splits != []
207
+ # allow it to do all the double checks we later skip, just in case :)
208
+ self.convert_incoming_to_split_sectors incoming
209
+ end
210
+ mutes = incoming["mutes"] || {}
211
+ blanks = incoming["blank_outs"] || {}
212
+ mutes = mutes.map{|k, v| get_secs(k, v, -subtract_this_from_beginnings, add_this_to_all_ends, splits) + [:mute]}
213
+ blanks = blanks.map{|k, v| get_secs(k, v, -subtract_this_from_beginnings, add_this_to_all_ends, splits) + [:blank]}
214
+ combined = (mutes+blanks).sort
215
+
216
+ # detect overlap...
217
+ previous = nil
218
+ combined.each_with_index{|current, idx|
219
+ s,e,t = current
220
+ if e < s
221
+ raise SyntaxError.new("detected an end before a start: #{e} < #{s}") if e < s unless splits.length > 0
222
+ end
223
+ if previous
224
+ ps, pe, pt = previous
225
+ if (s < pe)
226
+ raise SyntaxError.new("detected an overlap #{[s,e,t].join(' ')} #{previous.join(' ')}") unless splits.length > 0
227
+ # our start might be within the previous' in which case its their start, with (greater of our, their ending)
228
+ preferred_end = [e,pe].max
229
+ preferred_type = [t,pt].detect{|t| t == :blank} || :mute # prefer blank to mute
230
+ combined[idx-1] = [ps, preferred_end, preferred_type]
231
+ combined[idx] = nil # allow it to be culled later
232
+ end
233
+
234
+ end
235
+ previous = current
236
+ }
237
+ combined.compact
238
+ end
239
+
240
+ # its reverse: translate_time_to_human_readable
241
+ def self.translate_string_to_seconds s
242
+ # might actually already be a float, or int, depending on the yaml
243
+ # int for 8 => 9 and also for 1:09 => 1:10
244
+ if s.is_a? Numeric
245
+ return s.to_f
246
+ end
247
+
248
+ # s is like 1:01:02.0
249
+ total = 0.0
250
+ seconds = nil
251
+ begin
252
+ seconds = s.split(":")[-1]
253
+ rescue Exception => e
254
+ p 'failed!', s
255
+ raise e
256
+ end
257
+ total += seconds.to_f
258
+ minutes = s.split(":")[-2] || "0"
259
+ total += 60 * minutes.to_i
260
+ hours = s.split(":")[-3] || "0"
261
+ total += 60* 60 * hours.to_i
262
+ total
263
+ end
264
+
265
+ # its reverse: translate_string_to_seconds
266
+ def self.translate_time_to_human_readable seconds, force_hour_stamp = false
267
+ # 3600 => "1:00:00"
268
+ out = ''
269
+ hours = seconds.to_i / 3600
270
+ if hours > 0 || force_hour_stamp
271
+ out << "%d" % hours
272
+ out << ":"
273
+ end
274
+ seconds = seconds - hours*3600
275
+ minutes = seconds.to_i / 60
276
+ out << "%02d" % minutes
277
+ seconds = seconds - minutes * 60
278
+ out << ":"
279
+
280
+ # avoid an ugly .0 at the end
281
+ if seconds == seconds.to_i
282
+ out << "%02d" % seconds
283
+ else
284
+ out << "%05.2f" % seconds # man that printf syntax is tricky...
285
+ end
286
+ end
287
+
288
+ def self.all_edl_files_parsed use_all_not_just_dvds
289
+ dir = EDL_DIR
290
+ dir += "/.." if use_all_not_just_dvds
291
+ Dir[dir + '/**/*.txt'].map{|filename|
292
+ begin
293
+ parsed = parse_file(filename)
294
+ [filename, parsed]
295
+ rescue SyntaxError => e
296
+ # ignore poorly formed edit lists for the auto choose phase...
297
+ p 'warning, unable to parse a file:' + filename + " " + e.to_s
298
+ nil
299
+ end
300
+ }.compact
301
+ end
302
+
303
+ # returns single matching filename
304
+ def self.find_single_edit_list_matching use_all = false
305
+ matching = all_edl_files_parsed(use_all).map{|filename, parsed|
306
+ yield(parsed) ? filename : nil
307
+ }.compact
308
+ if matching.length == 1
309
+ file = matching[0]
310
+ p "selecting the one only matching EDL: #{file}"
311
+ file
312
+ elsif matching.length > 1
313
+ p "found multiple matches for media? #{matching.inspect}"
314
+ nil
315
+ else
316
+ nil
317
+ end
318
+ end
319
+
320
+ def self.single_edit_list_matches_dvd dvd_id
321
+ return nil unless dvd_id
322
+ find_single_edit_list_matching {|parsed|
323
+ parsed["disk_unique_id"] == dvd_id
324
+ }
325
+ end
326
+
327
+
328
+ end
329
+
330
+ # == 1.8.7 1.9 Symbol compat
331
+
332
+ class Symbol
333
+ # Standard in ruby 1.9. See official documentation[http://ruby-doc.org/core-1.9/classes/Symbol.html]
334
+ def <=>(with)
335
+ return nil unless with.is_a? Symbol
336
+ to_s <=> with.to_s
337
+ end unless method_defined? :"<=>"
338
+ end
339
+
340
+ if $0 == __FILE__
341
+ p 'syntax: filename'
342
+ require 'rubygems'
343
+ require 'sane'
344
+ p EdlParser.parse_file(*ARGV)
345
+ end