npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@faeizfurqan/expo-story-video-and-image-editor

v1.0.5

Published

Instagram Stories-style video/image editor for Expo — color filters, text & sticker overlays, music mixing, and FFmpeg export, with native iOS color grading (no GPU readback).

Readme


Demo

Table of Contents

Why this over rolling your own?

Building a Stories-style editor from scratch means fighting FFmpeg command strings, GPU-readback stalls on filtered preview, and platform-specific decoder quirks — for weeks. This package already fought those battles:

| | This package | Banuba SDK | Rolling your own | |---|:---:|:---:|:---:| | Price | Free, MIT | $$$ license | Your time | | Native iOS color grading (no GPU readback) | Yes | Yes | Weeks of AVFoundation work | | Drag/pinch text + sticker overlays | Yes | Yes | Build it yourself | | FFmpeg export pipeline | Yes, bundled | Proprietary | Build it yourself | | Open source, auditable | Yes | No | — | | Vendor lock-in | None | SDK license required | — |

Features

  • Source: single video clip or still image (sourceType: 'video' | 'image')
  • Filters: normal, norway, neon, retro, warm, cool, bw, vintage, sunset, film, fade — swipeable carousel, live preview, burned into the export
  • Text overlays: font, color, alignment, background highlight — drag/pinch/rotate in preview
  • Stickers: image or GIF overlays (via Giphy, if configured) — drag/pinch/rotate
  • Music: one background track, trimmable, mixed with (or replacing) original audio
  • Export: single-pass FFmpeg pipeline — quality presets, format selection, progress callback

On iOS, filtered video preview runs through a bundled native module (GradedVideoPlayer) that applies color grading via AVMutableVideoComposition + CIColorMatrix inside AVFoundation's own decode pipeline — no extra GPU readback. On Android, filtered preview renders through @shopify/react-native-skia.

Installation

yarn add @faeizfurqan/expo-story-video-and-image-editor

Peer dependencies

yarn add @azzapp/[email protected] @shopify/react-native-skia @expo/vector-icons \
  @react-native-community/slider expo-audio expo-document-picker expo-file-system \
  expo-image expo-image-picker expo-modules-core expo-video \
  react-native-gesture-handler react-native-reanimated

@azzapp/react-native-skia-video must be exactly 0.9.0 — this library ships a patch-package patch pinned to that release (see Native setup below).

FFmpeg (required for export)

ffmpeg-kit-react-native was archived; install the community fork instead:

yarn add ffmpreg-kit-react-native@github:beedeez/ffmpreg-kit-react-native

Native setup

