Click here to Skip to main content
15,885,366 members
Articles / Web Development / ASP.NET
Article

Video Transcoding "YouTube-Style" with ffmpeg

Rate me:
Please Sign up or sign in to vote.
3.55/5 (7 votes)
3 Jul 2008CPOL3 min read 61K   1.2K   36   3
A webservice to transcode videos to FLV, while maintaining aspect ratio

Introduction

With the popularity of sites such as YouTube, it seems everyone wants a "submit your own video"-type (SYOV) Web site these days. Well, it so happens that one of our clients is just one of those people. A SYOV site has a bunch of moving parts: database integration to store and fetch video info, a user creation and management system to track users, uploading modules to get the video files there in the first place, a transcoder to convert uploaded videos into a consistent format, a video player to play the videos, etc. The focus of this article is the video transcoding part of the process, in particular, maintaining source aspect ratio when the target is a fixed size.

Background

An early decision in the project was how to play all the various formats that people might upload to the site. Video can be recorded in dozens of formats, with differing levels of compression, resolution, etc. We decided early on (like almost everyone else) to centre on Flash Video (FLV). The player is ubiquitous, it offers good quality to filesize, and (importantly) there are good open source tools to handle its manipulation. The open source FFMPEG can be used to query video files and learn their details (resolution, format, etc), as well as actually perform a transcode from one format to another. The details are important, as we want to maintain the aspect ratio of the uploaded file when we transcode it. That is, if the final player is 320X240 pixels, and the submitted video is widescreen, we need to "letterbox": add black bars on the top and bottom so that the final image doesn't look "squished".

Using the Code

The code is pretty straightforward. I set this up as a Web service, so that other services and apps can easily access it. This means you can also call it asynchronously from one service or app, and let the transcoding run without slowing down the user. Note that, depending on the size of the file you wish to transcode, the process can take from seconds to minutes. On our (decent) Web server (ca. 2008), an 80meg file takes some 20 minutes. A more reasonable 6meg file takes a few seconds.

I came across a couple of problems with FFMPEG: I downloaded a precompiled binary from a 3rd party site, and found that adding padding to the video was not working as expected. It seemed to overwrite the areas of the video it was meant to be padding. I struggled with this for longer than I care to admit, certainly I must have been doing something wrong. Finally, on a hunch, I downloaded a different precompiled binary (from here) and the problem went away!

Another problem came in the capturing of output from FFMPEG to get all of the file's metadata. I created a new process and streamReader, and captured the StandardOutput from the process. Nothing. Turns out that there is no specific command in FFMPEG to read metadata from a video file, instead this info comes out as a StandardError stream when FFMPEG is called with no output file specified. Once I switched from capturing the StandardOutput to StandardError, I had no problems.

VB.NET
Shared Function getAllSpecs(ByVal strTheFilename As String) As String

    Dim p As Process = New Process()
    Dim s As String

    p.StartInfo.FileName = strFfmpegPath
    p.StartInfo.Arguments = "-i " & strVidsPath & strTheFilename

    p.StartInfo.UseShellExecute = False
    p.StartInfo.CreateNoWindow = True
    p.StartInfo.RedirectStandardError = True
    p.Start()

    Dim sError As StreamReader = p.StandardError _
        'ffmpeg passes back file info as "error" stream, not output stream

    s = sError.ReadToEnd

    If Not p.HasExited Then
                      p.Kill()
    End If

    sError.Close()
    p.Close()

    Return s

End Function

I wrote a regex to pull the wXh info out of the source file. This part of the service is not as robust as it could be. A user could, for example, upload a file named myvid_320X240_320X240.avi and bork it. Typical output from ffmpeg looks like:

FFmpeg version Sherpya-r13537, Copyright (c) 2000-2008 Fabrice Bellard, et al.
  libavutil version: 49.6.0
  libavcodec version: 51.57.0
  libavformat version: 52.14.0
  libavdevice version: 52.0.0
  libavfilter version: 0.0.0
  built on May 29 2008 21:35:56, gcc: 4.2.3
[avi @ 00AFDBAC]Non interleaved AVI
Input #0, avi, from 'E:\PROJECTS\media\videos\1.avi':
  Duration: 00:00:27.90, start: 0.000000, bitrate: 1234 kb/s
    Stream #0.0: Video: mjpeg, yuvj420p, 320x240, 15.02 tb(r)
    Stream #0.1: Audio: pcm_u8, 8000 Hz, mono, 64 kb/s
Must supply at least one output file

And we need to get the 320X240 out of stream #0.0. The regex I use is given below. I am open to suggestions on how to improve this (or anything else in the code, for that matter).

VB.NET
Shared Function getWxH(ByVal strPassedAllSpecs As String) As vidTranscode.WxH
    'get the width and height info from ffmpeg output text and return it as a WxH object

    Dim thereturn As vidTranscode.WxH

    'parentheses added for legibility. first get the right lines
    Dim pattern1 = ".*(Stream.\#).*(Video\:).*" 
    Dim pattern2 As String = "[0-9]*x[0-9]\w+" 'then get the WxH
    'do it in 2 passes to reduce the risk of someone uploading a file named, say:
    '  "myvideo-320x240.avi" and throwing a false-positive
    Dim matches1 As MatchCollection
    Dim matches2 As MatchCollection
    Dim options As RegexOptions = RegexOptions.IgnoreCase Or RegexOptions.Compiled

    Dim optionRegex As New Regex(pattern1, options)
    ' Get matches of pattern in text
    matches1 = optionRegex.Matches(strPassedAllSpecs)

    If matches1.Count > 0 Then
        'we got a hit for a line with video info in it, parse for WxH
        Dim optionRegex2 As New Regex(pattern2, options)
        matches2 = optionRegex2.Matches(matches1(0).Value)
        If matches2.Count > 0 Then
            Dim splitWxH As Array
            splitWxH = Split(matches2(0).Value, "x")
            thereturn.W = splitWxH(0)
            thereturn.H = splitWxH(1)
        Else
            'there was video stream info, but no resolution info
            thereturn.W = 0
            thereturn.H = 0
        End If
    Else
        'ffmpeg choked on the file provided and didn't return any video stream info
        thereturn.W = 0
        thereturn.H = 0
    End If

    Return thereturn

End Function

Next Steps

Now to work on a "handler" Web service that brings all of those things mentioned at the top of the article (file uploads, database write, this transcode, etc.) together when the user clicks on the upload button.

History

  • 2nd July, 2008: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Chief Technology Officer zero one design Inc.
Canada Canada
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionThanks for your job. Pin
werfog13-Nov-12 3:34
werfog13-Nov-12 3:34 
Generalshared environment Pin
apex_13101-Oct-08 2:51
apex_13101-Oct-08 2:51 
GeneralRe: shared environment Pin
hcoder 190022-Sep-09 21:53
hcoder 190022-Sep-09 21:53 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.