distorted 0.5.2 → 0.5.7

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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +661 -0
  3. data/README.md +4 -139
  4. data/font/1252/LICENSE/MoreLessPerfectDOSVGA437/img/Less_Perfect_DOS_VGA.png +0 -0
  5. data/font/1252/LICENSE/MoreLessPerfectDOSVGA437/img/More_Perfect_DOS_VGA.png +0 -0
  6. data/font/1252/LICENSE/MoreLessPerfectDOSVGA437/img/Perfect_DOS_VGA.png +0 -0
  7. data/font/1252/LICENSE/MoreLessPerfectDOSVGA437/less_more_perfect_dos_vga_437.html +52 -0
  8. data/font/1252/LICENSE/PerfectDOSVGA437/font-comment.php@file=perfect_dos_vga_437.html +5 -0
  9. data/font/1252/LessPerfectDOSVGA.ttf +0 -0
  10. data/font/1252/MorePerfectDOSVGA.ttf +0 -0
  11. data/font/1252/Perfect DOS VGA 437 Win.ttf +0 -0
  12. data/font/437/Perfect DOS VGA 437.ttf +0 -0
  13. data/font/437/dos437.txt +72 -0
  14. data/font/65001/Anonymous Pro B.ttf +0 -0
  15. data/font/65001/Anonymous Pro BI.ttf +0 -0
  16. data/font/65001/Anonymous Pro I.ttf +0 -0
  17. data/font/65001/Anonymous Pro.ttf +0 -0
  18. data/font/65001/LICENSE/AnonymousPro/FONTLOG.txt +45 -0
  19. data/font/65001/LICENSE/AnonymousPro/OFL-FAQ.txt +235 -0
  20. data/font/65001/LICENSE/AnonymousPro/OFL.txt +94 -0
  21. data/font/65001/LICENSE/AnonymousPro/README.txt +55 -0
  22. data/font/850/ProFont-Bold-01/LICENSE +22 -0
  23. data/font/850/ProFont-Bold-01/readme.txt +28 -0
  24. data/font/850/ProFontWindows-Bold.ttf +0 -0
  25. data/font/850/ProFontWindows.ttf +0 -0
  26. data/font/850/Profont/LICENSE +22 -0
  27. data/font/850/Profont/readme.txt +31 -0
  28. data/font/932/LICENSE/README-ttf.txt +213 -0
  29. data/font/932/mona.ttf +0 -0
  30. data/lib/distorted/error_code.rb +8 -0
  31. data/lib/distorted/font.rb +192 -0
  32. data/lib/distorted/image.rb +121 -0
  33. data/lib/distorted/modular_technology/pango.rb +75 -0
  34. data/lib/distorted/monkey_business/hash.rb +33 -0
  35. data/lib/distorted/monkey_business/mnemoniq.rb +8 -0
  36. data/lib/distorted/monkey_business/set.rb +15 -0
  37. data/lib/distorted/monkey_business/string.rb +6 -0
  38. data/lib/distorted/pdf.rb +110 -0
  39. data/lib/distorted/svg.rb +21 -0
  40. data/lib/distorted/text.rb +241 -0
  41. data/lib/distorted/version.rb +20 -0
  42. data/lib/distorted/video.rb +193 -0
  43. data/test/distorted_test.rb +11 -0
  44. data/test/test_helper.rb +4 -0
  45. metadata +77 -5
