Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add experimental webp support #2020

Merged
merged 11 commits into from
Oct 25, 2023
4 changes: 4 additions & 0 deletions .github/workflows/reusable-workflow-rspec.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ jobs:

# Dependencies

- name: Install libvips package for image processing
run: |
sudo apt-get install libvips-dev
- name: Bundle install
run: |
bundle config path vendor/bundle
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/storybook.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ jobs:

# Dependencies

- name: Install libvips package for image processing
run: |
sudo apt-get install libvips-dev
- name: Bundle install
run: |
bundle config path vendor/bundle
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,10 @@ jobs:

# Dependencies

- name: Install libvips package for image processing
run: |
sudo apt-get install libvips-dev
- name: Install audiowaveform package
if: ${{ matrix.install-audiowaveform == true}}
run: |
Expand Down
25 changes: 20 additions & 5 deletions app/models/pageflow/image_file.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ module Pageflow
class ImageFile < ApplicationRecord
include UploadableFile
include ImageAndTextTrackProcessingStateMachine
include OutputSource

before_post_process :set_output_presences

# used in paperclip initializer to interpolate the storage path
# needs to be "processed_attachments" for images for legacy reasons
Expand Down Expand Up @@ -35,19 +38,19 @@ def attachment_styles(attachment)
panorama_format = File.extname(attachment.original_filename) == '.png' ? :PNG : :JPG

