storyboard 0.2.4 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/.rvmrc +1 -1
- data/Gemfile +0 -7
- data/Gemfile.lock +5 -22
- data/INSTALL.md +26 -0
- data/README.md +13 -19
- data/bin/storyboard +39 -14
- data/lib/storyboard.rb +43 -12
- data/lib/storyboard/bincheck.rb +34 -0
- data/lib/storyboard/generators/pdf.rb +12 -12
- data/lib/storyboard/generators/sub.rb +33 -24
- data/lib/storyboard/subtitles.rb +43 -3
- data/lib/storyboard/version.rb +1 -1
- data/storyboard.gemspec +1 -25
- data/vendor/suby/lib/suby/downloader.rb +3 -3
- data/vendor/suby/lib/suby/downloader/addic7ed.rb +22 -0
- data/vendor/suby/lib/suby/downloader/addicted.rb +7 -0
- data/vendor/suby/lib/suby/downloader/opensubtitles.rb +2 -13
- metadata +7 -7
- data/TODO +0 -6
- data/bin/storyboard-ffprobe +0 -0
data/.gitignore
CHANGED
data/.rvmrc
CHANGED
@@ -1 +1 @@
|
|
1
|
-
rvm --create use 1.9.3@storyboard
|
1
|
+
rvm --create use 1.9.3-p327@storyboard
|
data/Gemfile
CHANGED
@@ -2,11 +2,4 @@ source :rubygems
|
|
2
2
|
|
3
3
|
gemspec
|
4
4
|
|
5
|
-
gem 'tvdb_party', :git => 'https://github.com/nddrylliog/tvdb_party'
|
6
|
-
gem 'nokogiri'
|
7
5
|
gem 'suby', :path => 'vendor/suby', :require => 'suby'
|
8
|
-
gem 'rmagick'
|
9
|
-
gem 'prawn'
|
10
|
-
gem 'ruby-progressbar'
|
11
|
-
gem 'rubyzip'
|
12
|
-
gem 'levenshtein-ffi', :require => 'levenshtein'
|
data/Gemfile.lock
CHANGED
@@ -1,20 +1,13 @@
|
|
1
|
-
GIT
|
2
|
-
remote: https://github.com/nddrylliog/tvdb_party
|
3
|
-
revision: 60ce3c28e664078da0206075e025ee06547f5826
|
4
|
-
specs:
|
5
|
-
tvdb_party (0.6.2)
|
6
|
-
httparty (>= 0.6.1)
|
7
|
-
|
8
1
|
PATH
|
9
2
|
remote: .
|
10
3
|
specs:
|
11
|
-
storyboard (0.
|
4
|
+
storyboard (0.3.0)
|
12
5
|
levenshtein-ffi
|
13
6
|
mime-types (>= 1.19)
|
7
|
+
mini_magick
|
14
8
|
nokogiri
|
15
9
|
path (>= 1.3.0)
|
16
10
|
prawn
|
17
|
-
rmagick
|
18
11
|
ruby-progressbar
|
19
12
|
rubyzip
|
20
13
|
term-ansicolor
|
@@ -36,15 +29,12 @@ GEM
|
|
36
29
|
afm (0.2.0)
|
37
30
|
ffi (1.1.5)
|
38
31
|
hashery (2.1.0)
|
39
|
-
httparty (0.10.0)
|
40
|
-
multi_json (~> 1.0)
|
41
|
-
multi_xml
|
42
32
|
levenshtein-ffi (1.0.3)
|
43
33
|
ffi
|
44
34
|
ffi (~> 1.1.5)
|
45
35
|
mime-types (1.19)
|
46
|
-
|
47
|
-
|
36
|
+
mini_magick (3.4)
|
37
|
+
subexec (~> 0.2.1)
|
48
38
|
nokogiri (1.5.6)
|
49
39
|
path (1.3.1)
|
50
40
|
pdf-reader (1.3.0)
|
@@ -56,10 +46,10 @@ GEM
|
|
56
46
|
prawn (0.12.0)
|
57
47
|
pdf-reader (>= 0.9.0)
|
58
48
|
ttfunk (~> 1.0.2)
|
59
|
-
rmagick (2.13.1)
|
60
49
|
ruby-progressbar (1.0.2)
|
61
50
|
ruby-rc4 (0.1.5)
|
62
51
|
rubyzip (0.9.9)
|
52
|
+
subexec (0.2.2)
|
63
53
|
term-ansicolor (1.0.7)
|
64
54
|
ttfunk (1.0.3)
|
65
55
|
|
@@ -67,12 +57,5 @@ PLATFORMS
|
|
67
57
|
ruby
|
68
58
|
|
69
59
|
DEPENDENCIES
|
70
|
-
levenshtein-ffi
|
71
|
-
nokogiri
|
72
|
-
prawn
|
73
|
-
rmagick
|
74
|
-
ruby-progressbar
|
75
|
-
rubyzip
|
76
60
|
storyboard!
|
77
61
|
suby!
|
78
|
-
tvdb_party!
|
data/INSTALL.md
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# Installing Storybard
|
2
|
+
|
3
|
+
Storyboard relies on a number of external libraries to handle extracting information from the video files and generating the output. Primarily, it requires Ruby 1.9.3 or greater, FFMpeg 1.1 or greater, Imagemagick (which in turn requires ghostscript), and a compiler.
|
4
|
+
|
5
|
+
## OS X
|
6
|
+
|
7
|
+
To begin, you'll need to download GCC, a set of tools that will let you compile low level code. You can find a nice installer for it at [osx-gcc-installer](https://github.com/kennethreitz/osx-gcc-installer). Once GCC is installed, download and install [homebrew](http://mxcl.github.com/homebrew/), which is a tool that will automate of the installation process for Imagemagick and FFmpeg. If you haven't installed homebrew before, open a Terminal window and do the following:
|
8
|
+
|
9
|
+
ruby -e "$(curl -fsSkL raw.github.com/mxcl/homebrew/go)"
|
10
|
+
brew install ffmpeg imagemagick ghostscript
|
11
|
+
|
12
|
+
If you've already installed homebrew, update it and re-install FFmpeg if necessary. Storyboard requires FFmpeg 1.1, which became available on homebrew in Janurary 2013.
|
13
|
+
|
14
|
+
brew update
|
15
|
+
brew uninstall ffmpeg
|
16
|
+
brew upgrade ffmpeg
|
17
|
+
|
18
|
+
Finally, if you don't have Ruby 1.9.3 installed on your system, you can use <a href="http://rvm.io">RVM</a> to install it. In a termal window, run the following:
|
19
|
+
|
20
|
+
curl -L https://get.rvm.io | bash -s stable --ruby
|
21
|
+
rvm install 1.9.3-p374
|
22
|
+
rvm default 1.9.3-p374
|
23
|
+
|
24
|
+
When all of those are installed, you can install Storyboard with the `gem` command
|
25
|
+
|
26
|
+
gem install storyboard
|
data/README.md
CHANGED
@@ -1,16 +1,22 @@
|
|
1
1
|
# Storyboard
|
2
2
|
|
3
|
-
Read the TV and Movies you don't have time to watch.
|
3
|
+
Read the TV and Movies you don't have time to watch. Given a video file, it will generate a PDF (or soon, ePub and Mobi) containing every scene change and line of dialog.
|
4
4
|
|
5
5
|
## Storyboard
|
6
6
|
|
7
7
|
![Seinfeld](http://i.imgur.com/lTRuC.jpg)
|
8
8
|
|
9
|
-
Storyboard is _very much_ a work in progress,
|
9
|
+
Storyboard is _very much_ a work in progress, though it works most of the time. Using it is simple:
|
10
10
|
|
11
|
-
storyboard /path/to/video-file.mkv
|
11
|
+
$ storyboard /path/to/video-file.mkv
|
12
12
|
|
13
|
-
Storyboard will try to generate a file at `/path/to/video-file.pdf` containing the final product.
|
13
|
+
Storyboard will try to generate a file at `/path/to/video-file.pdf` containing the final product. Simiarly, you can pass in the path to a folder containing multiple files and it will output a PDF for each video it finds.
|
14
|
+
|
15
|
+
$ ls "~/TV/ShowName/Season 1"
|
16
|
+
ShowName.1x01.EpisodeName.mkv ShowName.1x02.AnotherEpisode.mkv ShowName.1x03.ThirdEp.mkv
|
17
|
+
$ storyboard "~/TV/ShowName/Season 1"
|
18
|
+
$ ls .
|
19
|
+
ShowName.1x01.EpisodeName.pdf ShowName.1x02.AnotherEpisode.pdf ShowName.1x03.ThirdEp.pdf
|
14
20
|
|
15
21
|
You can see available commands by running the program without any options
|
16
22
|
|
@@ -23,21 +29,9 @@ You can see available commands by running the program without any options
|
|
23
29
|
(pdf, mobi, epub)
|
24
30
|
-h, --help Show this message
|
25
31
|
|
26
|
-
## Requirements
|
27
|
-
|
28
|
-
Storyboard requires a recent version of *ffmpeg*. This gem includes a build of *ffprobe* that will only work on OS X 1.8, 64 bit. It's probably best not to use this on any different system for now. It's also best to run it on something with at least 8 cores.
|
29
32
|
|
30
|
-
##
|
31
|
-
|
32
|
-
* If there is a scene change followed by dialog in the next frame or two, the dialog may not be shown.
|
33
|
-
* Subtitles are always downloaded, never extracted from video files. Because the subtitles are searched for based on the filename it's best that you have then named in a standard format, e.g., `The X-Files - 1x21 - Tooms.avi`.
|
34
|
-
* Sometimes the wrong subtitle file will be returned from the site. In those cases, download it manually and use the `-s` option to pass in the path to an SRT formatted subtitle file. This is because the data lies, and there's nothing I can do about it.
|
35
|
-
* Some subtitles are encoded in UTF-16, and I haven't care quite enough yet to get them to work. Others use bad UTF8.
|
36
|
-
* The subtitles are uuuuuugly.
|
37
|
-
* Hardcoding 8 for the number of threads is a bad idea
|
38
|
-
* Almost definitely some path-escaping issues, so avoid files with apostrophes and slashes
|
39
|
-
* Some subtitles put all the text on one line, so that has to get broken up.
|
33
|
+
## Requirements
|
40
34
|
|
41
|
-
|
35
|
+
The INSTALL.md file talks about requirements some more, but Storyboard requires Ruby 1.9.3, FFMpeg 1.1, and any recent version of Imagemagick. If you already have those available, just install the storyboard gem.
|
42
36
|
|
43
|
-
|
37
|
+
gem install storyboard
|
data/bin/storyboard
CHANGED
@@ -9,15 +9,19 @@ require 'bundler'
|
|
9
9
|
ENV['BUNDLE_GEMFILE'] = File.join(File.dirname(__FILE__), '../Gemfile')
|
10
10
|
Bundler.require(:default)
|
11
11
|
|
12
|
-
require 'nokogiri'
|
13
|
-
require 'suby'
|
14
|
-
require 'rmagick'
|
15
|
-
require 'prawn'
|
16
|
-
require 'ruby-progressbar'
|
17
|
-
|
18
12
|
$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../lib"
|
19
13
|
require 'storyboard'
|
20
14
|
|
15
|
+
unless Storyboard.ffprobe_installed?
|
16
|
+
puts "Storyboard requires FFmpeg 1.1. Please check the README for instructions"
|
17
|
+
exit
|
18
|
+
end
|
19
|
+
|
20
|
+
unless Storyboard.magick_installed?
|
21
|
+
puts "Storyboard requires Imagemagick's mogrify tool. Please check the README for instructions on how to install Imagemagick if you need them."
|
22
|
+
exit
|
23
|
+
end
|
24
|
+
|
21
25
|
options = {:scenes => true, :verbose => true, :types => ['pdf'], :scene_threshold => 0.4}
|
22
26
|
|
23
27
|
# think about them
|
@@ -67,16 +71,37 @@ if ARGV.size < 1
|
|
67
71
|
puts "videofile required"
|
68
72
|
puts opts.to_s
|
69
73
|
exit 1
|
74
|
+
elsif ARGV.size == 2 && !File.directory?(File.realdirpath(ARGV.last))
|
75
|
+
puts "outputdir #{ARGV.last} is not a directory"
|
76
|
+
puts opts.to_s
|
77
|
+
exit 1
|
78
|
+
elsif ARGV.size > 2
|
79
|
+
puts "Too many arguments"
|
80
|
+
puts opts.to_s
|
81
|
+
exit 1
|
70
82
|
end
|
71
83
|
|
72
|
-
options[:
|
84
|
+
options[:vidpath] = File.realdirpath(ARGV.shift)
|
85
|
+
options[:write_to] = ARGV.shift || Dir.pwd
|
73
86
|
|
74
|
-
|
75
|
-
options[:
|
87
|
+
if !File.exists?(options[:vidpath])
|
88
|
+
puts("#{options[:vidpath]} doesn't exist.")
|
89
|
+
exit
|
90
|
+
end
|
91
|
+
|
92
|
+
filepaths = []
|
76
93
|
|
77
|
-
|
78
|
-
options[:
|
79
|
-
|
80
|
-
LOG.
|
94
|
+
if File.directory?(options[:vidpath])
|
95
|
+
LOG.info("Ignoring subtitle option because we're looking at a directory") if options[:subs]
|
96
|
+
options[:vidpath] = File.join(options[:vidpath], '*')
|
97
|
+
LOG.info("Reading video files in #{options[:vidpath]}")
|
98
|
+
filepaths += Dir[File.realdirpath(options[:vidpath])]
|
99
|
+
else
|
100
|
+
filepaths << options[:vidpath]
|
101
|
+
end
|
81
102
|
|
82
|
-
|
103
|
+
filepaths.each {|fp|
|
104
|
+
options[:file] = fp
|
105
|
+
runner = Storyboard.new(options)
|
106
|
+
runner.run if runner.video_file?
|
107
|
+
}
|
data/lib/storyboard.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'storyboard/subtitles.rb'
|
2
|
+
require 'storyboard/bincheck.rb'
|
2
3
|
require 'storyboard/thread-util.rb'
|
3
4
|
require 'storyboard/time.rb'
|
4
5
|
require 'storyboard/version.rb'
|
@@ -7,38 +8,47 @@ require 'storyboard/generators/sub.rb'
|
|
7
8
|
require 'storyboard/generators/pdf.rb'
|
8
9
|
|
9
10
|
require 'mime/types'
|
11
|
+
require 'fileutils'
|
12
|
+
require 'tmpdir'
|
13
|
+
|
14
|
+
require 'ruby-progressbar'
|
15
|
+
require 'mini_magick'
|
10
16
|
|
11
17
|
class Storyboard
|
12
18
|
attr_accessor :options, :capture_points, :subtitles, :timings
|
13
|
-
attr_accessor :length, :renderers
|
19
|
+
attr_accessor :length, :renderers, :mime
|
14
20
|
|
15
21
|
def initialize(o)
|
16
22
|
@capture_points = []
|
17
23
|
@renderers = []
|
18
24
|
@options = o
|
19
25
|
|
20
|
-
|
26
|
+
check_video
|
27
|
+
end
|
21
28
|
|
22
|
-
|
29
|
+
def run
|
30
|
+
LOG.info("Processing #{options[:file]}")
|
31
|
+
setup
|
23
32
|
|
24
33
|
@subtitles = SRT.new(options[:subs] ? File.read(options[:subs]) : get_subtitles, options)
|
25
|
-
# temp hack so I don't have to wait all the time.
|
34
|
+
# bit of a temp hack so I don't have to wait all the time.
|
26
35
|
@subtitles.save if options[:verbose]
|
27
36
|
|
37
|
+
|
28
38
|
@renderers << Storyboard::PDFRenderer.new(self) if options[:types].include?('pdf')
|
29
39
|
|
30
|
-
check_video
|
31
40
|
run_scene_detection if options[:scenes]
|
32
41
|
consolidate_frames
|
33
42
|
extract_frames
|
34
43
|
render_output
|
44
|
+
|
45
|
+
cleanup
|
35
46
|
end
|
36
47
|
|
37
48
|
def run_scene_detection
|
38
|
-
LOG.info("Scanning for scene changes. This may take a moment.")
|
39
49
|
pbar = ProgressBar.create(:title => " Analyzing Video", :format => '%t [%B] %e', :total => @length, :smoothing => 0.6)
|
40
50
|
bin = File.join(File.dirname(__FILE__), '../bin/storyboard-ffprobe')
|
41
|
-
Open3.popen3(
|
51
|
+
Open3.popen3('ffprobe', "-show_frames", "-of", "compact=p=0", "-f", "lavfi", %(movie=#{options[:file]},select=gt(scene\\,.30)), "-pretty") {|stdin, stdout, stderr, wait_thr|
|
42
52
|
begin
|
43
53
|
# trolololol
|
44
54
|
o = stdout.gets.split('|').inject({}){|hold,value| s = value.split('='); hold[s[0]]=s[1]; hold }
|
@@ -69,7 +79,7 @@ class Storyboard
|
|
69
79
|
end
|
70
80
|
|
71
81
|
def extract_frames
|
72
|
-
pool = Thread::Pool.new(
|
82
|
+
pool = Thread::Pool.new(2)
|
73
83
|
pbar = ProgressBar.create(:title => " Extracting Frames", :format => '%t [%c/%C|%B] %e', :total => @capture_points.count)
|
74
84
|
|
75
85
|
@capture_points.each_with_index {|f,i|
|
@@ -93,7 +103,6 @@ class Storyboard
|
|
93
103
|
end
|
94
104
|
|
95
105
|
def render_output
|
96
|
-
LOG.info("Rendering output files")
|
97
106
|
pbar = ProgressBar.create(:title => " Rendering Output", :format => '%t [%c/%C|%B] %e', :total => @capture_points.count)
|
98
107
|
@capture_points.each_with_index {|f,i|
|
99
108
|
image_name = File.join(@options[:save_directory], "%04d.jpg" % [i])
|
@@ -111,9 +120,31 @@ class Storyboard
|
|
111
120
|
end
|
112
121
|
|
113
122
|
def check_video
|
114
|
-
|
115
|
-
|
116
|
-
|
123
|
+
@mime = MIME::Types.type_for(options[:file])
|
124
|
+
if video_file?
|
125
|
+
@length = `ffmpeg -i "#{options[:file]}" 2>&1 | grep "Duration" | cut -d ' ' -f 4 | sed s/,//`
|
126
|
+
@length = STRTime.parse(length.strip+'0').value
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def video_file?
|
131
|
+
!@mime.grep(/video\//).empty?
|
132
|
+
end
|
133
|
+
|
134
|
+
def mkv?
|
135
|
+
!@mime.grep(/matroska/).empty?
|
136
|
+
end
|
137
|
+
|
138
|
+
def setup
|
139
|
+
@options[:basename] = File.basename(options[:file], ".*")
|
140
|
+
@options[:work_dir] = Dir.mktmpdir
|
141
|
+
Dir.mkdir(@options[:write_to]) unless File.directory?(@options[:write_to])
|
142
|
+
@options[:save_directory] = File.join(@options[:work_dir], 'raw_frames')
|
143
|
+
Dir.mkdir(@options[:save_directory]) unless File.directory?(@options[:save_directory])
|
144
|
+
end
|
145
|
+
|
146
|
+
def cleanup
|
147
|
+
FileUtils.remove_dir(@options[:work_dir])
|
117
148
|
end
|
118
149
|
|
119
150
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class Object
|
2
|
+
def command?(name)
|
3
|
+
`which #{name}`
|
4
|
+
$?.success?
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
class Storyboard
|
9
|
+
def self.mkvtools_installed?
|
10
|
+
command?("mkvextract") && command?("mkvmerge")
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.mp4box_insatlled?
|
14
|
+
command?("MP4Box")
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.magick_installed?
|
18
|
+
command?("mogrify")
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.ffprobe_installed?
|
22
|
+
good = command?("ffprobe")
|
23
|
+
if good
|
24
|
+
version = `ffprobe -version`.scan(/version ([\d\.]+)\n/)
|
25
|
+
if version.empty?
|
26
|
+
good = false
|
27
|
+
else
|
28
|
+
float_version = version.first[0].to_f
|
29
|
+
good = float_version >= 1.1
|
30
|
+
end
|
31
|
+
end
|
32
|
+
return good
|
33
|
+
end
|
34
|
+
end
|
@@ -1,7 +1,5 @@
|
|
1
1
|
require 'prawn'
|
2
|
-
|
3
|
-
require 'rmagick'
|
4
|
-
include Magick
|
2
|
+
require 'mini_magick'
|
5
3
|
|
6
4
|
class Storyboard
|
7
5
|
class PDFRenderer < Storyboard::Renderer
|
@@ -23,23 +21,25 @@ class Storyboard
|
|
23
21
|
end
|
24
22
|
|
25
23
|
def render_frame(frame_name, subtitle = nil)
|
26
|
-
|
27
|
-
|
24
|
+
output_filename = File.join(@storyboard.options[:save_directory], "sub-#{File.basename(frame_name)}")
|
25
|
+
image = MiniMagick::Image.open(frame_name)
|
28
26
|
|
29
27
|
if(@dimensions.empty?)
|
30
|
-
resize_height = (
|
28
|
+
resize_height = (image[:height] * (@storyboard.options[:max_width].to_f/image[:width])).ceil
|
31
29
|
set_dimensions(storyboard.options[:max_width], resize_height)
|
32
30
|
end
|
33
31
|
|
34
|
-
|
32
|
+
image.resize "#{@dimensions[0]}x#{@dimensions[1]}"
|
33
|
+
|
34
|
+
self.add_subtitle(image, subtitle, @dimensions) if subtitle
|
35
|
+
|
36
|
+
image.format 'jpeg'
|
37
|
+
image.write(output_filename)
|
38
|
+
image.destroy!
|
35
39
|
|
36
|
-
|
40
|
+
@pdf.image output_filename, :width => @dimensions[0], :height => @dimensions[1]
|
37
41
|
|
38
|
-
img.format = 'jpeg'
|
39
|
-
img.write(image_output) { self.quality = 60 }
|
40
|
-
img.destroy!
|
41
42
|
|
42
|
-
@pdf.image image_output, :width => @dimensions[0], :height => @dimensions[1]
|
43
43
|
end
|
44
44
|
|
45
45
|
end
|
@@ -1,32 +1,41 @@
|
|
1
|
+
require 'prawn'
|
1
2
|
class Storyboard
|
2
3
|
class Renderer
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
end
|
4
|
+
@@size_canvas = Prawn::Document.new
|
5
|
+
def add_subtitle(image, subtitle, dimensions)
|
6
|
+
offset = 0
|
7
|
+
subtitle.lines.reverse.each_with_index {|caption,i|
|
8
|
+
escaped = caption.gsub(/\\|'|"/) { |c| "\\#{c}" }
|
9
|
+
font_size = 30
|
10
|
+
text_width = dimensions[0] + 1
|
11
|
+
while(text_width > (dimensions[0] * 0.9))
|
12
|
+
font_size -= 1
|
13
|
+
text_width = @@size_canvas.width_of(caption, :size => font_size)
|
14
|
+
end
|
15
15
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
16
|
+
image.combine_options do |c|
|
17
|
+
c.font "helvetica"
|
18
|
+
c.fill "#333333"
|
19
|
+
c.strokewidth '1'
|
20
|
+
c.stroke '#000000'
|
21
|
+
c.pointsize font_size.to_s
|
22
|
+
c.gravity "south"
|
23
|
+
c.draw "text 0, #{offset} '#{escaped}'"
|
24
|
+
end
|
20
25
|
|
21
|
-
|
22
|
-
|
23
|
-
|
26
|
+
#and the shadow
|
27
|
+
image.combine_options do |c|
|
28
|
+
c.font "helvetica"
|
29
|
+
c.fill "#ffffff"
|
30
|
+
c.strokewidth '1'
|
31
|
+
c.stroke 'transparent'
|
32
|
+
c.pointsize font_size.to_s
|
33
|
+
c.gravity "south"
|
34
|
+
c.draw "text -2, #{offset-2} '#{escaped}'"
|
35
|
+
end
|
24
36
|
|
25
|
-
|
26
|
-
|
27
|
-
txt.stroke = "#000000"
|
28
|
-
}
|
29
|
-
txt = nil
|
37
|
+
offset += (@@size_canvas.height_of(caption, :size => font_size)).ceil
|
38
|
+
}
|
30
39
|
end
|
31
40
|
end
|
32
41
|
end
|
data/lib/storyboard/subtitles.rb
CHANGED
@@ -1,5 +1,29 @@
|
|
1
1
|
class Storyboard
|
2
2
|
def get_subtitles
|
3
|
+
extensionless = File.join(File.dirname(options[:file]), File.basename(options[:file], ".*") + '.srt')
|
4
|
+
|
5
|
+
if mkv?
|
6
|
+
if Storyboard.mkvtools_installed?
|
7
|
+
output = `mkvmerge -i "#{options[:file]}"`
|
8
|
+
subs = output.scan(/Track ID (\d+): subtitles \(S_TEXT\/UTF8\)/)
|
9
|
+
# until I can play with better output, take the first.
|
10
|
+
if not subs.empty?
|
11
|
+
LOG.info("Extracting embedded subtitles")
|
12
|
+
LOG.info("Multiple subtitles found in the mkv. Taking the first.") if subs.count > 1
|
13
|
+
`mkvextract tracks "#{options[:file]}" #{subs.first[0]}:"#{options[:work_dir]}/subtitles.srt"`
|
14
|
+
return File.read("#{options[:work_dir]}/subtitles.srt", )
|
15
|
+
end
|
16
|
+
else
|
17
|
+
LOG.debug("File is mkv, but no mkvtoolnix installed.")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
if File.exists?(options[:file] + '.srt')
|
22
|
+
return File.read(options[:file] + '.srt')
|
23
|
+
elsif File.exists?(extensionless)
|
24
|
+
return File.read(extensionless)
|
25
|
+
end
|
26
|
+
|
3
27
|
if false == "the file has subtitles embedded"
|
4
28
|
|
5
29
|
else
|
@@ -8,8 +32,23 @@ class Storyboard
|
|
8
32
|
# that it uses to call file.basename instead of File.basename(file),
|
9
33
|
#but "file" has to be a "Path", so, whatever.
|
10
34
|
suby_file = Path(options[:file])
|
11
|
-
downloader = Suby::Downloader::OpenSubtitles.new(suby_file, 'en')
|
35
|
+
#downloader = Suby::Downloader::OpenSubtitles.new(suby_file, 'en')
|
36
|
+
# try Addic7ed first, as, on average, it seems a bit better.
|
37
|
+
downloader = nil
|
38
|
+
begin
|
39
|
+
LOG.debug("Searching for subtitles on Addic7ed")
|
40
|
+
#downloader = Suby::Downloader::Addic7ed.new(suby_file, 'en')
|
41
|
+
rescue Exception => e
|
42
|
+
LOG.debug(e)
|
43
|
+
end
|
44
|
+
|
45
|
+
if downloader.nil?
|
46
|
+
LOG.info("Searching for subtitles on OpenSubtitles")
|
47
|
+
downloader = Suby::Downloader::OpenSubtitles.new(suby_file, 'en')
|
48
|
+
end
|
49
|
+
|
12
50
|
LOG.debug("Found #{downloader.download_url}")
|
51
|
+
#LOG.debug(downloader.found)
|
13
52
|
downloader.extract(downloader.download_url)
|
14
53
|
end
|
15
54
|
end
|
@@ -37,8 +76,9 @@ class Storyboard
|
|
37
76
|
page = nil
|
38
77
|
@text.each_line {|l|
|
39
78
|
l = l.strip
|
79
|
+
l = l.encode("UTF-32", :invalid=>:replace, :replace=>"?").encode("UTF-8")
|
40
80
|
# Some files have BOM markers. Why? Why would you add a BOM marker.
|
41
|
-
l.gsub!("\xEF\xBB\xBF"
|
81
|
+
l.gsub!("\xEF\xBB\xBF", '') if page.nil?
|
42
82
|
case phase
|
43
83
|
when :line_no
|
44
84
|
if l =~ /^\d+$/
|
@@ -60,7 +100,7 @@ class Storyboard
|
|
60
100
|
phase = :line_no
|
61
101
|
@pages << page
|
62
102
|
else
|
63
|
-
page[:lines] <<
|
103
|
+
page[:lines] << l.gsub(/<\/?[^>]+?>/, '')
|
64
104
|
end
|
65
105
|
end
|
66
106
|
}
|
data/lib/storyboard/version.rb
CHANGED
data/storyboard.gemspec
CHANGED
@@ -17,7 +17,7 @@ Gem::Specification.new do |gem|
|
|
17
17
|
gem.version = Storyboard::VERSION
|
18
18
|
|
19
19
|
gem.add_dependency 'nokogiri'
|
20
|
-
gem.add_dependency '
|
20
|
+
gem.add_dependency 'mini_magick'
|
21
21
|
gem.add_dependency 'prawn'
|
22
22
|
gem.add_dependency 'ruby-progressbar'
|
23
23
|
gem.add_dependency 'levenshtein-ffi'
|
@@ -28,28 +28,4 @@ Gem::Specification.new do |gem|
|
|
28
28
|
gem.add_dependency 'term-ansicolor'
|
29
29
|
gem.add_dependency 'mime-types', '>= 1.19'
|
30
30
|
|
31
|
-
if File.exist?('.git')
|
32
|
-
`git submodule --quiet foreach 'echo $path'`.split($\).each do |submodule_path|
|
33
|
-
# for each submodule, change working directory to that submodule
|
34
|
-
Dir.chdir(submodule_path) do
|
35
|
-
# issue git ls-files in submodule's directory
|
36
|
-
submodule_files = `git ls-files`.split($\)
|
37
|
-
|
38
|
-
|
39
|
-
# prepend the submodule path to create absolute file paths
|
40
|
-
submodule_files_fullpaths = submodule_files.map do |filename|
|
41
|
-
"#{submodule_path}/#{filename}"
|
42
|
-
end
|
43
|
-
|
44
|
-
# remove leading path parts to get paths relative to the gem's root dir
|
45
|
-
# (this assumes, that the gemspec resides in the gem's root dir)
|
46
|
-
submodule_files_paths = submodule_files_fullpaths.map do |filename|
|
47
|
-
filename.gsub "#{File.dirname(__FILE__)}/", ""
|
48
|
-
end
|
49
|
-
|
50
|
-
# add relative paths to gem.files
|
51
|
-
gem.files += submodule_files_paths
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|
55
31
|
end
|
@@ -12,7 +12,7 @@ module Suby
|
|
12
12
|
DOWNLOADERS << downloader
|
13
13
|
end
|
14
14
|
|
15
|
-
attr_reader :show, :season, :episode, :video_data, :file, :lang
|
15
|
+
attr_reader :show, :season, :episode, :video_data, :file, :lang, :found
|
16
16
|
|
17
17
|
def initialize(file, *args)
|
18
18
|
@file = file
|
@@ -84,9 +84,9 @@ module Suby
|
|
84
84
|
get(url_or_response)
|
85
85
|
end
|
86
86
|
end
|
87
|
-
|
87
|
+
|
88
88
|
def save(contents)
|
89
|
-
sub_name(contents).write
|
89
|
+
sub_name(contents).write
|
90
90
|
end
|
91
91
|
|
92
92
|
def extract(url_or_response)
|
@@ -14,7 +14,29 @@ module Suby
|
|
14
14
|
FILTER_IGNORED = "Couldn't find any subs with the specified language. " +
|
15
15
|
"Filter ignored"
|
16
16
|
|
17
|
+
# TODO - cache the shit out of this
|
17
18
|
def subtitles_url
|
19
|
+
response = File.read('/Users/olson/Code/pdftv/shows.xml'); get("http://#{SITE}/ajax_getShows.php", {}, false)
|
20
|
+
series = {}
|
21
|
+
body = response.gsub(/<b>.*<\/b>/,'') #response.body.gsub(/<b>.*<\/b>/,'')
|
22
|
+
Nokogiri(body).css('option').each {|show|
|
23
|
+
series[show.inner_text.strip.downcase] = show['value'].to_i
|
24
|
+
}
|
25
|
+
|
26
|
+
potential_matches = [
|
27
|
+
@video_data[:show],
|
28
|
+
@video_data[:show].gsub(' & ', ' and '),
|
29
|
+
@video_data[:show].gsub(' and ', ' & '),
|
30
|
+
@video_data[:show].gsub('\'', '')
|
31
|
+
]
|
32
|
+
found = nil
|
33
|
+
potential_matches.each{|name|
|
34
|
+
p series[name.downcase]
|
35
|
+
}
|
36
|
+
#print response.body
|
37
|
+
exit
|
38
|
+
|
39
|
+
|
18
40
|
"/serie/#{CGI.escape show}/#{season}/#{episode}/#{LANG_IDS[lang]}"
|
19
41
|
end
|
20
42
|
|
@@ -37,20 +37,9 @@ module Suby
|
|
37
37
|
break subs
|
38
38
|
end
|
39
39
|
}
|
40
|
-
x = s.select { |t| t['SeriesSeason'].to_i == @video_data[:season].to_i }
|
41
|
-
x.each {|t|
|
42
|
-
puts t['MovieName'] + '/' + "\"#{@video_data[:show]}\" #{@video_data[:title]}" + " - " + Levenshtein.distance(t['MovieName'], "\"#{@video_data[:show]}\" #{@video_data[:title]}").to_i.to_s
|
43
|
-
}
|
40
|
+
x = @video_data[:season] ? s.select { |t| t['SeriesSeason'].to_i == @video_data[:season].to_i } : s
|
44
41
|
x.sort_by! { |t| Levenshtein.distance(t['MovieName'], "\"#{@video_data[:show]}\" #{@video_data[:title]}").to_i }
|
45
|
-
|
46
|
-
x.each {|t|
|
47
|
-
puts t['MovieName'] + '/' + "\"#{@video_data[:show]}\" #{@video_data[:title]}" + " - " + Levenshtein.distance(t['MovieName'], "\"#{@video_data[:show]}\" #{@video_data[:title]}").to_i.to_s
|
48
|
-
}
|
49
|
-
|
50
|
-
p "======"
|
51
|
-
p x.first
|
52
|
-
p x.last
|
53
|
-
p "-------"
|
42
|
+
@found = x.first
|
54
43
|
x.first['SubDownloadLink']
|
55
44
|
end
|
56
45
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: storyboard
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-01-
|
12
|
+
date: 2013-01-21 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: nokogiri
|
@@ -28,7 +28,7 @@ dependencies:
|
|
28
28
|
- !ruby/object:Gem::Version
|
29
29
|
version: '0'
|
30
30
|
- !ruby/object:Gem::Dependency
|
31
|
-
name:
|
31
|
+
name: mini_magick
|
32
32
|
requirement: !ruby/object:Gem::Requirement
|
33
33
|
none: false
|
34
34
|
requirements:
|
@@ -160,7 +160,6 @@ email:
|
|
160
160
|
- ! '"theothermarkolson@gmail.com"'
|
161
161
|
executables:
|
162
162
|
- storyboard
|
163
|
-
- storyboard-ffprobe
|
164
163
|
extensions: []
|
165
164
|
extra_rdoc_files: []
|
166
165
|
files:
|
@@ -168,12 +167,12 @@ files:
|
|
168
167
|
- .rvmrc
|
169
168
|
- Gemfile
|
170
169
|
- Gemfile.lock
|
170
|
+
- INSTALL.md
|
171
171
|
- README.md
|
172
|
-
- TODO
|
173
172
|
- bin/storyboard
|
174
|
-
- bin/storyboard-ffprobe
|
175
173
|
- lib/.DS_Store
|
176
174
|
- lib/storyboard.rb
|
175
|
+
- lib/storyboard/bincheck.rb
|
177
176
|
- lib/storyboard/generators/pdf.rb
|
178
177
|
- lib/storyboard/generators/sub.rb
|
179
178
|
- lib/storyboard/subtitles.rb
|
@@ -188,6 +187,7 @@ files:
|
|
188
187
|
- vendor/suby/lib/suby.rb
|
189
188
|
- vendor/suby/lib/suby/downloader.rb
|
190
189
|
- vendor/suby/lib/suby/downloader/addic7ed.rb
|
190
|
+
- vendor/suby/lib/suby/downloader/addicted.rb
|
191
191
|
- vendor/suby/lib/suby/downloader/opensubtitles.rb
|
192
192
|
- vendor/suby/lib/suby/downloader/tvsubtitles.rb
|
193
193
|
- vendor/suby/lib/suby/filename_parser.rb
|
@@ -224,7 +224,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
224
224
|
version: '0'
|
225
225
|
requirements: []
|
226
226
|
rubyforge_project:
|
227
|
-
rubygems_version: 1.8.
|
227
|
+
rubygems_version: 1.8.23
|
228
228
|
signing_key:
|
229
229
|
specification_version: 3
|
230
230
|
summary: Video to PDF/ePub generator
|
data/TODO
DELETED
data/bin/storyboard-ffprobe
DELETED
Binary file
|