web-dev-qa-db-fra.com

Ffmpeg peut-il afficher une barre de progression?

Je suis en train de convertir un fichier .avi en fichier .flv en utilisant ffmpeg. Comme il faut beaucoup de temps pour convertir un fichier, j'aimerais afficher une barre de progression. Quelqu'un peut-il me guider s'il vous plaît sur la façon de s'y prendre.

Je sais que ffmpeg doit en quelque sorte enregistrer la progression dans un fichier texte et je dois le lire à l'aide d'appels ajax. Mais comment faire pour que ffmpeg affiche la progression dans le fichier texte?

Merci beaucoup.

52
Pawan Rao

Je joue avec ça depuis quelques jours. Ce truc "ffmpegprogress" a aidé, mais il était très difficile de travailler avec ma configuration et de lire le code.

Pour afficher la progression de ffmpeg, vous devez procéder comme suit:

  1. lancez la commande ffmpeg à partir de php sans attendre de réponse (pour moi, c'était la partie la plus difficile)
  2. dire à ffmpeg d'envoyer sa sortie dans un fichier
  3. depuis le front-end (AJAX, Flash, peu importe) frappez directement ce fichier ou un fichier php capable d'extraire la progression de la sortie de ffmpeg.

Voici comment j'ai résolu chaque partie:

1 . J'ai eu l'idée suivante de "ffmpegprogress". C'est ce qu'il a fait: un fichier PHP en appelle un autre via un socket http. Le second exécute en fait le "exec" et le premier fichier est suspendu. Pour moi, sa mise en œuvre était trop complexe. Il utilisait "fsockopen". J'aime CURL. Alors voici ce que j'ai fait:

$url = "http://".$_SERVER["HTTP_Host"]."/path/to/exec/exec.php";
curl_setopt($curlH, CURLOPT_URL, $url);
$postData = "&cmd=".urlencode($cmd);
$postData .= "&outFile=".urlencode("path/to/output.txt");
curl_setopt($curlH, CURLOPT_POST, TRUE);
curl_setopt($curlH, CURLOPT_POSTFIELDS, $postData);

curl_setopt($curlH, CURLOPT_RETURNTRANSFER, TRUE);

// # this is the key!
curl_setopt($curlH, CURLOPT_TIMEOUT, 1);
$result = curl_exec($curlH);

Définir CURLOPT_TIMEOUT sur 1 signifie qu’il attendra une seconde pour obtenir une réponse. De préférence, ce serait inférieur. Il y a aussi le CURLOPT_TIMEOUT_MS qui prend des millisecondes, mais cela n'a pas fonctionné pour moi.

Après 1 seconde, CURL raccroche, mais la commande exec est toujours exécutée. Partie 1 résolu.

BTW - Quelques personnes ont suggéré d'utiliser la commande "Nohup" pour cela. Mais cela n'a pas semblé fonctionner pour moi.

*ÉGALEMENT! Avoir un fichier php sur votre serveur qui peut exécuter du code directement sur la ligne de commande est un risque de sécurité évident. Vous devriez avoir un mot de passe ou encoder les données de publication d'une manière ou d'une autre.

2 . Le script "exec.php" ci-dessus doit également indiquer à ffmpeg de générer un fichier. Voici le code pour cela:

exec("ffmpeg -i path/to/input.mov path/to/output.flv 1> path/to/output.txt 2>&1");

Notez le "1> chemin/à/sortie.txt 2> & 1". Je ne suis pas un expert en ligne de commande, mais d'après ce que je peux dire, cette ligne indique "envoie la sortie normale dans ce fichier ET envoie les erreurs au même endroit". Consultez cette URL pour plus d’informations: http://tldp.org/LDP/abs/html/io-redirection.html

3 . Depuis le serveur, appelez un script php en lui indiquant l'emplacement du fichier output.txt. Ce fichier php va alors extraire la progression du fichier texte. Voici comment j'ai fait ça:

// # get duration of source
preg_match("/Duration: (.*?), start:/", $content, $matches);

$rawDuration = $matches[1];

// # rawDuration is in 00:00:00.00 format. This converts it to seconds.
$ar = array_reverse(explode(":", $rawDuration));
$duration = floatval($ar[0]);
if (!empty($ar[1])) $duration += intval($ar[1]) * 60;
if (!empty($ar[2])) $duration += intval($ar[2]) * 60 * 60;


// # get the current time
preg_match_all("/time=(.*?) bitrate/", $content, $matches); 

$last = array_pop($matches);
// # this is needed if there is more than one match
if (is_array($last)) {
    $last = array_pop($last);
}

$curTime = floatval($last);


// # finally, progress is easy
$progress = $curTime/$duration;

J'espère que ça aide quelqu'un.

31
Mike Kellogg

Il existe un article en russe qui explique comment résoudre votre problème.

Le point est d'attraper la valeur Duration avant le codage et d'attraper les valeurs time=... pendant le codage.

--skipped--
Duration: 00:00:24.9, start: 0.000000, bitrate: 331 kb/s
--skipped--
frame=   41 q=7.0 size=     116kB time=1.6 bitrate= 579.7kbits/s
frame=   78 q=12.0 size=     189kB time=3.1 bitrate= 497.2kbits/s
frame=  115 q=13.0 size=     254kB time=4.6 bitrate= 452.3kbits/s
--skipped--
24
baltazar

FFmpeg utilise stdout pour la sortie des données multimédia et stderr pour la journalisation/les informations de progression. Vous devez juste rediriger stderr vers un fichier ou stdin d'un processus capable de le gérer.

Avec un shell unix, cela ressemble à quelque chose comme:

ffmpeg {ffmpeg arguments} 2> logFile

ou 

ffmpeg {ffmpeg arguments} 2| processFFmpegLog

Quoi qu'il en soit, vous devez exécuter ffmpeg en tant que thread ou processus distinct.

19
mouviciel

C’est très simple si vous utilisez la commande pipeview. Pour ce faire, transformer

ffmpeg -i input.avi {arguments}

à

pv input.avi | ffmpeg -i pipe:0 -v warning {arguments}

Pas besoin d'entrer dans le codage!

13
istefani

Vous pouvez le faire avec les arguments ffmpegs-progress et nc

WATCHER_PORT=9998

DURATION= $(ffprobe -select_streams v:0 -show_entries "stream=duration" \
    -of compact $INPUT_FILE | sed 's!.*=\(.*\)!\1!g')

nc -l $WATCHER_PORT | while read; do
    sed -n 's/out_time=\(.*\)/\1 of $DURATION/p')
