# Video to Slides - Extract Unique Slides to PDF + Text (OCR) (`kantolabs/video-to-slides`) Actor

Video to slides converter: turn a lecture, webinar or screen recording file into its unique slides - one image per slide with start/end timestamps, OCR text and a combined PDF. Duplicates and slide builds merged. $4 per 1,000 slides + $0.001 per video minute.

- **URL**: https://apify.com/kantolabs/video-to-slides.md
- **Developed by:** [Kanto Labs](https://apify.com/kantolabs) (community)
- **Categories:** Videos, AI, Automation
- **Security:** Limited permissions
- **Stats:** 1 total users, 0 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.20 / 1,000 slides

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Video to Slides - Extract Unique Slides to PDF + Text

**Video to slides** in one API call: give this Actor a **lecture, webinar, conference talk or screen recording** file and it returns **every unique slide** as an image, with its **start and end timestamp**, the **slide text (OCR)** and **one combined PDF** of all slides. A slide shown for five minutes becomes one slide. Bullet-by-bullet builds are merged and you get the **most complete version**. Slides the speaker goes back to are not repeated. The camera shots of the speaker are left out.

No API key, no LLM, no browser extension, no manual screenshots. It runs on CPU on Apify, so you can call it from **n8n, Make, Zapier, Python, JavaScript or an AI agent (MCP)**.

### What you get

For every video:

- **One dataset row per slide**: `slideIndex`, `startSeconds` / `endSeconds`, `timecode` / `endTimecode`, `durationSeconds`, `imageUrl` (full-size JPEG/PNG/WEBP), `text` (OCR, reading order), `ocrConfidence`, `width`, `height`, `buildSteps` (how many build steps were merged into it), `alsoShownAt` (later times the same slide came back).
- **One summary row** (`type: "video"`): `slideCount`, **`pdfUrl`** (all slides, one page each), `contactSheetUrl` (one overview image), **`allText`** (every slide's text with `--- slide N (hh:mm:ss) ---` markers, ready to paste into ChatGPT, Claude or a RAG index), duration, resolution, `billedMinutes`, `note`, `error`.

Field names match [Video Frame Extractor](https://apify.com/kantolabs/video-frame-extractor) where they overlap (`videoUrl`, `fileName`, `timestampSeconds`, `timecode`, `width`, `height`), so you can chain both Actors in one workflow.

### Use cases

- **Lecture video to PDF notes**: turn a recorded class or MOOC lesson into a slide handout you can annotate.
- **Webinar and meeting recordings**: get the deck that was presented, even if nobody sent it.
- **Conference talks**: index the slides of every talk with their timestamps.
- **Screen recordings and tutorials**: one image per screen, with the text on it.
- **Video RAG / LLM input**: send `allText` and the slide images to a vision model instead of hundreds of near-identical frames.

### How to extract slides from a video

1. Paste direct video file links into **Video file URLs** (MP4, MOV, WEBM, MKV, AVI, M4V, MPEG-TS...). Your own storage, S3/R2/GCS, a CDN, an Apify key-value store, or Dropbox / Google Drive links shared as "anyone with the link" all work.
2. Pick the **Slide language** for OCR (English/Chinese/Japanese, accented Latin languages, Cyrillic, Korean, Arabic, Devanagari, Greek, Thai).
3. Optional: set **Sensitivity**, **Minimum slide duration**, or a **Slide area (crop)** when the slides share the frame with a large speaker camera or burned-in subtitles.
4. Click **Start**, then open the **Slides** tab of the dataset, or the `pdfUrl` in the **Videos** tab.

Example input:

```json
{
  "videoUrls": ["https://example.com/recordings/lecture-03.mp4"],
  "sensitivity": "medium",
  "ocr": true,
  "ocrLanguage": "en",
  "outputPdf": true
}
```

Example slide row (shortened):

```json
{
  "type": "slide",
  "fileName": "lecture-03.mp4",
  "slideIndex": 5,
  "startSeconds": 146.25,
  "endSeconds": 213.75,
  "timecode": "00:02:26.250",
  "endTimecode": "00:03:33.750",
  "durationSeconds": 67.5,
  "timestampSeconds": 213.25,
  "imageUrl": "https://api.apify.com/v2/key-value-stores/.../records/...-slide-005.jpg",
  "width": 1920,
  "height": 1080,
  "text": "Uncitedness (publications):\n12% - 82% (Larivière & Gingras 2009)\nUncitedness (data):\n85% (Peters et al. 2016)",
  "ocrConfidence": 0.9749,
  "buildSteps": 8,
  "alsoShownAt": []
}
```

### How slide detection works

The video is decoded once at low resolution (1-2 pictures per second). A slide is a picture that **stays still** for at least the minimum slide duration. Regions that keep moving all the time - a small **webcam picture-in-picture**, a playing video, a ticking clock - are learned from the whole video and ignored. When a later picture only **adds** content on empty background (a bullet appears, a chart builds), it is merged into the same slide and the final state is kept. Slides identical to an earlier one are dropped and recorded in `alsoShownAt`. Only then is each slide grabbed at full resolution, OCR'd and written to the PDF.

Because camera footage never stays still, a **talk filmed on stage returns only the moments where a slide fills the screen** - the speaker shots are not billed as slides.

### Pricing

Pay per event, no subscription, no start fee beyond Apify's tiny default:

| Event | Price | Store discount |
|---|---|---|
| **Slide** (one unique slide: image + text + PDF page) | **$0.004** ($4 per 1,000 slides) | Silver $0.0036, Gold $0.0032 |
| **Video minute** (each started minute scanned, per video) | **$0.001** | Silver $0.0009, Gold $0.0008 |

Worked examples:

- **60-minute lecture with 40 slides**: 60 x $0.001 + 40 x $0.004 = $0.06 + $0.16 = **$0.22**.
- **10-minute screen recording with 15 screens**: $0.01 + $0.06 = **$0.07**.
- **20-minute talk filmed on stage with 6 full-screen slides**: $0.02 + $0.024 = **$0.044**.

**Free:** failed downloads, refused links, non-video files, videos where no slide was found, blank slides, duplicates, the PDF, the contact sheet and the OCR text. Set **Max cost per run** in the run options and the Actor stops cleanly at that amount: it scans only the minutes that fit, delivers the slides that fit, and marks later videos as `skipped` (free).

### Video to slides vs. doing it yourself

A frame every N seconds gives you hundreds of near-duplicates and half-built slides. ffmpeg's scene filter misses bullet builds and fires on every camera cut. Browser extensions and desktop apps need a person to press the button. This Actor dedupes, keeps the complete build state, skips the speaker shots and returns structured JSON plus a PDF - from a schedule, a webhook or an API call.

### Limits and tips

- **Direct video files only.** Links to YouTube, Vimeo, TikTok, Instagram, Facebook, X, LinkedIn, Twitch, Douyin or Bilibili pages are refused (free) because their terms forbid downloading.
- Default limits: 240 minutes and 2,000 MB per video, 300 slides per video (all adjustable).
- **Burned-in subtitles** are ignored when the rest of the slide stays the same. If a large speaker camera sits next to the slides, use **Slide area (crop)**, e.g. `{"x": 0, "y": 0, "width": 75, "height": 100}`.
- Screen recordings: every screen that stays still for the minimum duration counts (scrolling is skipped). Use **Sensitivity: low** to merge small highlights and pop-ups.
- A slide shown again at a **different zoom or crop** may appear twice. Photos shown full screen for several seconds count as slides.
- OCR is fast CPU OCR (PP-OCR): very small text, handwriting and fancy fonts can come out wrong. Pick the right **Slide language** - the default model does not read Cyrillic.

### FAQ

#### How do I convert a video to slides for free?

Apify's free plan includes monthly credit, which covers hundreds of slides with this Actor. You only pay per slide and per scanned minute.

#### Can I extract slides from a YouTube lecture?

Not from a YouTube page link. Use a video file you own or are allowed to process (for example your own Zoom, Teams or OBS recording uploaded to cloud storage).

#### Does it work with Zoom, Teams and Google Meet recordings?

Yes - upload the MP4 file and link it. Small webcam thumbnails are ignored automatically; for a large side-by-side camera use the crop option.

#### Can I get the slides as PowerPoint?

The Actor returns a PDF and one image per slide. Most tools (PowerPoint, Google Slides, Keynote) import the images or the PDF directly.

### Related Actors by Kanto Labs

- [Video Frame Extractor](https://apify.com/kantolabs/video-frame-extractor) - keyframes, scene changes, evenly spaced frames or the best thumbnails from any video file.
- [Video to LLM Context](https://apify.com/kantolabs/video-to-llm-context) - scenes, one keyframe per scene and the spoken transcript aligned to each scene, for AI agents.
- [Video Scene Detection & Shot List](https://apify.com/kantolabs/video-scene-detection) - every cut with timecodes, CSV/EDL and optional clips, for edited videos.

### Support

Found a video where slides are missed or duplicated? Open an issue on this Actor's **Issues** tab with the video link and the run ID - we answer within a day.

The built-in sample video is a 3-minute cut of the webinar "Academic SEO" by Peter Kraker (Open Knowledge Maps), licensed CC BY-SA 4.0, from Wikimedia Commons.

# Actor input Schema

## `videoUrls` (type: `array`):

Direct links to lecture, webinar, meeting or screen-recording files (MP4, MOV, WEBM, MKV, AVI, M4V, MPEG-TS...), one per line. Your own storage, S3/R2/GCS, CDNs, an Apify key-value store, Dropbox or Google Drive share links (shared as 'anyone with the link') all work. Pages of video platforms (YouTube, Vimeo, TikTok...) are not supported.

## `sensitivity` (type: `string`):

How small a change counts as a new slide. `medium` suits most decks. `low` merges more (fewer slides, ignores small highlights and pop-ups). `high` keeps subtle changes such as one changed number on an otherwise identical slide.

## `minSlideSeconds` (type: `integer`):

A picture must stay still at least this long to count as a slide. Skips transitions, flashes and scrolling. Videos longer than 2 hours are sampled every 2 seconds, so values below 4 act as 4 there.

## `cropBox` (type: `object`):

Only look at this part of the frame, in percent: {"x": 0, "y": 0, "width": 75, "height": 100} keeps the left 75 % (drops a speaker camera on the right). Use it for side-by-side layouts or burned-in subtitles. Small webcam overlays are ignored automatically. Leave empty for the full frame. The saved slide images are cropped the same way.

## `ocr` (type: `boolean`):

Read the text on every slide (free, included in the slide price) and return it per slide plus an `allText` block per video, ready for an LLM, notes or search.

## `ocrLanguage` (type: `string`):

Script of the slide text. `en` also reads Chinese and Japanese. Use `latin` for French, German, Spanish, Czech, Polish and other accented Latin languages, `cyrillic` for Russian, Ukrainian, Bulgarian.

## `outputPdf` (type: `boolean`):

Save all slides of a video as one PDF (one page per slide, free). Its link is in the video's summary row (`pdfUrl`).

## `contactSheet` (type: `boolean`):

Also save one overview image with every slide and its start time (free).

## `imageFormat` (type: `string`):

Format of the slide images. The PDF always uses JPEG pages.

## `maxWidth` (type: `integer`):

Wider slides are scaled down (aspect ratio kept). 0 keeps the video's full resolution.

## `imageQuality` (type: `integer`):

Compression quality of JPEG/WEBP slides and PDF pages.

## `maxSlidesPerVideo` (type: `integer`):

Upper limit per video (the first N slides in time order are kept), so a long screen recording cannot return thousands of slides.

## `maxVideoMinutes` (type: `integer`):

Longer videos are refused (free).

## `maxVideoSizeMb` (type: `integer`):

Larger files are refused (free).

## `downloadTimeoutSecs` (type: `integer`):

Give up on a video that does not download within this time (free).

## Actor input object example

```json
{
  "videoUrls": [
    "https://api.apify.com/v2/key-value-stores/aXcXwfChlioPWAUJv/records/sample-slides-webinar.mp4?signature=13u4YK5fJsZFm70P8hE8I"
  ],
  "sensitivity": "medium",
  "minSlideSeconds": 2,
  "ocr": true,
  "ocrLanguage": "en",
  "outputPdf": true,
  "contactSheet": true,
  "imageFormat": "jpg",
  "maxWidth": 1920,
  "imageQuality": 85,
  "maxSlidesPerVideo": 300,
  "maxVideoMinutes": 240,
  "maxVideoSizeMb": 2000,
  "downloadTimeoutSecs": 600
}
```

# Actor output Schema

## `results` (type: `string`):

No description

## `files` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "videoUrls": [
        "https://api.apify.com/v2/key-value-stores/aXcXwfChlioPWAUJv/records/sample-slides-webinar.mp4?signature=13u4YK5fJsZFm70P8hE8I"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("kantolabs/video-to-slides").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "videoUrls": ["https://api.apify.com/v2/key-value-stores/aXcXwfChlioPWAUJv/records/sample-slides-webinar.mp4?signature=13u4YK5fJsZFm70P8hE8I"] }

# Run the Actor and wait for it to finish
run = client.actor("kantolabs/video-to-slides").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "videoUrls": [
    "https://api.apify.com/v2/key-value-stores/aXcXwfChlioPWAUJv/records/sample-slides-webinar.mp4?signature=13u4YK5fJsZFm70P8hE8I"
  ]
}' |
apify call kantolabs/video-to-slides --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,kantolabs/video-to-slides"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/jOWgd3xTZO9LRskbz/builds/vPRMaamWe0nc7BFbm/openapi.json
