HTML5 / et transcodage en direct avec FFMPEG

donc depuis mon serveur web, J'aimerais utiliser FFMPEG pour transcoder un fichier multimédia pour l'utiliser avec une balise HTML <audio> ou <video> . Assez facile à droite?

la conversion doit avoir lieu en temps réel, lorsqu'un client HTTP demande le fichier converti. Idéalement, le fichier devrait être redirigé vers le client HTTP au fur et à mesure qu'il est transcodé (et pas plus tard à la fin, car cela pourrait prendre un certain temps avant que les données ne soient renvoyées).

ce serait très bien, sauf que dans les navigateurs d'aujourd'hui, une balise audio ou vidéo HTML5 demande le fichier multimédia dans plusieurs requêtes HTTP avec l'en-tête Range . voir cette question pour plus de détails .

dans cette question liée ci-dessus, vous pouvez voir que Safari demande des morceaux bizarres du fichier, y compris les quelques octets de fin. Cela pose un problème dans le sens où le serveur web devrait attendre la fin de la conversion, en ordre de livrer les octets finals du fichier conformément à la requête Range .

donc ma question Est, est-ce que mon train de pensée est juste? Y a-t-il une meilleure façon de livrer le contenu de transcodage à une étiquette <audio> ou <video> qui n'impliquerait pas d'attendre que la conversion complète soit terminée? Merci à l'avance!

20
demandé sur Community 2010-09-04 01:11:23

4 réponses

j'ai récemment rencontré le même problème puisque je veux servir ma bibliothèque aux navigateurs. Étonnamment, l'idée d'envoyer le flux par ffmpeg et de livrer à la volée fonctionne assez bien. Le principal problème était de trouver un soutien...

ci-dessous, vous trouvez des sniplets de code en Python en utilisant un flacon pour résoudre le problème:

Nous avons besoin d'une fonction pour diffuser le contenu:

@app.route('/media/<path:path>.ogv')
def media_content_ogv(path):
    d= os.path.abspath( os.path.join( config.media_folder, path ) )
    if not os.path.isfile( d ): abort(404)
    start= request.args.get("start") or 0
    def generate():
        cmdline= list()
        cmdline.append( config.ffmpeg )
        cmdline.append( "-i" )
        cmdline.append( d );
        cmdline.append( "-ss" )
        cmdline.append( str(start) );
        cmdline.extend( config.ffmpeg_args )
        print cmdline
        FNULL = open(os.devnull, 'w')
        proc= subprocess.Popen( cmdline, stdout=subprocess.PIPE, stderr=FNULL )
        try:
            f= proc.stdout
            byte = f.read(512)
            while byte:
                yield byte
                byte = f.read(512)
        finally:
            proc.kill()

    return Response(response=generate(),status=200,mimetype='video/ogg',headers={'Access-Control-Allow-Origin': '*', "Content-Type":"video/ogg","Content-Disposition":"inline","Content-Transfer-Enconding":"binary"})

alors nous avons besoin d'une fonction pour retourner le durée:

@app.route('/media/<path:path>.js')
def media_content_js(path):
    d= os.path.abspath( os.path.join( config.media_folder, path ) )
    if not os.path.isfile( d ): abort(404)
    cmdline= list()
    cmdline.append( config.ffmpeg )
    cmdline.append( "-i" )
    cmdline.append( d );
    duration= -1
    FNULL = open(os.devnull, 'w')
    proc= subprocess.Popen( cmdline, stderr=subprocess.PIPE, stdout=FNULL )
    try:
        for line in iter(proc.stderr.readline,''):
            line= line.rstrip()
            #Duration: 00:00:45.13, start: 0.000000, bitrate: 302 kb/s
            m = re.search('Duration: (..):(..):(..)\...', line)
            if m is not None: duration= int(m.group(1)) * 3600 + int(m.group(2)) * 60 + int(m.group(3)) + 1
    finally:
        proc.kill()

    return jsonify(duration=duration)

et enfin, nous Hacker que dans HTML5 en utilisant videojs:

<!DOCTYPE html>
<html>
<head>
    <link href="//vjs.zencdn.net/4.5/video-js.css" rel="stylesheet">
    <script src="//vjs.zencdn.net/4.5/video.js"></script>
    <script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
</head>
<body>
    <video id="video" class="video-js vjs-default-skin" controls preload="auto" width="640" height="264">
    </video>
    <script>
        var video= videojs('video');
        video.src("media/testavi.avi.ogv");

        // hack duration
        video.duration= function() { return video.theDuration; };
        video.start= 0;
        video.oldCurrentTime= video.currentTime;
        video.currentTime= function(time) 
        { 
            if( time == undefined )
            {
                return video.oldCurrentTime() + video.start;
            }
            console.log(time)
            video.start= time;
            video.oldCurrentTime(0);
            video.src("media/testavi.avi.ogv?start=" + time);
            video.play();
            return this;
        };

        $.getJSON( "media/testavi.avi.js", function( data ) 
        {
            video.theDuration= data.duration;
        });
    </script>
</body>

un exemple pratique se trouve à https://github.com/derolf/transcoder .

dero

8
répondu user3612643 2014-05-08 05:26:41

Merci pour la réponse Camilo . J'ai regardé de plus près la spécification HTTP concernant la requête Range et j'ai trouvé:

The header SHOULD indicate the total length of the full entity-body, unless
this length is unknown or difficult to determine. The asterisk "*" character
means that the instance-length is unknown at the time when the response was
generated.

donc c'est vraiment juste une question de tester comment les navigateurs réagissent en répondant avec un Content-Range: bytes 0-1/* , par exemple. Je vais vous laisser savoir ce qui se passe.

2
répondu TooTallNate 2010-09-10 05:32:02

AFAIK vous pouvez encoder pour stdout en ffmpeg. Vous pouvez donc configurer votre serveur HTTP comme suit:

  • démarrez l'encodage du cache lorsque vous le recevez.
  • stream plage d'octets demandée au client.
  • remplir le tampon et l'utiliser pour les plages suivantes.

Je n'en sais rien mais je pense qu'on peut s'en tirer sans connaître la longueur du dernier jet.

sur un côté note, je pense que C'est sujet à DoS.

0
répondu Camilo Martin 2010-09-09 05:18:02

cela devrait être faisable via VLC , j'ai pu le faire fonctionner en paramétrant VLC pour héberger un grand fichier avi et le transcoder à OGG, puis Mon html5 Référencé le flux:

<source src="http://localhost:8081/stream.ogg">

il a été en mesure de transcoder en vlc, et rendre très bien dans mon navigateur chrome et sur mon téléphone android, mais j'ai fini par prendre un solution différente plutôt que de passer par le travail de création de mon propre webapp pour héberger mes médias collection et créer des flux pour les fichiers demandés - j'ai regardé et ne pouvais pas trouver un libre déjà là-bas qui l'a fait d'une manière dont j'ai eu besoin/aimé.

-1
répondu TheDruidsKeeper 2011-08-11 20:21:35