done &

ffmpeg -y -i $INPUT_FILE -progress localhost:$WATCHER_PORT $OUTPUT_ARGS
8
iluvcapra

javascript devrait dire à php de commencer à convertir [1] puis à faire [2] ...


[1] php: lance la conversion et écrit le statut dans le fichier (voir ci-dessus):

exec("ffmpeg -i path/to/input.mov path/to/output.flv 1>path/to/output.txt 2>&1");

Pour la deuxième partie, nous avons besoin de juste de javascript pour lire le fichier . L'exemple suivant utilise dojo.request pour AJAX, mais vous pouvez aussi utiliser jQuery ou Vanilla ou autre:

[2] js: extraire la progression du fichier:

var _progress = function(i){
    i++;
    // THIS MUST BE THE PATH OF THE .txt FILE SPECIFIED IN [1] : 
    var logfile = 'path/to/output.txt';

/* (example requires dojo) */

request.post(logfile).then( function(content){
// AJAX success
    var duration = 0, time = 0, progress = 0;
    var resArr = [];

    // get duration of source
    var matches = (content) ? content.match(/Duration: (.*?), start:/) : [];
    if( matches.length>0 ){
        var rawDuration = matches[1];
        // convert rawDuration from 00:00:00.00 to seconds.
        var ar = rawDuration.split(":").reverse();
        duration = parseFloat(ar[0]);
        if (ar[1]) duration += parseInt(ar[1]) * 60;
        if (ar[2]) duration += parseInt(ar[2]) * 60 * 60;

        // get the time 
        matches = content.match(/time=(.*?) bitrate/g);
        console.log( matches );

        if( matches.length>0 ){
            var rawTime = matches.pop();
            // needed if there is more than one match
            if (lang.isArray(rawTime)){ 
                rawTime = rawTime.pop().replace('time=','').replace(' bitrate',''); 
            } else {
                rawTime = rawTime.replace('time=','').replace(' bitrate','');
            }

            // convert rawTime from 00:00:00.00 to seconds.
            ar = rawTime.split(":").reverse();
            time = parseFloat(ar[0]);
            if (ar[1]) time += parseInt(ar[1]) * 60;
            if (ar[2]) time += parseInt(ar[2]) * 60 * 60;

            //calculate the progress
            progress = Math.round((time/duration) * 100);
        }

        resArr['status'] = 200;
        resArr['duration'] = duration;
        resArr['current']  = time;
        resArr['progress'] = progress;

        console.log(resArr);

        /* UPDATE YOUR PROGRESSBAR HERE with above values ... */

        if(progress==0 && i>20){
            // TODO err - giving up after 8 sec. no progress - handle progress errors here
            console.log('{"status":-400, "error":"there is no progress while we tried to encode the video" }'); 
            return;
        } else if(progress<100){ 
            setTimeout(function(){ _progress(i); }, 400);
        }
    } else if( content.indexOf('Permission denied') > -1) {
        // TODO - err - ffmpeg is not executable ...
        console.log('{"status":-400, "error":"ffmpeg : Permission denied, either for ffmpeg or upload location ..." }');    
    } 
},
function(err){
// AJAX error
    if(i<20){
        // retry
        setTimeout(function(){ _progress(0); }, 400);
    } else {
        console.log('{"status":-400, "error":"there is no progress while we tried to encode the video" }');
        console.log( err ); 
    }
    return; 
});

}
setTimeout(function(){ _progress(0); }, 800);
2
sebilasse

