Example #1
0
        /// <summary>Creates a thumbnail</summary>
        /// <param name="info">Media information to use for header text</param>
        /// <param name="pFormatContext">The format context to use</param>
        /// <param name="vidStream">The video stream to use</param>
        /// <param name="options">Options setting the appearence of the sheet</param>
        /// <param name="onProgress">Is called after each taken frame and passes (int currentIndex, int positions.Count) - if true is returned, the loop is canceled</param>
        private unsafe static Bitmap ExtractVideoThumbnailSheet(FFmpegMediaInfo info, AVFormatContext* pFormatContext, AVStream* vidStream, VideoThumbSheetOptions options, Func<int, int, bool> onProgress)
        {
            #region Structure diagram
            /* m := Margin, p := Padding, tw := ThumbWidth, th := ThumbHeight, hh := HeaderHeight, ### := Thumbnail image
             
                 m ###tw### p ###tw### p ###tw### p ###tw### p ###tw### m
                +--------------------------------------------------------+ 
                |                                                        | m     }  Margin
                | +----------------------------------------------------+ | 
                | |Filename: xxxxxxxxxxxxxxxxxxxxxx.avi                | | xx    \
                | |Length: 0:00:00    Resolution: 640x480 @25FPS       | | hh     } Header height
                | |Streams: Video[div5/xvid], Audio[mp3a/MP3/eng]      | | xx    /
                | +----------------------------------------------------+ | 
                |                                                        | 2*p   }  2 * Padding
                | +----------------------------------------------------+ |
                | |########| |########| |########| |########| |########| | ##    \
                | |########| |########| |########| |########| |########| | th     } Thumbnail height
                | |########| |########| |########| |########| |########| | ##    /
                | +----------------------------------------------------+ |
                |                                                        | p     }  1* Padding
                | +----------------------------------------------------+ |
                | |########| |########| |########| |########| |########| |
                ...                         ...                        ...
                ...                         ...                        ...
                | |########| |########| |########| |########| |########| |
                + +----------------------------------------------------+ + 
                |                                                        | m     }  Margin
                +--------------------------------------------------------+ 
             */
            #endregion

            #region Header and image preperations
            // Get video info from codec
            int width = vidStream->codec->width;
            int height = vidStream->codec->height;
            AVRational framerate = vidStream->codec->framerate;
            long duration = pFormatContext->duration;

            // Create header text and calculate its height (using current video stream)
            string header = String.Format("Filename: {0}\r\nLength: {1}    Resolution: {2}x{3} @{4}FPS\r\nStreams: {5}",
                Path.GetFileName(info.Filename),
                ToFormattedString(info.Duration),
                width, height, ToDescString(framerate),
                String.Join(", ", info.Streams.Select(s => s.ToString()).ToArray())
            );
            int headerHight = Convert.ToInt32(MeassureString(header, options.HeaderFont).Height);

            // Calculate image sizes and create image
            int thumbHeight = height * options.ThumbWidth / width;
            int imgWidth = 2 * options.Margin + options.ThumbColumns * (options.ThumbWidth + options.Padding) - options.Padding;
            int imgHeight = 2 * options.Margin + headerHight + options.ThumbRows * (thumbHeight + options.Padding) + options.Padding;
            int thumbTop = options.Margin + headerHight + 2 * options.Padding;
            Bitmap bmp = new Bitmap(imgWidth, imgHeight);

            int count = options.ThumbColumns * options.ThumbRows;
            List<long> positions = CreateRegularTimePositionList(duration, count);
            #endregion

            using (Graphics g = Graphics.FromImage(bmp))
            using (Brush headerBrush = new SolidBrush(options.HeaderColor))
            using (Pen borderPen = new Pen(options.ThumbBorderColor, 1f))
            using (Brush indexShadowBrush = new SolidBrush(options.IndexShadowColor))
            using (Brush indexBrush = new SolidBrush(options.IndexColor))
            {
                // Set background color
                g.Clear(options.BackgroundColor);

                // Draw header text
                float marginF = Convert.ToSingle(options.Margin);
                g.DrawString(header, options.HeaderFont, headerBrush, new PointF(marginF, marginF));

                // Loop through images as they are extracted
                int c, r, x, y;
                string tsText;
                SizeF tsSize;
                float ix, iy;
                ExtractFrames(pFormatContext, vidStream, positions, options.ForceExactTimePosition, (i, ts, img) =>
                {
                    // Calculate positions and sizes
                    c = i % options.ThumbColumns; // column
                    r = i / options.ThumbColumns; // row
                    x = options.Margin + c * (options.ThumbWidth + options.Padding); // thumb left
                    y = thumbTop + r * (thumbHeight + options.Padding); // thumb top
                    tsText = String.Format("{0:00}:{1:00}:{2:00}", ts.Hours, ts.Minutes, ts.Seconds); // timestamp text
                    tsSize = g.MeasureString(tsText, options.IndexFont); // timestamp text size
                    ix = Convert.ToSingle(x + options.ThumbWidth) - tsSize.Width - 3f; // timestamp text left
                    iy = Convert.ToSingle(y + thumbHeight) - tsSize.Height - 3f; // timestamp text top

                    // Insert thumbnail image resized
                    g.DrawImage(img, x, y, options.ThumbWidth, thumbHeight);

                    // Overdraw edges with border
                    if (options.DrawThumbnailBorder)
                        g.DrawRectangle(borderPen, x, y, options.ThumbWidth, thumbHeight);

                    // Draw timestamp shadow
                    g.DrawString(tsText, options.IndexFont, indexShadowBrush, ix - 1f, iy - 1f);
                    g.DrawString(tsText, options.IndexFont, indexShadowBrush, ix + 1f, iy + 1f);
                    // Draw timestamp
                    g.DrawString(tsText, options.IndexFont, indexBrush, ix, iy);

                    // Dispose the thumbnail image because it's not needed anymore
                    img.Dispose();

                    // Publish progress
                    if (onProgress != null)
                        return onProgress(i, count);
                    else
                        return false;
                });
            }

            return bmp;
        }
Example #2
0
        /// <summary>Creates a thumbnail sheet</summary>
        /// <param name="streamIndex">The index of the video stream to extract the frames from (-1 selects the first video stream)</param>
        /// <param name="options">Options setting the thumbnail sheet's appearence</param>
        /// <param name="onProgress">Is called after each taken frame and passes (int currentIndex, int positions.Count) - if true is returned, the loop is canceled</param>
        public Bitmap GetThumbnailSheet(int streamIndex, VideoThumbSheetOptions options, Func<int, int, bool> onProgress)
        {
            FFmpegStreamInfo stream;
            if (streamIndex == -1)
                stream = this.Streams.FirstOrDefault(s => s.StreamType == FFmpegStreamType.Video);
            else
                stream = this.Streams[streamIndex];

            if (stream == null || stream.StreamType != FFmpegStreamType.Video)
                throw new Exception("No video stream selected!");

            return ExtractVideoThumbnailSheet(this, this.AVFormatContext, stream.AVStream, options, onProgress);
        }