/
preview.rb
131 lines (113 loc) · 4.91 KB
/
preview.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# frozen_string_literal: true
# = Active Storage \Preview
#
# Some non-image blobs can be previewed: that is, they can be presented as images. A video blob can be previewed by
# extracting its first frame, and a PDF blob can be previewed by extracting its first page.
#
# A previewer extracts a preview image from a blob. Active Storage provides previewers for videos and PDFs.
# ActiveStorage::Previewer::VideoPreviewer is used for videos whereas ActiveStorage::Previewer::PopplerPDFPreviewer
# and ActiveStorage::Previewer::MuPDFPreviewer are used for PDFs. Build custom previewers by
# subclassing ActiveStorage::Previewer and implementing the requisite methods. Consult the ActiveStorage::Previewer
# documentation for more details on what's required of previewers.
#
# To choose the previewer for a blob, Active Storage calls +accept?+ on each registered previewer in order. It uses the
# first previewer for which +accept?+ returns true when given the blob. In a \Rails application, add or remove previewers
# by manipulating +Rails.application.config.active_storage.previewers+ in an initializer:
#
# Rails.application.config.active_storage.previewers
# # => [ ActiveStorage::Previewer::PopplerPDFPreviewer, ActiveStorage::Previewer::MuPDFPreviewer, ActiveStorage::Previewer::VideoPreviewer ]
#
# # Add a custom previewer for Microsoft Office documents:
# Rails.application.config.active_storage.previewers << DOCXPreviewer
# # => [ ActiveStorage::Previewer::PopplerPDFPreviewer, ActiveStorage::Previewer::MuPDFPreviewer, ActiveStorage::Previewer::VideoPreviewer, DOCXPreviewer ]
#
# Outside of a \Rails application, modify +ActiveStorage.previewers+ instead.
#
# The built-in previewers rely on third-party system libraries. Specifically, the built-in video previewer requires
# {FFmpeg}[https://www.ffmpeg.org]. Two PDF previewers are provided: one requires {Poppler}[https://poppler.freedesktop.org],
# and the other requires {muPDF}[https://mupdf.com] (version 1.8 or newer). To preview PDFs, install either Poppler or muPDF.
#
# These libraries are not provided by \Rails. You must install them yourself to use the built-in previewers. Before you
# install and use third-party software, make sure you understand the licensing implications of doing so.
class ActiveStorage::Preview
include ActiveStorage::Blob::Servable
class UnprocessedError < StandardError; end
delegate :filename, :content_type, to: :presentation
attr_reader :blob, :variation
def initialize(blob, variation_or_variation_key)
@blob, @variation = blob, ActiveStorage::Variation.wrap(variation_or_variation_key)
end
# Processes the preview if it has not been processed yet. Returns the receiving +ActiveStorage::Preview+ instance for convenience:
#
# blob.preview(resize_to_limit: [100, 100]).processed.url
#
# Processing a preview generates an image from its blob and attaches the preview image to the blob. Because the preview
# image is stored with the blob, it is only generated once.
def processed
process unless processed?
variant.processed if variant?
self
end
# Returns the blob's attached preview image.
def image
blob.preview_image
end
# Returns the URL of the preview's variant on the service. Raises ActiveStorage::Preview::UnprocessedError if the
# preview has not been processed yet.
#
# This method synchronously processes a variant of the preview image, so do not call it in views. Instead, generate
# a stable URL that redirects to the URL returned by this method.
def url(**options)
if processed?
presentation.url(**options)
else
raise UnprocessedError
end
end
# Returns a combination key of the blob and the variation that together identifies a specific variant.
def key
if processed?
presentation.key
else
raise UnprocessedError
end
end
# Downloads the file associated with this preview's variant. If no block is
# given, the entire file is read into memory and returned. That'll use a lot
# of RAM for very large files. If a block is given, then the download is
# streamed and yielded in chunks. Raises ActiveStorage::Preview::UnprocessedError
# if the preview has not been processed yet.
def download(&block)
if processed?
presentation.download(&block)
else
raise UnprocessedError
end
end
private
def processed?
image.attached?
end
def process
previewer.preview(service_name: blob.service_name) do |attachable|
ActiveRecord::Base.connected_to(role: ActiveRecord.writing_role) do
image.attach(attachable)
end
end
end
def variant
image.variant(variation)
end
def variant?
variation.transformations.present?
end
def presentation
variant? ? variant.processed : image
end
def previewer
previewer_class.new(blob)
end
def previewer_class
ActiveStorage.previewers.detect { |klass| klass.accept?(blob) }
end
end