Eu des problèmes avec la deuxième partie php. Donc j'utilise ceci à la place:

    $log = @file_get_contents($txt);
    preg_match("/Duration:([^,]+)/", $log, $matches);
    list($hours,$minutes,$seconds,$mili) = split(":",$matches[1]);
    $seconds = (($hours * 3600) + ($minutes * 60) + $seconds);
    $seconds = round($seconds);

    $page = join("",file("$txt"));
    $kw = explode("time=", $page);
    $last = array_pop($kw);
    $values = explode(' ', $last);
    $curTime = round($values[0]);
    $percent_extracted = round((($curTime * 100)/($seconds)));

Sorties parfaitement.

Voudrais voir quelque chose pour plusieurs téléchargements pour une autre barre de progression. Ce passage pour le fichier en cours pour un pourcentage. Puis une barre de progression globale. Presque là.

Aussi, si les gens ont du mal à obtenir:

exec("ffmpeg -i path/to/input.mov path/to/output.flv 1> path/to/output.txt 2>&1");

Travailler.

Essayer:

exec("ffmpeg -i path/to/input.mov path/to/output.flv 1>path/to/output.txt 2>&1");

" 1> chemin " à " 1> chemin " OR " 2> chemin " à " 2> chemin "

Il m'a fallu un peu de temps pour le comprendre. FFMPEG a continué à échouer. Travaillé quand j'ai changé à aucun espace.

1
Neotropic

L'appel de la fonction système de php bloque ce fil, vous aurez donc besoin de générer 1 requête HTTP pour effectuer la conversion et une autre interrogation pour la lecture du fichier txt en cours de génération.

Ou, mieux encore, les clients soumettent la vidéo à la conversion, puis un autre processus est chargé d’exécuter la conversion. Ainsi, la connexion du client ne sera pas interrompue pendant l'attente de la fin de l'appel système. Le vote se fait comme ci-dessus. 

1
Allain Lalonde

Malheureusement, ffmpeg ne peut toujours pas afficher de barre de progression. De plus, bon nombre des solutions de transition précitées à base de bash ou de python sont devenues obsolètes et non fonctionnelles.

Ainsi, je recommande de donner au tout nouveau ffmpeg-progressbar-cli a essayer:

 ffmpeg-progressbar-cli screencast

C'est un wrapper pour l'exécutable ffmpeg, montrant une barre de progression centrée colorée et le temps restant.

En outre, il est open-source, basé sur Node.js et activement développé, prenant en charge la plupart des bizarreries mentionnées (divulgation complète: je suis son principal développeur actuel).

0
sidneys

Si vous avez juste besoin de masquer toutes les informations et d'afficher la progression par défaut, comme ffmpeg, à la dernière ligne, vous pouvez utiliser l'option -stats:

ffmpeg -v warning -hide_banner -stats ${your_params}
0
Geograph