Using FFMPEG to reliably convert videos to mp4 for iphone/ipod and flash players

59,564

Solution 1

WARNING: this answer is 10 years old and reported not to work anymore.


I think the issue is the H.264 level being level 4.2.

Some of the Apple devices only support up to 3.0.

Here's the FFMPEG settings I usually use:

ffmpeg -i YOUR-INPUT.wmv -s qvga -b 384k -vcodec libx264 -r 23.976 -acodec libfaac -ac 2 -ar 44100 -ab 64k -vpre baseline -crf 22 -deinterlace -o YOUR-OUTPUT.MP4

You can adjust the rate, size and bitrate as needed. The important settings are in the baseline config param.

Solution 2

The ffmpeg wiki provides some useful up to date guidance on how to encode H.264 for particular devices. Here's an excerpt:

iOS Compatability ([​source][2])
Profile  Level Devices                                                     Options
Baseline 3.0  All devices                                                  -profile:v baseline -level 3.0
Baseline 3.1  iPhone 3G and later, iPod touch 2nd generation and later     -profile:v baseline -level 3.1
Main     3.1  iPad (all vers), Apple TV 2 and later, iPhone 4 and later    -profile:v main -level 3.1
Main     4.0  Apple TV 3 and later, iPad 2 and later, iPhone 4s and later  -profile:v main -level 4.0
High     4.0  Apple TV 3 and later, iPad 2 and later, iPhone 4s and later  -profile:v high -level 4.0
High     4.1  iPad 2 and later, iPhone 4s and later, iPhone 5c and later   -profile:v high -level 4.1
High     4.2  iPad Air and later, iPhone 5s and later                      -profile:v high -level 4.2

Solution 3

Try this python script.

I wrote it for myself. Maybe you will find it useful too. It converts files to mp4.

Because of SO rules here the complete source code:

#!/usr/bin/python

# Copyright (C) 2007-2010 CDuke
# This program is free software. You may distribute it under the terms of
# the GNU General Public License as published by the Free Software
# Foundation, version 2.
#
# This program is distributed in the hope that it will be useful, but

# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
# Public License for more details.
#
# This program converts video files to mp4, suitable to be played on an iPod
# or an iPhone. It is careful about maintaining the proper aspect ratio.

from __future__ import division
from datetime import datetime
import sys
import argparse
import os
import re
import shlex
import time
from subprocess import Popen, PIPE

DEFAULT_ARGS = '-f mp4 -y -vcodec libxvid -maxrate 1000k -mbd 2 -qmin 3 -qmax 5 -g 300 -bf 0 -acodec libfaac -ac 2 -flags +mv4 -trellis 2 -cmp 2 -subcmp 2'
#DEFAULT_ARGS = '-f mp4 -y -vcodec mpeg4 -vtag xvid -maxrate 1000k -mbd 2 -qmin 3 -qmax 5 -g 300 -bf 0 -acodec libfaac -ac 2 -r 30000/1001 -flags +mv4 -trellis 2 -cmp 2 -subcmp 2'
#DEFAULT_ARGS = '-y -f mp4 -vcodec libxvid -acodec libfaac'
DEFAULT_BUFSIZE = '4096k'
DEFAULT_AUDIO_BITRATE = '128k'
DEFAULT_VIDEO_BITRATE = '400k'
FFMPEG = '/usr/bin/ffmpeg'

class device:
    '''Describe properties of device'''
    def __init__(self, name, width, height):
        self.name = name
        self.width = width
        self.height = height

class videoFileInfo:
    def __init__(self, width, height, duration):
        self.width = width
        self.height = height
        self.duration = duration

devices = [device('ipod', 320, 240), device('iphone', 480, 320),
device('desire', 800, 480)]

def getOutputFileName(inputFileName, outDir):
    if outDir == None:
        outFileName = os.path.splitext(inputFileName)[0] + '.mp4'
    else:
        outFileName = os.path.join(outDir, os.path.basename(inputFileName))
    return outFileName