Pageflow
.config.thumbnail_styles
.config.thumbnail_styles.transform_values { |options| options.merge(style_defaults) }
.merge(
print: {geometry: '300x300>',
format: :JPG,
**style_defaults,
convert_options: '-quality 10 -interlace Plane'},
medium: {geometry: '1024x1024>',
format: :JPG,
**style_defaults,
convert_options: '-quality 70 -interlace Plane'},
large: {geometry: '1920x1920>',
format: :JPG,
**style_defaults,
convert_options: '-quality 70 -interlace Plane'},
ultra: {geometry: '3840x3840>',
format: :JPG,
**style_defaults,
convert_options: '-quality 90 -interlace Plane'},
panorama_medium: {geometry: ImageFile.scale_down_to_cover(1024, 1024),
format: panorama_format,
Expand Down Expand Up @@ -82,5 +85,17 @@ def save_image_dimensions
self.height = geo.height
rescue Paperclip::Errors::NotIdentifiedByImageMagickError
end

def style_defaults
if output_present?(:webp)
{format: :webp, processors: [:pageflow_webp]}
else
{format: :JPG}
end
end

def set_output_presences
self.output_presences = {webp: true} if entry&.feature_state('webp_images')
end
end
end
8 changes: 7 additions & 1 deletion app/models/pageflow/image_file_url_templates.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,18 @@ module Pageflow
class ImageFileUrlTemplates
def call
styles.each_with_object({}) do |style, result|
result[style] = UrlTemplate.from_attachment(example_file.attachment, style)
result[style] = replace_extension_with_placeholder(
UrlTemplate.from_attachment(example_file.attachment, style)
)
end
end

private

def replace_extension_with_placeholder(url)
url.gsub(/.JPG$/, '.:processed_extension')
end

def styles
example_file.attachment_styles(example_file.attachment).keys + [:original]
end
Expand Down
1 change: 1 addition & 0 deletions app/views/pageflow/image_files/_image_file.json.jbuilder
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
json.call(image_file, :width, :height)
json.processed_extension image_file.output_present?(:webp) ? 'webp' : 'JPG'
json.created_at image_file.created_at.try(:utc).try(:iso8601, 0)
1 change: 1 addition & 0 deletions config/initializers/features.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
Pageflow.configure do |config|
config.features.register('webp_images')
config.features.register('highdef_video_encoding')
config.features.register('force_fullhd_video_quality')
config.features.register('selectable_themes')
Expand Down
4 changes: 4 additions & 0 deletions config/initializers/paperclip.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
require 'pageflow/paperclip_processors/vtt'
require 'pageflow/paperclip_processors/audio_waveform'
require 'pageflow/paperclip_processors/webp'
require 'pageflow/paperclip_processors/noop'

Paperclip.interpolates(:pageflow_s3_root) do |_attachment, _style|
Expand Down Expand Up @@ -33,6 +34,9 @@
end

Paperclip.configure do |config|
config.register_processor(:pageflow_webp,
Pageflow::PaperclipProcessors::Webp)

config.register_processor(:pageflow_vtt,
Pageflow::PaperclipProcessors::Vtt)

Expand Down
4 changes: 4 additions & 0 deletions config/locales/new/webp_images.de.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
de:
pageflow:
webp_images:
feature_name: "webp Bilder"
4 changes: 4 additions & 0 deletions config/locales/new/webp_images.en.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
en:
pageflow:
webp_images:
feature_name: "webp images"
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class AddOutputPresencesToImageFiles < ActiveRecord::Migration[5.2]
def change
add_column :pageflow_image_files, :output_presences, :text
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,16 @@ describe('file', () => {
expect(result).toHaveProperty('urls.high', 'http://example.com/my-video.mp4');
});

it('interpolates processed extension into file url template', () => {
const files = {'image_files': [{id: 2004, perma_id: 31, processed_extension: 'webp', basename: 'image'}]};
const fileUrlTemplates = {'image_files': {'medium': 'http://example.com/:basename.:processed_extension'}};
const state = sample({files, fileUrlTemplates});

const result = file('imageFiles', {id: 31})(state);

expect(result).toHaveProperty('urls.medium', 'http://example.com/image.webp');
});

it('interpolates hls qualities into video file url template', () => {
const files = {'video_files': [{
id: 2004,
Expand Down Expand Up @@ -231,10 +241,12 @@ describe('fileExists', () => {
function sample({
files,
fileUrlTemplates = {
image_files: {},
video_files: {},
text_track_files: {},
},
modelTypes = {
image_files: 'Pageflow::ImageFile',
video_files: 'Pageflow::VideoFile',
text_track_files: 'Pageflow::TextTrackFile'
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ function getFileUrl(collectionName, file, quality, urlTemplates) {
return template
.replace(':id_partition', idPartition(file.id))
.replace(':basename', file.basename)
.replace(':processed_extension', file.processedExtension)
.replace(':pageflow_hls_qualities', () => hlsQualities(file));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ export default {

idAttribute: 'perma_id',
attributes: [
'id', 'perma_id', 'basename', 'variants', 'is_ready',
'id', 'perma_id', 'basename', 'processed_extension',
'variants', 'is_ready',
'parent_file_id', 'parent_file_model_type',
'width', 'height', 'duration_in_ms', 'rights', 'created_at'
],
Expand Down
66 changes: 14 additions & 52 deletions entry_types/scrolled/package/spec/entryState/useFile-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ describe('useFile', () => {
seed: {
fileUrlTemplates: {
imageFiles: {
large: '/image_files/:id_partition/large.jpg'
original: '/image_files/:id_partition/original/:basename.:extension',
large: '/image_files/:id_partition/large/:basename.:processed_extension',
}
},
fileModelTypes: {
Expand All @@ -24,7 +25,8 @@ describe('useFile', () => {
id: 100,
permaId: 1,
basename: 'image',
extension: 'jpg',
extension: 'svg',
processedExtension: 'webp',
rights: 'author',
configuration: {
some: 'value'
Expand All @@ -41,12 +43,13 @@ describe('useFile', () => {
id: 100,
permaId: 1,
modelType: 'Pageflow::ImageFile',
extension: 'jpg',
extension: 'svg',
configuration: {
some: 'value'
},
urls: {
large: '/image_files/000/000/100/large.jpg'
original: '/image_files/000/000/100/original/image.svg',
large: '/image_files/000/000/100/large/image.webp',
}
});
});
Expand All @@ -58,7 +61,8 @@ describe('useFile', () => {
seed: {
fileUrlTemplates: {
imageFiles: {
large: '/image_files/:id_partition/large.jpg'
original: '/image_files/:id_partition/original/:basename.:extension',
large: '/image_files/:id_partition/large/:basename.:processed_extension',
}
},
fileModelTypes: {
Expand All @@ -75,7 +79,8 @@ describe('useFile', () => {
id: 100,
perma_id: 1,
basename: 'image',
extension: 'jpg',
extension: 'svg',
processed_extension: 'webp',
rights: 'author',
configuration: {
some: 'value'
Expand All @@ -95,12 +100,13 @@ describe('useFile', () => {
permaId: 1,
modelType: 'Pageflow::ImageFile',
basename: 'image',
extension: 'jpg',
extension: 'svg',
configuration: {
some: 'value'
},
urls: {
large: '/image_files/000/000/100/large.jpg'
original: '/image_files/000/000/100/original/image.svg',
large: '/image_files/000/000/100/large/image.webp',
}
});
});
Expand Down Expand Up @@ -165,50 +171,6 @@ describe('useFile', () => {
});
});

it('interpolates file basename and extension', () => {
const {result} = renderHookInEntry(
() => useFile({collectionName: 'imageFiles', permaId: 1}),
{
seed: {
fileUrlTemplates: {
imageFiles: {
original: '/image_files/:id_partition/:basename.:extension'
}
},
fileModelTypes: {
imageFiles: 'Pageflow::ImageFile'
},
imageFiles: [
{
id: 100,
permaId: 1,
basename: 'image',
extension: 'svg',
rights: 'author',
configuration: {
some: 'value'
}
}
]
}
}
);

const file = result.current;

expect(file).toMatchObject({
id: 100,
permaId: 1,
modelType: 'Pageflow::ImageFile',
configuration: {
some: 'value'
},
urls: {
original: '/image_files/000/000/100/image.svg'
}
});
});

it('interpolates hls qualities into video file url templates', () => {
const {result} = renderHookInEntry(
() => useFile({collectionName: 'videoFiles', permaId: 1}),
Expand Down
1 change: 1 addition & 0 deletions entry_types/scrolled/package/src/entryState/extendFile.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ function getFileUrl(collectionName, file, quality, urlTemplates) {
.replace(':id_partition', idPartition(file.id))
.replace(':basename', file.basename)
.replace(':extension', file.extension)
.replace(':processed_extension', file.processedExtension)
.replace(':pageflow_hls_qualities', () => hlsQualities(file));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export function watchCollections(entry, {dispatch}) {
name: camelize(collectionName),
attributes: ['id', {permaId: 'perma_id'}, 'width', 'height',
'basename', 'extension', 'rights',
{processedExtension: 'processed_extension'},
{isReady: 'is_ready'},
{variants: variants => variants && variants.map(variant => camelize(variant))},
{durationInMs: 'duration_in_ms'},
Expand Down
63 changes: 63 additions & 0 deletions lib/pageflow/paperclip_processors/webp.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
require 'vips'

module Pageflow
module PaperclipProcessors
# @api private
class Webp < Paperclip::Processor
ANIMATED_FORMATS = %w[.gif].freeze

def initialize(file, options = {}, attachment = nil)
super

geometry = options[:geometry].to_s
@should_crop = geometry[-1, 1] == '#'

@target_geometry = Paperclip::Geometry.parse(geometry)
@whiny = options.fetch(:whiny, true)

@current_format = File.extname(file.path)
@basename = File.basename(@file.path, @current_format)
end

def make
source = @file
filename = [@basename, '.webp'].join
destination = Paperclip::TempfileFactory.new.generate(filename)

begin
thumbnail = Vips::Image.thumbnail(
ANIMATED_FORMATS.include?(@current_format) ? "#{source.path}[n=-1]" : source.path,
width,
size: :down,
height: height,
crop: crop
)
thumbnail.webpsave(destination.path)
rescue Vips::Error => e
if @whiny
message = "There was an error processing the thumbnail for #{@basename}:\n" + e.message
raise Paperclip::Error, message
end
end

destination
end

private

def crop
return unless @should_crop

@options[:crop] || :centre
end

def width
@target_geometry.width
end

def height
@target_geometry.height
end
end
end
end
Loading