Skip to content

Commit

Permalink
Merge pull request #2020 from tf/webp
Browse files Browse the repository at this point in the history
Add experimental webp support
  • Loading branch information
tf authored Oct 25, 2023
2 parents 64fb70a + 493ef22 commit 847b2d1
Show file tree
Hide file tree
Showing 25 changed files with 299 additions and 65 deletions.
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

0 comments on commit 847b2d1

Please sign in to comment.