Python3: utilisation du module ffprobe3 (et surtout trouver de l'aide à son propos)?

Bonjour à tous,

Je souhaite récupérer la résolution de plusieurs vidéos d’un même répertoire afin de classer toutes les 720p ensembles, les 1080p ensembles, etc…

Pour cela, j’utilise (ou plutôt j’essaye d’utiliser) le module ffprobe3.

J’ai suivi l’exemple de la homepage du projet sur github.
Certes, j’obtiens bien le nb de frames de chacune de mes vidéos… super.

Maintenant, plutôt que le nb de frames, je voudrais extraire la hauteur (clé height du ffprobe “classique” sous bash).
Comme la page github est peu explicite, je recherche dans l’aide du module :

Python 3.7.3 (default, Apr  3 2019, 05:39:12) 
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import ffprobe3
>>> help(ffprobe3)

et j’obtiens :

Help on package ffprobe3:

NAME
    ffprobe3

PACKAGE CONTENTS
    exceptions
    ffprobe

FILE
    /home/r-one/.local/share/virtualenvs/tmp-XVr6zr33/lib/python3.7/site-packages/ffprobe3/__init__.py

(END)

Pas beaucoup plus aidé sur ce coup là !

Je tente à nouveau ma chance avec la méthode FFProbe du module :

Python 3.7.3 (default, Apr  3 2019, 05:39:12) 
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import ffprobe3
>>> help(ffprobe3)

>>> help(ffprobe3.FFProbe)

et j’obtiens cette fois :

Help on class FFProbe in module ffprobe3.ffprobe:

class FFProbe(builtins.object)
 |  FFProbe(video_file)
 |  
 |  FFProbe wraps the ffprobe command and pulls the data into an object form::
 |      metadata=FFProbe('multimedia-file.mov')
 |  
 |  Methods defined here:
 |  
 |  __init__(self, video_file)
 |      Initialize self.  See help(type(self)) for accurate signature.
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |  
 |  __dict__
 |      dictionary for instance variables (if defined)
 |  
 |  __weakref__
 |      list of weak references to the object (if defined)
(END)

Pas vraiment mieux !

Aussi, étant débutant en Python, j’imagine que je me débrouile mal dans mes recherches.
Quelqu’un pourrait-il m’aider en me donnant la méthode (sans mauvais jeu de mot) pour trouver l’info dont j’ai besoin, à savoir comment utiliser ffprobe3 pour extraire la résolution d’une video, svp ?

Merci par avance pour votre aide,
Cordialement
R-one

Bonjour @r-one,

Je ne connais pas Python.
J’ai cherché « ffprobe3 get resolution »

Je trouve, en particulier (un fork) :
https://github.com/jmetz/ffprobe3/blob/master/ffprobe3/ffprobe.py#L114

frame_size
Returns the pixel frame size as an integer tuple (width,height) if the stream is a video stream.

Depuis l’exemple d’usage situé à : https://github.com/DheerendraRathor/ffprobe3
Je verrais bien pour commencer une adaptation un peu comme :

#!/usr/bin/env python

from ffprobe3 import FFProbe

metadata=FFProbe('test-media-file.mov')

for stream in metadata.streams:
    if stream.is_video():
        print('Stream have {} resolution.'.format(stream.frame_size()))

C’est vraiment une recherche d’amateur :wink:
Qui pourrait retourner « an integer tuple (width,height) »

rem@n73sm ~/Vidéos $ python3.6 ./resolution.py
Stream have (640, 360) resolution.
rem@n73sm ~/Vidéos $ 

Je suis tout bonnement incompétent pour n’extraire que la valeur height.

Le test is_video est en surcharge dans la fonction frame_size.

Je ne sais pas si frame_size est implémentée dans le code officiel…
En fait si : https://github.com/DheerendraRathor/ffprobe3/blob/master/ffprobe3/ffprobe.py#L147

Bon courage !

ps :

ffprobes3 semble documenté mais la dernière compilation (de la doc ?) remonte à un an et demi ; C’est soit disant une version améliorée. https://readthedocs.org/projects/ffprobes3/

Le code source de https://github.com/DheerendraRathor/ffprobe3/blob/master/ffprobe3/ffprobe.py ne fait qu’un peu plus de 200 lignes ; c’est pas si énorme en vrai. Il décrit toutes les fonctions utilisables.

je ne connais pas python mais la “vrai” commande ffprobe donne l’info directement
cf https://trac.ffmpeg.org/wiki/FFprobeTips

exemple

ffprobe -v error -select_streams v:0 -show_entries stream=height,width -of csv=s=x:p=0 essai.mp4
512x384

Une base de travail

(ffprobe -v error -select_streams v:0 -show_entries stream=height,width -of csv=s=x:p=0 essai.mp4) >size
cut -c 1,2,3,4 size

Ce petit exemple fonctionne chez moi

#!/bin/bash
#set -x 
date
exec 2>/dev/null
cd /home/$USER/Vidéos/ 
rm liste-des-mp4-avec-leur-taille
for file in "$PWD"/*.mp4
do
#echo $file
taille=$(ffprobe -v error -select_streams v:0 -show_entries stream=height,width -of csv=s=x:p=0 "$file")
echo $file $taille >> liste-des-mp4-avec-leur-taille
done
echo "fini le"&&date

On peut rediriger différemment :

for file in ....
do
   taille=$(ffprobe ....)
   echo $file $taille
done > liste_mp4_taille

ce qui simplifie la création du fichier de sortie en remplaçant les ouvertures et fermetures à chaque ligne écrite par une seule ouverture et une seule fermeture quand toutes les lignes d’un seul coup.

Cordialement,
Regards,
Mit freundlichen Grüßen,
مع تحياتي الخالصة


F. Petitjean
Ingénieur civil du Génie Maritime.

« Comme la tartine, l’ivrogne tombe toujours du côté qui est complètement beurré. »
– Professeur Choron

Merci à tous pour vos réponses !

@anon61356901 :
Effectivement, très bonne idée de regarder dans les fichiers du dépôt (surtout quand il n’y en a qu’un seul !), je n’y ai pas du tout pensé : que ça me serve de leçon !

@grandtoubab :
@littlejohn75 :
C’est vrai que je ne l’ai pas précisé dans ma demande, mais j’avais trouvé comment récupérer la résolution avec ffprobe en bash, sauf que je souhaite en plus faire des manip sur le nom de chaque fichier : virer certains patterns + ajouter " (720p)" ou " (1080p)" le cas échant avant l’extension.
Aussi, n’étant pas à l’aise avec ce genre de manip en bash, j’ai préféré me diriger vers Python qui gère ça super facilement avec .replace() et os.rename().

Par contre, je n’arrive pas à saisir l’option -of csv=s=x:p=0 à la fin de la commande ffprobe :
-of csv pour imposer le format csv à la sortie de la commande ffprobe,
mais malgré un parcours du man, je ne vois pas à quoi correspond =s=x:p=0 :slightly_frowning_face:

Bonjour à tous,

Je reviens sur ce post car je me suis rendu compte de quelque chose que je ne m’explique pas !

Je souhaite toujours utiliser le module ffprobe3 comme indiqué au tout début.
D’après cette page, le lien homepage du projet renvoit vers le github du mainteneur : jusque là, tout va bien.

Ensuite, j’utilise pipenv pour installer ce module :

r-one@wks01:/tmp/test$ pipenv install ffprobe3
Creating a virtualenv for this project…
Pipfile: /tmp/test/Pipfile
Using /usr/bin/python3 (3.7.3) to create virtualenv…
⠏ Creating virtual environment...Already using interpreter /usr/bin/python3
Using base prefix '/usr'
New python executable in /home/r-one/.local/share/virtualenvs/test-hxkKlP5o/bin/python3
Also creating executable in /home/r-one/.local/share/virtualenvs/test-hxkKlP5o/bin/python
Installing setuptools, pip, wheel...
done.
✔ Successfully created virtual environment! 
Virtualenv location: /home/r-one/.local/share/virtualenvs/test-hxkKlP5o
Creating a Pipfile for this project…
Installing ffprobe3…
✔ Installation Succeeded 
Pipfile.lock not found, creating…
Locking [dev-packages] dependencies…
Locking [packages] dependencies…
✔ Success! 
Updated Pipfile.lock (f479b4)!
Installing dependencies from Pipfile.lock (f479b4)…
  🐍   ▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉ 1/1 — 00:00:00
To activate this project's virtualenv, run pipenv shell.
Alternatively, run a command inside the virtualenv with pipenv run.
r-one@wks01:/tmp/test$

Et, sans même aller plus loin, si je visualise le script principal (ffprobe.py) du module qui vient d’être installé :

r-one@wks01:/tmp/test$ cd /home/r-one/.local/share/virtualenvs/test-hxkKlP5o
r-one@wks01:~/.local/share/virtualenvs/test-hxkKlP5o$ find . -name "ffprobe.py"
./lib/python3.7/site-packages/ffprobe3/ffprobe.py
r-one@wks01:~/.local/share/virtualenvs/test-hxkKlP5o$ cat -n ./lib/python3.7/site-packages/ffprobe3/ffprobe.py
     1	"""
     2	Python wrapper for ffprobe command line tool. ffprobe must exist in the path.
     3	"""
     4	import os
     5	import pipes
     6	import platform
     7	import re
     8	import subprocess
     9	
    10	from ffprobe3.exceptions import FFProbeError
    11	
    12	
    13	class FFProbe:
    14	    """
    15	    FFProbe wraps the ffprobe command and pulls the data into an object form::
    16	        metadata=FFProbe('multimedia-file.mov')
    17	    """
    18	
    19	    def __init__(self, video_file):
    20	        self.video_file = video_file
    21	        try:
    22	            with open(os.devnull, 'w') as tempf:
    23	                subprocess.check_call(["ffprobe", "-h"], stdout=tempf, stderr=tempf)
    24	        except:
    25	            raise IOError('ffprobe not found.')
    26	        if os.path.isfile(video_file):
    27	            if str(platform.system()) == 'Windows':
    28	                cmd = ["ffprobe", "-show_streams", self.video_file]
    29	            else:
    30	                cmd = ["ffprobe -show_streams " + pipes.quote(self.video_file)]
    31	
    32	            p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
    33	            self.format = None
    34	            self.created = None
    35	            self.duration = None
    36	            self.start = None
    37	            self.bitrate = None
    38	            self.streams = []
    39	            self.video = []
    40	            self.audio = []
    41	            data_lines = []
    42	            for a in iter(p.stdout.readline, b''):
    43	                a = a.decode('UTF-8')
    44	                if re.match(r'\[STREAM\]', a):
    45	                    data_lines = []
    46	                elif re.match(r'\[/STREAM\]', a):
    47	                    self.streams.append(FFStream(data_lines))
    48	                    data_lines = []
    49	                else:
    50	                    data_lines.append(a)
    51	            for a in iter(p.stderr.readline, b''):
    52	                a = a.decode('UTF-8')
    53	                if re.match(r'\[STREAM\]', a):
    54	                    data_lines = []
    55	                elif re.match(r'\[/STREAM\]', a):
    56	                    self.streams.append(FFStream(data_lines))
    57	                    data_lines = []
    58	                else:
    59	                    data_lines.append(a)
    60	            p.stdout.close()
    61	            p.stderr.close()
    62	            for a in self.streams:
    63	                if a.is_audio():
    64	                    self.audio.append(a)
    65	                if a.is_video():
    66	                    self.video.append(a)
    67	        else:
    68	            raise IOError('No such media file ' + video_file)
    69	
    70	
    71	class FFStream:
    72	    """
    73	    An object representation of an individual stream in a multimedia file.
    74	    """
    75	
    76	    def __init__(self, data_lines):
    77	        for a in data_lines:
    78	            (key, val) = a.strip().split('=')
    79	            self.__dict__[key] = val
    80	
    81	    def is_audio(self):
    82	        """
    83	        Is this stream labelled as an audio stream?
    84	        """
    85	        val = False
    86	        if self.__dict__['codec_type']:
    87	            if str(self.__dict__['codec_type']) == 'audio':
    88	                val = True
    89	        return val
    90	
    91	    def is_video(self):
    92	        """
    93	        Is the stream labelled as a video stream.
    94	        """
    95	        val = False
    96	        if self.__dict__['codec_type']:
    97	            if self.__dict__['codec_type'] == 'video':
    98	                val = True
    99	        return val
   100	
   101	    def is_subtitle(self):
   102	        """
   103	        Is the stream labelled as a subtitle stream.
   104	        """
   105	        val = False
   106	        if self.__dict__['codec_type']:
   107	            if self.__dict__['codec_type'] == 'subtitle':
   108	                val = True
   109	        return val
   110	
   111	    def frame_size(self):
   112	        """
   113	        Returns the pixel frame size as an integer tuple (width,height) if the stream is a video stream.
   114	        Returns None if it is not a video stream.
   115	        """
   116	        size = None
   117	        if self.is_video():
   118	            width = self.__dict__['width']
   119	            height = self.__dict__['height']
   120	            if width and height:
   121	                try:
   122	                    size = (int(width), int(height))
   123	                except ValueError:
   124	                    raise FFProbeError("None integer size %s:%s" % (width, height))
   125	
   126	        return size
   127	
   128	    def pixel_format(self):
   129	        """
   130	        Returns a string representing the pixel format of the video stream. e.g. yuv420p.
   131	        Returns none is it is not a video stream.
   132	        """
   133	        f = None
   134	        if self.is_video():
   135	            if self.__dict__['pix_fmt']:
   136	                f = self.__dict__['pix_fmt']
   137	        return f
   138	
   139	    def frames(self):
   140	        """
   141	        Returns the length of a video stream in frames. Returns 0 if not a video stream.
   142	        """
   143	        frame_count = 0
   144	        if self.is_video() or self.is_audio():
   145	            if self.__dict__['nb_frames']:
   146	                try:
   147	                    frame_count = int(self.__dict__['nb_frames'])
   148	                except ValueError:
   149	                    raise FFProbeError('None integer frame count')
   150	        return frame_count
   151	
   152	    def duration_seconds(self):
   153	        """
   154	        Returns the runtime duration of the video stream as a floating point number of seconds.
   155	        Returns 0.0 if not a video stream.
   156	        """
   157	        duration = 0.0
   158	        if self.is_video() or self.is_audio():
   159	            if self.__dict__['duration']:
   160	                try:
   161	                    duration = float(self.__dict__['duration'])
   162	                except ValueError:
   163	                    raise FFProbeError('None numeric duration')
   164	        return duration
   165	
   166	    def language(self):
   167	        """
   168	        Returns language tag of stream. e.g. eng
   169	        """
   170	        lang = None
   171	        if self.__dict__['TAG:language']:
   172	            lang = self.__dict__['TAG:language']
   173	        return lang
   174	
   175	    def codec(self):
   176	        """
   177	        Returns a string representation of the stream codec.
   178	        """
   179	        codec_name = None
   180	        if self.__dict__['codec_name']:
   181	            codec_name = self.__dict__['codec_name']
   182	        return codec_name
   183	
   184	    def codec_description(self):
   185	        """
   186	        Returns a long representation of the stream codec.
   187	        """
   188	        codec_d = None
   189	        if self.__dict__['codec_long_name']:
   190	            codec_d = self.__dict__['codec_long_name']
   191	        return codec_d
   192	
   193	    def codec_tag(self):
   194	        """
   195	        Returns a short representative tag of the stream codec.
   196	        """
   197	        codec_t = None
   198	        if self.__dict__['codec_tag_string']:
   199	            codec_t = self.__dict__['codec_tag_string']
   200	        return codec_t
   201	
   202	    def bit_rate(self):
   203	        """
   204	        Returns bit_rate as an integer in bps
   205	        """
   206	        b = 0
   207	        if self.__dict__['bit_rate']:
   208	            try:
   209	                b = int(self.__dict__['bit_rate'])
   210	            except ValueError:
   211	                raise FFProbeError('None integer bit_rate')
   212	        return b
r-one@wks01:~/.local/share/virtualenvs/test-hxkKlP5o$

Je me rends compte que ce fichier ne correspond pas du tout au fichier posé sur le github !
On le voit très nettement si on compare la boucle for stream in self.streams: (ligne 62 ci-dessus) à celle sur le github (ligne 72).

Comment peut-on expliquer cela ?
Est-ce que je fait une erreur quelque part ?
Et du coup, comment installer la version contenue sur le github ??

Merci d’avance pour vos éclaircissements.
R1

En fait, la version installée par pipenv est une vieille version du module ffprobe3 (last commit = 3 juin 2017) => https://github.com/jmetz/ffprobe3/blob/master/ffprobe3/ffprobe.py (le fork indiqué par doo d’ailleurs)

:crazy_face:

Pour info :

(test) r-one@wks01:/tmp/test$ pip list
Package    Version
---------- -------
ffprobe3   0.1.2  
pip        20.0.2 
setuptools 46.1.3 
wheel      0.34.2 
(test) r-one@wks01:/tmp/test$

(et je suis sur une Buster à jour)