יצירת תמונות ממוזערות אוטומטית לסרטונים עם FFmpeg

ללא תצוגה מקדימה איכותית, סרטונים מאבדים קליקים ופוגעים בחוויית המשתמש. אנחנו מייצרים אוטומטית פריימים לתצוגה מקדימה עם FFmpeg, ויוצרים פוסטרים אינפורמטיביים ו-sprite sheets עבור פס ההתקדמות. הצוות שלנו מספק את הפרויקט במפתח מלא—מההתקנה ועד לתמיכה שוטפת.

פיתוח ותחזוקה של כל סוגי האתרים:

אתרי מידע או יישומי אינטרנט
אתרי תדמית, דפי נחיתה, אתרי חברה, קטלוגים מקוונים, חידונים, אתרי קידום, בלוגים, מקורות חדשות, פורטלי מידע, פורומים, אגרגטורים
אתרי מסחר אלקטרוני או יישומי אינטרנט
חנויות מקוונות, פורטלי B2B, שווקים, בורסות מקוונות, אתרי קאשבק, בורסות, פלטפורמות דרופשיפינג, מנתחי מוצרים
יישומי אינטרנט לניהול תהליכים עסקיים
מערכות CRM, מערכות ERP, פורטלים ארגוניים, מערכות ניהול ייצור, מנתחי מידע
אתרי שירות אלקטרוני או יישומי אינטרנט
פלטפורמות מודעות, בתי ספר מקוונים, בתי קולנוע מקוונים, בוני אתרים, פורטלים לשירותים אלקטרוניים, פלטפורמות אירוח וידאו, פורטלים נושאיים

אלה רק חלק מהסוגים הטכניים של אתרים שאנו עובדים איתם, ולכל אחד מהם יכולים להיות מאפיינים ופונקציונליות ספציפיים משלו, וכן ניתן להתאים אותם לצרכים ולמטרות הספציפיים של הלקוח.

השירותים שאנו מציעים
מציג 1 מתוך 1כל 2062 השירותים
יצירת תמונות ממוזערות אוטומטית לסרטונים עם FFmpeg
בינוני
מ- 1 יום עד 3 ימים

הכישורים שלנו:

שאלות נפוצות

העבודות האחרונות

  • פיתוח אפליקציית ווב עבור FEEDME
    פיתוח אפליקציית ווב עבור FEEDME
    1365
  • פיתוח חנות מקוונת לחברת FURNORO
    פיתוח חנות מקוונת לחברת FURNORO
    1330
  • פיתוח אפליקציית ווב עבור Enviok
    פיתוח אפליקציית ווב עבור Enviok
    1077
  • פיתוח CRM עבור Chasseurs
    פיתוח CRM עבור Chasseurs
    1113
  • פיתוח אתר עבור SBH Partners
    פיתוח אתר עבור SBH Partners
    1201
  • פיתוח אתר עבור Red Pear
    פיתוח אתר עבור Red Pear
    619

הערה: כאשר משתמש מעלה וידאו, הדבר הראשון שהוא רואה הוא התצוגה המקדימה. אם היא חסרה או לא אינפורמטיבית, מדדי המעורבות וה-SEO יורדים. ללא תצוגה מקדימה, דף וידאו מאבד עד 30% מהקליקים בתוצאות החיפוש, ו-Core Web Vitals נפגעים עקב חוסר אופטימיזציה של LCP. אנו הופכים את חילוץ פריימים לתצוגה מקדימה לאוטומטי באמצעות FFmpeg, כך שכל וידאו מקבל פוסטר איכותי ו-sprite sheet עבור סרגל ההתקדמות. הניסיון שלנו בפיתוח אתרים עולה על 8 שנים, עם יותר מ-50 פרויקטים הכוללים תצוגות מקדימות של וידאו בפלטפורמות מ-Laravel ועד Next.js. צרו קשר לייעוץ — נבחן את הפרויקט שלכם.

האתגר המרכזי הוא בחירת פריים אינפורמטיבי. פריים אקראי הוא לעיתים קרובות כהה או מטושטש. FFmpeg פותר זאת עם מסנן ה-thumbnail, אשר מנתח פריימים באמצעות מדד SAD (Sum of Absolute Differences) ומוצא את הטוב ביותר. הבעיה השנייה היא ביצועים: עם מספר גדול של וידאו, יצירת תצוגות מקדימות באופן סינכרוני אינה יעילה. אנו משתמשים ב-Laravel queues עם Jobs, ומפעילים את היצירה לאחר העלאת הווידאו. הודות לעיבוד ברקע, השרת אינו חסום, והמשתמש רואה מיד placeholder סטנדרטי עד לסיום היצירה.

יצירת הפוסטר עם FFmpeg

האפשרות הפשוטה ביותר היא פריים בנקודת זמן ספציפית:

ffmpeg -i input.mp4 -ss 00:00:05 -vframes 1 -q:v 2 thumbnail.jpg 

