titlekit 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (119) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.travis.yml +3 -0
  4. data/Gemfile +3 -0
  5. data/LICENSE +22 -0
  6. data/README.md +335 -0
  7. data/Rakefile +8 -0
  8. data/lib/titlekit/have.rb +90 -0
  9. data/lib/titlekit/job.rb +446 -0
  10. data/lib/titlekit/parsers/ass.rb +175 -0
  11. data/lib/titlekit/parsers/ass.treetop +72 -0
  12. data/lib/titlekit/parsers/srt.rb +139 -0
  13. data/lib/titlekit/parsers/srt.treetop +73 -0
  14. data/lib/titlekit/parsers/ssa.rb +201 -0
  15. data/lib/titlekit/parsers/ssa.treetop +72 -0
  16. data/lib/titlekit/specification.rb +131 -0
  17. data/lib/titlekit/utilities.rb +3 -0
  18. data/lib/titlekit/version.rb +3 -0
  19. data/lib/titlekit/want.rb +24 -0
  20. data/lib/titlekit.rb +9 -0
  21. data/spec/ass_spec.rb +113 -0
  22. data/spec/automatic_grouping/automatic_grouping_spec.rb +55 -0
  23. data/spec/automatic_grouping/dual_tracks/expected.srt +15 -0
  24. data/spec/automatic_grouping/dual_tracks/one.srt +11 -0
  25. data/spec/automatic_grouping/dual_tracks/out.srt +15 -0
  26. data/spec/automatic_grouping/dual_tracks/two.srt +11 -0
  27. data/spec/automatic_grouping/single_track/expected.srt +24 -0
  28. data/spec/automatic_grouping/single_track/one.srt +11 -0
  29. data/spec/automatic_grouping/single_track/out.srt +24 -0
  30. data/spec/automatic_grouping/single_track/two.srt +11 -0
  31. data/spec/encoding_detection/a/in.ass +0 -0
  32. data/spec/encoding_detection/b/in.srt +2389 -0
  33. data/spec/encoding_detection/b/out.srt +2389 -0
  34. data/spec/encoding_detection/c/in.srt +5320 -0
  35. data/spec/encoding_detection/c/out.srt +5320 -0
  36. data/spec/encoding_detection/encoding_detection_spec.rb +81 -0
  37. data/spec/files/ass/authentic.ass +0 -0
  38. data/spec/files/ass/hard.ass +37 -0
  39. data/spec/files/ass/simple.ass +28 -0
  40. data/spec/files/srt/authentic.srt +2708 -0
  41. data/spec/files/srt/coordinates.srt +13 -0
  42. data/spec/files/srt/simple.srt +12 -0
  43. data/spec/files/ssa/simple.ssa +26 -0
  44. data/spec/files/try/unsupported-output.try +0 -0
  45. data/spec/files/try/unsupported.try +7 -0
  46. data/spec/format_conversion/ass_srt/expected.srt +2327 -0
  47. data/spec/format_conversion/ass_srt/in.ass +485 -0
  48. data/spec/format_conversion/ass_srt/out.srt +2327 -0
  49. data/spec/format_conversion/format_conversion_spec.rb +112 -0
  50. data/spec/format_conversion/srt_ass/expected.ass +19 -0
  51. data/spec/format_conversion/srt_ass/in.srt +12 -0
  52. data/spec/format_conversion/srt_ass/out.ass +19 -0
  53. data/spec/format_conversion/srt_ssa/expected.ssa +19 -0
  54. data/spec/format_conversion/srt_ssa/in.srt +12 -0
  55. data/spec/format_conversion/srt_ssa/out.ssa +19 -0
  56. data/spec/format_conversion/ssa_srt/expected.srt +9 -0
  57. data/spec/format_conversion/ssa_srt/in.ssa +26 -0
  58. data/spec/format_conversion/ssa_srt/out.srt +9 -0
  59. data/spec/job_spec.rb +162 -0
  60. data/spec/simultaneous_subtitles/dual/ass/expected.ass +22 -0
  61. data/spec/simultaneous_subtitles/dual/ass/out.ass +22 -0
  62. data/spec/simultaneous_subtitles/dual/one.srt +11 -0
  63. data/spec/simultaneous_subtitles/dual/srt/expected.srt +27 -0
  64. data/spec/simultaneous_subtitles/dual/srt/out.srt +27 -0
  65. data/spec/simultaneous_subtitles/dual/ssa/expected.ssa +22 -0
  66. data/spec/simultaneous_subtitles/dual/ssa/out.ssa +22 -0
  67. data/spec/simultaneous_subtitles/dual/two.srt +11 -0
  68. data/spec/simultaneous_subtitles/simultaneous_subtitles_spec.rb +220 -0
  69. data/spec/simultaneous_subtitles/triple/ass/expected.ass +25 -0
  70. data/spec/simultaneous_subtitles/triple/ass/out.ass +25 -0
  71. data/spec/simultaneous_subtitles/triple/one.srt +11 -0
  72. data/spec/simultaneous_subtitles/triple/srt/expected.srt +55 -0
  73. data/spec/simultaneous_subtitles/triple/srt/out.srt +55 -0
  74. data/spec/simultaneous_subtitles/triple/ssa/expected.ssa +25 -0
  75. data/spec/simultaneous_subtitles/triple/ssa/out.ssa +25 -0
  76. data/spec/simultaneous_subtitles/triple/three.srt +11 -0
  77. data/spec/simultaneous_subtitles/triple/two.srt +11 -0
  78. data/spec/simultaneous_subtitles/triple_plus/ass/expected.ass +93 -0
  79. data/spec/simultaneous_subtitles/triple_plus/ass/out.ass +93 -0
  80. data/spec/simultaneous_subtitles/triple_plus/five.srt +11 -0
  81. data/spec/simultaneous_subtitles/triple_plus/four.srt +11 -0
  82. data/spec/simultaneous_subtitles/triple_plus/one.srt +11 -0
  83. data/spec/simultaneous_subtitles/triple_plus/six.srt +11 -0
  84. data/spec/simultaneous_subtitles/triple_plus/srt/expected.srt +149 -0
  85. data/spec/simultaneous_subtitles/triple_plus/srt/out.srt +149 -0
  86. data/spec/simultaneous_subtitles/triple_plus/ssa/expected.ssa +93 -0
  87. data/spec/simultaneous_subtitles/triple_plus/ssa/out.ssa +93 -0
  88. data/spec/simultaneous_subtitles/triple_plus/three.srt +11 -0
  89. data/spec/simultaneous_subtitles/triple_plus/two.srt +11 -0
  90. data/spec/spec_helper.rb +7 -0
  91. data/spec/specifications_spec.rb +138 -0
  92. data/spec/srt_spec.rb +134 -0
  93. data/spec/ssa_spec.rb +90 -0
  94. data/spec/timecode_correction/double_reference/expected.srt +13 -0
  95. data/spec/timecode_correction/double_reference/in.srt +12 -0
  96. data/spec/timecode_correction/double_reference/out.srt +13 -0
  97. data/spec/timecode_correction/framerate/expected.srt +5 -0
  98. data/spec/timecode_correction/framerate/in.srt +4 -0
  99. data/spec/timecode_correction/framerate/out.srt +5 -0
  100. data/spec/timecode_correction/framerate_plus_reference/expected.srt +13 -0
  101. data/spec/timecode_correction/framerate_plus_reference/in.srt +12 -0
  102. data/spec/timecode_correction/framerate_plus_reference/out.srt +13 -0
  103. data/spec/timecode_correction/single_reference/expected.srt +13 -0
  104. data/spec/timecode_correction/single_reference/in.srt +12 -0
  105. data/spec/timecode_correction/single_reference/out.srt +13 -0
  106. data/spec/timecode_correction/timecode_correction_spec.rb +124 -0
  107. data/spec/transcoding/gb2312-ascii/in.srt +12 -0
  108. data/spec/transcoding/iso-8859-1_utf-8/expected.srt +12 -0
  109. data/spec/transcoding/iso-8859-1_utf-8/in.srt +11 -0
  110. data/spec/transcoding/iso-8859-1_utf-8/out.srt +12 -0
  111. data/spec/transcoding/transcoding_spec.rb +116 -0
  112. data/spec/transcoding/utf-8_gbk/expected.srt +12 -0
  113. data/spec/transcoding/utf-8_gbk/in.srt +11 -0
  114. data/spec/transcoding/utf-8_gbk/out.srt +12 -0
  115. data/spec/transcoding/windows-1252_utf-8/expected.srt +12 -0
  116. data/spec/transcoding/windows-1252_utf-8/in.srt +11 -0
  117. data/spec/transcoding/windows-1252_utf-8/out.srt +12 -0
  118. data/titlekit.gemspec +28 -0
  119. metadata +313 -0