@@ -0,0 +1,6 @@
1
+ class String
2
+ # https://stackoverflow.com/a/22586646
3
+ def map
4
+ size.times.with_object('') {|i,s| s << yield(self[i])}
5
+ end
6
+ end
@@ -0,0 +1,110 @@
1
+ require 'set'
2
+
3
+ require 'hexapdf'
4
+ require 'mime/types'
5
+
6
+
7
+ module Cooltrainer
8
+ module DistorteD
9
+ class PDF
10
+
11
+ MEDIA_TYPE = 'application'.freeze
12
+ SUB_TYPE = 'pdf'.freeze
13
+
14
+ MIME_TYPES = MIME::Types["#{MEDIA_TYPE}/#{SUB_TYPE}"].to_set
15
+
16
+ # https://developer.mozilla.org/en-US/docs/Web/HTML/Element/object#Attributes
17
+ # https://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/pdf_open_parameters.pdf
18
+ PDF_OPEN_PARAMS = Array[
19
+ # Keep the PDF Open Params in the order they are defined
20
+ # in the Adobe documentation, since it says they should
21
+ # be specified in the URL in that same order.
22
+ # Ruby's Set doesn't guarantee order, so use a plain Array here.
23
+ :nameddest,
24
+ :page,
25
+ :comment,
26
+ :collab,
27
+ :zoom,
28
+ :view,
29
+ :viewrect,
30
+ :pagemode,
31
+ :scrollbar,
32
+ :search,
33
+ :toolbar,
34
+ :statusbar,
35
+ :messages,
36
+ :navpanes,
37
+ :highlight,
38
+ :fdf,
39
+ ]
40
+ ATTRS = Set[
41
+ :alt,
42
+ :caption,
43
+ :height, #<object> viewer container height.
44
+ :width, # <object> viewer container width.
45
+ ].merge(PDF_OPEN_PARAMS)
46
+
47
+ # "You cannot use the reserved characters =, #, and &.
48
+ # There is no way to escape these special characters."
49
+ RESERVED_CHARACTERS_FRAGMENT = '[^=#&]+'.freeze
50
+
51
+ FLOAT_INT_FRAGMENT = '[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)'.freeze
52
+ ZERO_TO_ONE_HUNDRED = /^(([1-9]\d?|1\d{1})([.,]\d{0,1})?|100([.,]0{1})?)$/
53
+ BOOLEAN_SET = Set[0, 1, false, true, '0'.freeze, '1'.freeze, 'false'.freeze, 'true'.freeze]
54
+
55
+ ATTRS_DEFAULT = {
56
+ :height => '100%'.freeze,
57
+ :width => '100%'.freeze,
58
+ # BEGIN PDF Open Parameters
59
+ :page => 1,
60
+ :view => :Fit,
61
+ :pagemode => :none,
62
+ :scrollbar => 1,
63
+ :toolbar => 1,
64
+ :statusbar => 1,
65
+ :messages => 0,
66
+ :navpanes => 1,
67
+ # END PDF Open Parameters
68
+ }
69
+
70
+ # Adobe's PDF Open Parameters documentation sez:
71
+ # "Individual parameters, together with their values (separated by & or #),
72
+ # can be no greater then 32 characters in length."
73
+ # …but then goes on to show some examples (like `comment`)
74
+ # that are clearly longer than 32 characters.
75
+ # Dunno. I'll err on the side of giving you a footgun.
76
+ ATTRS_VALUES = {
77
+ :nameddest => /^#{RESERVED_CHARACTERS_FRAGMENT}$/,
78
+ :page => /\d/,
79
+ :comment => /^#{RESERVED_CHARACTERS_FRAGMENT}$/,
80
+ :collab => /^(DAVFDF|FSFDF|DB)@#{RESERVED_CHARACTERS_FRAGMENT}$/,
81
+ :zoom => /^#{FLOAT_INT_FRAGMENT}(,#{FLOAT_INT_FRAGMENT},#{FLOAT_INT_FRAGMENT})?$/,
82
+ :view => /^Fit(H|V|B|BH|BV(,#{FLOAT_INT_FRAGMENT})?)?$/,
83
+ :viewrect => /^#{FLOAT_INT_FRAGMENT},#{FLOAT_INT_FRAGMENT},#{FLOAT_INT_FRAGMENT},#{FLOAT_INT_FRAGMENT}$/,
84
+ :pagemode => Set[:none, :thumbs, :bookmarks],
85
+ :scrollbar => BOOLEAN_SET,
86
+ :search => /^#{RESERVED_CHARACTERS_FRAGMENT}(,\s#{RESERVED_CHARACTERS_FRAGMENT})*$/,
87
+ :toolbar => BOOLEAN_SET,
88
+ :statusbar => BOOLEAN_SET,
89
+ :messages => BOOLEAN_SET,
90
+ :navpanes => BOOLEAN_SET,
91
+ :fdf => /^#{RESERVED_CHARACTERS_FRAGMENT}$/,
92
+ }
93
+
94
+
95
+ def self.optimize(src, dest)
96
+ HexaPDF::Document.open(src) do |doc|
97
+ doc.task(
98
+ :optimize,
99
+ compact: true,
100
+ object_streams: :generate,
101
+ xref_streams: :generate,
102
+ compress_pages: false,
103
+ )
104
+ doc.write(dest)
105
+ end
106
+ end
107
+
108
+ end # PDF
109
+ end # DistorteD
110
+ end # Cooltrainer
@@ -0,0 +1,21 @@
1
+ require 'set'
2
+
3
+ require 'mime/types'
4
+ require 'svg_optimizer'
5
+
6
+ module Cooltrainer
7
+ module DistorteD
8
+ class SVG < Image
9
+
10
+ SUB_TYPE = 'svg'.freeze
11
+
12
+ MIME_TYPES = MIME::Types[/^#{self::MEDIA_TYPE}\/#{self::SUB_TYPE}/, :complete => true].to_set
13
+
14
+ def self.optimize(src, dest)
15
+ # TODO: Make optimizations/plugins configurable
16
+ SvgOptimizer.optimize_file(src, dest, SvgOptimizer::DEFAULT_PLUGINS)
17
+ end
18
+
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,241 @@
1
+ require 'set'
2
+
3
+ require 'ttfunk' # Font metadata extraction
4
+ require 'charlock_holmes' # Text file charset detection
5
+
6
+ require 'distorted/monkey_business/string' # String#map
7
+ require 'distorted/modular_technology/pango'
8
+
9
+ require 'distorted/image'
10
+
11
+ require 'mime/types'
12
+
13
+ # No need to do all the fancy library versioning in a subclass.
14
+ require 'vips'
15
+
16
+
17
+ module Cooltrainer
18
+ module DistorteD
19
+ class Text < Image
20
+
21
+ include Cooltrainer::DistorteD::Tech::Pango;
22
+
23
+
24
+ MEDIA_TYPE = 'text'.freeze
25
+
26
+ MIME_TYPES = MIME::Types[/^#{self::MEDIA_TYPE}\/(plain|x-nfo)/, :complete => true].to_set
27
+
28
+ ATTRS = Set[
29
+ :alt,
30
+ :crop,
31
+ :font,
32
+ :encoding,
33
+ :spacing,
34
+ :dpi,
35
+ ]
36
+ ATTRS_VALUES = {
37
+ :spacing => Set[:monospace, :proportional],
38
+ }
39
+ ATTRS_DEFAULT = {
40
+ :crop => :none,
41
+ :dpi => 144,
42
+ }
43
+
44
+ # Track supported fonts by codepage.
45
+ # Avoid renaming these from the original archives / websites.
46
+ # Try not to go nuts here bloating the size of our Gem for a
47
+ # very niche feature, but I want to ensure good coverage too.
48
+ #
49
+ # Treat codepage 8859 documents as codepage 1252 to avoid breaking smart-
50
+ # quotes and other printable chars in 1252 that are control chars in 8859.
51
+ # https://encoding.spec.whatwg.org/#names-and-labels
52
+ #
53
+ # Numeric key for UTF-8 is codepage 65001 like Win32:
54
+ # https://docs.microsoft.com/en-us/windows/win32/intl/code-page-identifiers
55
+ FONT_FILENAME = {
56
+ :anonpro => 'Anonymous Pro.ttf'.freeze,
57
+ :anonpro_b => 'Anonymous Pro B.ttf'.freeze,
58
+ :anonpro_bi => 'Anonymous Pro BI.ttf'.freeze,
59
+ :anonpro_i => 'Anonymous Pro I.ttf'.freeze,
60
+ :lessperfectdosvga => 'LessPerfectDOSVGA.ttf'.freeze,
61
+ :moreperfectdisvga => 'MorePerfectDOSVGA.ttf'.freeze,
62
+ :perfectdosvgawin => 'Perfect DOS VGA 437 Win.ttf'.freeze,
63
+ :mona => 'mona.ttf'.freeze,
64
+ :perfectdosvga => 'Perfect DOS VGA 437.ttf'.freeze,
65
+ :profont => 'ProFontWindows.ttf'.freeze,
66
+ :profont_b => 'ProFontWindows-Bold.ttf'.freeze,
67
+ }
68
+ # Certain fonts are more suitable for certain codepages,
69
+ # so track each codepage's available fonts…
70
+ CODEPAGE_FONT = {
71
+ 65001 => [
72
+ :anonpro,
73
+ :anonpro_b,
74
+ :anonpro_bi,
75
+ :anonpro_i,
76
+ ],
77
+ 1252 => [
78
+ :lessperfectdosvga,
79
+ :moreperfectdosvga,
80
+ :perfectdosvgawin,
81
+ ],
82
+ 932 => [
83
+ :mona,
84
+ ],
85
+ 850 => [
86
+ :profont,
87
+ :profont_b,
88
+ ],
89
+ 437 => [
90
+ :perfectdosvga,
91
+ ],
92
+ }
93
+ # …as well as the inverse, the numeric codepage for each font:
94
+ FONT_CODEPAGE = CODEPAGE_FONT.reduce(Hash.new([])) { |memo, (key, values)|
95
+ values.each { |value| memo[value] = key }
96
+ memo
97
+ }
98
+
99
+
100
+ # Using a numeric key for things for simplicity.
101
+ # TODO: Replace this with Ruby's built-in Encoding class after I have
102
+ # a better idea what I want to do.
103
+ def codepage
104
+ case @encoding
105
+ when 'UTF-8'.freeze then 65001
106
+ when 'Shift_JIS'.freeze then 932
107
+ when 'IBM437'.freeze then 437
108
+ else 1252
109
+ end
110
+ end
111
+
112
+ # Return a Pango Markup escaped version of the document.
113
+ def to_pango
114
+ # https://developer.gnome.org/glib/stable/glib-Simple-XML-Subset-Parser.html#g-markup-escape-text
115
+ escaped = @contents.map{ |c|
116
+ g_markup_escape_char(c)
117
+ }
118
+ if spacing == :monospace
119
+ "<tt>" << escaped << "</tt>"
120
+ else
121
+ escaped
122
+ end
123
+ end
124
+
125
+ def initialize(src, encoding: nil, font: nil, spacing: nil, dpi: ATTRS_DEFAULT[:dpi])
126
+ @src = src
127
+ @liquid_spacing = spacing
128
+
129
+ # VIPS makes us provide the text content as a single variable,
130
+ # so we may as well just one-shot File.read() it into memory.
131
+ # https://kunststube.net/encoding/
132
+ contents = File.read(@src)
133
+
134
+ # It's not easy or even possible in some cases to tell the "true" codepage
135
+ # we should use for any given text document, but using character detection
136
+ # is worth a shot if the user gave us nothing.
137
+ detected = CharlockHolmes::EncodingDetector.detect(contents)
138
+ @encoding = (encoding || detected[:encoding] || 'UTF-8'.freeze).to_s
139
+ @contents = CharlockHolmes::Converter.convert(contents, @encoding, 'UTF-8'.freeze)
140
+
141
+ # Set the shorthand symbol key for our chosen font.
142
+ @font = font&.to_sym || self.singleton_class.const_get(:CODEPAGE_FONT)[codepage].first
143
+
144
+ # Load font metadata directly from the file so we don't have to
145
+ # duplicate it here to feed to Vips/Pango.
146
+ #
147
+ # irb(main)> font_meta.name.font_name
148
+ # => ["Perfect DOS VGA 437", "\x00P\x00e\x00r\x00f\x00e\x00c\x00t\x00 \x00D\x00O\x00S\x00 \x00V\x00G\x00A\x00 \x004\x003\x007"]
149
+ # irb(main)> font_meta.name.font_family
150
+ # => ["Perfect DOS VGA 437", "\x00P\x00e\x00r\x00f\x00e\x00c\x00t\x00 \x00D\x00O\x00S\x00 \x00V\x00G\x00A\x00 \x004\x003\x007"]
151
+ # irb(main)> font_meta.name.font_subfamily
152
+ # => ["Regular", "\x00R\x00e\x00g\x00u\x00l\x00a\x00r"]
153
+ # irb(main)> font_meta.name.postscript_name
154
+ # => "PerfectDOSVGA437"
155
+ # irb(main)> font_meta.line_gap
156
+ # => 0
157
+ @font_meta = TTFunk::File.open(font_path)
158
+
159
+ # https://libvips.github.io/libvips/API/current/libvips-create.html#vips-text
160
+ @image = Vips::Image.text(
161
+ # This string must be well-escaped Pango Markup:
162
+ # https://developer.gnome.org/pango/stable/pango-Markup.html
163
+ # However the official function for escaping text is
164
+ # not implemented in Ruby GLib, so we have to do it ourselves.
165
+ to_pango,
166
+ **{
167
+ # String absolute path to TTF
168
+ :fontfile => font_path,
169
+ # It's not enough to just specify the TTF path;
170
+ # we must also specify a font family, subfamily, and size.
171
+ :font => "#{font_name} 16",
172
+ # Space between lines (in Points).
173
+ :spacing => @font_meta.line_gap,
174
+ :justify => true, # Requires libvips 8.8
175
+ :dpi => dpi.to_i,
176
+ },
177
+ )
178
+ end
179
+
180
+ protected
181
+
182
+ # Return the String absolute path to the TTF file
183
+ def font_path
184
+ File.join(
185
+ File.dirname(__FILE__), # distorted
186
+ '..'.freeze, # lib
187
+ '..'.freeze, # DistorteD-Ruby
188
+ 'font'.freeze,
189
+ font_codepage,
190
+ font_filename,
191
+ )
192
+ end
193
+
194
+ # Returns the numeric representation of the codepage
195
+ # covered by our font.
196
+ def font_codepage
197
+ self.singleton_class.const_get(:FONT_CODEPAGE)&.dig(@font).to_s
198
+ end
199
+
200
+ # Returns the basename (with file extension) of our font.
201
+ def font_filename
202
+ self.singleton_class.const_get(:FONT_FILENAME)&.dig(@font)
203
+ end
204
+
205
+ # Returns a boolean for whether or not this font is monospaced.
206
+ # true == monospace
207
+ # false == proportional
208
+ def spacing
209
+ # Monospace fonts will (read: should) have the same width
210
+ # for every glyph, so we can tell a monospace font by
211
+ # checking if a deduplicated widths table has size == 1:
212
+ # irb(main)> font.horizontal_metrics.widths.count
213
+ # => 256
214
+ # irb(main)> font.horizontal_metrics.widths.uniq.compact.length
215
+ # => 1
216
+ @font_meta.horizontal_metrics.widths.uniq.compact.length == 1 ? :monospace : :proportional
217
+ end
218
+
219
+ # Returns the Family and Subfamily as one string suitable for libvips
220
+ def font_name
221
+ "#{@font_meta.name.font_family.first.encode('UTF-8')} #{@font_meta.name.font_subfamily.first.encode('UTF-8')}"
222
+ end
223
+
224
+ # Returns the Pango-Markup-encoded UTF-8 String version + revision of the font
225
+ def font_version
226
+ g_markup_escape_text(@font_meta.name&.version&.first&.encode('UTF-8').to_s)
227
+ end
228
+
229
+ # Returns the Pango-Markup-encoded UTF-8 String font file description
230
+ def font_description
231
+ g_markup_escape_text(@font_meta.name&.description&.first&.encode('UTF-8').to_s)
232
+ end
233
+
234
+ # Returns the Pango-Markup-encoded UTF-8 String copyright information of the font
235
+ def font_copyright
236
+ g_markup_escape_text(@font_meta.name&.copyright&.first&.encode('UTF-8').to_s)
237
+ end
238
+
239
+ end # Text
240
+ end # DistorteD
241
+ end # Cooltrainer
@@ -0,0 +1,20 @@
1
+ #
2
+ # `.........-` `:/:::://.`
3
+ # `+/``+ssss:``-/:` `:o+y. `::::-----``
4
+ # -+- :hmNNmdhs- `o++` `-:--/ -/... ./shhyoy. +mmmmds+:--...`
5
+ # .//``odmNmmd+++` :+/h:-------------:+. ./...-..........---/--:.------// -.--------//o//o+/+- :hhdmmmmmh/`-/-`
6
+ # `:+- :ymNmmyy:...-:` /: ./////////////- ::/:/- `++++++++` -y /:.`.////+- /////- `/+++++++. :- .::+sydmNm+ `/+.
7
+ # -+:` /syyyo:--:+oso` /: ---:::::::syys. -yoy+s` :mmmmmmmd- .h` ``:osshhyyy` /oshhs` -:::::::. :: `-..-/dmo` `/yo:
8
+ # `/o:--------:+syyy++``+s+++++++++/``:h+os``oshdos``odhhhddyy-`.h.`.ssyhmmdmoh/``o+mmho``:++++++++++y+``----::``-oyss.
9
+ # +sssssssyyyyyhdmmsyo-+/----------.`.yooy-`-hodss/``---------.`.h-`.sohdhsymhsy.`:+yNmy/`.-----------:++ooooooo+sss+`
10
+ # `+dmmmdddddmmNNddhsyosooooooooooooooysoysssyymssssssssssssssssshyssyoh:` .mhsyssssodddyooooooooooooooysoyyyyyyyyy/
11
+ # /mNmdddhmdmmdy+-+mdhhhhhhhhhhhhhhhmhhyhhhmdmmdhhhhhhhhhhhhhhhdhhhsm+ ydddhhhhd-hdmdddddddddddddddyhdhdmmNms.
12
+ # .---.......` -yhNmmhddhhhdhmhmhddmNNmyyy+dmNdydhyyyhddhmmmmNmhh. -hyhNNNh: .mNmdmdddhhhdddmmyssssssso:
13
+ # `/oooooooooooooo:`-+o+/. `+oo+o++++ooo+oooooo- `-+oo/` :oooooooooooooo/
14
+ #
15
+
16
+ module Cooltrainer
17
+ module DistorteD
18
+ VERSION = '0.5.7'.freeze
19
+ end
20
+ end
@@ -0,0 +1,193 @@
1
+ #!/usr/bin/env ruby
2
+ # Tell the user to install the shared library if it's missing.
3
+ begin
4
+ require 'gst'
5
+ rescue LoadError => le
6
+ raise unless le.message =~ /libgst/
7
+
8
+ # Multiple OS help
9
+ help = <<~INSTALL
10
+
11
+ Please install the GStreamer library for your system.
12
+ INSTALL
13
+
14
+ # Re-raise with install message
15
+ raise $!, "#{help}\n#{$!}", $!.backtrace
16
+ end
17
+
18
+ require 'set'
19
+
20
+ require 'mime/types'
21
+
22
+ module Cooltrainer
23
+ module DistorteD
24
+ class Video
25
+
26
+ MEDIA_TYPE = 'video'.freeze
27
+ MIME_TYPES = MIME::Types[/^#{MEDIA_TYPE}/, :complete => true].to_set
28
+
29
+ # Attributes for our <video>.
30
+ # Automatically enabled as attrs for DD Liquid Tag.
31
+ # https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video#Attributes
32
+ ATTRS = Set[:caption]
33
+
34
+ # Defaults for HTML Element attributes.
35
+ # Not every attr has to be listed here.
36
+ # Many need no default and just won't render.
37
+ ATTRS_DEFAULT = {}
38
+ ATTRS_VALUES = {}
39
+
40
+ attr_accessor :dest
41
+
42
+
43
+ def initialize(src, dest, basename)
44
+ @src = src
45
+ @dest = dest
46
+ @basename = basename
47
+ end
48
+
49
+ def rotate(angle=nil)
50
+ false
51
+ end
52
+
53
+ def clean
54
+ false
55
+ end
56
+
57
+ def generate
58
+ self.generate_hls
59
+ begin
60
+ self.generate_dash
61
+ rescue Gst::ParseError::NoSuchElement
62
+ # This is going away once the new dashsink2 lands in Gst so :effort:
63
+ end
64
+ end
65
+
66
+ def generate_dash
67
+ orig_dest = @dest
68
+ orig_path = @src
69
+
70
+ FileUtils.mkdir_p(File.dirname(orig_dest))
71
+
72
+ hls_dest = File.join(File.dirname(orig_dest), @basename + '.dash')
73
+ FileUtils.mkdir_p(hls_dest)
74
+ Jekyll.logger.debug(@tag_name, "Re-muxing #{orig_path} to #{hls_dest}.")
75
+
76
+ #FileUtils.rm(orig_dest) if File.exist?(orig_dest)
77
+ if not File.file?(orig_dest)
78
+ FileUtils.cp(orig_path, orig_dest)
79
+ end
80
+
81
+ # https://gstreamer.freedesktop.org/documentation/tools/gst-launch.html?gi-language=c#pipeline-description
82
+ # TODO: Convert this from parse_launch() pipeline notation to Element objects
83
+ # TODO: Get source video duration/resolution/etc and use it to compute a
84
+ # value for `target-duration`.
85
+ # TODO: Also support urldecodebin for remote media.
86
+ pipeline, error = Gst.parse_launch("filesrc name=src ! decodebin name=demux ! videoconvert ! vaapih264enc ! queue2 ! h264parse ! queue2 ! mux.video dashsink name=mux max-files=0 playlist-length=0 target-duration=2 demux. ! audioconvert ! voaacenc ! queue2 ! mux.audio")
87
+
88
+ if pipeline.nil?
89
+ Jekyll.logger.error(@tag_name, "Parse error: #{error.message}")
90
+ return false
91
+ end
92
+
93
+ filesrc = pipeline.get_by_name('src')
94
+ filesrc.location = orig_path
95
+
96
+ hls_playlist = "#{hls_dest}/#{@basename}.m3u8"
97
+ hls = pipeline.get_by_name('mux')
98
+ hls.location = "#{hls_dest}/#{@basename}%05d.ts"
99
+ hls.playlist_location = hls_playlist
100
+
101
+ # TODO: config option for absolute vs relative segment URIs in the playlist.
102
+ #hls.playlist_root = @url
103
+
104
+ # TODO: dashsink support once there is a stable GStreamer release including it:
105
+ # https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/merge_requests/704
106
+
107
+ pipeline.play
108
+
109
+ # Play until End Of Stream
110
+ event_loop(pipeline)
111
+
112
+ pipeline.stop
113
+
114
+ end
115
+
116
+ def generate_hls
117
+ orig_dest = @dest
118
+ orig_path = @src
119
+
120
+ FileUtils.mkdir_p(File.dirname(orig_dest))
121
+
122
+ hls_dest = File.join(File.dirname(orig_dest), @basename + '.hls')
123
+ FileUtils.mkdir_p(hls_dest)
124
+ Jekyll.logger.debug(@tag_name, "Re-muxing #{orig_path} to #{hls_dest}.")
125
+
126
+ #FileUtils.rm(orig_dest) if File.exist?(orig_dest)
127
+ if not File.file?(orig_dest)
128
+ FileUtils.cp(orig_path, orig_dest)
129
+ end
130
+
131
+ # https://gstreamer.freedesktop.org/documentation/tools/gst-launch.html?gi-language=c#pipeline-description
132
+ # TODO: Convert this from parse_launch() pipeline notation to Element objects
133
+ # TODO: Get source video duration/resolution/etc and use it to compute a
134
+ # value for `target-duration`.
135
+ # TODO: Also support urldecodebin for remote media.
136
+ pipeline, error = Gst.parse_launch("filesrc name=src ! decodebin name=demux ! videoconvert ! vaapih264enc ! queue2 ! h264parse ! queue2 ! mux.video hlssink2 name=mux max-files=0 playlist-length=0 target-duration=2 demux. ! audioconvert ! voaacenc ! queue2 ! mux.audio")
137
+
138
+ if pipeline.nil?
139
+ Jekyll.logger.error(@tag_name, "Parse error: #{error.message}")
140
+ return false
141
+ end
142
+
143
+ filesrc = pipeline.get_by_name('src')
144
+ filesrc.location = orig_path
145
+
146
+ hls_playlist = "#{hls_dest}/#{@basename}.m3u8"
147
+ hls = pipeline.get_by_name('mux')
148
+ hls.location = "#{hls_dest}/#{@basename}%05d.ts"
149
+ hls.playlist_location = hls_playlist
150
+
151
+ # TODO: config option for absolute vs relative segment URIs in the playlist.
152
+ #hls.playlist_root = @url
153
+
154
+ # TODO: dashsink support once there is a stable GStreamer release including it:
155
+ # https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/merge_requests/704
156
+
157
+ pipeline.play
158
+
159
+ # Play until End Of Stream
160
+ event_loop(pipeline)
161
+
162
+ pipeline.stop
163
+
164
+ # HACK HACK HACK: Replace X-ALLOW-CACHE line in playlist with YES.
165
+ # This property does not seem to be exposed to the outside of hlssink:
166
+ # https://cgit.freedesktop.org/gstreamer/gst-plugins-bad/tree/ext/hls/gsthlssink.c
167
+ text = File.read(hls_playlist)
168
+ File.write(hls_playlist, text.gsub(/^#EXT-X-ALLOW-CACHE:NO$/, '#EXT-X-ALLOW-CACHE:YES'))
169
+ end
170
+
171
+ def event_loop(pipeline)
172
+ running = true
173
+ bus = pipeline.bus
174
+
175
+ while running
176
+ message = bus.poll(Gst::MessageType::ANY, -1)
177
+
178
+ case message.type
179
+ when Gst::MessageType::EOS
180
+ running = false
181
+ when Gst::MessageType::WARNING
182
+ warning, _debug = message.parse_warning
183
+ Jekyll.logger.warning(@tag_name, warning)
184
+ when Gst::MessageType::ERROR
185
+ error, _debug = message.parse_error
186
+ Jekyll.logger.error(@tag_name, error)
187
+ running = false
188
+ end
189
+ end
190
+ end
191
+ end # Image
192
+ end # DistorteD
193
+ end # Cooltrainer