ffmpeg -i input.mp4 -ss 00:00:05 -vframes 1 -q:v 2 thumbnail.jpg — מיקום (5 שניות מתחילת הווידאו). -ss 00:00:05 — פריים אחד. -vframes 1 — איכות JPEG (1 = הטוב ביותר, 31 = הגרוע ביותר). עדיף לא למקם אותו בשנייה 0 (לעיתים קרובות פריים שחור) ולא בשנייה האחרונה. הuristic טוב הוא 10% מאורך הווידאו, אך לא פחות מ-3 שניות ולא יותר מ-30.

כיצד FFmpeg בוחר את הפריים הטוב ביותר?

FFmpeg יכול לבחור פריימים לפי מדד שינוי באמצעות מסנן ה-thumbnail:

ffmpeg -i input.mp4 -vf "thumbnail=300" -vframes 1 thumbnail.jpg 

לפי תיעוד FFmpeg, מסנן ה-thumbnail מנתח כל פריים 300, מחשב SAD ביחס לפריים הקודם, ובוחר את הפריים עם השינוי המקסימלי. זה בערך פעם בכל 10–12 שניות ב-24 fps. זה איטי יותר מבחירה ישירה לפי זמן, אך התוצאה אינפורמטיבית יותר.

בחירת נקודת הפוסטר האופטימלית

אנו ממליצים על גישה היברידית: אם הווידאו מכיל סצנה מרכזית (לדוגמה, לוגו בתחילתו), השתמשו בחותמת זמן קבועה. אחרת, השתמשו בבחירה אוטומטית. בשני המקרים, יש להחיל scaling עם padding לגודל אחיד.

השוו בין שיטות בחירת פריימים:

פרמטר פריים אקראי מסנן Thumbnail
מהירות מיידי ~2–5 שניות לדקת וידאו
איכות לעיתים קרובות כהה/מטושטש מייצג
מקרה שימוש פוסטר ברירת מחדל פוסטר לוידאו חשוב

יתרונות Sprite Sheet על פני תצוגות מקדימות בודדות

Sprite sheet הוא תמונה אחת עם רשת של פריימים שהנגן משתמש בה כדי להציג תצוגות מקדימות בעת ריחוף מעל סרגל ההתקדמות. זה מפחית בקשות HTTP לאחת, מוריד את עומס השרת ומאיץ את התגובה. עבור וידאו ארוך (10+ דקות), זה קריטי — תצוגות מקדימות בודדות היו יוצרות עשרות קבצים.

עבור וידאו של 10 דקות, רשת של 10×10 נותנת מרווח של 6 שניות. רוחב פריים בודד הוא 160px, הרשת היא 1600×900px (ב-thumb של 16:9). קוד בצד הלקוח מחשב את מיקום ה-sprite לפי זמן:

function getThumbnailPosition(currentTime, spriteData) {
  const { columns, rows, thumbWidth, interval, duration } = spriteData;
  const thumbHeight = Math.round(thumbWidth * 9 / 16);
  const frameIndex = Math.min(Math.floor(currentTime / interval), columns * rows - 1);
  const col = frameIndex % columns;
  const row = Math.floor(frameIndex / columns);
  return { x: col * thumbWidth, y: row * thumbHeight, width: thumbWidth, height: thumbHeight };
}

בריחוף מעל סרגל ההתקדמות, פשוט הגדירו background-image והזיזו את הרקע לקואורדינטות המחושבות. בממוצע, זה מפחית בקשות ב-90% בהשוואה לטעינת תמונות ממוזערות בודדות.

שירות PHP ליצירת תמונות ממוזערות

קוד מלא עבור VideoThumbnailService
namespace App\Services;

class VideoThumbnailService
{
    public function generatePoster(
        string $videoPath,
        string $outputPath,
        ?float $offsetSeconds = null,
        int $width = 1280,
        int $height = 720
    ): void {
        $duration = $this->getVideoDuration($videoPath);
        $offset = $offsetSeconds ?? max(3.0, $duration * 0.1);
        $offset = min($offset, $duration - 1.0);

        $scaleFilter = "scale={$width}:{$height}:force_original_aspect_ratio=decrease," .
            "pad={$width}:{$height}:(ow-iw)/2:(oh-ih)/2:black";

        $cmd = implode(' ', [
            'ffmpeg -y',
            '-ss ' . number_format($offset, 3, '.', ''),
            '-i ' . escapeshellarg($videoPath),
            '-vframes 1',
            "-vf " . escapeshellarg($scaleFilter),
            '-q:v 3',
            escapeshellarg($outputPath),
            '2>&1',
        ]);

        exec($cmd, $output, $exitCode);

        if ($exitCode !== 0 || !file_exists($outputPath)) {
            throw new \RuntimeException("Thumbnail generation failed: " . implode("\n", $output));
        }
    }

