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

Feat: PDF support via Imgproxy #1637

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 18 additions & 12 deletions components/file-upload.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ export const FileUpload = forwardRef(({ children, className, onSelect, onUpload,
const s3Upload = useCallback(async file => {
const element = file.type.startsWith('image/')
? new window.Image()
: document.createElement('video')
: file.type.startsWith('video/')
? document.createElement('video')
: document.createElement('object') // pdf

file = await removeExifData(file)

Expand All @@ -33,8 +35,8 @@ export const FileUpload = forwardRef(({ children, className, onSelect, onUpload,
avatar,
type: file.type,
size: file.size,
width: element.width,
height: element.height
width: element.width || element.scrollWidth,
height: element.height || element.scrollHeight
}
try {
({ data } = await getSignedPOST({ variables }))
Expand Down Expand Up @@ -72,16 +74,20 @@ export const FileUpload = forwardRef(({ children, className, onSelect, onUpload,
resolve(id)
}

// img fire 'load' event while videos fire 'loadeddata'
element.onload = onload
element.onloadeddata = onload
if (file.type === 'application/pdf') {
onload() // pdf can be loaded directly
} else {
// img fire 'load' event while videos fire 'loadeddata'
element.onload = onload
element.onloadeddata = onload

element.onerror = reject
element.src = window.URL.createObjectURL(file)
element.onerror = reject
element.src = window.URL.createObjectURL(file)

// iOS Force the video to load metadata
if (element.tagName === 'VIDEO') {
element.load()
// iOS Force the video to load metadata
if (element.tagName === 'VIDEO') {
element.load()
}
}
})
}, [toaster, getSignedPOST])
Expand All @@ -101,7 +107,7 @@ export const FileUpload = forwardRef(({ children, className, onSelect, onUpload,
for (const file of Array.from(fileList)) {
try {
if (accept.indexOf(file.type) === -1) {
throw new Error(`file must be ${accept.map(t => t.replace(/^(image|video)\//, '')).join(', ')}`)
throw new Error(`file must be ${accept.map(t => t.replace(/^(image|video|application)\//, '')).join(', ')}`)
}
if (onSelect) await onSelect?.(file, s3Upload)
else await s3Upload(file)
Expand Down
3 changes: 2 additions & 1 deletion lib/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ export const UPLOAD_TYPES_ALLOW = [
'video/quicktime',
'video/mp4',
'video/mpeg',
'video/webm'
'video/webm',
'application/pdf'
]
export const AVATAR_TYPES_ALLOW = UPLOAD_TYPES_ALLOW.filter(t => t.startsWith('image/'))
export const INVOICE_ACTION_NOTIFICATION_TYPES = ['ITEM_CREATE', 'ZAP', 'DOWN_ZAP', 'POLL_VOTE', 'BOOST']
Expand Down
13 changes: 9 additions & 4 deletions worker/imgproxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,11 +123,16 @@ const getMetadata = async (url) => {
const options = '/vm:1/d:1/f:1'
const imgproxyUrl = new URL(createImgproxyPath({ url, options, pathname: '/info' }), IMGPROXY_URL).toString()
const res = await fetch(imgproxyUrl)
const { width, height, format, video_streams: videoStreams } = await res.json()
return { dimensions: { width, height }, format, video: !!videoStreams?.length }
const { width, height, format, pages, video_streams: videoStreams } = await res.json()
return { dimensions: { width, height }, format, video: !!videoStreams?.length, pdf: format === 'pdf', pages: pages || 1 }
}

const createImgproxyPath = ({ url, pathname = '/', options }) => {
// pdf processing, pg:1 for first page, q:90 for quality, cc:ffffff for white background, dpr:2 for retina
if (url.endsWith('.pdf')) {
options = `/pg:1${options}/q:90/cc:ffffff/dpr:2`
}

const b64Url = Buffer.from(url, 'utf-8').toString('base64url')
const target = path.join(options, b64Url)
const signature = sign(target)
Expand All @@ -151,7 +156,7 @@ const isMediaURL = async (url, { forceFetch }) => {
// https://stackoverflow.com/a/68118683
const res = await fetchWithTimeout(url, { timeout: 1000, method: 'HEAD' })
const buf = await res.blob()
isMedia = buf.type.startsWith('image/') || buf.type.startsWith('video/')
isMedia = buf.type.startsWith('image/') || buf.type.startsWith('video/' || buf.type === 'application/pdf')
} catch (err) {
console.log(url, err)
}
Expand All @@ -167,7 +172,7 @@ const isMediaURL = async (url, { forceFetch }) => {
try {
const res = await fetchWithTimeout(url, { timeout: 10000 })
const buf = await res.blob()
isMedia = buf.type.startsWith('image/') || buf.type.startsWith('video/')
isMedia = buf.type.startsWith('image/') || buf.type.startsWith('video/' || buf.type === 'application/pdf')
} catch (err) {
console.log(url, err)
}
Expand Down
Loading