distorted 0.5.3 → 0.6.0
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 +4 -4
- data/LICENSE +661 -0
- data/README.md +4 -139
- data/font/1252/LICENSE/MoreLessPerfectDOSVGA437/img/Less_Perfect_DOS_VGA.png +0 -0
- data/font/1252/LICENSE/MoreLessPerfectDOSVGA437/img/More_Perfect_DOS_VGA.png +0 -0
- data/font/1252/LICENSE/MoreLessPerfectDOSVGA437/img/Perfect_DOS_VGA.png +0 -0
- data/font/1252/LICENSE/MoreLessPerfectDOSVGA437/less_more_perfect_dos_vga_437.html +52 -0
- data/font/1252/LICENSE/PerfectDOSVGA437/font-comment.php@file=perfect_dos_vga_437.html +5 -0
- data/font/1252/LessPerfectDOSVGA.ttf +0 -0
- data/font/1252/MorePerfectDOSVGA.ttf +0 -0
- data/font/1252/Perfect DOS VGA 437 Win.ttf +0 -0
- data/font/437/Perfect DOS VGA 437.ttf +0 -0
- data/font/437/dos437.txt +72 -0
- data/font/65001/Anonymous Pro B.ttf +0 -0
- data/font/65001/Anonymous Pro BI.ttf +0 -0
- data/font/65001/Anonymous Pro I.ttf +0 -0
- data/font/65001/Anonymous Pro.ttf +0 -0
- data/font/65001/LICENSE/AnonymousPro/FONTLOG.txt +45 -0
- data/font/65001/LICENSE/AnonymousPro/OFL-FAQ.txt +235 -0
- data/font/65001/LICENSE/AnonymousPro/OFL.txt +94 -0
- data/font/65001/LICENSE/AnonymousPro/README.txt +55 -0
- data/font/850/ProFont-Bold-01/LICENSE +22 -0
- data/font/850/ProFont-Bold-01/readme.txt +28 -0
- data/font/850/ProFontWindows-Bold.ttf +0 -0
- data/font/850/ProFontWindows.ttf +0 -0
- data/font/850/Profont/LICENSE +22 -0
- data/font/850/Profont/readme.txt +31 -0
- data/font/932/LICENSE/README-ttf.txt +213 -0
- data/font/932/mona.ttf +0 -0
- data/lib/distorted/checking_you_out.rb +116 -0
- data/lib/distorted/error_code.rb +51 -0
- data/lib/distorted/injection_of_love.rb +247 -0
- data/lib/distorted/modular_technology/pango.rb +90 -0
- data/lib/distorted/modular_technology/triple_counter.rb +45 -0
- data/lib/distorted/modular_technology/ttfunk.rb +48 -0
- data/lib/distorted/modular_technology/vips.rb +17 -0
- data/lib/distorted/modular_technology/vips_load.rb +77 -0
- data/lib/distorted/modular_technology/vips_save.rb +172 -0
- data/lib/distorted/molecule/C18H27NO3.rb +10 -0
- data/lib/distorted/molecule/font.rb +198 -0
- data/lib/distorted/molecule/image.rb +36 -0
- data/lib/distorted/molecule/pdf.rb +119 -0
- data/lib/distorted/molecule/svg.rb +60 -0
- data/lib/distorted/molecule/text.rb +225 -0
- data/lib/distorted/molecule/video.rb +195 -0
- data/lib/distorted/monkey_business/hash.rb +33 -0
- data/lib/distorted/monkey_business/mnemoniq.rb +8 -0
- data/lib/distorted/monkey_business/set.rb +15 -0
- data/lib/distorted/monkey_business/string.rb +6 -0
- data/lib/distorted/types/README +4 -0
- data/lib/distorted/types/application.yaml +8 -0
- data/lib/distorted/types/font.yaml +29 -0
- data/lib/distorted/version.rb +22 -0
- data/test/distorted_test.rb +11 -0
- data/test/test_helper.rb +4 -0
- metadata +102 -5
@@ -0,0 +1,36 @@
|
|
1
|
+
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
require 'distorted/checking_you_out'
|
5
|
+
require 'distorted/modular_technology/vips'
|
6
|
+
require 'distorted/injection_of_love'
|
7
|
+
|
8
|
+
|
9
|
+
module Cooltrainer
|
10
|
+
module DistorteD
|
11
|
+
module Image
|
12
|
+
|
13
|
+
|
14
|
+
# Attributes for our <picture>/<img>.
|
15
|
+
# Automatically enabled as attrs for DD Liquid Tag.
|
16
|
+
# https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture#Attributes
|
17
|
+
# https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#Attributes
|
18
|
+
# https://developer.mozilla.org/en-US/docs/Web/Performance/Lazy_loading
|
19
|
+
ATTRIBUTES = Set[:alt, :caption, :href, :loading]
|
20
|
+
|
21
|
+
# Defaults for HTML Element attributes.
|
22
|
+
# Not every attr has to be listed here.
|
23
|
+
# Many need no default and just won't render.
|
24
|
+
ATTRIBUTES_DEFAULT = {
|
25
|
+
:loading => :eager,
|
26
|
+
}
|
27
|
+
ATTRIBUTES_VALUES = {
|
28
|
+
:loading => Set[:eager, :lazy],
|
29
|
+
}
|
30
|
+
|
31
|
+
include Cooltrainer::DistorteD::Technology::Vips
|
32
|
+
include Cooltrainer::DistorteD::InjectionOfLove
|
33
|
+
|
34
|
+
end # Image
|
35
|
+
end # DistorteD
|
36
|
+
end # Cooltrainer
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
require 'hexapdf'
|
4
|
+
|
5
|
+
require 'distorted/checking_you_out'
|
6
|
+
require 'distorted/injection_of_love'
|
7
|
+
require 'distorted/molecule/C18H27NO3'
|
8
|
+
|
9
|
+
|
10
|
+
module Cooltrainer
|
11
|
+
module DistorteD
|
12
|
+
module PDF
|
13
|
+
|
14
|
+
|
15
|
+
include Cooltrainer::DistorteD::Molecule::C18H27NO3
|
16
|
+
|
17
|
+
LOWER_WORLD = CHECKING::YOU::IN("application/pdf")
|
18
|
+
|
19
|
+
# https://developer.mozilla.org/en-US/docs/Web/HTML/Element/object#Attributes
|
20
|
+
# https://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/pdf_open_parameters.pdf
|
21
|
+
PDF_OPEN_PARAMS = Array[
|
22
|
+
# Keep the PDF Open Params in the order they are defined
|
23
|
+
# in the Adobe documentation, since it says they should
|
24
|
+
# be specified in the URL in that same order.
|
25
|
+
# Ruby's Set doesn't guarantee order, so use a plain Array here.
|
26
|
+
:nameddest,
|
27
|
+
:page,
|
28
|
+
:comment,
|
29
|
+
:collab,
|
30
|
+
:zoom,
|
31
|
+
:view,
|
32
|
+
:viewrect,
|
33
|
+
:pagemode,
|
34
|
+
:scrollbar,
|
35
|
+
:search,
|
36
|
+
:toolbar,
|
37
|
+
:statusbar,
|
38
|
+
:messages,
|
39
|
+
:navpanes,
|
40
|
+
:highlight,
|
41
|
+
:fdf,
|
42
|
+
]
|
43
|
+
ATTRIBUTES = Set[
|
44
|
+
:alt,
|
45
|
+
:caption,
|
46
|
+
:height, #<object> viewer container height.
|
47
|
+
:width, # <object> viewer container width.
|
48
|
+
].merge(PDF_OPEN_PARAMS)
|
49
|
+
|
50
|
+
# "You cannot use the reserved characters =, #, and &.
|
51
|
+
# There is no way to escape these special characters."
|
52
|
+
RESERVED_CHARACTERS_FRAGMENT = '[^=#&]+'.freeze
|
53
|
+
|
54
|
+
FLOAT_INT_FRAGMENT = '[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)'.freeze
|
55
|
+
ZERO_TO_ONE_HUNDRED = /^(([1-9]\d?|1\d{1})([.,]\d{0,1})?|100([.,]0{1})?)$/
|
56
|
+
|
57
|
+
ATTRIBUTES_DEFAULT = {
|
58
|
+
:height => '100%'.freeze,
|
59
|
+
:width => '100%'.freeze,
|
60
|
+
# BEGIN PDF Open Parameters
|
61
|
+
:page => 1,
|
62
|
+
:view => :Fit,
|
63
|
+
:pagemode => :none,
|
64
|
+
:scrollbar => 1,
|
65
|
+
:toolbar => 1,
|
66
|
+
:statusbar => 1,
|
67
|
+
:messages => 0,
|
68
|
+
:navpanes => 1,
|
69
|
+
# END PDF Open Parameters
|
70
|
+
}
|
71
|
+
|
72
|
+
# Adobe's PDF Open Parameters documentation sez:
|
73
|
+
# "Individual parameters, together with their values (separated by & or #),
|
74
|
+
# can be no greater then 32 characters in length."
|
75
|
+
# …but then goes on to show some examples (like `comment`)
|
76
|
+
# that are clearly longer than 32 characters.
|
77
|
+
# Dunno. I'll err on the side of giving you a footgun.
|
78
|
+
ATTRIBUTES_VALUES = {
|
79
|
+
:nameddest => /^#{RESERVED_CHARACTERS_FRAGMENT}$/,
|
80
|
+
:page => /\d/,
|
81
|
+
:comment => /^#{RESERVED_CHARACTERS_FRAGMENT}$/,
|
82
|
+
:collab => /^(DAVFDF|FSFDF|DB)@#{RESERVED_CHARACTERS_FRAGMENT}$/,
|
83
|
+
:zoom => /^#{FLOAT_INT_FRAGMENT}(,#{FLOAT_INT_FRAGMENT},#{FLOAT_INT_FRAGMENT})?$/,
|
84
|
+
:view => /^Fit(H|V|B|BH|BV(,#{FLOAT_INT_FRAGMENT})?)?$/,
|
85
|
+
:viewrect => /^#{FLOAT_INT_FRAGMENT},#{FLOAT_INT_FRAGMENT},#{FLOAT_INT_FRAGMENT},#{FLOAT_INT_FRAGMENT}$/,
|
86
|
+
:pagemode => Set[:none, :thumbs, :bookmarks],
|
87
|
+
:scrollbar => BOOLEAN_ATTR_VALUES,
|
88
|
+
:search => /^#{RESERVED_CHARACTERS_FRAGMENT}(,\s#{RESERVED_CHARACTERS_FRAGMENT})*$/,
|
89
|
+
:toolbar => BOOLEAN_ATTR_VALUES,
|
90
|
+
:statusbar => BOOLEAN_ATTR_VALUES,
|
91
|
+
:messages => BOOLEAN_ATTR_VALUES,
|
92
|
+
:navpanes => BOOLEAN_ATTR_VALUES,
|
93
|
+
:fdf => /^#{RESERVED_CHARACTERS_FRAGMENT}$/,
|
94
|
+
}
|
95
|
+
|
96
|
+
include Cooltrainer::DistorteD::InjectionOfLove
|
97
|
+
|
98
|
+
# TODO: Use MuPDF instead of libvips magick-based PDF loader.
|
99
|
+
|
100
|
+
def self.optimize(src, dest)
|
101
|
+
HexaPDF::Document.open(src) do |doc|
|
102
|
+
doc.task(
|
103
|
+
:optimize,
|
104
|
+
compact: true,
|
105
|
+
object_streams: :generate,
|
106
|
+
xref_streams: :generate,
|
107
|
+
compress_pages: false,
|
108
|
+
)
|
109
|
+
doc.write(dest)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def to_application_pdf(*a, **k, &b)
|
114
|
+
copy_file(*a, **k, &b)
|
115
|
+
end
|
116
|
+
|
117
|
+
end # PDF
|
118
|
+
end # DistorteD
|
119
|
+
end # Cooltrainer
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
require 'svg_optimizer'
|
4
|
+
|
5
|
+
require 'distorted/checking_you_out'
|
6
|
+
require 'distorted/injection_of_love'
|
7
|
+
require 'distorted/molecule/C18H27NO3'
|
8
|
+
|
9
|
+
|
10
|
+
module Cooltrainer
|
11
|
+
module DistorteD
|
12
|
+
module SVG
|
13
|
+
|
14
|
+
SUB_TYPE = 'svg'.freeze
|
15
|
+
include Cooltrainer::DistorteD::Molecule::C18H27NO3
|
16
|
+
|
17
|
+
#WISHLIST: Support VML for old IE compatibility.
|
18
|
+
# Example: RaphaëlJS — https://en.wikipedia.org/wiki/Rapha%C3%ABl_(JavaScript_library)
|
19
|
+
LOWER_WORLD = CHECKING::YOU::IN(/^image\/svg/)
|
20
|
+
|
21
|
+
ATTRIBUTES = Set[
|
22
|
+
:alt,
|
23
|
+
:caption,
|
24
|
+
:href,
|
25
|
+
:loading,
|
26
|
+
:optimize,
|
27
|
+
]
|
28
|
+
ATTRIBUTES_VALUES = {
|
29
|
+
:optimize => BOOLEAN_ATTR_VALUES,
|
30
|
+
}
|
31
|
+
ATTRIBUTES_DEFAULT = {
|
32
|
+
:optimize => false,
|
33
|
+
}
|
34
|
+
|
35
|
+
include Cooltrainer::DistorteD::Technology::VipsSave
|
36
|
+
include Cooltrainer::DistorteD::InjectionOfLove
|
37
|
+
|
38
|
+
def to_vips_image
|
39
|
+
# TODO: Load-time options for various formats, like SVG's `unlimited`:
|
40
|
+
# "SVGs larger than 10MB are normally blocked for security. Set unlimited to allow SVGs of any size."
|
41
|
+
# https://libvips.github.io/libvips/API/current/VipsForeignSave.html#vips-svgload
|
42
|
+
@vips_image ||= Vips::Image.new_from_file(path)
|
43
|
+
end
|
44
|
+
|
45
|
+
def to_image_svg_xml(dest, *a, **k, &b)
|
46
|
+
if abstract(:optimize)
|
47
|
+
SvgOptimizer.optimize_file(path, dest, SvgOptimizer::DEFAULT_PLUGINS)
|
48
|
+
else
|
49
|
+
copy_file(dest, *a, **k, &b)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.optimize(src, dest)
|
54
|
+
# TODO: Make optimizations/plugins configurable
|
55
|
+
SvgOptimizer.optimize_file(src, dest, SvgOptimizer::DEFAULT_PLUGINS)
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,225 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
require 'charlock_holmes' # Text file charset detection
|
4
|
+
|
5
|
+
require 'distorted/monkey_business/string' # String#map
|
6
|
+
require 'distorted/modular_technology/pango'
|
7
|
+
require 'distorted/modular_technology/ttfunk'
|
8
|
+
require 'distorted/modular_technology/vips_save'
|
9
|
+
|
10
|
+
require 'distorted/checking_you_out'
|
11
|
+
require 'distorted/injection_of_love'
|
12
|
+
require 'distorted/molecule/image'
|
13
|
+
|
14
|
+
|
15
|
+
module Cooltrainer
|
16
|
+
module DistorteD
|
17
|
+
module Text
|
18
|
+
|
19
|
+
|
20
|
+
LOWER_WORLD = CHECKING::YOU::IN(/^text\/(plain|x-nfo)/)
|
21
|
+
OUTER_LIMITS = CHECKING::YOU::IN(/^text\/(plain|x-nfo)/)
|
22
|
+
|
23
|
+
# Track supported fonts by codepage.
|
24
|
+
# Avoid renaming these from the original archives / websites.
|
25
|
+
# Try not to go nuts here bloating the size of our Gem for a
|
26
|
+
# very niche feature, but I want to ensure good coverage too.
|
27
|
+
#
|
28
|
+
# Treat codepage 8859 documents as codepage 1252 to avoid breaking smart-
|
29
|
+
# quotes and other printable chars in 1252 that are control chars in 8859.
|
30
|
+
# https://encoding.spec.whatwg.org/#names-and-labels
|
31
|
+
#
|
32
|
+
# Numeric key for UTF-8 is codepage 65001 like Win32:
|
33
|
+
# https://docs.microsoft.com/en-us/windows/win32/intl/code-page-identifiers
|
34
|
+
FONT_FILENAME = {
|
35
|
+
:anonpro => 'Anonymous Pro.ttf'.freeze,
|
36
|
+
:anonpro_b => 'Anonymous Pro B.ttf'.freeze,
|
37
|
+
:anonpro_bi => 'Anonymous Pro BI.ttf'.freeze,
|
38
|
+
:anonpro_i => 'Anonymous Pro I.ttf'.freeze,
|
39
|
+
:lessperfectdosvga => 'LessPerfectDOSVGA.ttf'.freeze,
|
40
|
+
:moreperfectdisvga => 'MorePerfectDOSVGA.ttf'.freeze,
|
41
|
+
:perfectdosvgawin => 'Perfect DOS VGA 437 Win.ttf'.freeze,
|
42
|
+
:mona => 'mona.ttf'.freeze,
|
43
|
+
:perfectdosvga => 'Perfect DOS VGA 437.ttf'.freeze,
|
44
|
+
:profont => 'ProFontWindows.ttf'.freeze,
|
45
|
+
:profont_b => 'ProFontWindows-Bold.ttf'.freeze,
|
46
|
+
}
|
47
|
+
# Certain fonts are more suitable for certain codepages,
|
48
|
+
# so track each codepage's available fonts…
|
49
|
+
CODEPAGE_FONT = {
|
50
|
+
65001 => [
|
51
|
+
:anonpro,
|
52
|
+
:anonpro_b,
|
53
|
+
:anonpro_bi,
|
54
|
+
:anonpro_i,
|
55
|
+
],
|
56
|
+
1252 => [
|
57
|
+
:lessperfectdosvga,
|
58
|
+
:moreperfectdosvga,
|
59
|
+
:perfectdosvgawin,
|
60
|
+
],
|
61
|
+
932 => [
|
62
|
+
:mona,
|
63
|
+
],
|
64
|
+
850 => [
|
65
|
+
:profont,
|
66
|
+
:profont_b,
|
67
|
+
],
|
68
|
+
437 => [
|
69
|
+
:perfectdosvga,
|
70
|
+
],
|
71
|
+
}
|
72
|
+
# …as well as the inverse, the numeric codepage for each font:
|
73
|
+
FONT_CODEPAGE = self::CODEPAGE_FONT.reduce(Hash.new([])) { |memo, (key, values)|
|
74
|
+
values.each { |value| memo[value] = key }
|
75
|
+
memo
|
76
|
+
}
|
77
|
+
|
78
|
+
self::OUTER_LIMITS.each { |t|
|
79
|
+
define_method(t.distorted_method) { |*a, **k, &b|
|
80
|
+
copy_file(*a, **k, &b)
|
81
|
+
}
|
82
|
+
}
|
83
|
+
|
84
|
+
ATTRIBUTES = Set[
|
85
|
+
:alt,
|
86
|
+
:crop,
|
87
|
+
:font,
|
88
|
+
:encoding,
|
89
|
+
:spacing,
|
90
|
+
:dpi,
|
91
|
+
]
|
92
|
+
ATTRIBUTES_VALUES = {
|
93
|
+
:spacing => Set[:monospace, :proportional],
|
94
|
+
:font => self::FONT_FILENAME.keys.to_set,
|
95
|
+
}
|
96
|
+
ATTRIBUTES_DEFAULT = {
|
97
|
+
:crop => :none,
|
98
|
+
:dpi => 144,
|
99
|
+
:encoding => 'UTF-8'.freeze
|
100
|
+
}
|
101
|
+
|
102
|
+
include Cooltrainer::DistorteD::Technology::TTFunk
|
103
|
+
include Cooltrainer::DistorteD::Technology::Pango
|
104
|
+
include Cooltrainer::DistorteD::Technology::VipsSave
|
105
|
+
include Cooltrainer::DistorteD::InjectionOfLove
|
106
|
+
|
107
|
+
# Using a numeric key for things for simplicity.
|
108
|
+
# TODO: Replace this with Ruby's built-in Encoding class after I have
|
109
|
+
# a better idea what I want to do.
|
110
|
+
def codepage
|
111
|
+
case text_file_encoding
|
112
|
+
when 'UTF-8'.freeze then 65001
|
113
|
+
when 'Shift_JIS'.freeze then 932
|
114
|
+
when 'IBM437'.freeze then 437
|
115
|
+
else 1252
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Return a Pango Markup escaped version of the document.
|
120
|
+
def to_pango
|
121
|
+
# https://developer.gnome.org/glib/stable/glib-Simple-XML-Subset-Parser.html#g-markup-escape-text
|
122
|
+
escaped = text_file_utf8_content.map{ |c|
|
123
|
+
g_markup_escape_char(c)
|
124
|
+
}
|
125
|
+
if font_spacing == :monospace
|
126
|
+
"<tt>" << escaped << "</tt>"
|
127
|
+
else
|
128
|
+
escaped
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
protected
|
133
|
+
|
134
|
+
def text_file_content
|
135
|
+
# VIPS makes us provide the text content as a single variable,
|
136
|
+
# so we may as well just one-shot File.read() it into memory.
|
137
|
+
# https://kunststube.net/encoding/
|
138
|
+
@text_file_content ||= File.read(path)
|
139
|
+
end
|
140
|
+
|
141
|
+
def text_file_utf8_content
|
142
|
+
CharlockHolmes::Converter.convert(text_file_content, text_file_encoding, 'UTF-8'.freeze)
|
143
|
+
end
|
144
|
+
|
145
|
+
def text_file_encoding
|
146
|
+
# It's not easy or even possible in some cases to tell the "true" codepage
|
147
|
+
# we should use for any given text document, but using character detection
|
148
|
+
# is worth a shot if the user gave us nothing.
|
149
|
+
#
|
150
|
+
# TODO: Figure out if/how we can get IBM437 files to not be detected as ISO-8859-1
|
151
|
+
@text_file_encoding ||= (
|
152
|
+
abstract(:encoding).to_s ||
|
153
|
+
CharlockHolmes::EncodingDetector.detect(text_file_content)[:encoding] ||
|
154
|
+
'UTF-8'.freeze
|
155
|
+
).to_s
|
156
|
+
end
|
157
|
+
|
158
|
+
def vips_font
|
159
|
+
# Set the shorthand Symbol key for our chosen font.
|
160
|
+
return abstract(:font)&.to_sym || CODEPAGE_FONT[codepage].first
|
161
|
+
end
|
162
|
+
|
163
|
+
def to_vips_image
|
164
|
+
# Load font metadata directly from the file so we don't have to
|
165
|
+
# duplicate it here to feed to Vips/Pango.
|
166
|
+
#
|
167
|
+
# irb(main)> font_meta.name.font_name
|
168
|
+
# => ["Perfect DOS VGA 437", "\x00P\x00e\x00r\x00f\x00e\x00c\x00t\x00 \x00D\x00O\x00S\x00 \x00V\x00G\x00A\x00 \x004\x003\x007"]
|
169
|
+
# irb(main)> font_meta.name.font_family
|
170
|
+
# => ["Perfect DOS VGA 437", "\x00P\x00e\x00r\x00f\x00e\x00c\x00t\x00 \x00D\x00O\x00S\x00 \x00V\x00G\x00A\x00 \x004\x003\x007"]
|
171
|
+
# irb(main)> font_meta.name.font_subfamily
|
172
|
+
# => ["Regular", "\x00R\x00e\x00g\x00u\x00l\x00a\x00r"]
|
173
|
+
# irb(main)> font_meta.name.postscript_name
|
174
|
+
# => "PerfectDOSVGA437"
|
175
|
+
# irb(main)> font_meta.line_gap
|
176
|
+
# => 0
|
177
|
+
|
178
|
+
# https://libvips.github.io/libvips/API/current/libvips-create.html#vips-text
|
179
|
+
Vips::Image.text(
|
180
|
+
# This string must be well-escaped Pango Markup:
|
181
|
+
# https://developer.gnome.org/pango/stable/pango-Markup.html
|
182
|
+
# However the official function for escaping text is
|
183
|
+
# not implemented in Ruby GLib, so we have to do it ourselves.
|
184
|
+
to_pango,
|
185
|
+
**{
|
186
|
+
# String absolute path to TTF
|
187
|
+
:fontfile => font_path,
|
188
|
+
# It's not enough to just specify the TTF path;
|
189
|
+
# we must also specify a font family, subfamily, and size.
|
190
|
+
:font => "#{font_name} 16",
|
191
|
+
# Space between lines (in Points).
|
192
|
+
:spacing => to_ttfunk.line_gap,
|
193
|
+
:justify => true, # Requires libvips 8.8
|
194
|
+
:dpi => abstract(:dpi)&.to_i,
|
195
|
+
},
|
196
|
+
)
|
197
|
+
end
|
198
|
+
|
199
|
+
# Return the String absolute path to the TTF file
|
200
|
+
def font_path
|
201
|
+
File.join(
|
202
|
+
File.dirname(__FILE__), # molecule
|
203
|
+
'..'.freeze, # distorted
|
204
|
+
'..'.freeze, # lib
|
205
|
+
'..'.freeze, # DistorteD-Ruby
|
206
|
+
'font'.freeze,
|
207
|
+
font_codepage.to_s,
|
208
|
+
font_filename,
|
209
|
+
)
|
210
|
+
end
|
211
|
+
|
212
|
+
# Returns the numeric representation of the codepage
|
213
|
+
# covered by our font.
|
214
|
+
def font_codepage
|
215
|
+
FONT_CODEPAGE.dig(vips_font).to_s
|
216
|
+
end
|
217
|
+
|
218
|
+
# Returns the basename (with file extension) of our font.
|
219
|
+
def font_filename
|
220
|
+
FONT_FILENAME.dig(vips_font)
|
221
|
+
end
|
222
|
+
|
223
|
+
end # Text
|
224
|
+
end # DistorteD
|
225
|
+
end # Cooltrainer
|
@@ -0,0 +1,195 @@
|
|
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 'distorted/checking_you_out'
|
21
|
+
require 'distorted/injection_of_love'
|
22
|
+
|
23
|
+
|
24
|
+
module Cooltrainer
|
25
|
+
module DistorteD
|
26
|
+
module Video
|
27
|
+
|
28
|
+
LOWER_WORLD = CHECKING::YOU::IN(/^video\/mp4/)
|
29
|
+
|
30
|
+
# Attributes for our <video>.
|
31
|
+
# Automatically enabled as attrs for DD Liquid Tag.
|
32
|
+
# https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video#Attributes
|
33
|
+
ATTRIBUTES = Set[:caption]
|
34
|
+
|
35
|
+
# Defaults for HTML Element attributes.
|
36
|
+
# Not every attr has to be listed here.
|
37
|
+
# Many need no default and just won't render.
|
38
|
+
ATTRIBUTES_DEFAULT = {}
|
39
|
+
ATTRIBUTES_VALUES = {}
|
40
|
+
include Cooltrainer::DistorteD::InjectionOfLove
|
41
|
+
|
42
|
+
attr_accessor :dest
|
43
|
+
|
44
|
+
|
45
|
+
def initialize(src, dest, basename)
|
46
|
+
@src = src
|
47
|
+
@dest = dest
|
48
|
+
@basename = basename
|
49
|
+
end
|
50
|
+
|
51
|
+
def rotate(angle=nil)
|
52
|
+
false
|
53
|
+
end
|
54
|
+
|
55
|
+
def clean
|
56
|
+
false
|
57
|
+
end
|
58
|
+
|
59
|
+
def generate
|
60
|
+
self.generate_hls
|
61
|
+
begin
|
62
|
+
self.generate_dash
|
63
|
+
rescue Gst::ParseError::NoSuchElement
|
64
|
+
# This is going away once the new dashsink2 lands in Gst so :effort:
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def generate_dash
|
69
|
+
orig_dest = @dest
|
70
|
+
orig_path = @src
|
71
|
+
|
72
|
+
FileUtils.mkdir_p(File.dirname(orig_dest))
|
73
|
+
|
74
|
+
hls_dest = File.join(File.dirname(orig_dest), @basename + '.dash')
|
75
|
+
FileUtils.mkdir_p(hls_dest)
|
76
|
+
Jekyll.logger.debug(@tag_name, "Re-muxing #{orig_path} to #{hls_dest}.")
|
77
|
+
|
78
|
+
#FileUtils.rm(orig_dest) if File.exist?(orig_dest)
|
79
|
+
if not File.file?(orig_dest)
|
80
|
+
FileUtils.cp(orig_path, orig_dest)
|
81
|
+
end
|
82
|
+
|
83
|
+
# https://gstreamer.freedesktop.org/documentation/tools/gst-launch.html?gi-language=c#pipeline-description
|
84
|
+
# TODO: Convert this from parse_launch() pipeline notation to Element objects
|
85
|
+
# TODO: Get source video duration/resolution/etc and use it to compute a
|
86
|
+
# value for `target-duration`.
|
87
|
+
# TODO: Also support urldecodebin for remote media.
|
88
|
+
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")
|
89
|
+
|
90
|
+
if pipeline.nil?
|
91
|
+
Jekyll.logger.error(@tag_name, "Parse error: #{error.message}")
|
92
|
+
return false
|
93
|
+
end
|
94
|
+
|
95
|
+
filesrc = pipeline.get_by_name('src')
|
96
|
+
filesrc.location = orig_path
|
97
|
+
|
98
|
+
hls_playlist = "#{hls_dest}/#{@basename}.m3u8"
|
99
|
+
hls = pipeline.get_by_name('mux')
|
100
|
+
hls.location = "#{hls_dest}/#{@basename}%05d.ts"
|
101
|
+
hls.playlist_location = hls_playlist
|
102
|
+
|
103
|
+
# TODO: config option for absolute vs relative segment URIs in the playlist.
|
104
|
+
#hls.playlist_root = @url
|
105
|
+
|
106
|
+
# TODO: dashsink support once there is a stable GStreamer release including it:
|
107
|
+
# https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/merge_requests/704
|
108
|
+
|
109
|
+
pipeline.play
|
110
|
+
|
111
|
+
# Play until End Of Stream
|
112
|
+
event_loop(pipeline)
|
113
|
+
|
114
|
+
pipeline.stop
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
def generate_hls
|
119
|
+
orig_dest = @dest
|
120
|
+
orig_path = @src
|
121
|
+
|
122
|
+
FileUtils.mkdir_p(File.dirname(orig_dest))
|
123
|
+
|
124
|
+
hls_dest = File.join(File.dirname(orig_dest), @basename + '.hls')
|
125
|
+
FileUtils.mkdir_p(hls_dest)
|
126
|
+
Jekyll.logger.debug(@tag_name, "Re-muxing #{orig_path} to #{hls_dest}.")
|
127
|
+
|
128
|
+
#FileUtils.rm(orig_dest) if File.exist?(orig_dest)
|
129
|
+
if not File.file?(orig_dest)
|
130
|
+
FileUtils.cp(orig_path, orig_dest)
|
131
|
+
end
|
132
|
+
|
133
|
+
# https://gstreamer.freedesktop.org/documentation/tools/gst-launch.html?gi-language=c#pipeline-description
|
134
|
+
# TODO: Convert this from parse_launch() pipeline notation to Element objects
|
135
|
+
# TODO: Get source video duration/resolution/etc and use it to compute a
|
136
|
+
# value for `target-duration`.
|
137
|
+
# TODO: Also support urldecodebin for remote media.
|
138
|
+
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")
|
139
|
+
|
140
|
+
if pipeline.nil?
|
141
|
+
Jekyll.logger.error(@tag_name, "Parse error: #{error.message}")
|
142
|
+
return false
|
143
|
+
end
|
144
|
+
|
145
|
+
filesrc = pipeline.get_by_name('src')
|
146
|
+
filesrc.location = orig_path
|
147
|
+
|
148
|
+
hls_playlist = "#{hls_dest}/#{@basename}.m3u8"
|
149
|
+
hls = pipeline.get_by_name('mux')
|
150
|
+
hls.location = "#{hls_dest}/#{@basename}%05d.ts"
|
151
|
+
hls.playlist_location = hls_playlist
|
152
|
+
|
153
|
+
# TODO: config option for absolute vs relative segment URIs in the playlist.
|
154
|
+
#hls.playlist_root = @url
|
155
|
+
|
156
|
+
# TODO: dashsink support once there is a stable GStreamer release including it:
|
157
|
+
# https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/merge_requests/704
|
158
|
+
|
159
|
+
pipeline.play
|
160
|
+
|
161
|
+
# Play until End Of Stream
|
162
|
+
event_loop(pipeline)
|
163
|
+
|
164
|
+
pipeline.stop
|
165
|
+
|
166
|
+
# HACK HACK HACK: Replace X-ALLOW-CACHE line in playlist with YES.
|
167
|
+
# This property does not seem to be exposed to the outside of hlssink:
|
168
|
+
# https://cgit.freedesktop.org/gstreamer/gst-plugins-bad/tree/ext/hls/gsthlssink.c
|
169
|
+
text = File.read(hls_playlist)
|
170
|
+
File.write(hls_playlist, text.gsub(/^#EXT-X-ALLOW-CACHE:NO$/, '#EXT-X-ALLOW-CACHE:YES'))
|
171
|
+
end
|
172
|
+
|
173
|
+
def event_loop(pipeline)
|
174
|
+
running = true
|
175
|
+
bus = pipeline.bus
|
176
|
+
|
177
|
+
while running
|
178
|
+
message = bus.poll(Gst::MessageType::ANY, -1)
|
179
|
+
|
180
|
+
case message.type
|
181
|
+
when Gst::MessageType::EOS
|
182
|
+
running = false
|
183
|
+
when Gst::MessageType::WARNING
|
184
|
+
warning, _debug = message.parse_warning
|
185
|
+
Jekyll.logger.warning(@tag_name, warning)
|
186
|
+
when Gst::MessageType::ERROR
|
187
|
+
error, _debug = message.parse_error
|
188
|
+
Jekyll.logger.error(@tag_name, error)
|
189
|
+
running = false
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end # Image
|
194
|
+
end # DistorteD
|
195
|
+
end # Cooltrainer
|