Alors que je reprenais ma recherche d'emploi (oui, je suis toujours #OpenToWork, pingez-moi !), dans l'une des candidatures, on m'a demandé d'implémenter un prototype qui traite des données vidéo. Pendant que je travaillais sur le projet, j'ai reçu de manière inattendue beaucoup d'aide de la part des chatbots génératifs d'IA en raison de ma relative inexpérience dans le domaine.
Comme mentionné dans le titre, ffmpeg a été utilisé pour effectuer certains travaux de prétraitement. L'un des objectifs du projet était de pouvoir lire plusieurs fichiers vidéo les uns après les autres. Bien qu'il existe plusieurs façons d'y parvenir, j'ai décidé d'opter pour la solution la plus évidente, en les concaténant ensemble.
$ cat video1 video2 video3 | python further-work.py
Pour y parvenir, j'ai d'abord dû réencoder les fichiers dans des formats qui le permettraient. Après avoir « discuté » avec Google Gemini à ce sujet, le chatbot m'a recommandé d'utiliser MPEG-TS à cet effet.
MPEG Transport Stream (MPEG-TS) fonctionne en encapsulant des flux élémentaires en paquets. Ces flux comprennent des données audio, vidéo et PSIP, qui sont mises en paquets en petits segments. Chaque flux est découpé en sections de 188 octets et entrelacé. Ce processus garantit moins de latence et une plus grande résilience aux erreurs, ce qui le rend idéal pour les vidéoconférences où les images volumineuses peuvent introduire un retard audio.
Cité de https://castr.com/blog/mpeg-transport-stream-mpeg-ts/
Il existe d'autres formats de fichiers qui pourraient être utilisés à cet effet, mais ils n'ont aucun rapport avec la discussion. Une fois la vidéo réencodée dans ce format, les données vidéo seront envoyées dans une file d'attente, pour être consommées par d'autres modules, exécutés dans d'autres processus.
Après avoir défini à la fois l'entrée (une liste de fichiers vidéo à récupérer en ligne) et la sortie (le contenu du fichier vidéo réencodé), il était temps de comprendre comment procéder. Malheureusement, ffmpeg est un utilitaire tellement compliqué qui fait tellement de choses. Il y a/y a eu plusieurs tentatives pour fournir une interface pour aider les utilisateurs (je voulais vraiment essayer ça, mais il est mort maintenant, apparemment). Cependant, compte tenu de l’utilité actuelle de l’IA générative, il suffit de quelques invites pour obtenir la bonne commande.
ffmpeg -hwaccel cuda -i pipe:0 -c:v h264_nvenc -b:v 1.5M -c:a aac -b:a 128k -f mpegts -y pipe:1
Il a même donné une explication sur la signification de chacun de ces arguments, comme le montre la capture d'écran ci-dessous.
Tentative de Gemini d'expliquer la commande ffmpeg
En bref, la commande accepte le contenu du fichier vidéo via stdin et génère le contenu du fichier vidéo réencodé sous forme de sortie standard.
Il est maintenant temps de coder l'implémentation, car je voulais à la fois lire et écrire dans ffmpeg simultanément, ce sera donc une application asyncio. La bibliothèque client http que nous utilisons cette fois est httpx, qui dispose d'une méthode pour récupérer le téléchargement par lots plus petits :
$ cat video1 video2 video3 | python further-work.py
Nous nous soucierons du traitement réel plus tard, pour l'instant, nous obtiendrons simplement le code pour imprimer les morceaux à l'écran.
Ensuite, nous écrivons une fonction pour appeler ffmpeg, via asyncio.create_subprocess_exec
ffmpeg -hwaccel cuda -i pipe:0 -c:v h264_nvenc -b:v 1.5M -c:a aac -b:a 128k -f mpegts -y pipe:1
Idéalement, nous utiliserions ici process.communicate(file_content) comme conseillé dans la documentation, malheureusement si nous faisions cela, nous devrions d'abord télécharger l'intégralité du fichier, ce qui retarderait inévitablement la réponse, ce qui n'était pas idéal.
À la place, nous pourrions utiliser process.stdin.write(), mettons à jour la fonction write_input d'origine :
import httpx client = httpx.AsyncClient() async def write_input( client: httpx.AsyncClient, video_link: str, process: asyncio.subprocess.Process ) -> None: async with client.stream("GET", video_link) as response: async for chunk in response.aiter_raw(1024): print(chunk) # this is the downloaded video file, in chunks
Avec chaque morceau téléchargé,
Retour à la fonction video_send, nous continuons la fonction en lisant process.stdout. Être capable de lire et d'écrire est exactement la raison pour laquelle nous faisons cela via asyncio. Auparavant, en mode synchrone, nous ne pouvions effectuer les opérations que les unes après les autres dans un ordre fixe, mais nous pouvions désormais laisser le planificateur se soucier de l'ordre. Maintenant, la fonction a ajouté le code suivant pour lire le contenu du fichier réencodé et le publier dans la file d'attente :
async def video_send(client: httpx.AsyncClient, video_link: str) -> None: logger.info("DATA: Fetching video from link", link=video_link) process = await asyncio.create_subprocess_exec( "ffmpeg", "-hwaccel", "cuda", "-i", "pipe:0", "-c:v", "h264_nvenc", # "libx264", "-b:v", "1.5M", "-c:a", "aac", "-b:a", "128k", "-f", "mpegts", "-y", "pipe:1", stdin=subprocess.PIPE, stdout=subprocess.PIPE, ) asyncio.create_task(write_input(client, video_link, process))
En boucle, nous
Cela semble très simple maintenant, mais il m'a fallu toute la nuit pour faire cela correctement (et j'étais encore en train de réviser le code en écrivant ceci). La moitié du temps, je vérifiais la documentation pour m'assurer de ne rien manquer, le reste du temps, je demandais à Gemini de réviser mon code.
J'espère que cela vous sera utile, et c'est tout pour aujourd'hui. J'espère que nous reviendrons la semaine prochaine au contenu de l'Avent of Code précédemment promis.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!