rsub 1.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.
Files changed (4) hide show
  1. checksums.yaml +7 -0
  2. data/bin/rsub +2 -0
  3. data/bin/rsub.rb +288 -0
  4. metadata +46 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f12925562f101b59a40894bfd9c42660f12de8b5ee4de1e650a0a416796c78de
4
+ data.tar.gz: 6466318d3b39644aaa06eb79da0bfef5e6107dcb20e3ca414e760d62042388dd
5
+ SHA512:
6
+ metadata.gz: a2fb5398fb7a927f39d7be6bcf0767fe1c8919f80bc7f74b9ac10bef6f11a7ad6d4ac2c28e4fe47e142fa9162c8ec92d5677db18a265200ea4755c1af6796ba9
7
+ data.tar.gz: d7baee093ce0fb00971de77d2795fd18280443e481f41191af6ebb4264247cce957d939b24aa760222452258b10071e34b1c6a789ae5049498d73e6db1007044
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env ruby
2
+ require_relative 'rsub.rb'
@@ -0,0 +1,288 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # rsub.rb - Ruby script which changes the timing of srt (SubRip) subtitle files.
4
+ #
5
+ # Copyright (c) 2014-2017 Gabor Bata
6
+ #
7
+ # Permission is hereby granted, free of charge, to any person
8
+ # obtaining a copy of this software and associated documentation files
9
+ # (the "Software"), to deal in the Software without restriction,
10
+ # including without limitation the rights to use, copy, modify, merge,
11
+ # publish, distribute, sublicense, and/or sell copies of the Software,
12
+ # and to permit persons to whom the Software is furnished to do so,
13
+ # subject to the following conditions:
14
+ #
15
+ # The above copyright notice and this permission notice shall be
16
+ # included in all copies or substantial portions of the Software.
17
+ #
18
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
22
+ # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
23
+ # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
24
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
+ # SOFTWARE.
26
+
27
+ require 'fileutils'
28
+ require 'optparse'
29
+
30
+ # Class for representing time
31
+ class SrtTime
32
+ # [,:] is added for some incorrectly formatted subs
33
+ TIME_PATTERN = /(\d{2}):(\d{2}):(\d{2})[,:](\d{3})/
34
+ TIME_FORMAT = '%02d:%02d:%02d,%03d'
35
+
36
+ def initialize(time_str)
37
+ @value = -1.0
38
+ begin
39
+ h, m, s, ms = time_str.scan(TIME_PATTERN).flatten.map{ |i| Float(i) }
40
+ @value = (h * 60.0 + m) * 60.0 + s + ms / 1000.0
41
+ rescue
42
+ warn "ERROR: could not read time entry: #{time_str}"
43
+ end
44
+ end
45
+
46
+ def multiply!(factor)
47
+ @value = @value * factor
48
+ end
49
+
50
+ def shift!(time)
51
+ @value = @value + time
52
+ end
53
+
54
+ def valid?
55
+ @value >= 0.0
56
+ end
57
+
58
+ def to_s
59
+ s = @value.floor
60
+ ms = ((@value - s) * 1000.0).to_i
61
+ m = s / 60
62
+ s = s - m * 60
63
+ h = m / 60
64
+ m = m - h * 60
65
+ return sprintf(TIME_FORMAT, h, m, s, ms)
66
+ end
67
+ end
68
+
69
+ # Class for representing an srt subtitle entry
70
+ class SrtEntry
71
+ def initialize(order, start_time, end_time, text)
72
+ @order = order
73
+ @start_time = SrtTime.new(start_time)
74
+ @end_time = SrtTime.new(end_time)
75
+ # '<i>' and '</i>' will be removed as they are not supported by all players
76
+ @text = text.gsub('<i>', '').gsub('</i>', '')
77
+ end
78
+
79
+ def multiply!(factor)
80
+ @start_time.multiply!(factor)
81
+ @end_time.multiply!(factor)
82
+ end
83
+
84
+ def shift!(time)
85
+ @start_time.shift!(time)
86
+ @end_time.shift!(time)
87
+ end
88
+
89
+ def valid?
90
+ @start_time.valid? && @end_time.valid?
91
+ end
92
+
93
+ def to_s(order = nil)
94
+ return "#{order ? order : @order}\n#{@start_time.to_s} --> #{@end_time.to_s}\n#{@text}\n\n"
95
+ end
96
+ end
97
+
98
+ # Class for reading/writing srt files
99
+ class SrtFile
100
+ BACKUP_EXTENSION = '.bak'
101
+
102
+ def initialize(file_name, encoding = 'ISO-8859-2')
103
+ @file_name = file_name
104
+ @encoding = encoding
105
+ end
106
+
107
+ def read(use_backup_as_input = false)
108
+ name_postfix = use_backup_as_input && File.exist?("#{@file_name}#{BACKUP_EXTENSION}") ? BACKUP_EXTENSION : ''
109
+ in_file_name = "#{@file_name}#{name_postfix}"
110
+ puts "Reading subtitles from '#{in_file_name}'..."
111
+ entry_list = []
112
+ buffer = []
113
+ file = File.open("#{in_file_name}", "r:#{@encoding}")
114
+ begin
115
+ file.each_line do |line|
116
+ entry_line = line.chomp.strip
117
+ if entry_line.empty? && !buffer.empty?
118
+ flush_buffer(entry_list, buffer)
119
+ elsif !entry_line.empty?
120
+ buffer.push(entry_line)
121
+ end
122
+ end
123
+ flush_buffer(entry_list, buffer) if !buffer.empty?
124
+ rescue => error
125
+ warn "ERROR: in file: [#{in_file_name}] with entry: #{buffer}\n"
126
+ trace = error.backtrace.join("\n")
127
+ warn "Backtrace: #{error}\n#{trace}\n\n"
128
+ entry_list = []
129
+ end
130
+ file.close
131
+ puts "Done: #{entry_list.size} entries have been read."
132
+ return entry_list
133
+ end
134
+
135
+ def write(entry_list, create_backup = false, recount = true)
136
+ if entry_list && !entry_list.empty?
137
+ backup if create_backup
138
+ puts "Writing subtitles to '#{@file_name}'..."
139
+ file = File.open(@file_name, "w:#{@encoding}")
140
+ counter = 0
141
+ entry_list.each do |entry|
142
+ next if !entry.valid?
143
+ counter += 1
144
+ file.print(entry.to_s(recount ? counter : nil))
145
+ end
146
+ file.close
147
+ puts "Done: #{counter} entries have been written."
148
+ end
149
+ end
150
+
151
+ def backup
152
+ backup_name = @file_name + BACKUP_EXTENSION
153
+ if File.exist?(@file_name) && !File.exist?(backup_name)
154
+ puts "Creating backup file '#{backup_name}'..."
155
+ FileUtils.cp(@file_name, backup_name)
156
+ end
157
+ end
158
+
159
+ private
160
+
161
+ def flush_buffer(entry_list, buffer)
162
+ range = buffer[1].split(' --> ')
163
+ entry = SrtEntry.new(buffer[0], range[0], range[1], buffer[2..-1].join("\n"))
164
+ entry_list.push(entry)
165
+ buffer.clear
166
+ end
167
+ end
168
+
169
+ # Class for representing a change command for a subtitle entry
170
+ class SrtChangeCommand
171
+ FPS_25 = 25.0
172
+ FPS_23 = 23.976
173
+
174
+ def initialize(command, argument)
175
+ case command
176
+ when :fps
177
+ case argument
178
+ when '23'
179
+ @method = :multiply!
180
+ @param = FPS_25 / FPS_23
181
+ when '25'
182
+ @method = :multiply!
183
+ @param = FPS_23 / FPS_25
184
+ end
185
+ when :shift
186
+ if argument
187
+ @method = :shift!
188
+ @param = argument
189
+ end
190
+ end
191
+ end
192
+
193
+ def execute(entry)
194
+ entry.send(@method, @param) if valid?
195
+ end
196
+
197
+ def valid?
198
+ !@method.nil?
199
+ end
200
+ end
201
+
202
+ # Parse command-line options
203
+ def parse_options(args)
204
+ options = {}
205
+ options[:create_backup] = true
206
+ options[:use_backup_as_input] = false
207
+ options[:recount] = true
208
+ options[:encoding] = "ISO-8859-2"
209
+ begin
210
+ optparser = OptionParser.new do |opts|
211
+ opts.banner = "Usage: rsub.rb [options] file_path"
212
+ opts.separator ""
213
+ opts.separator "Specific options:"
214
+
215
+ opts.on("-s", "--shift N", Float, "Shift subtitles by N seconds (float)") do |seconds|
216
+ options[:shift] = seconds
217
+ end
218
+
219
+ opts.on("-f", "--fps FPS", ["23", "25"], "Change frame rate (23, 25)", "23: 25.000 fps -> 23,976 fps", "25: 23.976 fps -> 25.000 fps") do |fps|
220
+ options[:fps] = fps
221
+ end
222
+
223
+ opts.on("-b", "--no-backup", "Do not create backup files") do
224
+ options[:create_backup] = false
225
+ end
226
+
227
+ opts.on("-u", "--use-backup-as-input", "Use backup files as input (if exist)") do
228
+ options[:use_backup_as_input] = true
229
+ end
230
+
231
+ opts.on("-r", "--no-recount", "Do not recount subtitle numbering") do
232
+ options[:recount] = false
233
+ end
234
+
235
+ opts.on("-e", "--encoding ENCODING", "Subtitle encoding (default: #{options[:encoding]})") do |encoding|
236
+ options[:encoding] = encoding
237
+ end
238
+
239
+ opts.on("-h", "--help", "Show this message") do
240
+ puts opts
241
+ exit
242
+ end
243
+ end
244
+ optparser.parse!
245
+
246
+ if args.empty?
247
+ puts "#{optparser.help}\n"
248
+ raise "missing file_path"
249
+ else
250
+ file_path = args[0]
251
+ # ensure file path ends with the '.srt' extension
252
+ options[:file_path] = "#{file_path}#{!file_path.downcase.end_with?('.srt') ? '.srt' : ''}"
253
+ end
254
+ rescue => error
255
+ warn "ERROR: #{error}"
256
+ exit
257
+ end
258
+ return options
259
+ end
260
+
261
+ def main(options)
262
+ files = Dir.glob(options[:file_path])
263
+ if files.empty?
264
+ warn "Could not found subtitle files on the given path: '#{options[:file_path]}'"
265
+ else
266
+ commands = []
267
+ [:fps, :shift].each do |command|
268
+ cmd = SrtChangeCommand.new(command, options[command])
269
+ commands.push(cmd) if cmd.valid?
270
+ end
271
+ if commands.empty?
272
+ warn "Nothing to change..."
273
+ else
274
+ files.each do |file|
275
+ srt_file = SrtFile.new(file, options[:encoding])
276
+ entry_list = srt_file.read(options[:use_backup_as_input])
277
+ entry_list.each do |entry|
278
+ commands.each do |command|
279
+ command.execute(entry)
280
+ end
281
+ end
282
+ srt_file.write(entry_list, options[:create_backup], options[:recount])
283
+ end
284
+ end
285
+ end
286
+ end
287
+
288
+ main(parse_options(ARGV))
metadata ADDED
@@ -0,0 +1,46 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rsub
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Gabor Bata
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-09-15 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description:
14
+ email:
15
+ executables:
16
+ - rsub.rb
17
+ - rsub
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - bin/rsub
22
+ - bin/rsub.rb
23
+ homepage: https://github.com/gaborbata/rsub
24
+ licenses:
25
+ - MIT
26
+ metadata: {}
27
+ post_install_message:
28
+ rdoc_options: []
29
+ require_paths:
30
+ - lib
31
+ required_ruby_version: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: '0'
36
+ required_rubygems_version: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ requirements: []
42
+ rubygems_version: 3.0.3
43
+ signing_key:
44
+ specification_version: 4
45
+ summary: Script which changes the timing of srt (SubRip) subtitle files
46
+ test_files: []