OverviewFilesCommitsIssuesDocsReleasesPeers

holepunchto-deps / @holepunchto / keet-media / worker / media.js

126 lines · javascript ref:
import { image, video } from 'bare-media'
import b4a from 'b4a'

import {
  CHAT_MESSAGE_PREVIEW,
  CHAT_LINK_PREVIEW,
  CHAT_MESSAGE_MAX_FRAMES,
  PROFILE_AVATAR,
  ROOM_AVATAR
} from '../shared/constants'

export async function createChatImagePreview({ path }) {
  const rgba = await image(path).decode()
  return _createChatFilePreview(rgba)
}

export async function createChatVideoPreview({ path }) {
  const rgba = await video(path).extractFrames({ frameIndex: 1 })
  return _createChatFilePreview(rgba)
}

export async function createChatLinkPreview({ path, buffer }) {
  const rgba = path ? await image(path).decode() : await image.decode(buffer)
  return _createChatMessagePreviewSingle(rgba, CHAT_LINK_PREVIEW)
}

export async function createProfileAvatar({ path, crop }) {
  return _createAvatar(path, crop, PROFILE_AVATAR)
}

export async function createRoomAvatar({ path, crop }) {
  return _createAvatar(path, crop, ROOM_AVATAR)
}

export async function decodeImage({ httpLink }) {
  const rgba = await image(httpLink).decode()
  const { width, height, data } = rgba

  return {
    metadata: {
      dimensions: { width, height }
    },
    data
  }
}

async function _createChatFilePreview(rgba) {
  const sliced = image.slice(rgba, {
    end: CHAT_MESSAGE_MAX_FRAMES
  })

  const [small, medium, large] = await Promise.all([
    _createChatMessagePreviewSingle(sliced, CHAT_MESSAGE_PREVIEW.small),
    _createChatMessagePreviewSingle(sliced, CHAT_MESSAGE_PREVIEW.medium),
    _createChatMessagePreviewSingle(rgba, CHAT_MESSAGE_PREVIEW.large)
  ])

  return {
    metadata: {
      dimensions: {
        width: rgba.width,
        height: rgba.height
      }
    },
    small,
    medium,
    large
  }
}

async function _createChatMessagePreviewSingle(rgba, opts) {
  const resized = await image.resize(rgba, opts.resize)
  const buffer = await image.encode(resized, opts.encode)

  const data = opts.base64 ? { base64: b4a.toString(buffer, 'base64') } : { buffer }

  return {
    metadata: {
      mimetype: opts.encode.mimetype,
      dimensions: {
        width: resized.width,
        height: resized.height
      }
    },
    ...data
  }
}

async function _createAvatar(path, crop, opts) {
  const rgba = await image(path).decode()
  const { width, height } = rgba
  const short = Math.min(width, height)

  const cropArea = crop || {
    left: width > height ? Math.floor((width - height) / 2) : 0,
    top: height > width ? Math.floor((height - width) / 2) : 0,
    width: short,
    height: short
  }

  const cropped = await image.crop(rgba, cropArea)

  const [small, medium] = await Promise.all([
    _createAvatarSingle(cropped, opts.small),
    _createAvatarSingle(cropped, opts.medium)
  ])

  return { small, medium }
}

async function _createAvatarSingle(rgba, opts) {
  const resized = await image.resize(rgba, opts.resize)
  const buffer = await image.encode(resized, opts.encode)

  return {
    metadata: {
      mimetype: opts.encode.mimetype,
      dimensions: {
        width: resized.width,
        height: resized.height
      }
    },
    base64: b4a.toString(buffer, 'base64')
  }
}