def getVideoFileInfo(fileName):
    p = Popen([FFMPEG, '-i', fileName], stdout = PIPE, stderr = PIPE)
    fileInfo = p.communicate()[1]
    videoRes = re.search(b'Video:.+ (\d+)x(\d+)', fileInfo)
    w = float(videoRes.group(1))
    h = float(videoRes.group(2))
    duratMatch = re.search(b'Duration:\s+(\d+):(\d+):(\d+)\.(\d+)', fileInfo)
    duration = float(duratMatch.group(1)) * 3600
    duration += float(duratMatch.group(2)) * 60
    duration += float(duratMatch.group(3))
    duration += float(duratMatch.group(4)) / 10
    fileInfo = videoFileInfo(w, h, duration)
    return fileInfo

def getArguments(width, height, aspect):
    args = {}
    w = width
    h = w // aspect
    h -= (h % 2)
    if h <= height:
        pad = (height - h) // 2
        pad -= (pad % 2)
        pady = pad
        padx = 0
    else:
        # recalculate using the height as the baseline rather than the width
        h = height
        w = int(h * aspect)
        width -= (width % 2)
        pad = (width - w) // 2
        pad -= (pad % 2)
        padx = pad
        pady = 0

    args['width'] = w
    args['height'] = h
    args['padx'] = padx
    args['pady'] = pady
    return args

def getProgressBar(perc):
    convInfo = 'Converted: [{}] {:.2%} \r'
    num_hashes = round(perc * 100 // 2)
    bar = '=' * num_hashes + ' ' * (50 - num_hashes)
    return convInfo.format(bar, perc)

def convert(inputFileName, outputFileName, args, audioBitrate, videoBitrate, devWidth, devHeight, aspect, duration):
    cmd = '{ffmpeg} -i {inFile} {defaultArgs} -bufsize {bufsize} -s {width}x{height} -vf "pad={devWidth}:{devHeight}:{padx}:{pady},aspect={aspect}" -ab {audioBitrate} -b {videoBitrate} {outFile}'.format(ffmpeg=FFMPEG, inFile=inputFileName, defaultArgs=DEFAULT_ARGS, bufsize=DEFAULT_BUFSIZE, devWidth=devWidth, devHeight=devHeight, padx=args['padx'], pady=args['pady'], width=args['width'], height=args['height'], aspect=aspect, audioBitrate=audioBitrate, videoBitrate=videoBitrate, outFile=outputFileName)
#    cmd = '{ffmpeg} -i {inFile} {defaultArgs} -bufsize {bufsize} -s {width}x{height} -ab {audioBitrate} -b {videoBitrate} {outFile}'.format(ffmpeg=FFMPEG, inFile=inputFileName, defaultArgs=DEFAULT_ARGS, bufsize=DEFAULT_BUFSIZE, width=args['width'], height=args['height'], audioBitrate=audioBitrate, videoBitrate=videoBitrate, outFile=outputFileName)
    print(cmd)
    print()
    start = datetime.today()
    print('Converting started at ' + str(start))
    conv = Popen(shlex.split(cmd), shell=False, stdout=PIPE, stderr=PIPE)
    while conv.poll() is None:
       out = os.read(conv.stderr.fileno(), 2048)
       last = out.splitlines()[-1]
       timeMatch = re.search(b'time=([^\s]+)', last)
       if timeMatch:
           timeDone = float(timeMatch.group(1))
           perc = timeDone / duration
           if sys.version_info > (3, 0):
               exec("print(getProgressBar(perc), end='')")
           else:
               exec("print getProgressBar(perc),")
           sys.stdout.flush()
#       else:
#           print(out)
       time.sleep(0.5)
    print(getProgressBar(1))
    end = datetime.today()
    print('Converting ended at ' + str(end))
    print('Spended time: ' + str(end - start))

class mp4Converter(argparse.Action):
    def __call__(self, parser, namespace, values, option_string = None):
        outdir = namespace.outdir
        for f in values:
            outFileName = getOutputFileName(f.name, outdir)
            fileInfo = getVideoFileInfo(f.name)
            aspect = fileInfo.width / fileInfo.height
            dev = next(d for d in devices if d.name == namespace.device)
            args = getArguments(dev.width, dev.height, aspect)
            convert(f.name, outFileName, args, namespace.AUDIO_BITRATE, namespace.VIDEO_BITRATE, dev.width, dev.height, aspect, fileInfo.duration)
            print('file "{0}" converted successful'.format(f.name))

opts = argparse.ArgumentParser(
    description = 'Converter to MP4',
    epilog = 'made by CDuke 2010')
opts.add_argument('-V','--version',
                  action = 'version',
                  version = '0.0.1')
opts.add_argument('-v', '--verbose',
                  action = 'store_true',
                  default = False,
                  help = 'verbose')
opts.add_argument('-a', '--audio',
                  dest = 'AUDIO_BITRATE',
                  default = DEFAULT_AUDIO_BITRATE,
                  help = 'override default audio bitrate {0}'.format(DEFAULT_AUDIO_BITRATE))
opts.add_argument('-b', '--video',
                  dest = 'VIDEO_BITRATE',
                  default = DEFAULT_VIDEO_BITRATE,
                  help = 'override default video bitrate {0}'.format(DEFAULT_VIDEO_BITRATE))
opts.add_argument('-d', '--device',
                  choices = [d.name for d in devices],
                  default = 'ipod',
                  help = 'device that will play video')
opts.add_argument('-o', '--outdir',
                  help = 'write files to given directory')
opts.add_argument('file',
                  nargs = '+',
                  type = argparse.FileType('r'),
                  action = mp4Converter,
                  help = 'file that will be converted')

opts.parse_args()

Solution 4

ffmpeg -i input.mov -c:v libx264 -pix_fmt yuv420p -profile:v main -crf 1 -preset medium -c:a aac -movflags +faststart output.mp4

Solution 5

The listed ffmpeg settings didn't work for me (I don't seem to have the "baseline" preset listed), ffmpeg settings that don't reference baseline, I posted over here: iPhone "cannot play" .mp4 H.264 video file

