subtitle-library 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/bin/subtitle-library +113 -0
- data/lib/subtitle-library.rb +3 -0
- data/lib/subtitle-library/changer.rb +117 -0
- data/lib/subtitle-library/cue.rb +16 -0
- data/lib/subtitle-library/reader.rb +357 -0
- data/lib/subtitle-library/regex-patterns.rb +8 -0
- data/lib/subtitle-library/writer.rb +93 -0
- data/test/integration/command-line-spec.rb +626 -0
- data/test/unit/changer-spec.rb +891 -0
- data/test/unit/reader-spec.rb +1053 -0
- data/test/unit/writer-spec.rb +830 -0
- metadata +84 -0
@@ -0,0 +1,113 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'subtitle-library'
|
4
|
+
|
5
|
+
HELP_MENU = <<-eos
|
6
|
+
-f : The local file, to do an operation on.
|
7
|
+
-op : The operation to be performed.
|
8
|
+
'recognise' - recognise the subtitle format
|
9
|
+
'verify' - verify subtitle syntax
|
10
|
+
'carriage' - set the max line length
|
11
|
+
'shift' - shift the subtitles by seconds/frames
|
12
|
+
'stretch' - stretch the subtitles
|
13
|
+
'save' - save the subtitles in new file (perhaps with a new format)
|
14
|
+
-cr : The max line length to be used
|
15
|
+
-ss : The amount of seconds to be used when shifting/stretching
|
16
|
+
-fs : The amount of frames to be used when shifting/stretching
|
17
|
+
-fps : Frames per second (will be used when saving/changing subtitles)
|
18
|
+
-nf : The path to the new file to be saved
|
19
|
+
-t : The type of the new file
|
20
|
+
'sr' - SubRip subtitle type
|
21
|
+
'md' - MicroDVD type
|
22
|
+
'sv' - SubViewer type
|
23
|
+
eos
|
24
|
+
|
25
|
+
index = 0
|
26
|
+
fps = -1
|
27
|
+
while index < ARGV.length
|
28
|
+
case ARGV[index]
|
29
|
+
when '-f'
|
30
|
+
index += 1
|
31
|
+
subs_path = ARGV[index]
|
32
|
+
when '-op'
|
33
|
+
index += 1
|
34
|
+
operation = ARGV[index]
|
35
|
+
when '-cr'
|
36
|
+
index += 1
|
37
|
+
carriage = ARGV[index].to_i
|
38
|
+
when '-ss'
|
39
|
+
index += 1
|
40
|
+
seconds = ARGV[index].to_f
|
41
|
+
when '-fs'
|
42
|
+
index += 1
|
43
|
+
frames = ARGV[index].to_f
|
44
|
+
when '-fps'
|
45
|
+
index += 1
|
46
|
+
fps = ARGV[index].to_f
|
47
|
+
when '-nf'
|
48
|
+
index += 1
|
49
|
+
new_path = ARGV[index]
|
50
|
+
when '-t'
|
51
|
+
index += 1
|
52
|
+
type = ARGV[index]
|
53
|
+
end
|
54
|
+
index += 1
|
55
|
+
end
|
56
|
+
|
57
|
+
if operation and subs_path
|
58
|
+
reader = SubsReader.new subs_path
|
59
|
+
if reader.type == 'uk'
|
60
|
+
puts 'Unknown file format.'
|
61
|
+
else
|
62
|
+
case operation
|
63
|
+
when 'recognise'
|
64
|
+
case reader.type
|
65
|
+
when 'sr'
|
66
|
+
puts 'SubRip format.'
|
67
|
+
when 'md'
|
68
|
+
puts 'MicroDVD format.'
|
69
|
+
when 'sv'
|
70
|
+
puts 'SubViewer format.'
|
71
|
+
end
|
72
|
+
when 'verify'
|
73
|
+
puts reader.check_syntax
|
74
|
+
when 'carriage'
|
75
|
+
if not carriage or carriage <= 0
|
76
|
+
puts "Select a positive number for a max line length (-cr option).\n" + HELP_MENU
|
77
|
+
else
|
78
|
+
SubsChanger.new(subs_path).set_max_line carriage
|
79
|
+
end
|
80
|
+
when 'shift'
|
81
|
+
if not seconds and not frames
|
82
|
+
puts "Select an amount of seconds (-ss) or frames (-fs) to shift by.\n" + HELP_MENU
|
83
|
+
elsif seconds
|
84
|
+
SubsChanger.new(subs_path).shift 'ss', seconds, fps
|
85
|
+
else
|
86
|
+
SubsChanger.new(subs_path).shift 'fs', frames, fps
|
87
|
+
end
|
88
|
+
when 'stretch'
|
89
|
+
if not seconds and not frames
|
90
|
+
puts "Select an amount of seconds (-ss) or frames (-fs) to stretch by.\n" + HELP_MENU
|
91
|
+
elsif seconds
|
92
|
+
SubsChanger.new(subs_path).stretch 'ss', seconds, fps
|
93
|
+
else
|
94
|
+
SubsChanger.new(subs_path).stretch 'fs', frames, fps
|
95
|
+
end
|
96
|
+
when 'save'
|
97
|
+
if not new_path or not type
|
98
|
+
puts "Select the new name of the file (-nf) and the type it should be (-t).\n" + HELP_MENU
|
99
|
+
elsif not ['sr', 'md', 'sv'].member? type
|
100
|
+
puts "Select the type of the new file (-t option) from one of the listed below.\n" + HELP_MENU
|
101
|
+
else
|
102
|
+
reader.load_cues
|
103
|
+
SubsWriter.new(reader).save_as new_path, type, fps
|
104
|
+
end
|
105
|
+
else
|
106
|
+
puts "Select one of the operations listed below.\n" + HELP_MENU
|
107
|
+
end
|
108
|
+
end
|
109
|
+
elsif not operation
|
110
|
+
puts "Select an operation to be performed (-op option).\n" + HELP_MENU
|
111
|
+
else
|
112
|
+
puts "Select a local subtitle file (-f option).\n" + HELP_MENU
|
113
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
class SubsChanger
|
2
|
+
require 'subtitle-library/reader'
|
3
|
+
require 'subtitle-library/writer'
|
4
|
+
|
5
|
+
def initialize(subs_path)
|
6
|
+
@subs_path = subs_path
|
7
|
+
@reader = SubsReader.new subs_path
|
8
|
+
@reader.load_cues
|
9
|
+
end
|
10
|
+
|
11
|
+
def shift(disp_type, pos, fps = -1)
|
12
|
+
fps = @reader.fps if fps == -1
|
13
|
+
if @reader.type == 'md'
|
14
|
+
disposition_microdvd pos, fps, false, disp_type == 'ss'
|
15
|
+
else
|
16
|
+
disposition_timing pos, fps, false, disp_type == 'ss'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def stretch(disp_type, pos, fps = -1)
|
21
|
+
fps = @reader.fps if fps == -1
|
22
|
+
if @reader.type == 'md'
|
23
|
+
disposition_microdvd pos, fps, true, disp_type == 'ss'
|
24
|
+
else
|
25
|
+
disposition_timing pos, fps, true, disp_type == 'ss'
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def disposition_microdvd(pos, fps, stretch, disp_seconds)
|
30
|
+
bottom_time = Time.mktime 1, 1, 1
|
31
|
+
last_end_time = bottom_time
|
32
|
+
invalid_timing = false
|
33
|
+
if stretch
|
34
|
+
step = disp_seconds ? pos * fps : pos
|
35
|
+
disposition = 0
|
36
|
+
else
|
37
|
+
disposition = disp_seconds ? (pos * fps).ceil : pos.ceil
|
38
|
+
end
|
39
|
+
@reader.cues.each do |cue|
|
40
|
+
cue.start += disposition
|
41
|
+
cue.ending += disposition
|
42
|
+
new_start = bottom_time + cue.start / fps
|
43
|
+
new_end = bottom_time + cue.ending / fps
|
44
|
+
if new_start.year + new_start.month + new_start.day +
|
45
|
+
new_end.year + new_end.month + new_end.day != 6 or
|
46
|
+
new_start < bottom_time or new_end < bottom_time or
|
47
|
+
new_start < last_end_time
|
48
|
+
invalid_timing = true
|
49
|
+
puts 'Invalid timing'
|
50
|
+
break
|
51
|
+
end
|
52
|
+
last_end_time = new_end
|
53
|
+
disposition = (disposition + step).ceil if stretch
|
54
|
+
end
|
55
|
+
SubsWriter.new(@reader).save_as(@subs_path, @reader.type) unless invalid_timing
|
56
|
+
end
|
57
|
+
|
58
|
+
def disposition_timing(pos, fps, stretch, disp_seconds)
|
59
|
+
bottom_time = Time.mktime 1, 1, 1
|
60
|
+
last_end_time = bottom_time
|
61
|
+
invalid_timing = false
|
62
|
+
if stretch
|
63
|
+
step = disp_seconds ? pos : pos / fps
|
64
|
+
disposition = 0
|
65
|
+
else
|
66
|
+
disposition = disp_seconds ? pos : pos / fps
|
67
|
+
end
|
68
|
+
@reader.cues.each do |cue|
|
69
|
+
cue.start += disposition
|
70
|
+
cue.ending += disposition
|
71
|
+
if cue.start.year + cue.start.month + cue.start.day +
|
72
|
+
cue.ending.year + cue.ending.month + cue.ending.day != 6 or
|
73
|
+
cue.start < bottom_time or cue.ending < bottom_time or
|
74
|
+
cue.start < last_end_time
|
75
|
+
invalid_timing = true
|
76
|
+
puts 'Invalid timing'
|
77
|
+
break
|
78
|
+
end
|
79
|
+
last_end_time = cue.ending
|
80
|
+
disposition += step if stretch
|
81
|
+
end
|
82
|
+
SubsWriter.new(@reader).save_as(@subs_path, @reader.type) unless invalid_timing
|
83
|
+
end
|
84
|
+
|
85
|
+
def set_max_line(max_line)
|
86
|
+
line_break = @reader.type == 'sr' ? "\n" : (@reader.type == 'md' ? '|' : '[br]')
|
87
|
+
@reader.cues.each do |cue|
|
88
|
+
text = cue.text
|
89
|
+
lines = text.split /\n+/
|
90
|
+
carriage_needed = false
|
91
|
+
lines.each do |line|
|
92
|
+
if line.length > max_line
|
93
|
+
carriage_needed = true
|
94
|
+
break
|
95
|
+
end
|
96
|
+
end
|
97
|
+
if carriage_needed
|
98
|
+
new_text = ''
|
99
|
+
words = lines.collect { |line| line.split /\s+/ }.flatten
|
100
|
+
current_line = ''
|
101
|
+
for i in (0 .. words.length - 1)
|
102
|
+
current_line += (current_line == '' ? '' : ' ') + words[i]
|
103
|
+
if i == words.length - 1
|
104
|
+
new_text += current_line
|
105
|
+
break
|
106
|
+
end
|
107
|
+
if current_line.length > max_line
|
108
|
+
new_text += current_line + line_break
|
109
|
+
current_line = ''
|
110
|
+
end
|
111
|
+
end
|
112
|
+
cue.text = new_text
|
113
|
+
end
|
114
|
+
end
|
115
|
+
SubsWriter.new(@reader).save_as @subs_path, @reader.type
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class Cue
|
2
|
+
attr_accessor :start, :ending, :text
|
3
|
+
|
4
|
+
def initialize(start, ending, text)
|
5
|
+
@start = start
|
6
|
+
@ending = ending
|
7
|
+
@text = text
|
8
|
+
end
|
9
|
+
|
10
|
+
def ==(other_cue)
|
11
|
+
@start == other_cue.start and
|
12
|
+
@ending == other_cue.ending and
|
13
|
+
@text == other_cue.text
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
@@ -0,0 +1,357 @@
|
|
1
|
+
class SubsReader
|
2
|
+
require 'subtitle-library/regex-patterns'
|
3
|
+
require 'subtitle-library/cue'
|
4
|
+
include Patterns
|
5
|
+
|
6
|
+
attr_reader :type, :fps
|
7
|
+
attr_accessor :cues
|
8
|
+
|
9
|
+
def initialize(subs_path)
|
10
|
+
@subs_path = subs_path
|
11
|
+
@type = recognize
|
12
|
+
case @type
|
13
|
+
when 'sr'
|
14
|
+
@inner_reader = SubRipReader.new subs_path
|
15
|
+
when 'md'
|
16
|
+
@inner_reader = MicroDVDReader.new subs_path
|
17
|
+
when 'sv'
|
18
|
+
@inner_reader = SubviewerReader.new subs_path
|
19
|
+
end
|
20
|
+
@fps = 23.976
|
21
|
+
end
|
22
|
+
|
23
|
+
def recognize
|
24
|
+
File.open(@subs_path, 'r') do |subs|
|
25
|
+
while line = subs.gets
|
26
|
+
return 'sr' if SUB_RIP_LINE =~ line
|
27
|
+
return 'md' if MICRO_DVD_LINE =~ line
|
28
|
+
return 'sv' if SUBVIEWER_LINE =~ line
|
29
|
+
end
|
30
|
+
end
|
31
|
+
'uk'
|
32
|
+
end
|
33
|
+
|
34
|
+
def load_cues
|
35
|
+
if @inner_reader
|
36
|
+
@cues, @fps = @inner_reader.read_subs false
|
37
|
+
else
|
38
|
+
'Unknown file format'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def check_syntax
|
43
|
+
if @inner_reader
|
44
|
+
@inner_reader.read_subs true
|
45
|
+
else
|
46
|
+
'Unknown file format'
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
class SubRipReader
|
53
|
+
include Patterns
|
54
|
+
|
55
|
+
def initialize(subs_path)
|
56
|
+
@subs_path = subs_path
|
57
|
+
@fps = 23.976
|
58
|
+
end
|
59
|
+
|
60
|
+
def read_subs(check_syntax)
|
61
|
+
cues = []
|
62
|
+
actual_lines = 0
|
63
|
+
error_log = ""
|
64
|
+
is_eof = false
|
65
|
+
last_end_time = Time.new 1, 1, 1
|
66
|
+
File.open(@subs_path, 'r') do |subs|
|
67
|
+
while true
|
68
|
+
is_eof, actual_lines, strip_line = read_until_timing(subs, actual_lines)
|
69
|
+
break if is_eof
|
70
|
+
if SUB_RIP_LINE =~ strip_line
|
71
|
+
start_time, end_time = parse_timing strip_line
|
72
|
+
valid_timing, error_log = check_timing start_time, end_time, last_end_time, error_log, check_syntax, actual_lines
|
73
|
+
unless valid_timing
|
74
|
+
is_eof, actual_lines, strip_line = read_until_index subs, actual_lines, strip_line, false
|
75
|
+
break if is_eof
|
76
|
+
next
|
77
|
+
end
|
78
|
+
last_end_time = end_time
|
79
|
+
line = subs.gets
|
80
|
+
break unless line
|
81
|
+
actual_lines += 1
|
82
|
+
text = line.strip
|
83
|
+
strip_line = line.strip
|
84
|
+
while strip_line != ''
|
85
|
+
line = subs.gets
|
86
|
+
unless line
|
87
|
+
is_eof = true
|
88
|
+
break
|
89
|
+
end
|
90
|
+
actual_lines += 1
|
91
|
+
strip_line = line.strip
|
92
|
+
text += "\n" + strip_line
|
93
|
+
end
|
94
|
+
if is_eof
|
95
|
+
cues << Cue.new(start_time, end_time, text.rstrip) unless check_syntax
|
96
|
+
break
|
97
|
+
end
|
98
|
+
line = subs.gets
|
99
|
+
unless line
|
100
|
+
cues << Cue.new(start_time, end_time, text.rstrip) unless check_syntax
|
101
|
+
break
|
102
|
+
end
|
103
|
+
actual_lines += 1
|
104
|
+
is_eof, actual_lines, strip_line, text = read_until_index subs, actual_lines, line, true, text
|
105
|
+
cues << Cue.new(start_time, end_time, text.strip) unless check_syntax
|
106
|
+
break if is_eof
|
107
|
+
elsif check_syntax
|
108
|
+
error_log += "Syntax error at line #{actual_lines}.\n"
|
109
|
+
line = subs.gets
|
110
|
+
break unless line
|
111
|
+
actual_lines += 1
|
112
|
+
is_eof, actual_lines, strip_line = read_until_index subs, actual_lines, line, false
|
113
|
+
break if is_eof
|
114
|
+
else
|
115
|
+
line = subs.gets
|
116
|
+
break unless line
|
117
|
+
actual_lines += 1
|
118
|
+
is_eof, actual_lines, strip_line = read_until_index subs, actual_lines, line, false
|
119
|
+
break if is_eof
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
if check_syntax
|
124
|
+
error_log == '' ? 'No errors were found.' : error_log.rstrip
|
125
|
+
else
|
126
|
+
[cues, @fps]
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def check_timing(start_time, end_time, last_end_time, error_log, check_syntax, actual_lines)
|
131
|
+
if start_time.year + start_time.month + start_time.day +
|
132
|
+
end_time.year + end_time.month + end_time.day != 6 or
|
133
|
+
start_time >= end_time or start_time < last_end_time
|
134
|
+
if check_syntax
|
135
|
+
error_log += "Invalid timing at line #{actual_lines}.\n"
|
136
|
+
else
|
137
|
+
puts "Invalid timing at line #{actual_lines}.\n"
|
138
|
+
end
|
139
|
+
return [false, error_log]
|
140
|
+
end
|
141
|
+
[true, error_log]
|
142
|
+
end
|
143
|
+
|
144
|
+
def read_until_timing(subs, actual_lines)
|
145
|
+
line = subs.gets
|
146
|
+
return true unless line
|
147
|
+
actual_lines += 1
|
148
|
+
strip_line = line.strip
|
149
|
+
while strip_line == '' or /\A\d+$/ =~ strip_line
|
150
|
+
line = subs.gets
|
151
|
+
unless line
|
152
|
+
is_eof = true
|
153
|
+
break
|
154
|
+
end
|
155
|
+
actual_lines += 1
|
156
|
+
strip_line = line.strip
|
157
|
+
end
|
158
|
+
[is_eof, actual_lines, strip_line]
|
159
|
+
end
|
160
|
+
|
161
|
+
def parse_timing(line)
|
162
|
+
match = SUB_RIP_TIMING.match line
|
163
|
+
time_args = [1, 1, 1] + match.to_s.split(/,|:/).collect(&:to_i)
|
164
|
+
time_args[6] *= 1000
|
165
|
+
start_time = Time.mktime *time_args
|
166
|
+
match = SUB_RIP_TIMING.match match.post_match
|
167
|
+
time_args = [1, 1, 1] + match.to_s.split(/,|:/).collect(&:to_i)
|
168
|
+
time_args[6] *= 1000
|
169
|
+
[start_time, Time.mktime(*time_args)]
|
170
|
+
end
|
171
|
+
|
172
|
+
def read_until_index(subs, actual_lines, line, append, text = nil)
|
173
|
+
strip_line = line.strip
|
174
|
+
while not /\A\d+$/ =~ strip_line
|
175
|
+
text += "\n" + strip_line if append
|
176
|
+
line = subs.gets
|
177
|
+
unless line
|
178
|
+
is_eof = true
|
179
|
+
break
|
180
|
+
end
|
181
|
+
actual_lines += 1
|
182
|
+
strip_line = line.strip
|
183
|
+
end
|
184
|
+
[is_eof, actual_lines, strip_line] + (append ? [text] : [])
|
185
|
+
end
|
186
|
+
|
187
|
+
end
|
188
|
+
|
189
|
+
class MicroDVDReader
|
190
|
+
include Patterns
|
191
|
+
|
192
|
+
def initialize(subs_path)
|
193
|
+
@subs_path = subs_path
|
194
|
+
@fps = 23.976
|
195
|
+
end
|
196
|
+
|
197
|
+
def read_subs(check_syntax)
|
198
|
+
cues = []
|
199
|
+
error_log = ''
|
200
|
+
last_end_frame = 0
|
201
|
+
File.open(@subs_path, 'r') do |subs|
|
202
|
+
line, actual_lines = find_out_fps subs
|
203
|
+
while line
|
204
|
+
strip_line = line.strip
|
205
|
+
if strip_line != ''
|
206
|
+
if MICRO_DVD_LINE =~ strip_line
|
207
|
+
last_end_frame, error_log = add_new_line strip_line, cues, last_end_frame, error_log, check_syntax, actual_lines
|
208
|
+
elsif check_syntax
|
209
|
+
error_log += "Syntax error at line #{actual_lines}.\n"
|
210
|
+
end
|
211
|
+
end
|
212
|
+
line = subs.gets
|
213
|
+
actual_lines += 1
|
214
|
+
end
|
215
|
+
end
|
216
|
+
if check_syntax
|
217
|
+
error_log == '' ? 'No errors were found.' : error_log.rstrip
|
218
|
+
else
|
219
|
+
[cues, @fps]
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
def find_out_fps(subs)
|
224
|
+
line = subs.gets
|
225
|
+
actual_lines = 1
|
226
|
+
while line
|
227
|
+
strip_line = line.strip
|
228
|
+
if strip_line != ''
|
229
|
+
if MICRO_DVD_LINE =~ strip_line
|
230
|
+
first_line = MICRO_DVD_LINE.match(strip_line).post_match.strip
|
231
|
+
if /\A\d*\.?\d*$/ =~ first_line
|
232
|
+
@fps = first_line.to_f
|
233
|
+
line = subs.gets
|
234
|
+
actual_lines += 1
|
235
|
+
end
|
236
|
+
end
|
237
|
+
break
|
238
|
+
end
|
239
|
+
line = subs.gets
|
240
|
+
actual_lines += 1
|
241
|
+
end
|
242
|
+
[line, actual_lines]
|
243
|
+
end
|
244
|
+
|
245
|
+
def add_new_line(line, cues, last_end_frame, error_log, check_syntax, actual_lines)
|
246
|
+
match = /\d+/.match line
|
247
|
+
start_frame = match.to_s.to_i
|
248
|
+
match = /\d+/.match match.post_match
|
249
|
+
end_frame = match.to_s.to_i
|
250
|
+
if start_frame <= end_frame and start_frame >= last_end_frame
|
251
|
+
unless check_syntax
|
252
|
+
text = MICRO_DVD_LINE.match(line).post_match
|
253
|
+
cues << Cue.new(start_frame, end_frame, text.gsub('|', "\n"))
|
254
|
+
end
|
255
|
+
last_end_frame = end_frame
|
256
|
+
elsif check_syntax
|
257
|
+
error_log += "Syntax error at line #{actual_lines}.\n"
|
258
|
+
end
|
259
|
+
[last_end_frame, error_log]
|
260
|
+
end
|
261
|
+
|
262
|
+
end
|
263
|
+
|
264
|
+
class SubviewerReader
|
265
|
+
include Patterns
|
266
|
+
|
267
|
+
def initialize(subs_path)
|
268
|
+
@subs_path = subs_path
|
269
|
+
@fps = 23.976
|
270
|
+
end
|
271
|
+
|
272
|
+
def read_subs(check_syntax)
|
273
|
+
cues = []
|
274
|
+
error_log = ''
|
275
|
+
last_end_time = Time.mktime 1, 1, 1
|
276
|
+
File.open(@subs_path, 'r') do |subs|
|
277
|
+
actual_lines, error_log, line = read_metadata subs, check_syntax
|
278
|
+
while line
|
279
|
+
strip_line = line.strip
|
280
|
+
if strip_line != ''
|
281
|
+
if SUBVIEWER_LINE =~ strip_line
|
282
|
+
start_time, end_time = parse_timing strip_line
|
283
|
+
valid_timing, error_log = check_timing start_time, end_time, last_end_time, error_log, check_syntax, actual_lines
|
284
|
+
unless valid_timing
|
285
|
+
break unless subs.gets
|
286
|
+
line = subs.gets
|
287
|
+
actual_lines += 2
|
288
|
+
next
|
289
|
+
end
|
290
|
+
line = subs.gets
|
291
|
+
break unless line
|
292
|
+
actual_lines += 1
|
293
|
+
cues << Cue.new(start_time, end_time, line.strip.gsub('[br]', "\n")) unless check_syntax
|
294
|
+
last_end_time = end_time
|
295
|
+
else
|
296
|
+
error_log += "Syntax error at line #{actual_lines}.\n" if check_syntax
|
297
|
+
break unless subs.gets
|
298
|
+
line = subs.gets
|
299
|
+
actual_lines += 2
|
300
|
+
next
|
301
|
+
end
|
302
|
+
end
|
303
|
+
line = subs.gets
|
304
|
+
actual_lines += 1
|
305
|
+
end
|
306
|
+
end
|
307
|
+
if check_syntax
|
308
|
+
error_log == '' ? 'No errors were found.' : error_log.rstrip
|
309
|
+
else
|
310
|
+
[cues, @fps]
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
def read_metadata(subs, check_syntax)
|
315
|
+
actual_lines = 0
|
316
|
+
error_log = ''
|
317
|
+
metadata = ''
|
318
|
+
while line = subs.gets
|
319
|
+
actual_lines += 1
|
320
|
+
strip_line = line.strip
|
321
|
+
if strip_line != ''
|
322
|
+
if /\A\d/ =~ strip_line
|
323
|
+
error_log += "Syntax error in metadata.\n" if check_syntax and not SUBVIEWER_METADATA =~ metadata
|
324
|
+
break
|
325
|
+
end
|
326
|
+
metadata += strip_line
|
327
|
+
end
|
328
|
+
end
|
329
|
+
[actual_lines, error_log, line]
|
330
|
+
end
|
331
|
+
|
332
|
+
def parse_timing(line)
|
333
|
+
start_end = line.split ','
|
334
|
+
time_args = [1,1,1] + start_end[0].split(/\.|:/).collect(&:to_i)
|
335
|
+
time_args[6] *= 1000
|
336
|
+
start_time = Time.mktime *time_args
|
337
|
+
time_args = [1,1,1] + start_end[1].split(/\.|:/).collect(&:to_i)
|
338
|
+
time_args[6] *= 1000
|
339
|
+
[start_time, Time.mktime(*time_args)]
|
340
|
+
end
|
341
|
+
|
342
|
+
def check_timing(start_time, end_time, last_end_time, error_log, check_syntax, actual_lines)
|
343
|
+
if start_time.year + start_time.month + start_time.day +
|
344
|
+
end_time.year + end_time.month + end_time.day != 6 or
|
345
|
+
start_time >= end_time or start_time < last_end_time
|
346
|
+
if check_syntax
|
347
|
+
error_log += "Invalid timing at line #{actual_lines}.\n"
|
348
|
+
else
|
349
|
+
puts "Invalid timing at line #{actual_lines}.\n"
|
350
|
+
end
|
351
|
+
return [false, error_log]
|
352
|
+
end
|
353
|
+
[true, error_log]
|
354
|
+
end
|
355
|
+
|
356
|
+
end
|
357
|
+
|