Skip to main content
All media processing in GenieHelper is local. No asset is sent to a cloud service for processing. FFmpeg runs on the dedicated server, jobs are queued via BullMQ, and results write back to media_assets in Directus. The media worker process handles image operations, video operations, scraping, publishing, and onboarding jobs in sequence — one at a time, no concurrency — to stay within the 16 GB RAM constraint.
The media worker was split from a single 2,000-line index.js into a modular operations/ directory on 2026-03-15. The current index.js is ~310 lines — only the operations map, worker definitions, and scheduler registrations.

Architecture

The media worker (media-worker/) is a BullMQ consumer managed by PM2. It listens on three separate queues:

media-jobs — image and video processing

Handles all FFmpeg-based transformations. Concurrency: 1. Jobs are queued by the agent via the media.process MCP plugin or by Action Runner bullmq_enqueue steps.Operations handled: convert_image, resize_image, crop_image, resize_video, crop_video, compress_video, download_video, trim_videos, join_videos, apply_watermark, create_teaser, strip_metadata, apply_steganographic_watermark, ytdlp_info.
Handles platform scraping and post publishing via Stagehand. Concurrency: 1 — only one browser session runs at a time. Jobs include scrape_profile, scrape_post_performance, and publish_post.
Handles the multi-phase creator onboarding pipeline. These jobs are LLM-heavy and run sequentially to avoid competing for RAM with Ollama’s pinned model allocation.

Operations module structure

media-worker/
├── index.js                  # ~310 lines — OPERATIONS map, workers, schedulers
└── operations/
    ├── helpers.js            # Shared utilities (path resolution, temp cleanup, job logging)
    ├── media.js              # 14 FFmpeg operation handlers
    ├── scrape.js             # scrape_profile, scrape_post_performance
    ├── publish.js            # publish_post + scrape_post_performance trigger
    └── onboarding.js         # Onboarding pipeline phase handlers

Media processing operations

All 14 handlers live in operations/media.js.
OperationDescription
convert_imageConvert between image formats (JPEG, PNG, WebP, AVIF)
resize_imageResize to specified dimensions, maintaining or ignoring aspect ratio
crop_imageCrop to a defined region — supports center crop and custom offsets
apply_watermarkOverlay a visible watermark image at specified position and opacity
strip_metadataRemove EXIF and XMP metadata from image files before distribution
OperationDescription
resize_videoRescale video to target resolution using FFmpeg scale filter
crop_videoCrop video to a defined region and resolution
compress_videoRe-encode video with target bitrate or CRF — for platform size limits
download_videoDownload video from a URL using yt-dlp integration
trim_videosCut a segment from a video file by start/end timecode
join_videosConcatenate multiple video files in order using FFmpeg concat
apply_watermarkOverlay a visible watermark on a video at specified position
create_teaserExtract a preview clip from a video — for PPV teasers
strip_metadataRemove metadata from video files before distribution
OperationDescription
ytdlp_infoProbe a URL with yt-dlp to retrieve video metadata before downloading
apply_steganographic_watermarkEmbed an invisible fan-specific watermark in an image or video

Steganographic watermarking

Every piece of purchased media gets an invisible, fan-specific steganographic watermark embedded at delivery time. If a file leaks, the watermark identifies exactly which fan’s purchase it came from.

How it works

1

Fan purchases content

A fan completes a PPV purchase or custom content delivery. The system identifies the fan’s fan_profiles record and their unique fan ID.
2

Watermark job enqueued

A apply_steganographic_watermark job is added to the media-jobs queue with the fan ID as the payload identifier. The original asset is referenced by its media_assets record ID.
3

Invisible watermark embedded

The apply_steganographic_watermark handler in operations/media.js encodes the fan-specific identifier into the pixel data of the media file using steganographic techniques. The modification is invisible to the eye.
4

Watermarked copy stored

A new media_assets record is created for the watermarked copy, linked to the original and to the fan’s delivery record. The original remains unmodified.
5

Leak traced

If the watermarked file appears publicly, the ProtectionInspectorPanel (left wing) can extract the embedded identifier and match it to the fan who received that specific copy.
Steganographic watermarks survive most common re-encoding and screenshot attacks but are not indestructible. They provide strong evidence for leak attribution, not a guarantee against all possible circumvention.

Media collections

Media collections in media_collections group assets into sellable bundles:
  • PPV bundles — a set of images or videos sold together as a pay-per-view package
  • Themed drops — curated sets released around a specific theme or event
  • Exclusive packs — subscriber-tier content groups with access gating
Each collection has a title, description, pricing, and access tier. Assets are linked to collections via media_asset_usages — one asset can appear in multiple collections.

Asset intelligence

Every uploaded asset in media_assets can carry AI-generated metadata:
FieldDescription
ai_descriptionA natural-language description of the image or video content, generated by Ollama
transcription_textFull transcription of spoken audio in video files
Taxonomy tagsAutomatic classification against the 3,205-node adult content taxonomy
Asset intelligence is generated asynchronously as a post-processing step after the initial upload. The ai_description feeds the content search index and the agent’s context when discussing or recommending specific assets. Transcriptions make video content searchable by spoken word.
All AI inference for asset intelligence runs locally via Ollama. No image or video is sent to an external API for description or transcription.

Schedulers

The media worker runs three internal schedulers alongside the queue consumers:
SchedulerIntervalPurpose
post_schedulerEvery 60 secondsChecks scheduled_posts for due posts and enqueues publish_post jobs
scrape_schedulerEvery 6 hoursTriggers periodic platform stat scrapes for all connected accounts
job_monitorEvery 5 minutesChecks for stalled or failed jobs and alerts via the diagnostics system

Build docs developers (and LLMs) love