subshifter 0.0.2
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.
- checksums.yaml +7 -0
- data/bin/subshift +23 -0
- data/lib/duration.rb +39 -0
- data/lib/error/empty_body_error.rb +2 -0
- data/lib/error/negative_time_error.rb +3 -0
- data/lib/error/no_time_error.rb +2 -0
- data/lib/error/overlap_error.rb +2 -0
- data/lib/error/parse_error.rb +2 -0
- data/lib/subtitle.rb +50 -0
- data/lib/subtitle_chunk.rb +80 -0
- metadata +52 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: d13c057ce4824a8dbb9e009e257d9a0e8423a1c5859685a8f789d5802e339bdb
|
|
4
|
+
data.tar.gz: 5e256de24659debae5352673f36a2420e6d70f5aea02e6201366bcc1d5de4a2d
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: f2f73225262802c3b7c14111361bb905a94a72179904c31afd7d40c9893baaf3719a62704c36d9d7c0671900657ce880ef57a19e75c627056fe35a5d1fca5a27
|
|
7
|
+
data.tar.gz: e2e2b34bcc44173232f56264332b0dad33a2499ac5b8a95f96014d35a168e8e78fa0d3d39c923ec6aa51f76ac0390a46e3f34595462a981f880d62ebd2c45f23
|
data/bin/subshift
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
$:.unshift "#{File.dirname(__FILE__)}/lib"
|
|
4
|
+
require "subtitle"
|
|
5
|
+
|
|
6
|
+
begin
|
|
7
|
+
shift = Float(ARGV.shift)
|
|
8
|
+
rescue ArgumentError, TypeError
|
|
9
|
+
puts <<-HELP
|
|
10
|
+
Usage: subshift.rb <duration> [files]
|
|
11
|
+
<duration> is in seconds and can be decimal.
|
|
12
|
+
Positive number – subtitles will appear later.
|
|
13
|
+
Negative number – subtitles will appear sooner.
|
|
14
|
+
|
|
15
|
+
Example: ruby subshift.rb -10.7 subtitle.srt
|
|
16
|
+
HELP
|
|
17
|
+
exit
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
subtitle = Subtitle.new $<.read
|
|
21
|
+
subtitle.shift! shift
|
|
22
|
+
|
|
23
|
+
print subtitle
|
data/lib/duration.rb
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
require 'error/negative_time_error'
|
|
2
|
+
require 'error/no_time_error'
|
|
3
|
+
|
|
4
|
+
class Duration
|
|
5
|
+
include Comparable
|
|
6
|
+
|
|
7
|
+
def initialize seconds
|
|
8
|
+
raise NoTimeError.new "Time must be set." if seconds.nil?
|
|
9
|
+
|
|
10
|
+
@seconds = seconds.to_f
|
|
11
|
+
raise NegativeTimeError.new "Duration cannot be negative." if @seconds < 0
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def to_f
|
|
15
|
+
@seconds
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def + add
|
|
19
|
+
shifted = @seconds + add
|
|
20
|
+
Duration.new(shifted)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def - subtract
|
|
24
|
+
self + (-subtract)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def <=> compare
|
|
28
|
+
to_f <=> compare.to_f
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def to_s
|
|
32
|
+
seconds = @seconds % 60
|
|
33
|
+
total_minutes = (@seconds / 60).floor
|
|
34
|
+
minutes = total_minutes % 60
|
|
35
|
+
hours = (total_minutes / 60).floor
|
|
36
|
+
"#{'%02d' % hours}:#{'%02d' % minutes}:#{('%06.3f' % seconds).tr '.', ','}"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
end
|
data/lib/subtitle.rb
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
=begin
|
|
2
|
+
A whole subtitle stream consisting of chunks representing the
|
|
3
|
+
single dialogs. Accepts an IO object (e.g. a File) or a string
|
|
4
|
+
itself in the SRT format.
|
|
5
|
+
=end
|
|
6
|
+
|
|
7
|
+
require 'subtitle_chunk'
|
|
8
|
+
|
|
9
|
+
class Subtitle
|
|
10
|
+
attr_reader :chunks
|
|
11
|
+
|
|
12
|
+
def initialize input
|
|
13
|
+
if input.is_a? Array
|
|
14
|
+
@chunks = input
|
|
15
|
+
else
|
|
16
|
+
input = input.read if input.respond_to? :read
|
|
17
|
+
|
|
18
|
+
@chunks = []
|
|
19
|
+
|
|
20
|
+
chunks = input.split /(\r?\n){2}/
|
|
21
|
+
chunks.each do |chunk|
|
|
22
|
+
chunk.strip!
|
|
23
|
+
@chunks << SubtitleChunk.new(chunk) unless chunk.empty?
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def initialize_copy source
|
|
29
|
+
super
|
|
30
|
+
@chunks = @chunks.dup
|
|
31
|
+
@chunks.map! { |chunk| chunk.dup }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def to_s
|
|
35
|
+
@chunks.map.with_index { |chunk, order| chunk.to_s(order + 1) }.join "\n"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def shift seconds
|
|
39
|
+
subtitle = self.dup
|
|
40
|
+
subtitle.shift! seconds
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def shift! seconds
|
|
44
|
+
@chunks.each do |chunk|
|
|
45
|
+
chunk.shift! seconds
|
|
46
|
+
end
|
|
47
|
+
self
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
=begin
|
|
2
|
+
Single subtitle chunk. Dialog part with a begin and end timestamp.
|
|
3
|
+
Both timestamps can be manipulated at the same time using the
|
|
4
|
+
shift method or the +/- operators.
|
|
5
|
+
=end
|
|
6
|
+
|
|
7
|
+
require 'duration'
|
|
8
|
+
require 'error/empty_body_error'
|
|
9
|
+
require 'error/parse_error'
|
|
10
|
+
require 'error/no_time_error'
|
|
11
|
+
require 'error/overlap_error'
|
|
12
|
+
class SubtitleChunk
|
|
13
|
+
attr_reader :body, :begin, :end
|
|
14
|
+
|
|
15
|
+
# Can be initialized either from a string in the SRT format,
|
|
16
|
+
# or from a hash of the begin and end timestamps and the body.
|
|
17
|
+
def initialize chunk
|
|
18
|
+
if chunk.is_a? Hash
|
|
19
|
+
@body = chunk[:body]
|
|
20
|
+
@begin = Duration.new chunk[:begin]
|
|
21
|
+
@end = Duration.new chunk[:end]
|
|
22
|
+
else
|
|
23
|
+
chunk.match /^\d+(?<NEWLINE>\r?\n)(?<BEGIN>(?<TIME>\d{2}:\d{2}:\d{2},\d{3})) --> (?<END>\g<TIME>)\k<NEWLINE>(?<BODY>.+)$/m do |match|
|
|
24
|
+
@body = match['BODY']
|
|
25
|
+
@begin = duration match['BEGIN']
|
|
26
|
+
@end = duration match['END']
|
|
27
|
+
end or raise ParseError.new "Invalid subtitle chunk: #{chunk}"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
@body.strip!
|
|
31
|
+
raise EmptyBodyError.new "Body cannot be empty." if @body.empty?
|
|
32
|
+
raise OverlapError.new "Duration cannot be negative." if @begin.to_f > @end.to_f
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def initialize_copy source
|
|
36
|
+
super
|
|
37
|
+
@begin, @end = @begin.dup, @end.dup
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def shift! seconds
|
|
41
|
+
shifted = self + seconds
|
|
42
|
+
@begin, @end = shifted.begin, shifted.end
|
|
43
|
+
self
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def shift seconds
|
|
47
|
+
self + seconds
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def + seconds
|
|
51
|
+
SubtitleChunk.new body: @body,
|
|
52
|
+
begin: @begin + seconds,
|
|
53
|
+
end: @end + seconds
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def - seconds
|
|
57
|
+
self + (-seconds)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def to_s order = nil
|
|
61
|
+
output = ""
|
|
62
|
+
output << "#{order}\n" unless order.nil?
|
|
63
|
+
output << <<-EOS
|
|
64
|
+
#{@begin} --> #{@end}
|
|
65
|
+
#{@body}
|
|
66
|
+
EOS
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
protected
|
|
70
|
+
# Parse the SRT timestamp format HH:MM:SS,SSS into Duration.
|
|
71
|
+
def duration stamp
|
|
72
|
+
stamp.match /(?<HOURS>\d{2}):(?<MINUTES>\d{2}):(?<SECONDS>\d{2}),(?<MILLISECONDS>\d{3})/ do |match|
|
|
73
|
+
hours = match['HOURS'].to_f
|
|
74
|
+
minutes = hours * 60 + match['MINUTES'].to_f
|
|
75
|
+
seconds = minutes * 60 + "#{match['SECONDS']}.#{match['MILLISECONDS']}".to_f
|
|
76
|
+
Duration.new seconds
|
|
77
|
+
end or raise ArgumentError.new "Invalid duration stamp: #{stamp}"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: subshifter
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.2
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Glutexo
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2019-04-13 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
13
|
+
description: A simple application to shift time in SRT subtitle files
|
|
14
|
+
email: glutexo@icloud.com
|
|
15
|
+
executables:
|
|
16
|
+
- subshift
|
|
17
|
+
extensions: []
|
|
18
|
+
extra_rdoc_files: []
|
|
19
|
+
files:
|
|
20
|
+
- bin/subshift
|
|
21
|
+
- lib/duration.rb
|
|
22
|
+
- lib/error/empty_body_error.rb
|
|
23
|
+
- lib/error/negative_time_error.rb
|
|
24
|
+
- lib/error/no_time_error.rb
|
|
25
|
+
- lib/error/overlap_error.rb
|
|
26
|
+
- lib/error/parse_error.rb
|
|
27
|
+
- lib/subtitle.rb
|
|
28
|
+
- lib/subtitle_chunk.rb
|
|
29
|
+
homepage: https://github.com/Glutexo/subshifter
|
|
30
|
+
licenses:
|
|
31
|
+
- MIT
|
|
32
|
+
metadata: {}
|
|
33
|
+
post_install_message:
|
|
34
|
+
rdoc_options: []
|
|
35
|
+
require_paths:
|
|
36
|
+
- lib
|
|
37
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
38
|
+
requirements:
|
|
39
|
+
- - ">="
|
|
40
|
+
- !ruby/object:Gem::Version
|
|
41
|
+
version: '0'
|
|
42
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0'
|
|
47
|
+
requirements: []
|
|
48
|
+
rubygems_version: 3.0.3
|
|
49
|
+
signing_key:
|
|
50
|
+
specification_version: 4
|
|
51
|
+
summary: Subtitle shifter
|
|
52
|
+
test_files: []
|