@@ -0,0 +1,139 @@
1
+ require 'treetop'
2
+
3
+ module Titlekit
4
+ module SRT
5
+
6
+ class Subtitles < Treetop::Runtime::SyntaxNode
7
+ def build
8
+ elements.map { |subtitle| subtitle.build }
9
+ end
10
+ end
11
+
12
+ class Subtitle < Treetop::Runtime::SyntaxNode
13
+ def build
14
+ {
15
+ id: id.text_value.to_i,
16
+ start: from.build,
17
+ end: to.build,
18
+ lines: lines.text_value.rstrip
19
+ }
20
+ end
21
+ end
22
+
23
+ class Timecode < Treetop::Runtime::SyntaxNode
24
+ def build
25
+ value = 0
26
+ value += hours.text_value.to_i * 3600
27
+ value += minutes.text_value.to_i * 60
28
+ value += seconds.text_value.to_i
29
+ value += "0.#{fractions.text_value}".to_f
30
+ value
31
+ end
32
+ end
33
+
34
+ # Parses the supplied string and returns the results.
35
+ #
36
+ #
37
+ def self.import(string)
38
+ Treetop.load(File.join(__dir__, 'srt'))
39
+ parser = SRTParser.new
40
+ syntax_tree = parser.parse(string)
41
+
42
+ if syntax_tree
43
+ return syntax_tree.build
44
+ else
45
+ failure = "failure_index #{parser.failure_index}\n"
46
+ failure += "failure_line #{parser.failure_line}\n"
47
+ failure += "failure_column #{parser.failure_column}\n"
48
+ failure += "failure_reason #{parser.failure_reason}\n"
49
+
50
+ raise failure
51
+ end
52
+ end
53
+
54
+ # Master the subtitles for best possible usage of the format's features.
55
+ #
56
+ # @param subtitles [Array<Hash>] the subtitles to master
57
+ def self.master(subtitles)
58
+ tracks = subtitles.map { |subtitle| subtitle[:track] }.uniq
59
+
60
+ if tracks.length == 1
61
+
62
+ # maybe styling? aside that: nada más!
63
+
64
+ elsif tracks.length >= 2
65
+
66
+ mastered_subtitles = []
67
+
68
+ # Determine timeframes with a discrete state
69
+ cuts = subtitles.map { |s| [s[:start], s[:end]] }.flatten.uniq.sort
70
+ frames = []
71
+ cuts.each_cons(2) do |pair|
72
+ frames << { start: pair[0], end: pair[1] }
73
+ end
74
+
75
+ frames.each do |frame|
76
+ intersecting = subtitles.select do |subtitle|
77
+ (subtitle[:end] == frame[:end] || subtitle[:start] == frame[:start] ||
78
+ (subtitle[:start] < frame[:start] && subtitle[:end] > frame[:end]))
79
+ end
80
+
81
+ if intersecting.any?
82
+ intersecting.sort_by! { |subtitle| tracks.index(subtitle[:track]) }
83
+
84
+ subtitle = {}
85
+ subtitle[:id] = mastered_subtitles.length+1
86
+ subtitle[:start] = frame[:start]
87
+ subtitle[:end] = frame[:end]
88
+
89
+ # Combine two or more than three simultaneous tracks by
90
+ # stacking them directly, with different colors.
91
+
92
+ colored_lines = intersecting.map do |subtitle|
93
+ color = DEFAULT_PALETTE[tracks.index(subtitle[:track]) % DEFAULT_PALETTE.length]
94
+ "<font color=\"##{color}\">#{subtitle[:lines]}</font>"
95
+ end
96
+
97
+ subtitle[:lines] = colored_lines.join("\n")
98
+
99
+ mastered_subtitles << subtitle
100
+ end
101
+ end
102
+
103
+ subtitles.replace(mastered_subtitles)
104
+ end
105
+ end
106
+
107
+ def self.export(subtitles)
108
+ result = ''
109
+
110
+ subtitles.each_with_index do |subtitle, index|
111
+ result << (index+1).to_s
112
+ result << "\n"
113
+ result << SRT.build_timecode(subtitle[:start])
114
+ result << ' --> '
115
+ result << SRT.build_timecode(subtitle[:end])
116
+ result << "\n"
117
+ result << subtitle[:lines]
118
+ result << "\n\n"
119
+ end
120
+
121
+ return result
122
+ end
123
+
124
+ protected
125
+
126
+ def self.build_timecode(seconds)
127
+ sprintf("%02d:%02d:%02d,%s",
128
+ seconds / 3600,
129
+ (seconds%3600) / 60,
130
+ seconds % 60,
131
+ sprintf("%.3f", seconds)[-3, 3])
132
+ end
133
+
134
+ def self.parse_timecode(timecode)
135
+ mres = timecode.match(/(?<h>\d+):(?<m>\d+):(?<s>\d+),(?<ms>\d+)/)
136
+ "#{mres["h"].to_i * 3600 + mres["m"].to_i * 60 + mres["s"].to_i}.#{mres["ms"]}".to_f
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,73 @@
1
+ grammar SRT
2
+ rule subtitles
3
+ subtitle* <Titlekit::SRT::Subtitles>
4
+ end
5
+
6
+ rule subtitle
7
+ id eol from ' --> ' to coordinates? eol lines eos <Titlekit::SRT::Subtitle>
8
+ end
9
+
10
+ rule id
11
+ number
12
+ end
13
+
14
+ rule from
15
+ timecode
16
+ end
17
+
18
+ rule to
19
+ timecode
20
+ end
21
+
22
+ rule timecode
23
+ hours ':' minutes ':' seconds (',' / '.') fractions <Titlekit::SRT::Timecode>
24
+ end
25
+
26
+ rule hours
27
+ number
28
+ end
29
+
30
+ rule minutes
31
+ number
32
+ end
33
+
34
+ rule seconds
35
+ number
36
+ end
37
+
38
+ rule fractions
39
+ number
40
+ end
41
+
42
+ rule coordinates
43
+ ' X1:' number ' X2:' number ' Y1:' number ' Y2:' number
44
+ end
45
+
46
+ rule lines
47
+ line+
48
+ end
49
+
50
+ rule line
51
+ string (eol / eof)
52
+ end
53
+
54
+ rule eos
55
+ eol+ / eof
56
+ end
57
+
58
+ rule eol
59
+ "\r\n" / "\n" / "\r"
60
+ end
61
+
62
+ rule eof
63
+ !.
64
+ end
65
+
66
+ rule number
67
+ [0-9]+
68
+ end
69
+
70
+ rule string
71
+ (!eol .)+
72
+ end
73
+ end
@@ -0,0 +1,201 @@
1
+ require 'treetop'
2
+
3
+ module Titlekit
4
+ module SSA
5
+
6
+ class Subtitles < Treetop::Runtime::SyntaxNode
7
+ def build
8
+ event_section.events.build
9
+ end
10
+ end
11
+
12
+ class ScriptInfo < Treetop::Runtime::SyntaxNode
13
+ def build
14
+ # elements.map { |subtitle| subtitle.build }
15
+ end
16
+ end
17
+
18
+ class V4PStyles < Treetop::Runtime::SyntaxNode
19
+ def build
20
+ # elements.map { |subtitle| subtitle.build }
21
+ end
22
+ end
23
+
24
+ class Events < Treetop::Runtime::SyntaxNode
25
+ def build
26
+ elements.map do |line|
27
+ subtitle = {}
28
+
29
+ fields = line.text_value.split(',')
30
+
31
+ subtitle[:id] = elements.index(line) + 1
32
+ subtitle[:start] = SSA.parse_timecode(fields[1])
33
+ subtitle[:end] = SSA.parse_timecode(fields[2])
34
+ subtitle[:lines] = fields[9..-1].join.gsub('\N', "\n")
35
+
36
+ subtitle
37
+ end
38
+ end
39
+ end
40
+
41
+ # class Subtitle < Treetop::Runtime::SyntaxNode
42
+ # def build
43
+ # {
44
+ # id: id.text_value.to_i,
45
+ # start: from.build,
46
+ # end: to.build,
47
+ # lines: lines.text_value.rstrip
48
+ # }
49
+ # end
50
+ # end
51
+
52
+ # class Timecode < Treetop::Runtime::SyntaxNode
53
+ # def build
54
+ # value = 0
55
+ # value += hours.text_value.to_i * 3600
56
+ # value += minutes.text_value.to_i * 60
57
+ # value += seconds.text_value.to_i
58
+ # value += "0.#{fractions.text_value}".to_f
59
+ # value
60
+ # end
61
+ # end
62
+
63
+ # Parses the supplied string and returns the results.
64
+ #
65
+ #
66
+ def self.import(string)
67
+ Treetop.load(File.join(__dir__, 'ssa'))
68
+ parser = SSAParser.new
69
+ syntax_tree = parser.parse(string)
70
+
71
+ if syntax_tree
72
+ return syntax_tree.build
73
+ else
74
+ failure = "failure_index #{parser.failure_index}\n"
75
+ failure += "failure_line #{parser.failure_line}\n"
76
+ failure += "failure_column #{parser.failure_column}\n"
77
+ failure += "failure_reason #{parser.failure_reason}\n"
78
+
79
+ raise failure
80
+ end
81
+ end
82
+
83
+ # Master the subtitles for best possible usage of the format's features.
84
+ #
85
+ # @param subtitles [Array<Hash>] the subtitles to master
86
+ def self.master(subtitles)
87
+ tracks = subtitles.map { |subtitle| subtitle[:track] }.uniq
88
+
89
+ if tracks.length == 1
90
+
91
+ # maybe styling? aside that: nada más!
92
+
93
+ elsif (2..3).include?(tracks.length)
94
+
95
+ subtitles.each do |subtitle|
96
+ case tracks.index(subtitle[:track])
97
+ when 0
98
+ subtitle[:style] = 'Default'
99
+ when 1
100
+ subtitle[:style] = 'Top'
101
+ when 2
102
+ subtitle[:style] = 'Middle'
103
+ end
104
+ end
105
+
106
+ elsif tracks.length >= 4
107
+
108
+ mastered_subtitles = []
109
+
110
+ # Determine timeframes with a discrete state
111
+ cuts = subtitles.map { |s| [s[:start], s[:end]] }.flatten.uniq.sort
112
+ frames = []
113
+ cuts.each_cons(2) do |pair|
114
+ frames << { start: pair[0], end: pair[1] }
115
+ end
116
+
117
+ frames.each do |frame|
118
+ intersecting = subtitles.select do |subtitle|
119
+ (subtitle[:end] == frame[:end] || subtitle[:start] == frame[:start] ||
120
+ (subtitle[:start] < frame[:start] && subtitle[:end] > frame[:end]))
121
+ end
122
+
123
+ if intersecting.any?
124
+ intersecting.sort_by! { |subtitle| tracks.index(subtitle[:track]) }
125
+ intersecting.each do |subtitle|
126
+ new_subtitle = {}
127
+ new_subtitle[:id] = mastered_subtitles.length+1
128
+ new_subtitle[:start] = frame[:start]
129
+ new_subtitle[:end] = frame[:end]
130
+
131
+ color = DEFAULT_PALETTE[tracks.index(subtitle[:track]) % DEFAULT_PALETTE.length]
132
+ new_subtitle[:style] = color
133
+ new_subtitle[:lines] = subtitle[:lines]
134
+
135
+ mastered_subtitles << new_subtitle
136
+ end
137
+ end
138
+ end
139
+
140
+ subtitles.replace(mastered_subtitles)
141
+ end
142
+ end
143
+
144
+ def self.export(subtitles)
145
+ result = ''
146
+
147
+ result << "[Script Info]\nScriptType: v4.00\n\n"
148
+
149
+ result << "[V4 Styles]\nFormat: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, TertiaryColour, BackColour, Bold, Italic, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding\n"
150
+ result << "Style: Default,Arial,16,16777215,16777215,16777215,-2147483640,0,0,1,3,0,2,70,70,40,0,0\n"
151
+ result << "Style: Middle,Arial,16,16777215,16777215,16777215,-2147483640,0,0,1,3,0,10,70,70,40,0,0\n"
152
+ result << "Style: Top,Arial,16,16777215,16777215,16777215,-2147483640,0,0,1,3,0,6,70,70,40,0,0\n"
153
+
154
+ DEFAULT_PALETTE.each do |color|
155
+ # reordered_color = ""
156
+ # reordered_color << color[4..5]
157
+ # reordered_color << color[2..3]
158
+ # reordered_color << color[0..1]\
159
+ processed_color = (color[4..5]+color[2..3]+color[0..1]).to_i(16)
160
+ result << "Style: #{color},Arial,16,#{processed_color},#{processed_color},#{processed_color},-2147483640,0,0,1,3,0,2,70,70,40,0,0\n"
161
+ end
162
+
163
+ result << "\n" # Close styles section
164
+
165
+ result << "[Events]\nFormat: Marked, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\n"
166
+ subtitles.each do |subtitle|
167
+ fields = [
168
+ 'Dialogue: 0', # Format: Marked
169
+ SSA.build_timecode(subtitle[:start]), # Start
170
+ SSA.build_timecode(subtitle[:end]), # End
171
+ subtitle[:style] || 'Default', # Style
172
+ '', # Name
173
+ '0000', # MarginL
174
+ '0000', # MarginR
175
+ '0000', # MarginV
176
+ '',# Effect
177
+ subtitle[:lines].gsub("\n", '\N') # Text
178
+ ]
179
+
180
+ result << (fields.join(',') + "\n")
181
+ end
182
+
183
+ return result
184
+ end
185
+
186
+ protected
187
+
188
+ def self.build_timecode(seconds)
189
+ sprintf("%01d:%02d:%02d.%s",
190
+ seconds / 3600,
191
+ (seconds%3600) / 60,
192
+ seconds % 60,
193
+ sprintf("%.2f", seconds)[-2, 3])
194
+ end
195
+
196
+ def self.parse_timecode(timecode)
197
+ mres = timecode.match(/(?<h>\d):(?<m>\d{2}):(?<s>\d{2})[:|\.](?<ms>\d+)/)
198
+ return "#{mres["h"].to_i * 3600 + mres["m"].to_i * 60 + mres["s"].to_i}.#{mres["ms"]}".to_f
199
+ end
200
+ end
201
+ end
@@ -0,0 +1,72 @@
1
+ grammar SSA
2
+ rule subtitles
3
+ info?
4
+ styles?
5
+ event_section
6
+ fonts?
7
+ graphics?
8
+ end_of_data
9
+
10
+ <Titlekit::SSA::Subtitles>
11
+ end
12
+
13
+ rule info
14
+ '[Script Info]' end_of_line lines* end_of_section <Titlekit::SSA::ScriptInfo>
15
+ end
16
+
17
+ rule styles
18
+ '[V4 Styles]' end_of_line lines* end_of_section <Titlekit::SSA::V4PStyles>
19
+ end
20
+
21
+ rule event_section
22
+ '[Events]' end_of_line line events end_of_section
23
+ end
24
+
25
+ rule events
26
+ line* <Titlekit::SSA::Events>
27
+ end
28
+
29
+ rule event
30
+ 'Dialogue'
31
+ end
32
+
33
+ rule fonts
34
+ '[Fonts]' end_of_line lines* end_of_section
35
+ end
36
+
37
+ rule graphics
38
+ '[Graphics]' end_of_line lines* end_of_section
39
+ end
40
+
41
+ rule lines
42
+ line+
43
+ end
44
+
45
+ rule line
46
+ string (end_of_line / end_of_file)
47
+ end
48
+
49
+ rule end_of_section
50
+ end_of_line+ / end_of_file
51
+ end
52
+
53
+ rule end_of_line
54
+ "\r\n" / "\n" / "\r"
55
+ end
56
+
57
+ rule end_of_data
58
+ end_of_line+ / end_of_file
59
+ end
60
+
61
+ rule end_of_file
62
+ !.
63
+ end
64
+
65
+ rule number
66
+ [0-9]+
67
+ end
68
+
69
+ rule string
70
+ (!end_of_line .)+
71
+ end
72
+ end
@@ -0,0 +1,131 @@
1
+ module Titlekit
2
+ class Specification
3
+
4
+ # Only for internal usage by the job control center
5
+ attr_accessor :subtitles
6
+
7
+ def initialize
8
+ @encoding = nil
9
+ @file = nil
10
+ @fps = nil
11
+ @references = {}
12
+ @subtitles = []
13
+ @track = nil
14
+
15
+ return self
16
+ end
17
+
18
+ # @param [String] A string specifying the encoding, e.g. 'utf-8' or 'ISO-8859-1'
19
+ # @return If you omit the argument, it returns the already specified encoding
20
+ def encoding(*args)
21
+ if args.empty?
22
+ return @encoding
23
+ else
24
+ @encoding = args[0]
25
+ return self
26
+ end
27
+ end
28
+
29
+ # @param [String] A string specifying the path to the file
30
+ # @return If you omit the argument, it returns the already specified path
31
+ def file(*args)
32
+ if args.empty?
33
+ return @file
34
+ else
35
+ @file = args[0]
36
+ return self
37
+ end
38
+ end
39
+
40
+ # @param [String] A string specifying the track identifier
41
+ # @return If you omit the argument, it returns the already specified track
42
+ def track(*args)
43
+ if args.empty?
44
+ return @track
45
+ else
46
+ @track = args[0]
47
+ return self
48
+ end
49
+ end
50
+
51
+ # @param [Float] A float specifying the frames per second, e.g. 23.976
52
+ # @return If you omit the argument, it returns the already specified fps
53
+ def fps(*args)
54
+ if args.empty?
55
+ return @fps
56
+ else
57
+ @fps = args[0]
58
+ return self
59
+ end
60
+ end
61
+
62
+ # Returns all named references you have specified
63
+ def references
64
+ return @references
65
+ end
66
+
67
+ # Places a named reference (in the form of a string or a symbol)
68
+ # on a timecode specified by either +hours+, +minutes+, +seconds+
69
+ # or +milliseconds+.
70
+ #
71
+ # Its typical use-case is to reference a specific subtitle you can
72
+ # recognize in both the movie and your subtitle file, where usually
73
+ # for the subtitle file (represented by {Have}) you will reference
74
+ # the subtitle index and for the movie (represented by {Want}) you
75
+ # will reference the timecode that is displayed when the line occurs
76
+ # in the movie.
77
+ #
78
+ # @example Referencing a timecode by hours
79
+ # have.reference('Earl grey, hot', hours: 0.963)
80
+ #
81
+ # @example Referencing a timecode by seconds
82
+ # have.reference('In a galaxy ...', seconds: 14.2)
83
+ #
84
+ # @example Referencing a timecode by an SRT-style timecode
85
+ # have.reference('In a galaxy ...', srt_timecode: '00:00:14,200')
86
+ #
87
+ # @example Referencing a timecode by an ASS-style timecode
88
+ # have.reference('In a galaxy ...', ass_timecode: '0:00:14,20')
89
+ #
90
+ # @example Referencing a timecode by an SSA-style timecode
91
+ # have.reference('In a galaxy ...', ssa_timecode: '0:00:14,20')
92
+ #
93
+ # @example Symbols can be used as references as well!
94
+ # have.reference(:narrator_begins, minutes: 9.6)
95
+ #
96
+ # @param name [String, Symbol] The name of the reference
97
+ # @param hours [Float]
98
+ # @param minutes [Float]
99
+ # @param seconds [Float]
100
+ # @param milliseconds [Float]
101
+ def reference(name,
102
+ *args,
103
+ hours: nil,
104
+ minutes: nil,
105
+ seconds: nil,
106
+ milliseconds: nil,
107
+ srt_timecode: nil,
108
+ ssa_timecode: nil,
109
+ ass_timecode: nil)
110
+
111
+ @references[name] = case
112
+ when hours
113
+ { timecode: hours * 3600 }
114
+ when minutes
115
+ { timecode: minutes * 60 }
116
+ when seconds
117
+ { timecode: seconds }
118
+ when milliseconds
119
+ { timecode: milliseconds / 1000 }
120
+ when srt_timecode
121
+ { timecode: Titlekit::SRT.parse_timecode(srt_timecode) }
122
+ when ssa_timecode
123
+ { timecode: Titlekit::SSA.parse_timecode(ssa_timecode) }
124
+ when ass_timecode
125
+ { timecode: Titlekit::ASS.parse_timecode(ass_timecode) }
126
+ end
127
+
128
+ return self
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,3 @@
1
+ module Titlekit
2
+ DEFAULT_PALETTE = %w{EDF393 F5E665 FFC472 FFA891 89BABE}
3
+ end
@@ -0,0 +1,3 @@
1
+ module Titlekit
2
+ VERSION = '1.0.0'
3
+ end
@@ -0,0 +1,24 @@
1
+ module Titlekit
2
+ class Want < Specification
3
+
4
+ def initialize
5
+ super
6
+ @glue_treshold = 0.08
7
+ end
8
+
9
+ # For dual subtitles the starts and ends of simultaneously occuring
10
+ # subtitles can be micro-adjusted together if their distance is smaller
11
+ # than the glue_treshold. Normally defaults to 0.08 (super-conservative).
12
+ #
13
+ # @param [Float] Specifies the new glue_treshold
14
+ # @return If you omit the argument, it returns the set glue_treshold
15
+ def glue_treshold(*args)
16
+ if args.empty?
17
+ return @glue_treshold
18
+ else
19
+ @glue_treshold = args[0]
20
+ return self
21
+ end
22
+ end
23
+ end
24
+ end
data/lib/titlekit.rb ADDED
@@ -0,0 +1,9 @@
1
+ require 'titlekit/utilities'
2
+ require 'titlekit/parsers/ass'
3
+ require 'titlekit/parsers/ssa'
4
+ require 'titlekit/parsers/srt'
5
+ require 'titlekit/specification'
6
+ require 'titlekit/have'
7
+ require 'titlekit/want'
8
+ require 'titlekit/job'
9
+ require 'titlekit/version'