Spoiler:

ffmpeg -i INPUT -s 320x240 -r 30000/1001 -b 200k -bt 240k -vcodec libx264 -coder 0 -bf 0 -refs 1 -flags2 -wpred-dct8x8 -level 30 -maxrate 10M -bufsize 10M -acodec libfaac -ac 2 -ar 48000 -ab 192k OUTPUT.mp4

The official Apple reference on the subject: http://developer.apple.com/library/safari/#documentation/AppleApplications/Reference/SafariWebContent/CreatingVideoforSafarioniPhone/CreatingVideoforSafarioniPhone.html

Share:
59,564
Jake Stevenson
Author by

Jake Stevenson

Updated on July 17, 2022

Comments

  • Jake Stevenson
    Jake Stevenson almost 2 years

    I need to convert videos for use in both a flash player and the iphone/ipod touch. I'm using the following batch script with ffmpeg:

    @echo off
    ffmpeg.exe -i %1 -s qvga -acodec libfaac -ar 22050 -ab 128k -vcodec libx264 -threads 0 -f ipod %2
    

    This always outputs an mp4 file, and I can always play it on my PC. The videos also seem to play fine on my iphone 3GS. But with some input files it won't work for older iphone versions (3G and iPod touch).

    Here's the ffmpeg output from one such file:

    D:\ffmpeg>encode.bat d:\temp\recording.flv d:\temp\out.m4v
    FFmpeg version SVN-r18709, Copyright (c) 2000-2009 Fabrice Bellard, et al.
      configuration: --enable-memalign-hack --prefix=/mingw --cross-prefix=i686-ming
    w32- --cc=ccache-i686-mingw32-gcc --target-os=mingw32 --arch=i686 --cpu=i686 --e
    nable-avisynth --enable-gpl --enable-zlib --enable-bzlib --enable-libgsm --enabl
    e-libfaac --enable-libfaad --enable-pthreads --enable-libvorbis --enable-libtheo
    ra --enable-libspeex --enable-libmp3lame --enable-libopenjpeg --enable-libxvid -
    -enable-libschroedinger --enable-libx264
      libavutil     50. 3. 0 / 50. 3. 0
      libavcodec    52.27. 0 / 52.27. 0
      libavformat   52.32. 0 / 52.32. 0
      libavdevice   52. 2. 0 / 52. 2. 0
      libswscale     0. 7. 1 /  0. 7. 1
      built on Apr 28 2009 04:04:42, gcc: 4.2.4
    [flv @ 0x187d650]skipping flv packet: type 18, size 164, flags 0
    Input #0, flv, from 'd:\temp\recording.flv':
      Duration: 00:00:07.17, start: 0.001000, bitrate: N/A
        Stream #0.0: Video: flv, yuv420p, 320x240, 1k tbr, 1k tbn, 1k tbc
        Stream #0.1: Audio: nellymoser, 44100 Hz, mono, s16
    [libx264 @ 0x13518b0]using cpu capabilities: MMX2 SSE2Fast SSSE3 FastShuffle SSE
    4.2
    [libx264 @ 0x13518b0]profile Baseline, level 4.2
    Output #0, ipod, to 'd:\temp\out.m4v':
        Stream #0.0: Video: libx264, yuv420p, 320x240, q=2-31, 200 kb/s, 1k tbn, 1k
    tbc
        Stream #0.1: Audio: libfaac, 22050 Hz, mono, s16, 128 kb/s
    Stream mapping:
      Stream #0.0 -> #0.0
      Stream #0.1 -> #0.1
    Press [q] to stop encoding
    frame=   90 fps=  0 q=-1.0 Lsize=     128kB time=6.87 bitrate= 152.4kbits/s
    video:92kB audio:32kB global headers:1kB muxing overhead 2.620892%
    [libx264 @ 0x13518b0]slice I:8     Avg QP:29.62  size:  7047
    [libx264 @ 0x13518b0]slice P:82    Avg QP:30.83  size:   467
    [libx264 @ 0x13518b0]mb I  I16..4: 17.9%  0.0% 82.1%
    [libx264 @ 0x13518b0]mb P  I16..4:  0.6%  0.0%  0.0%  P16..4: 23.1%  0.0%  0.0%
     0.0%  0.0%    skip:76.3%
    [libx264 @ 0x13518b0]final ratefactor: 57.50
    [libx264 @ 0x13518b0]SSIM Mean Y:0.9544735
    [libx264 @ 0x13518b0]kb/s:8412.6
    

    My suspicion is that it has something to do with the audio encoding. If so, does anyone know how to force it to reencode the audio to the proper format?

    Any other ideas?

  • mxg
    mxg over 12 years
    When I try this, FFMPEG tells me "[libx264 @ 0x7f85e8845400] profile High, level 1.3" How can I fix this?
  • badbod99
    badbod99 over 12 years
    what/where is the baseline config file?
  • mulllhausen
    mulllhausen about 10 years
    these are probably the baseline configs referred to in the answer github.com/FFmpeg/FFmpeg/tree/master/presets
  • cdeutsch
    cdeutsch about 10 years
    -vpre baseline just means restrict to baseline. There isn't an actual file to my knowledge. (update answer and replaced the word "file" with "param")
  • brezanac
    brezanac almost 9 years
    While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes.
  • Pierz
    Pierz almost 9 years
    I've just added the table. It's tricky as some of the above answers have gone out of date but I agree it's useful to have the info in the answer. This way there's the wiki link and an inline copy just in case.
  • llogan
    llogan about 5 years
    -threads 0 (auto threads) is default, so you don't need that. -strict experimental is only needed by really old ffmpeg builds to use the now previously experimental built-in AAC encoder. So you probably don't need that either. 1280x720 will exceed Level 3.0 limits (refer to level limit warnings in console output). You probably meant -level 3.1, but you probably don't even need level and can let the encoder choose. Try -profile:v main before -profile:v baseline unless you are targeting the most ancient of devices.
  • slothstronaut
    slothstronaut over 4 years
    Does not work, tried it exactly as is and it has deprecated parameters. No longer relevant.
  • El_Vanja
    El_Vanja about 4 years
    Can you please edit your answer and add a clarification of your proposed solution (what you've changed, what it does, what was the problem with the OP's code...)?