{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Generate thumbnails from videos\n", "\n", "Automatically create preview thumbnails from video files at specific timestamps or intervals." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Problem\n", "\n", "You have video files that need preview thumbnails for galleries, search results, or video players. Manually extracting frames doesn't scale.\n", "\n", "| Use case | Videos | Need |\n", "|----------|--------|------|\n", "| Video platform | 10K videos | Preview thumbnails |\n", "| Media library | 5K clips | Gallery previews |\n", "| Content management | 1K uploads | Automated thumbnails |" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Solution\n", "\n", "**What's in this recipe:**\n", "\n", "- Extract thumbnail at a specific timestamp\n", "- Generate multiple thumbnails per video\n", "- Resize thumbnails to standard dimensions\n", "\n", "You add computed columns that extract frames from videos. Thumbnails are generated automatically when you insert new videos." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Setup" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%pip install -qU pixeltable" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import pixeltable as pxt\n", "import pixeltable.functions as pxtf" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Load videos" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Connected to Pixeltable database at: postgresql+psycopg://postgres:@/pixeltable?host=/Users/pjlb/.pixeltable/pgdata\n", "Created directory 'thumbnail_demo'.\n" ] }, { "data": { "text/plain": [ "" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Create a fresh directory\n", "pxt.drop_dir('thumbnail_demo', force=True)\n", "pxt.create_dir('thumbnail_demo')" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Created table 'videos'.\n" ] } ], "source": [ "# Create table for videos\n", "videos = pxt.create_table('thumbnail_demo/videos', {'video': pxt.Video})" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Inserting rows into `videos`: 2 rows [00:00, 382.20 rows/s]\n", "Inserted 2 rows with 0 errors.\n" ] }, { "data": { "text/plain": [ "2 rows inserted, 4 values computed." ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Insert sample videos from public S3 bucket\n", "s3_prefix = 's3://multimedia-commons/'\n", "video_paths = [\n", " 'data/videos/mp4/ffe/ffb/ffeffbef41bbc269810b2a1a888de.mp4',\n", " 'data/videos/mp4/ffe/feb/ffefebb41485539f964760e6115fbc44.mp4',\n", "]\n", "\n", "videos.insert([{'video': s3_prefix + path} for path in video_paths])" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
video
\n", " \n", "
\n", " \n", "
" ], "text/plain": [ " video\n", "0 /Users/pjlb/.pixeltable/file_cache/0441741fa96...\n", "1 /Users/pjlb/.pixeltable/file_cache/c15d7137a66..." ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# View videos\n", "videos.collect()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Extract thumbnail at timestamp\n", "\n", "Extract a single frame at a specific time (e.g., 1 second into the video):" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Added 2 column values with 0 errors.\n" ] }, { "data": { "text/plain": [ "2 rows updated, 2 values computed." ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Extract frame at 1 second as thumbnail\n", "videos.add_computed_column(\n", " thumbnail=pxtf.video.extract_frame(videos.video, timestamp=1.0)\n", ")" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
videothumbnail
\n", " \n", "
\n", " \n", "
\n", " \n", "
\n", " \n", "
" ], "text/plain": [ " video \\\n", "0 /Users/pjlb/.pixeltable/file_cache/0441741fa96... \n", "1 /Users/pjlb/.pixeltable/file_cache/c15d7137a66... \n", "\n", " thumbnail \n", "0 \n", " \n", " \n", " thumbnail_small\n", " width\n", " height\n", " \n", " \n", " \n", " \n", "
\n", " \n", "
\n", " 320\n", " 180\n", " \n", " \n", "
\n", " \n", "
\n", " 320\n", " 180\n", " \n", " \n", "" ], "text/plain": [ " thumbnail_small width height\n", "0 \n", " \n", " \n", " frame\n", " pos\n", " \n", " \n", " \n", " \n", "
\n", " \n", "
\n", " 0\n", " \n", " \n", "
\n", " \n", "
\n", " 1\n", " \n", " \n", "
\n", " \n", "
\n", " 2\n", " \n", " \n", "
\n", " \n", "
\n", " 3\n", " \n", " \n", "
\n", " \n", "
\n", " 4\n", " \n", " \n", "
\n", " \n", "
\n", " 5\n", " \n", " \n", "
\n", " \n", "
\n", " 6\n", " \n", " \n", "
\n", " \n", "
\n", " 7\n", " \n", " \n", "
\n", " \n", "
\n", " 8\n", " \n", " \n", "
\n", " \n", "
\n", " 9\n", " \n", " \n", "" ], "text/plain": [ " frame pos\n", "0