    public function generateSpriteSheet(
        string $videoPath,
        string $outputPath,
        int $columns = 10,
        int $rows = 10,
        int $thumbWidth = 160
    ): array {
        $duration = $this->getVideoDuration($videoPath);
        $totalFrames = $columns * $rows;
        $interval = $duration / $totalFrames;
        $fps = 1 / $interval;

        $filter = "fps={$fps},scale={$thumbWidth}:-1,tile={$columns}x{$rows}";

        $cmd = implode(' ', [
            'ffmpeg -y',
            '-i ' . escapeshellarg($videoPath),
            "-vf " . escapeshellarg($filter),
            '-vframes 1',
            '-q:v 5',
            escapeshellarg($outputPath),
            '2>&1',
        ]);

        exec($cmd, $output, $exitCode);

        if ($exitCode !== 0) {
            throw new \RuntimeException(implode("\n", $output));
        }

        return [
            'path' => $outputPath,
            'columns' => $columns,
            'rows' => $rows,
            'thumb_width' => $thumbWidth,
            'interval' => $interval,
            'duration' => $duration,
        ];
    }

    public function getVideoDuration(string $path): float
    {
        $cmd = "ffprobe -v error -show_entries format=duration -of csv=p=0 " .
            escapeshellarg($path);
        $output = shell_exec($cmd);
        return (float) trim($output ?? '0');
    }
}

אינטגרציה לתוך Laravel באמצעות Job

class GenerateVideoThumbnailsJob implements ShouldQueue {
    public int $timeout = 300;
    public int $tries = 2;

    public function __construct(private int $videoId) {}

    public function handle(VideoThumbnailService $service): void
    {
        $video = Video::findOrFail($this->videoId);
        $inputPath = Storage::disk('videos')->path($video->original_path);
        $dir = Storage::disk('videos')->path('thumbnails/' . $video->id);
        @mkdir($dir, 0755, true);

        $posterPath = "{$dir}/poster.jpg";
        $service->generatePoster($inputPath, $posterPath);

        $spritePath = "{$dir}/sprite.jpg";
        $spriteData = $service->generateSpriteSheet($inputPath, $spritePath);

        $video->update([
            'poster_path' => "thumbnails/{$video->id}/poster.jpg",
            'sprite_path' => "thumbnails/{$video->id}/sprite.jpg",
            'sprite_data' => $spriteData,
        ]);
    }
}

מטא-נתונים של Sprite (עמודות, שורות, מרווח) נשמרים בעמודת JSONB בטבלת הווידאו. הלקוח מקבל אותם יחד עם נתוני הווידאו ומשתמש בהם לרינדור תצוגות מקדימות על סרגל ההתקדמות.

תהליך העבודה שלנו

  1. ניתוח — חקר מבנה הווידאו ודרישות התצוגה המקדימה (גודל, פורמט).
  2. עיצוב — בחירת המחסנית: FFmpeg + Laravel queue. הגדרת פרמטרי ה-sprite sheet.
  3. יישום — כתיבת השירות וה-Job, אינטגרציה עם המודל.
  4. בדיקות — בדיקה על וידאו אמיתי באורכים שונים, התאמת ה-heuristics.
  5. פריסה — הגדרת queue, ניטור שגיאות.

מה כלול בעבודה

  • הגדרת FFmpeg עם פרמטרים אופטימליים לפוסטר ו-sprite sheet.
  • כתיבת מחלקת VideoThumbnailService וה-Job בתור.
  • אינטגרציה עם מודל הווידאו (שמירת מטא-נתונים ב-JSONB).
  • אופטימיזציה של התור (תור גבוה לוידאו בעדיפות, timeout של 300 שניות).
  • בדיקות על וידאו באורכים ורזולוציות שונות.
  • תיעוד נקודות קצה API ופורמט מטא-נתוני ה-sprite.

לוח זמנים ותמחור

לוח זמנים — מ-3 עד 5 ימים turnkey. התמחור מחושב באופן אישי בהתאם למורכבות האינטגרציה. בממוצע, לקוחות חוסכים עד 70% מהזמן המושקע בהכנת תצוגות מקדימות, ומפחיתים עלויות ניהול תוכן. ההשקעה באינטגרציה מחזירה את עצמה תוך 2–3 חודשים הודות לעיבוד תוכן מהיר יותר. צרו קשר — נבחן את הפרויקט שלכם.

לשם הבהרה, השוו בין הגישות:

פרמטר פוסטר בודד Sprite Sheet
מספר קבצים 1 1
בקשות HTTP 1 1
יצירה מהירה איטית יותר (מנתחת את כל הפריימים)
שימוש כריכת וידאו תצוגה מקדימה בסרגל התקדמות
גודל קובץ קטן (עד 100 KB) גדול (עד 5 MB)

אנו מבטיחים יצירה באיכות גבוהה העומדת בסטנדרטים מודרניים. הזמינו יישום של יצירת תמונות ממוזערות לתוכן הווידאו שלכם — צרו קשר לייעוץ.