{"id":183,"date":"2017-03-03T23:29:18","date_gmt":"2017-03-03T23:29:18","guid":{"rendered":"http:\/\/renaudguezennec.eu\/?p=183"},"modified":"2025-08-17T20:11:59","modified_gmt":"2025-08-17T20:11:59","slug":"montage-video-en-python","status":"publish","type":"post","link":"http:\/\/renaudguezennec.eu\/index.php\/2017\/03\/03\/montage-video-en-python\/","title":{"rendered":"Montage Vid\u00e9o en python"},"content":{"rendered":"<h2>Mon besoin<\/h2>\n<p>Dans le cadre de la promotion de Rolisteam, je diffuse en ligne des enregistrements de parties.<br \/>\nCes enregistrements n\u00e9cessitent diff\u00e9rents traitement afin d\u2019\u00eatre rendu plus audibles et int\u00e9ressants.<br \/>\nLes taches \u00e0 r\u00e9aliser sont les suivantes:<\/p>\n<p>1 &#8211; Associer la piste audio et la piste vid\u00e9o<br \/>\n2 &#8211; Ajouter le g\u00e9n\u00e9rique de d\u00e9but et de fin.<br \/>\n3 &#8211;\u00a0Couper la vid\u00e9o en fonction des silences<br \/>\n4 &#8211; Am\u00e9liorer le son (le compresser\/normaliser)<\/p>\n<p>Pour r\u00e9aliser la tache 1, une simple commande ffmpeg suffit. J\u2019ai facilement cr\u00e9\u00e9 un script bash pour l\u2019automatiser.<br \/>\nLa t\u00e2che 2 peut \u00e9galement \u00eatre r\u00e9alis\u00e9e par ffmpeg avec l\u2019option concat mais cela ne s\u2019est pas pass\u00e9 comme pr\u00e9vu.<br \/>\nLa solution de replie fut kdenlive, un logiciel de montage vid\u00e9o sur linux (Un des rares qui ne plante pas tout le temps quand on lui donne \u00e0 monter une vid\u00e9o de trois heures).<\/p>\n<p>L\u2019\u00e9tape 3 fut bien plus complexe \u00e0 r\u00e9aliser. Il n\u2019y a aucun outil cl\u00e9 en main pour faire cela dans ffmpeg ou kdenlive. C\u2019est tr\u00e8s probablement faisable avec ces outils mais je n\u2019ai pas trouv\u00e9 comment. Je ne me voyais pas couper les moments de silence \u00e0 la main.<br \/>\nJe commen\u00e7ais \u00e0 d\u00e9sesp\u00e9rer quand Ryzz sur Linuxfr.org a \u00e9voqu\u00e9 le package MoviePy. Un module de manipulation vid\u00e9o pour python avec un exemple d\u2019emploi proche de mon objectif.<\/p>\n<p>Cr\u00e9er un r\u00e9sum\u00e9 automatique d\u2019un match de foot: http:\/\/zulko.github.io\/blog\/2014\/07\/04\/automatic-soccer-highlights-compilations-with-python\/<br \/>\nLa doc de l\u2019API: http:\/\/zulko.github.io\/moviepy\/index.html<\/p>\n<h2>Le code python: MoviePy<\/h2>\n<p>Voila un outil pour manipuler le son et la vid\u00e9o en codant avec python. Outil parfait pour r\u00e9aliser les actions 2 et 3 de fa\u00e7on automatique.<br \/>\nMes fichiers d\u2019entr\u00e9es sont biens rang\u00e9s dans des dossiers, cela rend l\u2019automatisation plus facile.<\/p>\n<p>Pour la dernier \u00e9tape, j\u2019ai trouv\u00e9 un petit script python &#8220;ffmpeg-normalize&#8221; qui fait cela. Ce n\u2019est clairement pas aussi puissant que les filtres d\u2019audacity mais cela suffit.<\/p>\n<p>Premi\u00e8re \u00e9tape, Parcourir les dossiers, trouver fichier associant la vid\u00e9o et le son pour y ajouter les g\u00e9n\u00e9riques.<\/p>\n<p>[pastacode lang=&#8221;python&#8221; manual=&#8221;for%20subfolder%20in%20sorted(os.listdir(rootFolder))%3A%0Aif(%22_done%22%20not%20in%20subfolder)%3A%0Afor%20subfile%20in%20os.listdir(os.path.join(rootFolder%2Csubfolder))%3A%0Aif((%22mp4%22%20in%20subfile)and(%22Partie%22%20in%20subfile))%3A%0Alink%20%3D%20os.path.join(rootFolder%2Csubfolder)%0Avideo%20%3D%20os.path.join(link%2Csubfile)%0Adest%20%3D%20subfile.replace(%22.mp4%22%2C%22_ending.mp4%22%2C1)%0Adestination%20%3D%20os.path.join(link%2Cdest)%0Avideoclip%20%3D%20VideoFileClip(video)%0A%23concatenation%20of%20opening%2C%20video%20and%20ending%0Afinalclip%20%3D%20concatenate_videoclips(%5Bopening%2Cvideoclip%2Cending%5D)%0Afinalclip.write_videofile(destination%2Cfps%3D25)&#8221; message=&#8221;Parcours des dossiers&#8221; highlight=&#8221;&#8221; provider=&#8221;manual&#8221;\/]<\/p>\n<p>Je ne fais que reconstruire le chemin pour arriver jusqu\u2019\u00e0 l\u2019\u00e9pisode.<\/p>\n<p>Ensuite, l\u2019autre partie int\u00e9ressante (grandement inspir\u00e9 de l\u2019exemple sur le foot), est de d\u00e9couper le film en \u00e9chantillon d\u2019une seconde. Le volume de chaque \u00e9chantillon est calcul\u00e9.<br \/>\nIl faut stock\u00e9 le volume de chaque \u00e9chantillon.<\/p>\n<p>[pastacode lang=&#8221;python&#8221; manual=&#8221;%23split%20resulting%20video%20in%20audio%20subclip%0Aclip%20%3D%20VideoFileClip(destination)%0Acut%20%3D%20lambda%20i%3A%20clip.audio.subclip(i%2Ci%2B1).to_soundarray(fps%3D22000)%0Avolume%20%3D%20lambda%20array%3A%20np.sqrt(((1.0*array)**2).mean())%0Avolumes%20%3D%20%5Bvolume(cut(i))%20for%20i%20in%20range(0%2Cint(clip.audio.duration-2))%5D%0Afinal_times%3D%20%5B%5D&#8221; message=&#8221;Les volumes&#8221; highlight=&#8221;&#8221; provider=&#8221;manual&#8221;\/]<\/p>\n<p>Le derni\u00e8re \u00e9tape consiste \u00e0 regrouper les p\u00e9riodes de temps qui doivent \u00eatre sauvegard\u00e9. Dans un tableau \u00e0 deux \u00e9l\u00e9ments, je conserve le d\u00e9but et la fin de chaque p\u00e9riode \u00e0 conserver.<\/p>\n<p>[pastacode lang=&#8221;python&#8221; manual=&#8221;i%20%3D%201%0Aduo%20%3D%20%5B%5D%0Astart%20%3D%20-1%0Aend%20%3D%200%0AsumVideo%20%3D%200%0A%23identify%20all%20part%20with%20sounds.%20What%20we%20keep.%0Afor%20vol%20in%20volumes%3A%0Aif((%20vol%20%3D%3D%200.0%20)and%20(start!%3D-1))%3A%0Aend%20%3D%20i-1%0Aduo%20%3D%20%5Bstart%2Cend%5D%0Afinal_times.append(duo)%0AsumVideo%20%2B%3D%20(end-start)%0Astart%20%3D%20-1%0Aif((start%20%3D%3D%20-1)and%20(vol%3E0.0))%3A%0Astart%3Di%0Ai%3Di%2B1&#8243; message=&#8221;S\u00e9paration des duos&#8221; highlight=&#8221;&#8221; provider=&#8221;manual&#8221;\/]<\/p>\n<p>Quand l\u2019ensemble des duos sont identifi\u00e9s, il suffit de les concatener dans le fichier de sortie.<\/p>\n<p>[pastacode lang=&#8221;python&#8221; manual=&#8221;finalpath%20%3D%20destination.replace(%22_ending%22%2C%22_cutted%22)%0Aprint%20finalpath%0Aprint%20final_times%0A%23concatenate%20all%20kept%20parts.%0Afinal%20%3D%20concatenate_videoclips(%5Bclip.subclip(t%5B0%5D%2Ct%5B1%5D)%0Afor%20t%20in%20final_times%5D)%0A%23write%20the%20file%0Afinal.write_videofile(finalpath%2Cfps%3D25)&#8221; message=&#8221;Sauvegarde des \u00e9chantillons&#8221; highlight=&#8221;&#8221; provider=&#8221;manual&#8221;\/]<\/p>\n<p>Code complet: <a href=\"http:\/\/www.renaudguezennec.eu\/file\/cutVideos.py\">http:\/\/www.renaudguezennec.eu\/file\/cutVideos.py<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Mon besoin Dans le cadre de la promotion de Rolisteam, je diffuse en ligne des enregistrements de parties. Ces enregistrements n\u00e9cessitent diff\u00e9rents traitement afin d\u2019\u00eatre rendu plus audibles et int\u00e9ressants. Les taches \u00e0 r\u00e9aliser sont les suivantes: 1 &#8211; Associer la piste audio et la piste vid\u00e9o 2 &#8211; Ajouter le g\u00e9n\u00e9rique de d\u00e9but et [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":184,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_import_markdown_pro_load_document_selector":0,"_import_markdown_pro_submit_text_textarea":"","footnotes":""},"categories":[31,80],"tags":[48,47,50,49],"class_list":["post-183","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-tips","category-fr","tag-montage","tag-python","tag-script","tag-video"],"_links":{"self":[{"href":"http:\/\/renaudguezennec.eu\/index.php\/wp-json\/wp\/v2\/posts\/183","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/renaudguezennec.eu\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/renaudguezennec.eu\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/renaudguezennec.eu\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/renaudguezennec.eu\/index.php\/wp-json\/wp\/v2\/comments?post=183"}],"version-history":[{"count":7,"href":"http:\/\/renaudguezennec.eu\/index.php\/wp-json\/wp\/v2\/posts\/183\/revisions"}],"predecessor-version":[{"id":193,"href":"http:\/\/renaudguezennec.eu\/index.php\/wp-json\/wp\/v2\/posts\/183\/revisions\/193"}],"wp:featuredmedia":[{"embeddable":true,"href":"http:\/\/renaudguezennec.eu\/index.php\/wp-json\/wp\/v2\/media\/184"}],"wp:attachment":[{"href":"http:\/\/renaudguezennec.eu\/index.php\/wp-json\/wp\/v2\/media?parent=183"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/renaudguezennec.eu\/index.php\/wp-json\/wp\/v2\/categories?post=183"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/renaudguezennec.eu\/index.php\/wp-json\/wp\/v2\/tags?post=183"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}