Je reprends ici avec son accord un MP que yo_fr m'a adressé car finalement ses questions sont légitimes et peuvent intéresser les autres lecteurs.
yo_fr a écrit :J'essaye de comprendre ton script et j'ai toujours des petits soucis. Si tu as 2 minutes pour m'expliquer le fonctionnement de ce bout de script car ça fait pas mal de temps que je cherche et j'ai un peu l'impression d'être un débile...
D'abord je ne suis pas familier avec les notions de flux et de pipe, excuse moi d'avance de cela...et cela expliquera surement mes questions (idiotes).
Si tu peux me confirmer (où contredire!) la compréhension que j'en ai, se serais donc sympa.
J'ai mis en commentaire ce que je pense être (j'ai mis des N° pour pouvoir parler des lignes) :
C'est vrai que la syntaxe du perl est absconse, et si la dessus on greffe des concepts unix tels que les pipes, il y a de quoi se faire des noeuds au cerveau. Je vais donc essayer d'expliquer
yo_fr a écrit :Code : Tout sélectionner
open(OUT,"| ./ffmpeg -i $file -v 0 -r $fps -s ${w}x${h} -an tmp/img%05d.bmp");
# 1-pipe de commande, qui va lancer dans un thread différent ffmpeg qui génére les images.
# cette génération est continue, et peut durer jusqu'à la fin d'exécution de la boucle while.
En fait c'est la partie la plus importante du script, et oui
Tout d'abord je corrige une typo tout aussi importante: après l'argument "-i", on ne trouve pas "$file" mais "-" (un simple tiret). Cela indique à ffmpeg.exe non pas de lire un fichier sur disk, mais de lire le fichier depuis son entrée standard (stdin en C, souvent symbolisé dans les commandes unix par le pseudofichier "-").
Ce qu'il se passe avec le pipe (tuyau) ici, c'est que le fichier de sortie OUT de perl est relié à l'entrée standard de ffmpeg. Tout ce qu'on envoie sur OUT se retrouve à l'autre bout du tuyau dans l'entrée standard de ffmpeg. Or avec le "-i -", on instruit ffmpeg de lire la video non pas depuis un fichier disk, mais depuis son entrée standard. C'est à dire que ffmpeg va lire ce qu'on lui aura envoyé de la video via le fichier OUT.
Toute l'astuce réside là pour controller quand on aura atteint la fin du décodage. On va lire le fichier disk bout par bout dans le script perl, récupérer au fur et à mesure les images générées, et quand on aura lu tout le fichier, on sait qu'on aura récupéré l'intégralité des images disponibles.
yo_fr a écrit :Code : Tout sélectionner
open(IN, "<$file");
# 2-là ça commence mal : je ne vois pas du tout ce que "<" va faire avec le fichier film source....
Ce "<" en début de nom de fichier, c'est pour indiquer à perl d'ouvrir le fichier en lecture (on aurait utilisé ">" ou rien du tout pour specifier ouvrir en sortie).
yo_fr a écrit :Code : Tout sélectionner
&init_magick;
# 3- lancement de la procédure d'init plus bas (création du fichier de seuils et init de MAGIC)
$gif = Image::Magick->new(size=>"${w}x${h}");
# 4-Creation du conteneur de l'image GIF
oui et oui
yo_fr a écrit :Code : Tout sélectionner
$cpt = 1; my @c = (0)x8;
binmode(IN);
# 5- bon, ça rejoint le 2 : aucune idée...
Déclaration que le fichier source est à utiliser en mode binaire et non texte,
Oui c'est exactement ca, on veut lire le fichier disk en binaire pour éviter que les "\n" (chr$(10)) soient transformés en cr+lf (chr$(13)+chr$(10)).
yo_fr a écrit :Code : Tout sélectionner
binmode(OUT);
# 6- pas mieux, voir même pire puisse qu'il s'agit, pour moi, de l'exécution de ffmpeg... !
Oui voilà, on veut écrire dans OUT en considérant que c'est du binaire pur pour ne pas avoir de conversion de fin de ligne. A noter OUT n'est pas l'execution de ffmpeg, mais le bout du tuyau auquel ffmpeg est relié et à partir duquel il va lire les données vidéo.
yo_fr a écrit :Code : Tout sélectionner
$expected_size = $h*(($w*3 + 3)&~3) + 54 ;
# 7-Calcul de la taille théorique du BMP
while($ok) {
$name = sprintf("tmp/img%05d.bmp", $cpt);
#8-Nom du BMP à traiter dans cette itération de la boucle
# taille théorique d'une image, taille du fichier pointé
if($expected_size != -s $name) {
# image pas complete: on continue de nourrir ffmpeg
oui à tout
yo_fr a écrit :Code : Tout sélectionner
my $buf;
$read = read(IN,$buf,256);
# 9-Lecture du flux IN ??? Nb octets lus dans read
Oui on lit jusqu'à 256 octets d'un coup (c'était 4096 à l'origine) du fichier vidéo sur disk. $read vaut 0 en fin de fichier, sinon il nous dit combien sur les 256 (ou les 4096 dans le script d'origine) on a effectivement lu.
yo_fr a écrit :Code : Tout sélectionner
$ok = 0 unless $read; # était: last unless $read;
# 10 - ok = 0 si nb octet lu = 0 -> fini...
# mais on a lu le flux IN c'est à dire le film source...
Oui voilà: si on a lu 0, on est en fin de fichier IN (fichier source video, sur disk).
yo_fr a écrit :Code : Tout sélectionner
syswrite OUT, $buf, $read ;
# 11-Sortie dans OUT (le flux ffmpeg ???) du fichier lu (<$flie, soit le nom du film source) du buffer lu du flux IN.
# C'est ce que je lis mais ne comprend pas !!
On vient de lire dans $buf une certaine portion du fichier disk ($read octets) . On envoit alors cette partie lue (dans $buf) dans le bout du tuyau OUT, pour que ffmpeg le récupère de l'autre coté. Ainsi en parallèle, ffmpeg pourra manger à son tour ces $read octets supplementaires, décoder une nouvelle image et générer un nouveau fichier BMP dans le dossier tmp/.
yo_fr a écrit :
Pour moi ce que je ne comprend pas c'est que ffmpeg lui travaille tout seul dans son thread et nourri le répertoire d'images BMP.
Le fait de lire IN (9) et d'écrire OUT (10) n'as pas de sens... seul le fait de savoir que l'on est sur la dernière image devrait suffire :
Ne rien faire si l'image n'est pas entière et si on est pas à la fin de la génération de ffmpeg serait le but,
Maintenant c'est peut être la solution que tu utilises, mais je ne vois pas le mécanisme...
J'espère que mon explication ci-dessus a pu t'éclairer.
Une autre façon de faire aurait été de travailler sans ce fameux pipe. On aurait initialement lancé ffmpeg sur le fichier disk, et lui indiquer de générer TOUTES les images dans le dossier TMP. Ensuite on aurait lu une à une les images jusqu'à la dernière et on les aurait traités pour générer le gif sans avoir à nourir FFMPEG en cours de route via le PIPE car il aurait fait tout le boulot en amont.
Je n'ai pas choisi cette approche pour deux raisons
- Elle est beaucoup plus lente: il faut attendre que ffmpeg ait traité tout le fichier (1h ou 2h de video parfois) avant de faire la conversion des BMP.
- Elle occupe une place disk phénoménale. 1h de film à 10fps, ca fait 36000 images dans un seul dossier, et windows n'aime pas ca du tout. D'une part c'est très gros en volume (200Mo/heure environ), et surtout il met 3 plombes à effacer l'ensemble de ces fichiers.
- En plus j'ai jugé pas très malin de générer 36000 images pour n'en retenir que les 3600 premières correspondantes aux 5 minutes maxi du GIF (un GIF plus long mets 3 plombes aussi à être créé/optimisé).
(Oui ca fait 3 raisons. Je sais pas compter )
La solution du pipe permet au script perl et à ffmpeg de marcher de concert. Le fichier disk est lu petit bout par petit bout avec perl, puis chaque petit bout est envoyés à ffmpeg chacun à son tour. De temps en temps ffmpeg nous produit une nouvelle image que le script traite et efface avant de relire un nouveau petit bout du fichier video pour l'envoyer à ffmpeg, etc. En gros on a sur disk qu'une seule image à la fois dans le dossier TMP. Comme je travaille sur une machine pas puissante avec un disk presque plein, cette solution me convienait mieux. Mais c'est sur que si j'avais eu 1To de disk libre, j'aurais pu faire autrement (encore que windows n'aime pas trop non plus les repertoires trop pleins de fichiers.)