Skip to content

Commit

Permalink
refactoring, add function video_compose, documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
sdpython committed Dec 30, 2017
1 parent 3d8174e commit eda16c6
Show file tree
Hide file tree
Showing 10 changed files with 121 additions and 16 deletions.
3 changes: 2 additions & 1 deletion .local.jenkins.win.yml
Expand Up @@ -10,7 +10,8 @@ install:
- pip freeze - pip freeze
- pip freeze > pip_freeze.txt - pip freeze > pip_freeze.txt
script: script:
- { CMD: "python -X faulthandler -X showrefcount -u setup.py unittests", NAME: "UT" } - { CMD: "python -X faulthandler -X showrefcount -u setup.py unittests -g ".*noenv.*", NAME: "UT" }
- { CMD: "python -X faulthandler -X showrefcount -u setup.py unittests -e ".*noenv.*", NAME: "UT_NOENV" }
- { CMD: "python -X faulthandler -X showrefcount -u setup.py unittests_LONG", NAME: "UT_LONG" } - { CMD: "python -X faulthandler -X showrefcount -u setup.py unittests_LONG", NAME: "UT_LONG" }
- { CMD: "python -X faulthandler -X showrefcount -u setup.py unittests_SKIP", NAME: "UT_SKIP" } - { CMD: "python -X faulthandler -X showrefcount -u setup.py unittests_SKIP", NAME: "UT_SKIP" }
after_script: after_script:
Expand Down
18 changes: 18 additions & 0 deletions _doc/sphinxdoc/source/api/video.rst
Expand Up @@ -4,6 +4,18 @@
Vidéos et Sons Vidéos et Sons
============== ==============


Cette série de fonctions sont des exemples d'utilisation du module
:epkg:`moviepy`. Celui-ci permet de faire du montage vidéo
en programmant avec :epkg:`Python`. C'est sans doute moins puissant
qu'un vrai logiciel de montage tel que :epkg:`ShotCut` ou :epkg:`OpenShot`.
La progrommation a néanmoins deux avantages. Elle permet de répéter
un traitement facilement pour construire une vidéo. Elle peut opérer
la même opération sur chaque image d'un image comme l'extraction
des personnages du film (voir :ref:`imagemarypoppinsrst`).
Il est parfois plus simple de programmer plutôt que d'apprendre
à se servir d'un logiciel qui propose une interface utilisateur
pas toujours intuitive.

.. contents:: .. contents::
:local: :local:


Expand All @@ -12,19 +24,25 @@ Audio


.. autosignature:: code_beatrix.faq.faq_video.audio_compose .. autosignature:: code_beatrix.faq.faq_video.audio_compose


.. autosignature:: code_beatrix.faq.faq_video.audio_extract_audio

.. autosignature:: code_beatrix.faq.faq_video.audio_modification .. autosignature:: code_beatrix.faq.faq_video.audio_modification


.. autosignature:: code_beatrix.faq.faq_video.audio_save .. autosignature:: code_beatrix.faq.faq_video.audio_save


Video Video
+++++ +++++


.. autosignature:: code_beatrix.faq.faq_video.video_compose

.. autosignature:: code_beatrix.faq.faq_video.video_extract_video .. autosignature:: code_beatrix.faq.faq_video.video_extract_video


.. autosignature:: code_beatrix.faq.faq_video.video_enumerate_frames .. autosignature:: code_beatrix.faq.faq_video.video_enumerate_frames


.. autosignature:: code_beatrix.faq.faq_video.video_extract_audio .. autosignature:: code_beatrix.faq.faq_video.video_extract_audio


.. autosignature:: code_beatrix.faq.faq_video.video_extract_video

.. autosignature:: code_beatrix.faq.faq_video.video_replace_sound .. autosignature:: code_beatrix.faq.faq_video.video_replace_sound


.. autosignature:: code_beatrix.faq.faq_video.video_save .. autosignature:: code_beatrix.faq.faq_video.video_save
Expand Down
3 changes: 3 additions & 0 deletions _doc/sphinxdoc/source/conf.py
Expand Up @@ -75,8 +75,11 @@
epkg_dictionary['AutioClip'] = 'https://zulko.github.io/moviepy/ref/AudioClip/AudioClip.html' epkg_dictionary['AutioClip'] = 'https://zulko.github.io/moviepy/ref/AudioClip/AudioClip.html'
epkg_dictionary['deep learning'] = 'https://en.wikipedia.org/wiki/Deep_learning' epkg_dictionary['deep learning'] = 'https://en.wikipedia.org/wiki/Deep_learning'
epkg_dictionary['fcn'] = 'https://github.com/wkentaro/fcn' epkg_dictionary['fcn'] = 'https://github.com/wkentaro/fcn'
epkg_dictionary['gif'] = 'https://fr.wikipedia.org/wiki/Graphics_Interchange_Format'
epkg_dictionary['OpenShot'] = 'https://www.openshot.org/'
epkg_dictionary['pytube'] = 'https://github.com/nficano/pytube' epkg_dictionary['pytube'] = 'https://github.com/nficano/pytube'
epkg_dictionary['SciTe'] = 'http://www.scintilla.org/SciTE.html' epkg_dictionary['SciTe'] = 'http://www.scintilla.org/SciTE.html'
epkg_dictionary['scratch'] = "https://scratch.mit.edu/" epkg_dictionary['scratch'] = "https://scratch.mit.edu/"
epkg_dictionary['ShotCut'] = 'https://www.shotcut.org/'
epkg_dictionary['VideoClip'] = 'https://zulko.github.io/moviepy/ref/VideoClip/VideoClip.html' epkg_dictionary['VideoClip'] = 'https://zulko.github.io/moviepy/ref/VideoClip/VideoClip.html'
epkg_dictionary['youtube'] = "https://www.youtube.com/" epkg_dictionary['youtube'] = "https://www.youtube.com/"
4 changes: 3 additions & 1 deletion _unittests/ut_faq/data/README.txt
@@ -1 +1,3 @@
cartoon11.wav - source: http://www.grsites.com/ cartoon011.wav - source: http://www.grsites.com/
cartoon026.wav - source: http://www.grsites.com/

Binary file added _unittests/ut_faq/data/cartoon026.wav
Binary file not shown.
19 changes: 16 additions & 3 deletions _unittests/ut_faq/test_audio.py
Expand Up @@ -46,24 +46,37 @@


from pyquickhelper.loghelper import fLOG from pyquickhelper.loghelper import fLOG
from pyquickhelper.pycode import get_temp_folder, ExtTestCase from pyquickhelper.pycode import get_temp_folder, ExtTestCase
from src.code_beatrix.faq.faq_video import audio_compose, audio_save from src.code_beatrix.faq.faq_video import audio_compose, audio_save, audio_extract_audio




class TestAudio(ExtTestCase): class TestAudio(ExtTestCase):


def test_extract_audio(self): def test_compose_audio(self):
fLOG( fLOG(
__file__, __file__,
self._testMethodName, self._testMethodName,
OutputPrint=__name__ == "__main__") OutputPrint=__name__ == "__main__")
temp = get_temp_folder(__file__, "temp_audio") temp = get_temp_folder(__file__, "temp_audio_compose")
aud = os.path.join(temp, '..', 'data', 'cartoon011.wav') aud = os.path.join(temp, '..', 'data', 'cartoon011.wav')


aud2 = audio_compose(aud, aud, 1, 3) aud2 = audio_compose(aud, aud, 1, 3)
exp = os.path.join(temp, "courte_audio.wav") exp = os.path.join(temp, "courte_audio.wav")
audio_save(aud2, exp) audio_save(aud2, exp)
self.assertExists(exp) self.assertExists(exp)


def test_extract_audio(self):
fLOG(
__file__,
self._testMethodName,
OutputPrint=__name__ == "__main__")
temp = get_temp_folder(__file__, "temp_audio_extract")
aud = os.path.join(temp, '..', 'data', 'cartoon026.wav')

aud2 = audio_extract_audio(aud, '00:00:01', '00:00:02')
exp = os.path.join(temp, "courte_audio.wav")
audio_save(aud2, exp)
self.assertExists(exp)



if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()
Expand Up @@ -46,7 +46,7 @@


from pyquickhelper.loghelper import fLOG from pyquickhelper.loghelper import fLOG
from pyquickhelper.pycode import get_temp_folder, ExtTestCase from pyquickhelper.pycode import get_temp_folder, ExtTestCase
from src.code_beatrix.faq.faq_video import video_save, video_extract_video from src.code_beatrix.faq.faq_video import video_save, video_extract_video, video_compose




class TestVideo(ExtTestCase): class TestVideo(ExtTestCase):
Expand All @@ -62,6 +62,21 @@ def test_extract_avideo(self):
exp = os.path.join(temp, "courte.mp4") exp = os.path.join(temp, "courte.mp4")
video_save(vid2, exp) video_save(vid2, exp)
self.assertExists(exp) self.assertExists(exp)
exp = os.path.join(temp, "courte.gif")
video_save(vid2, exp)
self.assertExists(exp)

def test_compose_avideo(self):
fLOG(
__file__,
self._testMethodName,
OutputPrint=__name__ == "__main__")
temp = get_temp_folder(__file__, "temp_video_compose")
vid = os.path.join(temp, '..', 'data', 'videxa.mp4')
vid2 = video_compose(vid, vid, '00:00:01', '00:00:04')
exp = os.path.join(temp, "courte.mp4")
video_save(vid2, exp)
self.assertExists(exp)




if __name__ == "__main__": if __name__ == "__main__":
Expand Down
File renamed without changes.
73 changes: 63 additions & 10 deletions src/code_beatrix/faq/faq_video.py
Expand Up @@ -10,6 +10,7 @@
from imageio import imsave from imageio import imsave
import moviepy.audio.fx.all as afx import moviepy.audio.fx.all as afx
from moviepy.audio.AudioClip import AudioArrayClip, CompositeAudioClip from moviepy.audio.AudioClip import AudioArrayClip, CompositeAudioClip
from moviepy.video.compositing.CompositeVideoClip import CompositeVideoClip
from .moviepy_context import AudioContext, VideoContext from .moviepy_context import AudioContext, VideoContext




Expand Down Expand Up @@ -64,24 +65,50 @@ def video_extract_video(video_or_file, ta=0, tb=None):
return video.subclip(ta, tb) return video.subclip(ta, tb)




def audio_extract_audio(audio_or_file, ta=0, tb=None):
"""
Extracts a part of an audio.
Extrait une partie du son.
Uses `subclip <https://zulko.github.io/moviepy/ref/AudioClip.html?highlight=audioclip#moviepy.audio.AudioClip.AudioClip.subclip>`_.
@param audio_or_file string or :epkg:`AudioClip`
@param ta beginning
@param tb end
@return :epkg:`VideoClip`
"""
with AudioContext(audio_or_file) as audio:
return audio.subclip(ta, tb)


def video_save(video_or_file, filename, verbose=False, **kwargs): def video_save(video_or_file, filename, verbose=False, **kwargs):
""" """
Saves as a video. Saves as a video or as a :epkg:`gif`.
Enregistre une vidéo dans un fichier. Enregistre une vidéo dans un fichier.
Uses `write_videofile <https://zulko.github.io/moviepy/ref/VideoClip/VideoClip.html?highlight=videofileclip#moviepy.video.io.VideoFileClip.VideoFileClip.write_videofile>`_. Uses `write_videofile <https://zulko.github.io/moviepy/ref/VideoClip/VideoClip.html?highlight=videofileclip#moviepy.video.io.VideoFileClip.VideoFileClip.write_videofile>`_.
@param video_or_file string or :epkg:`VideoClip` @param video_or_file string or :epkg:`VideoClip`
@param verbose logging or not @param verbose logging or not
@param kwargs see `write_videofile <https://zulko.github.io/moviepy/ref/VideoClip/VideoClip.html?highlight=videofileclip#moviepy.video.io.VideoFileClip.VideoFileClip.write_videofile>`_ @param kwargs see `write_videofile <https://zulko.github.io/moviepy/ref/VideoClip/VideoClip.html?highlight=videofileclip#moviepy.video.io.VideoFileClip.VideoFileClip.write_videofile>`_
""" """
with VideoContext(video_or_file) as video: if isinstance(filename, str) and os.path.splitext(filename)[-1] == '.gif':
if verbose: with VideoContext(video_or_file) as video:
video.write_videofile(filename, verbose=verbose, **kwargs) if verbose:
else: video.write_gif(filename, verbose=verbose, **kwargs)
f = io.StringIO() else:
with redirect_stdout(f): f = io.StringIO()
with redirect_stderr(f): with redirect_stdout(f):
video.write_videofile(filename, verbose=verbose, **kwargs) with redirect_stderr(f):
video.write_gif(filename, verbose=verbose, **kwargs)
else:
with VideoContext(video_or_file) as video:
if verbose:
video.write_videofile(filename, verbose=verbose, **kwargs)
else:
f = io.StringIO()
with redirect_stdout(f):
with redirect_stderr(f):
video.write_videofile(
filename, verbose=verbose, **kwargs)




def audio_save(audio_or_file, filename, verbose=False, **kwargs): def audio_save(audio_or_file, filename, verbose=False, **kwargs):
Expand Down Expand Up @@ -212,7 +239,6 @@ def audio_compose(audio_or_file1, audio_or_file2, t1=0, t2=None):
@param audio_or_file2 son 2 @param audio_or_file2 son 2
@param t1 start of the first sound @param t1 start of the first sound
@param t2 start of the second sound (or None to add it ad @param t2 start of the second sound (or None to add it ad
@param concatenate concatenate or superpose
@return new sound @return new sound
""" """
with AudioContext(audio_or_file1) as audio1: with AudioContext(audio_or_file1) as audio1:
Expand All @@ -227,3 +253,30 @@ def audio_compose(audio_or_file1, audio_or_file2, t1=0, t2=None):
else: else:
add.append(audio2.set_start(t2)) add.append(audio2.set_start(t2))
return CompositeAudioClip(add) return CompositeAudioClip(add)


def video_compose(video_or_file1, video_or_file2, t1=0, t2=None, **kwargs):
"""
Concatenates or superposes two videos.
Ajoute ou superpose deux vidéos.
@param video_or_file1 vidéo 1
@param video_or_file2 vidéo 2
@param t1 start of the first sound
@param t2 start of the second sound (or None to add it ad
@param kwargs additional parameters,
sent to `CompositeVideoClip <https://zulko.github.io/moviepy/ref/VideoClip/VideoClip.html?highlight=compositevideoclip#compositevideoclip>`_
@return new sound
"""
with VideoContext(video_or_file1) as video1:
with VideoContext(video_or_file2) as video2:
add = []
if t1 != 0:
add.append(video1.set_start(t1))
else:
add.append(video1)
if t2 is None:
add.append(video2.set_start(video1.duration + t1))
else:
add.append(video2.set_start(t2))
return CompositeVideoClip(add)

0 comments on commit eda16c6

Please sign in to comment.