This library includes native iOS code (the GradedVideoPlayer module, autolinked automatically), but three things need manual wiring in your own app: an iOS config plugin for FFmpeg, a patch for @azzapp/react-native-skia-video, and a dev build.

  1. A dev build, not Expo Go. npx expo prebuild then npx expo run:ios / npx expo run:android, or an EAS dev build.

  2. iOS only — register the bundled FFmpeg config plugin. ffmpreg-kit-react-native's JS package alone is not enough on iOS: the underlying ffmpeg-kit-ios pod's original releases (from arthenica) are gone, so the actual native framework has to be pulled from a community mirror and injected into your Podfile before use_native_modules! runs. Without this, iOS builds fail at compile time with 'ffmpegkit/FFmpegKitConfig.h' file not found. Add it to your Expo config's plugins array, before any other plugin:

    // app.config.js / app.json
    plugins: [
      'expo-router', // or whatever else you already have first
      [
        '@faeizfurqan/expo-story-video-and-image-editor/plugin/with-ffmpeg-kit',
        { package: 'full' }, // 'full' | 'https' — matches ffmpreg-kit-react-native's build flavor
      ],
      // ...your other plugins
    ],

    This plugin resolves the pod's actual installed path at prebuild time (no hardcoded relative depth — safe whether your app is a monorepo or a standalone project), so no further configuration is needed beyond registering it.

  3. Android only — apply the bundled skia-video patch in your own app:

    • Add patch-package as a dev dependency: yarn add -D patch-package postinstall-postinstall
    • Add "postinstall": "patch-package" to your app's package.json scripts.
    • Copy the patch into your own ./patches/ directory:
      mkdir -p patches
      cp node_modules/@faeizfurqan/expo-story-video-and-image-editor/patches/@azzapp+react-native-skia-video+0.9.0.patch patches/
    • Run yarn install (or npx patch-package) so it applies. The patch is filename-pinned to @azzapp/[email protected] — it won't apply against any other version, per patch-package's own versioning convention.
  4. Re-run npx expo prebuild (add --clean if you're adding the FFmpeg plugin to an already-generated ios/ directory — the Podfile needs to be regenerated for the plugin to inject its pod line) / pod install after installing, so both GradedVideoPlayer and the FFmpeg pod link correctly on iOS.

Quick start

import { VideoEditor, type ExportResult } from '@faeizfurqan/expo-story-video-and-image-editor';

function EditorScreen({ videoUri }: { videoUri: string }) {
  return (
    <VideoEditor
      source={videoUri}
      sourceType="video"
      onExportComplete={(result: ExportResult) => {
        console.log('Exported:', result.uri, result.duration, result.width, result.height);
      }}
      onExportProgress={(progress) => console.log(`${Math.round(progress * 100)}%`)}
      onCancel={() => {
        /* navigate back */
      }}
    />
  );
}

VideoEditor props

| Prop | Type | Required | Notes | |---|---|---|---| | source | string | yes | Local file URI of the video or image | | sourceType | 'video' \| 'image' | no | Defaults to 'video'. An image gets a fixed preview/export duration. | | onExportComplete | (result: ExportResult) => void | yes | | | onExportProgress | (progress: number) => void | no | 01 | | onCancel | () => void | no | | | config | EditorConfig | no | See below | | isActive | boolean | no | Defaults to true. Wire to your router's focus state (e.g. useIsFocused()) so the editor's decoders stop running when the screen isn't visible — the library takes no dependency on any specific navigation library. |

EditorConfig

interface EditorConfig {
  features?: { text?: boolean; filters?: boolean; stickers?: boolean; music?: boolean };
  export?: Omit<ExportConfig, 'onProgress'>; // quality, format, maxDuration, bitRate, frameRate, resolution...
  theme?: { backgroundColor?: string; accentColor?: string; textColor?: string };
  giphyApiKey?: string; // sticker picker falls back to the photo library without this
}

ExportResult

interface ExportResult {
  uri: string;
  duration: number;
  size: number;
  width: number;
  height: number;
}

Lower-level access

useVideoEditor() exposes the underlying zustand-backed editor state and actions directly, if you want to build custom UI instead of using <VideoEditor>:

import { useVideoEditor } from '@faeizfurqan/expo-story-video-and-image-editor';

const editor = useVideoEditor();

editor.initialize(sourceUri, duration, width, height, sourceType);
editor.setFilter('norway');
editor.addText({
  text: 'Hello',
  font: 'System',
  fontSize: 24,
  color: '#FFF',
  position: { x: 0.5, y: 0.5 }, // normalized 0-1, anchored at center
  rotation: 0,
  scale: 1,
  alignment: 'center',
  startTime: 0,
  endTime: duration,
});
editor.addSticker({
  uri: gifUri,
  position: { x: 0.5, y: 0.5 },
  size: { width: 120, height: 120 },
  rotation: 0,
  scale: 1,
  startTime: 0,
  endTime: duration,
});
editor.setMusic({
  uri: musicUri,
  title: 'My Track',
  startTime: 0, // when it kicks in on the OUTPUT timeline
  duration: musicDuration, // full length of the source audio file
  trimStart: 0, // where in the SOURCE file playback begins
  volume: 1,
});
editor.toggleMute();

const result = await editor.exportVideo({ quality: 'high', format: 'mp4', onProgress: (p) => console.log(p) });

Also exported: FFmpegEngine, FFmpegCommandBuilder, ExportPipeline (the export internals), createEditorStore (the raw zustand store factory), FILTER_PRESETS / getFilterByPreset / IDENTITY_MATRIX (filter color matrices), and the shared TypeScript types (TextOverlay, StickerOverlay, AudioTrack, FilterPreset, EditorState, etc.) from core/types.

Tech stack

| Library | Purpose | |---|---| | beedeez/ffmpreg-kit-react-native | FFmpeg export pipeline (iOS + Android) | | @shopify/react-native-skia | Sticker/text canvas; Android filtered-video preview | | @azzapp/react-native-skia-video | Frame-accurate video decode for Skia preview (patched, pinned to 0.9.0) | | expo-video | Default (unfiltered) video preview playback | | Bundled GradedVideoPlayer native module | iOS filtered-video preview — AVMutableVideoComposition + CIColorMatrix, no separate GPU readback | | expo-audio | Music/voiceover playback in the editor | | expo-file-system | Export output paths, temp file cleanup | | react-native-reanimated | Gesture-driven overlays, playback sync | | react-native-gesture-handler | Drag/pinch/rotate for text and stickers | | zustand | Editor state |

Project structure

packages/video-editor/
  src/
    core/           # FFmpeg engine, command builder, export pipeline, shared types
    components/     # VideoEditor and its subcomponents (Preview, Text, Stickers, Audio, Filters)
    filters/        # Filter preset color matrices
    hooks/          # useVideoEditor
    store/          # zustand editor state
    utils/          # time/layout helpers
  ios/              # GradedVideoPlayer native module (Swift)
  GradedVideoPlayer.podspec
  expo-module.config.json

example/            # Expo example app exercising the library
docs/               # Architecture, API, and feature docs

Running the example app

yarn install
cd example
npx expo prebuild
npx expo start --dev-client

License

MIT