subtitle_it 0.7.7
Sign up to get free protection for your applications and to get access to all the features.
- data/.autotest +19 -0
- data/History.txt +53 -0
- data/License.txt +20 -0
- data/Manifest.txt +68 -0
- data/README.markdown +41 -0
- data/README.txt +102 -0
- data/Rakefile +4 -0
- data/bin/subtitle_it +66 -0
- data/config/hoe.rb +73 -0
- data/config/requirements.rb +15 -0
- data/lib/subtitle_it/bin.rb +132 -0
- data/lib/subtitle_it/fixes.rb +12 -0
- data/lib/subtitle_it/formats/ass.rb +13 -0
- data/lib/subtitle_it/formats/mpl.rb +29 -0
- data/lib/subtitle_it/formats/rsb.rb +37 -0
- data/lib/subtitle_it/formats/srt.rb +45 -0
- data/lib/subtitle_it/formats/sub.rb +49 -0
- data/lib/subtitle_it/formats/xml.rb +65 -0
- data/lib/subtitle_it/formats/yml.rb +18 -0
- data/lib/subtitle_it/generator.rb +15 -0
- data/lib/subtitle_it/languages.rb +60 -0
- data/lib/subtitle_it/movie.rb +24 -0
- data/lib/subtitle_it/movie_hasher.rb +30 -0
- data/lib/subtitle_it/platform_endl.rb +9 -0
- data/lib/subtitle_it/subdown.rb +107 -0
- data/lib/subtitle_it/subline.rb +25 -0
- data/lib/subtitle_it/substyle.rb +11 -0
- data/lib/subtitle_it/subtime.rb +41 -0
- data/lib/subtitle_it/subtitle.rb +76 -0
- data/lib/subtitle_it/version.rb +9 -0
- data/lib/subtitle_it.rb +22 -0
- data/script/console +10 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/script/txt2html +82 -0
- data/setup.rb +1585 -0
- data/spec/fixtures/godfather.srt +2487 -0
- data/spec/fixtures/huge.ass +22 -0
- data/spec/fixtures/movie.xml +28 -0
- data/spec/fixtures/movie.yml +163 -0
- data/spec/fixtures/pseudo.rsb +6 -0
- data/spec/fixtures/pulpfiction.sub +2025 -0
- data/spec/fixtures/puplfiction.mpl +12 -0
- data/spec/fixtures/sincity.yml +12 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +71 -0
- data/spec/subtitle_it/bin_spec.rb +142 -0
- data/spec/subtitle_it/fixes_spec.rb +0 -0
- data/spec/subtitle_it/formats/ass_spec.rb +5 -0
- data/spec/subtitle_it/formats/mpl_spec.rb +45 -0
- data/spec/subtitle_it/formats/rsb_spec.rb +42 -0
- data/spec/subtitle_it/formats/srt_spec.rb +57 -0
- data/spec/subtitle_it/formats/sub_spec.rb +49 -0
- data/spec/subtitle_it/formats/xml_spec.rb +60 -0
- data/spec/subtitle_it/formats/yml_spec.rb +20 -0
- data/spec/subtitle_it/generator_spec.rb +0 -0
- data/spec/subtitle_it/movie_hasher_spec.rb +13 -0
- data/spec/subtitle_it/movie_spec.rb +25 -0
- data/spec/subtitle_it/subdown_spec.rb +94 -0
- data/spec/subtitle_it/subline_spec.rb +30 -0
- data/spec/subtitle_it/substyle_spec.rb +1 -0
- data/spec/subtitle_it/subtime_spec.rb +73 -0
- data/spec/subtitle_it/subtitle_spec.rb +40 -0
- data/spec/subtitle_it_spec.rb +11 -0
- data/subtitle_it.gemspec +41 -0
- data/tasks/deployment.rake +34 -0
- data/tasks/environment.rake +7 -0
- data/tasks/rspec.rake +21 -0
- metadata +152 -0
@@ -0,0 +1,45 @@
|
|
1
|
+
# SubtitleIt
|
2
|
+
# SRT - Subrip format
|
3
|
+
#
|
4
|
+
# N
|
5
|
+
# 00:55:21,600 --> 00:55:27,197
|
6
|
+
# lt's not even 20 years. You've sold the
|
7
|
+
# casinos and made fortunes for all of us.
|
8
|
+
#
|
9
|
+
# Where N is the sub index number
|
10
|
+
#
|
11
|
+
module Formats
|
12
|
+
include PlatformEndLine
|
13
|
+
def parse_srt
|
14
|
+
endl = endline( @raw )
|
15
|
+
@raw.split( endl*2 ).inject([]) do |final,line|
|
16
|
+
line = line.split(endl)
|
17
|
+
line.delete_at(0)
|
18
|
+
time_on,time_off = line[0].split('-->').map { |t| t.strip }
|
19
|
+
line.delete_at(0)
|
20
|
+
text = line.join("|")
|
21
|
+
final << Subline.new(time_on, time_off, text)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_srt
|
26
|
+
endl = endline( @raw )
|
27
|
+
out = []
|
28
|
+
@lines.each_with_index do |l,i|
|
29
|
+
out << "#{i+1}"
|
30
|
+
out << "%s --> %s" % [l.time_on.to_s(','), l.time_off.to_s(',')]
|
31
|
+
out << l.text.gsub("|", endl) + endl
|
32
|
+
end
|
33
|
+
out.join( endl )
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
#looks like subrip accepts some styling:
|
38
|
+
# sdict.add(new StyledFormat(ITALIC, "i", true));
|
39
|
+
# sdict.add(new StyledFormat(ITALIC, "/i", false));
|
40
|
+
# sdict.add(new StyledFormat(BOLD, "b", true));
|
41
|
+
# sdict.add(new StyledFormat(BOLD, "/b", false));
|
42
|
+
# sdict.add(new StyledFormat(UNDERLINE, "u", true));
|
43
|
+
# sdict.add(new StyledFormat(UNDERLINE, "/u", false));
|
44
|
+
# sdict.add(new StyledFormat(STRIKETHROUGH, "s", true));
|
45
|
+
# sdict.add(new StyledFormat(STRIKETHROUGH, "/s", false));
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# SubtitleIt
|
2
|
+
# MPSub, MicroDVD or VOBSub format
|
3
|
+
#TODO: tricky.. detect which format we got.
|
4
|
+
#
|
5
|
+
# {1025}{1115}You always say that.|The same thing every time.
|
6
|
+
# {1118}{1177}"l'm throug h, never again,|too dangerous."
|
7
|
+
#
|
8
|
+
# MicroDVD:
|
9
|
+
# {1}{1}25.000
|
10
|
+
# {2447}{2513}You should come to the Drama Club, too.
|
11
|
+
# {2513}{2594}Yeah. The Drama Club is worried|that you haven't been coming.
|
12
|
+
# {2603}{2675}I see. Sorry, I'll drop by next time.
|
13
|
+
#
|
14
|
+
# Where N is ms / framerate / 1000 (ms -> s)
|
15
|
+
#
|
16
|
+
# parts of the code from 'simplesubtitler' from Marcin (tiraeth) Chwedziak
|
17
|
+
#
|
18
|
+
module Formats
|
19
|
+
include PlatformEndLine
|
20
|
+
#between our formats, what changes can be reduced to a value
|
21
|
+
def ratio
|
22
|
+
1
|
23
|
+
end
|
24
|
+
|
25
|
+
def parse_sub
|
26
|
+
@raw.to_a.inject([]) do |i,l|
|
27
|
+
line_data = l.scan(/^\{([0-9]{1,})\}\{([0-9]{1,})\}(.+)$/)
|
28
|
+
line_data = line_data.at 0
|
29
|
+
time_on, time_off, text = line_data
|
30
|
+
time_on, time_off = [time_on.to_i, time_off.to_i].map do |t|
|
31
|
+
(t.to_i / @fps * 1000 / ratio).to_i
|
32
|
+
end
|
33
|
+
i << Subline.new(time_on, time_off, text.chomp)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_sub
|
38
|
+
endl = endline( @raw )
|
39
|
+
line_ary = []
|
40
|
+
@lines.each do |l|
|
41
|
+
line_ary << "{%d}{%d}%s" % [parse_time(l.time_on), parse_time(l.time_off), l.text]
|
42
|
+
end
|
43
|
+
return line_ary.join( endl ) + endl
|
44
|
+
end
|
45
|
+
|
46
|
+
def parse_time(n)
|
47
|
+
n.to_i / 1000 * @fps * ratio
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# SubtitleIt
|
2
|
+
# XML TT Timed Text
|
3
|
+
# http://www.w3.org/TR/2006/CR-ttaf1-dfxp-20061116/
|
4
|
+
# http://livedocs.adobe.com/flash/9.0/main/wwhelp/wwhimpl/common/html/wwhelp.htm?context=LiveDocs_Parts&file=00000604.html
|
5
|
+
#
|
6
|
+
# <?xml version="1.0" encoding="UTF-8"?>
|
7
|
+
# <tt xml:lang="en" xmlns="http://www.w3.org/2006/04/ttaf1" xmlns:tts="http://www.w3.org/2006/04/ttaf1#styling">
|
8
|
+
# <head>
|
9
|
+
# <styling>
|
10
|
+
# <style id="1" tts:textAlign="right"/>
|
11
|
+
# <style id="2" tts:color="transparent"/>
|
12
|
+
# </styling>
|
13
|
+
# </head>
|
14
|
+
# <body>
|
15
|
+
# <div xml:lang="en">
|
16
|
+
# <p begin="00:00:00.00" dur="00:00:03.07">I had just joined <span tts:fontFamily="monospaceSansSerif,proportionalSerif,TheOther"tts:fontSize="+2">Macromedia</span> in 1996,</p>
|
17
|
+
# <p begin="00:00:03.07" dur="00:00:03.35">and we were trying to figure out what to do about the internet.</p>
|
18
|
+
# <p begin="00:00:29.02" dur="00:00:01.30" style="1">as <span tts:color="#ccc333">easy</span> as drawing on paper.</p>
|
19
|
+
# </div>
|
20
|
+
# </body>
|
21
|
+
#</tt>
|
22
|
+
require 'hpricot'
|
23
|
+
module Formats
|
24
|
+
include PlatformEndLine
|
25
|
+
|
26
|
+
def parse_xml
|
27
|
+
final = []
|
28
|
+
doc = Hpricot.XML(@raw)
|
29
|
+
(doc/'tt'/'p').each do |line|
|
30
|
+
time_on, time_off = ['begin', 'dur'].map { |str| line[str.to_sym] }
|
31
|
+
text = line.innerHTML
|
32
|
+
final << Subline.new(time_on,time_off,text)
|
33
|
+
end
|
34
|
+
return final
|
35
|
+
end
|
36
|
+
|
37
|
+
def xml_lines
|
38
|
+
endl = endline( @raw )
|
39
|
+
line_ary = []
|
40
|
+
@lines.each do |l|
|
41
|
+
toff = l.time_off - l.time_on
|
42
|
+
line_ary << " <p begin=\"#{l.time_on}\" dur=\"#{toff}\">#{l.text}</p>"
|
43
|
+
end
|
44
|
+
return line_ary.join( endl )
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_xml
|
48
|
+
endl = endline( @raw )
|
49
|
+
out = <<XML
|
50
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
51
|
+
<tt xml:lang="en" xmlns="http://www.w3.org/2006/04/ttaf1" xmlns:tts="http://www.w3.org/2006/04/ttaf1#styling">
|
52
|
+
<head>
|
53
|
+
<styling>#{@style + endl if @style}
|
54
|
+
</styling>
|
55
|
+
</head>
|
56
|
+
<body>
|
57
|
+
<div xml:lang="en">
|
58
|
+
#{xml_lines}
|
59
|
+
</div>
|
60
|
+
</body>
|
61
|
+
</tt>
|
62
|
+
XML
|
63
|
+
out.chomp
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# SubtitleIt
|
2
|
+
# YML Dump
|
3
|
+
#
|
4
|
+
require 'yaml'
|
5
|
+
module Formats
|
6
|
+
def parse_yml
|
7
|
+
@yaml = YAML::load(@raw)
|
8
|
+
header = @yaml.delete('header')
|
9
|
+
@title = header['title']
|
10
|
+
@author = header['authors']
|
11
|
+
@version = header['version']
|
12
|
+
@yaml['lines'].map { |l| Subline.new(l[0], l[1], l[2]) }
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_yml
|
16
|
+
YAML.dump(self)
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module SubtitleIt
|
2
|
+
# class Generate
|
3
|
+
# def generate_rsb(filename)
|
4
|
+
# dump = <<GEN
|
5
|
+
# - title: #{argv[0]}
|
6
|
+
# - authors: FIXME
|
7
|
+
# - version: FIXME
|
8
|
+
# 00:05:26.500 => 00:05:28.500 == worth killing for...
|
9
|
+
# 00:06:00.400 => 00:06:03.400 == worth dying for...
|
10
|
+
# 00:07:00.300 => 00:07:03.300 == worth going to the hell for...
|
11
|
+
# GEN
|
12
|
+
# write_out(filename, dump)
|
13
|
+
# end
|
14
|
+
# end
|
15
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module SubtitleIt
|
2
|
+
#TODO: create a lang class?
|
3
|
+
# now I`m more lost.... opensubtitle uses a 3 chars like
|
4
|
+
# Por => Portuguese
|
5
|
+
# Cze => Czech ....
|
6
|
+
LANGS = {
|
7
|
+
:aa => 'Afar',
|
8
|
+
:ab => 'Abkhazian',
|
9
|
+
:af => 'Afrikaans',
|
10
|
+
:ak => 'Akan',
|
11
|
+
:sq => 'Albanian',
|
12
|
+
:am => 'Amharic',
|
13
|
+
:ar => 'Arabic',
|
14
|
+
:pb => 'Brazilian Portuguese',
|
15
|
+
:hy => 'Armenian',
|
16
|
+
:bs => 'Bosnian',
|
17
|
+
:bg => 'Bulgarian',
|
18
|
+
:ca => 'Catalan',
|
19
|
+
:zh => 'Chinese',
|
20
|
+
:cs => 'Czech',
|
21
|
+
:da => 'Danish',
|
22
|
+
:nl => 'Dutch',
|
23
|
+
:et => 'Estonian',
|
24
|
+
:fr => 'French',
|
25
|
+
:de => 'German',
|
26
|
+
:gl => 'Galician',
|
27
|
+
:el => 'Greek',
|
28
|
+
:he => 'Hebrew',
|
29
|
+
:hi => 'Hindi',
|
30
|
+
:hr => 'Croatian',
|
31
|
+
:hu => 'Hungarian',
|
32
|
+
:is => 'Icelandic',
|
33
|
+
:id => 'Indonesian',
|
34
|
+
:it => 'Italian',
|
35
|
+
:ja => 'Japanese',
|
36
|
+
:kk => 'Kazakh',
|
37
|
+
:ko => 'Korean',
|
38
|
+
:lv => 'Latvian',
|
39
|
+
:lt => 'Lithuanian',
|
40
|
+
:lb => 'Luxembourgish',
|
41
|
+
:mk => 'Macedonian',
|
42
|
+
:ms => 'Malay',
|
43
|
+
:no => 'Norwegian',
|
44
|
+
:pl => 'Polish',
|
45
|
+
:pt => 'Portuguese',
|
46
|
+
:ru => 'Russian',
|
47
|
+
:sr => 'Serbian',
|
48
|
+
:es => 'Spanish',
|
49
|
+
:sk => 'Slovak',
|
50
|
+
:sl => 'Slovenian',
|
51
|
+
:sv => 'Swedish',
|
52
|
+
:th => 'Thai',
|
53
|
+
:tr => 'Turkish',
|
54
|
+
:uk => 'Ukrainian',
|
55
|
+
:vi => 'Vietnamese',
|
56
|
+
:ro => 'Romanian',
|
57
|
+
:pb => 'Brazilian',
|
58
|
+
:ay => 'Assyrian'
|
59
|
+
}
|
60
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# downsub - github.com/johanlunds/downsub
|
2
|
+
require 'subtitle_it/movie_hasher'
|
3
|
+
|
4
|
+
module SubtitleIt
|
5
|
+
class Movie
|
6
|
+
|
7
|
+
attr_reader :filename
|
8
|
+
attr_accessor :info
|
9
|
+
|
10
|
+
def initialize(filename)
|
11
|
+
@filename = filename
|
12
|
+
@haxx = nil
|
13
|
+
@info = {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def haxx
|
17
|
+
@haxx ||= MovieHasher::compute_haxx(@filename)
|
18
|
+
end
|
19
|
+
|
20
|
+
def size
|
21
|
+
File.size(@filename)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# downsub - github.com/johanlunds/downsub
|
2
|
+
module SubtitleIt
|
3
|
+
module MovieHasher
|
4
|
+
|
5
|
+
CHUNK_SIZE = 64 * 1024 # in bytes
|
6
|
+
|
7
|
+
def self.compute_haxx(filename)
|
8
|
+
filesize = File.size(filename)
|
9
|
+
haxx = filesize
|
10
|
+
|
11
|
+
# Read 64 kbytes, divide up into 64 bits and add each
|
12
|
+
# to hash. Do for beginning and end of file.
|
13
|
+
File.open(filename, 'rb') do |f|
|
14
|
+
# Q = unsigned long long = 64 bit
|
15
|
+
f.read(CHUNK_SIZE).unpack("Q*").each do |n|
|
16
|
+
haxx = haxx + n & 0xffffffffffffffff # to remain as 64 bit number
|
17
|
+
end
|
18
|
+
|
19
|
+
f.seek([0, filesize - CHUNK_SIZE].max, IO::SEEK_SET)
|
20
|
+
|
21
|
+
# And again for the end of the file
|
22
|
+
f.read(CHUNK_SIZE).unpack("Q*").each do |n|
|
23
|
+
haxx = haxx + n & 0xffffffffffffffff
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
sprintf("%016x", haxx)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
## downsub - github.com/johanlunds/downsub
|
2
|
+
require 'xmlrpc/client'
|
3
|
+
require 'zlib'
|
4
|
+
require 'stringio'
|
5
|
+
|
6
|
+
require 'subtitle_it/version'
|
7
|
+
require 'subtitle_it/subtitle'
|
8
|
+
|
9
|
+
module SubtitleIt
|
10
|
+
class Subdown
|
11
|
+
HOST = "http://www.opensubtitles.org/xml-rpc"
|
12
|
+
HOST_DEV = "http://dev.opensubtitles.org/xml-rpc"
|
13
|
+
|
14
|
+
USER_AGENT = "SubtitleIt #{SubtitleIt::VERSION::STRING}"
|
15
|
+
|
16
|
+
NO_TOKEN = %w(ServerInfo LogIn)
|
17
|
+
|
18
|
+
def initialize(host = HOST)
|
19
|
+
@client = XMLRPC::Client.new2(host)
|
20
|
+
@token = nil
|
21
|
+
end
|
22
|
+
|
23
|
+
def log_in!
|
24
|
+
result = call('LogIn', '', '', '', USER_AGENT)
|
25
|
+
@token = result['token'].to_s
|
26
|
+
end
|
27
|
+
|
28
|
+
def logged_in?
|
29
|
+
!@token.nil? && !@token.empty?
|
30
|
+
end
|
31
|
+
|
32
|
+
def log_out!
|
33
|
+
call('LogOut')
|
34
|
+
@token = nil
|
35
|
+
end
|
36
|
+
|
37
|
+
def server_info
|
38
|
+
call('ServerInfo')
|
39
|
+
end
|
40
|
+
|
41
|
+
def search_subtitles(movie, lang='')
|
42
|
+
lang ||= ""
|
43
|
+
puts la
|
44
|
+
args = {
|
45
|
+
'sublanguageid' => lang,
|
46
|
+
'moviehash' => movie.haxx,
|
47
|
+
'moviebytesize' => movie.size
|
48
|
+
}
|
49
|
+
|
50
|
+
result = call('SearchSubtitles', [args])
|
51
|
+
return [] unless result['data'] # if no results result['data'] == false
|
52
|
+
result['data'].inject([]) do |subs, sub_info|
|
53
|
+
subs << Subtitle.new({:info => sub_info})
|
54
|
+
subs
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def download_subtitle(sub)
|
59
|
+
result = call('DownloadSubtitles', [sub.osdb_id])
|
60
|
+
sub.data = self.class.decode_and_unzip(result['data'][0]['data'])
|
61
|
+
end
|
62
|
+
|
63
|
+
def upload_subtitle(movie, subs)
|
64
|
+
end
|
65
|
+
|
66
|
+
def imdb_info(movie)
|
67
|
+
result = call('CheckMovieHash', [movie.haxx])
|
68
|
+
movie.info = result['data'][movie.haxx] # TODO: Handle if no result for movie
|
69
|
+
end
|
70
|
+
|
71
|
+
def subtitle_languages
|
72
|
+
LANGS.map { |l| l[0].to_s }
|
73
|
+
# TODO.. get the correct codes
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def call(method, *args)
|
79
|
+
unless NO_TOKEN.include? method
|
80
|
+
raise 'Need to be logged in for this.' unless logged_in?
|
81
|
+
args = [@token, *args]
|
82
|
+
end
|
83
|
+
|
84
|
+
result = @client.call(method, *args)
|
85
|
+
# $LOG.debug "Client#call #{method}, #{args.inspect}: #{result.inspect}"
|
86
|
+
|
87
|
+
if !self.class.result_status_ok?(result)
|
88
|
+
raise XMLRPC::FaultException.new(result['status'].to_i, result['status'][4..-1]) # 'status' of the form 'XXX Message'
|
89
|
+
end
|
90
|
+
|
91
|
+
result
|
92
|
+
end
|
93
|
+
|
94
|
+
# Returns true if status is OK (ie. in range 200-299) or don't exists.
|
95
|
+
def self.result_status_ok?(result)
|
96
|
+
!result.key?('status') || (200...300) === result['status'].to_i
|
97
|
+
end
|
98
|
+
|
99
|
+
def prevent_session_expiration
|
100
|
+
call('NoOperation')
|
101
|
+
end
|
102
|
+
|
103
|
+
def self.decode_and_unzip(data)
|
104
|
+
Zlib::GzipReader.new(StringIO.new(XMLRPC::Base64.decode(data))).read
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# SubtitleIt
|
2
|
+
# Holds a subtitle`s line.
|
3
|
+
module SubtitleIt
|
4
|
+
class Subline
|
5
|
+
attr_accessor :time_on, :time_off, :text
|
6
|
+
# time_on/off may be:
|
7
|
+
# HH:MM:SS,MMM
|
8
|
+
# MM:SS
|
9
|
+
# S
|
10
|
+
# text lines should be separated by |
|
11
|
+
def initialize(time_on, time_off, text)
|
12
|
+
@time_on, @time_off = filter(time_on, time_off)
|
13
|
+
# ugly FIXME: when pseudo uses time => 3 or TT format
|
14
|
+
# need to add seconds on time_off to time_on
|
15
|
+
@time_off += @time_on if @time_off < @time_on
|
16
|
+
@text = text
|
17
|
+
end
|
18
|
+
|
19
|
+
def filter(*args)
|
20
|
+
args.map do |arg|
|
21
|
+
Subtime.new(arg)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# SubtitleIt
|
2
|
+
# Time class
|
3
|
+
module SubtitleIt
|
4
|
+
class Subtime
|
5
|
+
attr_accessor :hrs, :min, :sec, :ms
|
6
|
+
|
7
|
+
def initialize(data)
|
8
|
+
raise if data == nil
|
9
|
+
parse_data(data)
|
10
|
+
end
|
11
|
+
|
12
|
+
# parses string like '00:00:00,000' or single number as ms.
|
13
|
+
def parse_data(data)
|
14
|
+
case data
|
15
|
+
when Numeric
|
16
|
+
@sec, @ms = data.divmod(1000)
|
17
|
+
@min, @sec = @sec.divmod(60)
|
18
|
+
@hrs, @min = @min.divmod(60)
|
19
|
+
when String
|
20
|
+
time, float = data.split(/\.|\,/)
|
21
|
+
time = time.split(/:/).map { |s| s.to_i }
|
22
|
+
@ms = (("0.%d" % float.to_i).to_f * 1000).to_i if float
|
23
|
+
@sec, @min, @hrs = time.reverse
|
24
|
+
else raise "Format unknown."
|
25
|
+
end
|
26
|
+
#FIXME: dunno what to do about this.. nil = problems with to_i
|
27
|
+
@hrs ||= 0; @min ||= 0; @sec ||= 0; @ms ||= 0
|
28
|
+
end
|
29
|
+
|
30
|
+
# to_s(separator) => to_s(",") => 00:00:00,000
|
31
|
+
def to_s(sep='.'); "%02d:%02d:%02d#{sep}%03d" % [@hrs, @min, @sec, @ms]; end
|
32
|
+
|
33
|
+
# return time as a total in ms
|
34
|
+
def to_i; ( @hrs * 3600 + @min * 60 + @sec ) * 1000 + @ms; end
|
35
|
+
|
36
|
+
def +(other); Subtime.new(self.to_i + other.to_i); end
|
37
|
+
def -(other); Subtime.new(self.to_i - other.to_i); end
|
38
|
+
def <=>(other); self.to_i <=> other.to_i; end
|
39
|
+
include Comparable
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'subtitle_it/formats/srt'
|
2
|
+
require 'subtitle_it/formats/sub'
|
3
|
+
require 'subtitle_it/formats/yml'
|
4
|
+
require 'subtitle_it/formats/rsb'
|
5
|
+
require 'subtitle_it/formats/xml'
|
6
|
+
require 'subtitle_it/formats/mpl'
|
7
|
+
|
8
|
+
# http://en.wikipedia.org/wiki/List_of_ISO_639-2_codes
|
9
|
+
# http://www.opensubtitles.org/addons/export_languages.php
|
10
|
+
|
11
|
+
module SubtitleIt
|
12
|
+
|
13
|
+
MOVIE_EXTS = %w(3g2 3gp 3gp2 3gpp 60d ajp asf asx avchd avi bik bix box cam dat divx dmf dv dvr-ms evo flc fli flic flv flx gvi gvp h264 m1v m2p m2ts m2v m4e m4v mjp mjpeg mjpg mkv moov mov movhd movie movx mp4 mpe mpeg mpg mpv mpv2 mxf nsv nut ogg ogm omf ps qt ram rm rmvb swf ts vfw vid video viv vivo vob vro wm wmv wmx wrap wvx wx x264 xvid)
|
14
|
+
SUB_EXTS = %w(srt sub smi txt ssa ass mpl xml yml rsb)
|
15
|
+
|
16
|
+
|
17
|
+
class Subtitle
|
18
|
+
include Formats
|
19
|
+
attr_reader :id, :raw, :format, :lines, :style, :info, :filename, :rating, :language, :user, :release_name,
|
20
|
+
:osdb_id, :download_count, :download_url, :original_filename
|
21
|
+
|
22
|
+
def initialize(args = {})
|
23
|
+
# Looks like opensubtitle is the only provider around..
|
24
|
+
# If a second one comes need big refactor...
|
25
|
+
if @info = args[:info]
|
26
|
+
#@osdb_info = info
|
27
|
+
@osdb_id = @info['IDSubtitleFile'].to_s
|
28
|
+
@original_filename = @info['SubFileName'].to_s
|
29
|
+
@format = @info['SubFormat'].to_s
|
30
|
+
@user = @info['UserNickName'].to_s
|
31
|
+
@language = @info['LanguageName'].to_s
|
32
|
+
@release_name = @info['MovieReleaseName'].to_s
|
33
|
+
@download_count = @info['SubDownloadsCnt'].to_i
|
34
|
+
@rating = @info['SubRating'].to_f
|
35
|
+
@uploaded_at = @info['SubAddDate'].to_s # TODO: convert to time object?
|
36
|
+
@download_url = @info['SubDownloadLink'].to_s
|
37
|
+
end
|
38
|
+
@fps = args[:fps] || 23.976
|
39
|
+
parse_dump(args[:dump], args[:format]) if args[:dump]
|
40
|
+
end
|
41
|
+
|
42
|
+
def style=(s)
|
43
|
+
@style = s
|
44
|
+
end
|
45
|
+
|
46
|
+
def fps=(fps)
|
47
|
+
@fps = fps
|
48
|
+
end
|
49
|
+
|
50
|
+
def data=(data)
|
51
|
+
@raw = data
|
52
|
+
end
|
53
|
+
|
54
|
+
def <=>(other)
|
55
|
+
self.rating <=> other.rating
|
56
|
+
end
|
57
|
+
include Comparable
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def parse_dump(dump,format)
|
62
|
+
raise unless SUB_EXTS.include?(format)
|
63
|
+
@raw = dump.kind_of?(String) ? dump : dump.read
|
64
|
+
@format = format
|
65
|
+
parse_lines!
|
66
|
+
end
|
67
|
+
|
68
|
+
def parse_lines!
|
69
|
+
self.lines = send :"parse_#{@format}"
|
70
|
+
end
|
71
|
+
|
72
|
+
def lines=(lines)
|
73
|
+
@lines = lines
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
data/lib/subtitle_it.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__)) unless
|
2
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
3
|
+
require 'rubygems'
|
4
|
+
require 'subtitle_it/version'
|
5
|
+
require 'subtitle_it/languages'
|
6
|
+
require 'subtitle_it/subtime'
|
7
|
+
require 'subtitle_it/subline'
|
8
|
+
require 'subtitle_it/platform_endl'
|
9
|
+
require 'subtitle_it/subtitle'
|
10
|
+
require 'subtitle_it/subdown'
|
11
|
+
require 'subtitle_it/substyle'
|
12
|
+
require 'subtitle_it/movie'
|
13
|
+
require 'subtitle_it/bin'
|
14
|
+
|
15
|
+
module SubtitleIt
|
16
|
+
end
|
17
|
+
|
18
|
+
class Numeric
|
19
|
+
def reduce
|
20
|
+
self / ( 10 ** Math::log10(self).to_i)
|
21
|
+
end
|
22
|
+
end
|
data/script/console
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# File: script/console
|
3
|
+
irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
|
4
|
+
|
5
|
+
libs = " -r irb/completion"
|
6
|
+
# Perhaps use a console_lib to store any extra methods I may want available in the cosole
|
7
|
+
# libs << " -r #{File.dirname(__FILE__) + '/../lib/console_lib/console_logger.rb'}"
|
8
|
+
libs << " -r #{File.dirname(__FILE__) + '/../lib/subtitle_it.rb'}"
|
9
|
+
puts "Loading subtitle_it gem"
|
10
|
+
exec "#{irb} #{libs} --simple-prompt"
|
data/script/destroy
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'rubigen'
|
6
|
+
rescue LoadError
|
7
|
+
require 'rubygems'
|
8
|
+
require 'rubigen'
|
9
|
+
end
|
10
|
+
require 'rubigen/scripts/destroy'
|
11
|
+
|
12
|
+
ARGV.shift if ['--help', '-h'].include?(ARGV[0])
|
13
|
+
RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
|
14
|
+
RubiGen::Scripts::Destroy.new.run(ARGV)
|
data/script/generate
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'rubigen'
|
6
|
+
rescue LoadError
|
7
|
+
require 'rubygems'
|
8
|
+
require 'rubigen'
|
9
|
+
end
|
10
|
+
require 'rubigen/scripts/generate'
|
11
|
+
|
12
|
+
ARGV.shift if ['--help', '-h'].include?(ARGV[0])
|
13
|
+
RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
|
14
|
+
RubiGen::Scripts::Generate.new.run(ARGV)
|