Skip to content

Commit

Permalink
Merge pull request #2 from pyrva/useful-multimedia-manipulation
Browse files Browse the repository at this point in the history
Useful multimedia manipulation
  • Loading branch information
mikealfare authored Dec 3, 2020
2 parents 555e70a + 2415e17 commit 9f7e891
Show file tree
Hide file tree
Showing 441 changed files with 263 additions and 0 deletions.
3 changes: 3 additions & 0 deletions lectures/useful-multimedia-manipulation/.gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
PYTHON[[:space:]]PRESENTATION[[:space:]]RVA[[:space:]]2020[[:space:]]JULY.key filter=lfs diff=lfs merge=lfs -text
*ppts filter=lfs diff=lfs merge=lfs -text
PYTHON[[:space:]]PRESENTATION[[:space:]]RVA[[:space:]]2020[[:space:]]JULY.pptx filter=lfs diff=lfs merge=lfs -text
4 changes: 4 additions & 0 deletions lectures/useful-multimedia-manipulation/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
figures
abstract
*.html
mailmap
Git LFS file not shown
Binary file not shown.
Git LFS file not shown
29 changes: 29 additions & 0 deletions lectures/useful-multimedia-manipulation/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
Python for Quick, Useful Multimedia Manipulation: Anecdotes from a Python Programmer
=====================================================================================

One of Python's many killer features is that a programmer can create snippets of code, and organically and coherently join them into larger collections of module code. In this presentation, I describe and demonstrate relatively simple Python tools -- for image, video, and audio manipulation -- that I use every day at work and outside work.

* automatically cropping out whitespace in images (surprisingly easy to do).

* creating movies from a sequence of generated images.

* conversion of movie clips, either files or YouTube clips, into animated GIFs (useful where the online service does not allow for video animations from movie files, such as GitHub_ or `Read the Docs`_).

If there's time or interest, I can even describe and demonstrate how to retrieve and label music you might find, all within Python.

Demonstrations live in the ``demonstrations`` subdirectory, and each demonstration is its own directory within ``demonstrations``, and each demonstration has its own ``README.rst``. Here are the four demonstration directories with description.

1. ``autocropping_images``: autocropping a PNG image and a PDF image.

2. ``movie_image_demos``: converting a sequence of images into an MP4 file.

3. ``movie_gif_demos``: converting an MP4 file and a YouTube clip into animated GIFs.

4. ``making_music_youtube``: using a tool, `plex_music_songs`_, that takes metadata from MusicBrainz_ and the YouTube clip using `youtube-dl`_, into an M4A file.

.. _GitHub: https://github.com
.. _`Read the Docs`: https://www.readthedocs.io
.. _CloudConvert: https://cloudconvert.com
.. _`plex_music_songs`: https://plexstuff.readthedocs.io/plex-music/cli_tools/plex_music_cli.html?highlight=plex_music_songs#plex-music-songs
.. _MusicBrainz: https://musicbrainz.org
.. _`youtube-dl`: https://rg3.github.io/youtube-dl
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
iwanttobelieve_cropped.png
cumulative_plot_emission_cropped.pdf
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
INSTRUCTIONS
=============

1. Go to these locations:

a. autocropping lossy images: `nprstuff.core.autocrop_image.autocrop_image <autocrop_image_>`_

b. autocropping PDFs: `nprstuff.core.autocrop_image.crop_pdf_singlepage <autocrop_image_pdf_>`_

2. Cropping a PNG image:

.. code-block:: console
autoCropImage --input=iwanttobelieve_uncropped.png --output=iwanttobelieve_cropped.png
Compare ``iwanttobelieve_uncropped.png`` and ``iwanttobelieve_cropped.png`` in a browser.

3. Cropping a PDF image:

.. code-block:: console
autoCropImage --input=cumulative_plot_emission_uncropped.pdf --output=cumulative_plot_emission_cropped.pdf
Compare ``cumulative_plot_emission_uncropped.pdf`` and ``cumulative_plot_emission_cropped.pdf`` in a PDF viewer.


.. _`autocrop_image`: https://github.com/tanimislam/nprstuff/blob/f67e719ba4f2ca7120774937d27cb1adbb51c933/nprstuff/core/autocrop_image.py#L25

.. _`autocrop_image_pdf`: https://github.com/tanimislam/nprstuff/blob/f67e719ba4f2ca7120774937d27cb1adbb51c933/nprstuff/core/autocrop_image.py#L193
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.m4a
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
INSTRUCTIONS
=============

1. Go to these locations:

a. showing Musicbrainz_ getting metadata (given an artist, all the albums + all songs of artist): `plexstuff.plexmusic.plexmusic.get_music_metadata <get_music_metadata_>`_

b. showing choose the correct YouTube clip: `plexstuff.plexmusic.plexmusic.youtube_search <youtube_search_>`_

c. showing part of downloading from `youtube-dl`: `plexstuff.plexmusic.plexmusic.get_youtube_file <get_youtube_file_>`_

2. ``plex_music_songs`` documentation at `this website`_.

3. Show animation of getting songs, ``plex_music_songs_download_artist_songs.mp4``, with video viewer.

4. Show how to get ``All I Need`` by ``Air``

.. code-block:: console
plex_music_songs -a Air -s "All I Need" --musicbrainz
Which can take a bit of time.

.. _MusicBrainz: https://musicbrainz.org

.. _`get_music_metadata`: https://github.com/tanimislam/plexstuff/blob/37cfb9f9e52864d8bdd6a2e154dc93b48ff2c908/plexstuff/plexmusic/plexmusic.py#L411

.. _`youtube_search`: https://github.com/tanimislam/plexstuff/blob/37cfb9f9e52864d8bdd6a2e154dc93b48ff2c908/plexstuff/plexmusic/plexmusic.py#L888

.. _`youtube-dl`: https://rg3.github.io/youtube-dl

.. _`this website`: https://plexstuff.readthedocs.io/plex-music/cli_tools/plex_music_cli.html?highlight=plex_music_songs#plex-music-songs

.. _`get_youtube_file`: https://github.com/tanimislam/plexstuff/blob/37cfb9f9e52864d8bdd6a2e154dc93b48ff2c908/plexstuff/plexmusic/plexmusic.py#L848
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.gif
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
INSTRUCTIONS
=============

1. Go to these locations in `nprstuff.core.convert_image <convert_image_>`_.

a. mp4togif: `nprstuff.core.convert_image.mp4togif <mp4togif_>`_

b. youtube2gif: `nprstuff.core.convert_image.youtube2gif <youtube2gif_>`_

2. Lower level FFMPEG_ command comes from `this website`_.

3. MP4 to animated GIF:

.. code-block:: console
convertImage movie -f covid19_conus_cases_04072020.mp4
Creates animated GIF ``covid19_conus_cases_04072020.gif``, open in browser.

4. YouTube Clip to GIF:

.. code-block:: console
convertImage youtube -u "https://www.youtube.com/watch?v=R-pmYwr8zbU" -o "lucas_bros.gif" -q high
Creates animated GIF ``lucas_bros.gif``, open in browser.


.. _`convert_image`: https://github.com/tanimislam/nprstuff/blob/master/nprstuff/core/convert_image.py

.. _mp4togif: https://github.com/tanimislam/nprstuff/blob/807a3cba7e8bfd6ded70cdea3083cd9c9494e438/nprstuff/core/convert_image.py#L150

.. _youtube2gif: https://github.com/tanimislam/nprstuff/blob/807a3cba7e8bfd6ded70cdea3083cd9c9494e438/nprstuff/core/convert_image.py#L135

.. _`this website`: http://blog.pkh.me/p/21-high-quality-gif-with-ffmpeg.html

.. _FFMPEG: https://ffmpeg.org
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.mp4
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
INSTRUCTIONS
=============

1. Go to this gist: `demo_create_movie_sequence.py`_.

2. Instructions on the FFMPEG_ syntax to make an MP4 movie from a sequence of images come from `this website`_.

3. MP4 movie of MCMC demo:

.. code-block:: console
python3.7 demo_create_movie_sequence.py --prefix=img --output="mcmc_images.mp4" --dirname="mcmc_animation_images" --fps=10
Open ``mcmc_images.mp4`` with a video viewer. This is 10 frames per second.

4. MP4 movie of cumulative COVID-19 cases in continental United States:

.. code-block:: console
python3.7 demo_create_movie_sequence.py --prefix="covid19_conus_cases_04072020." --output="covid19_conus_cases_04072020.mp4" --dirname="covid19_conus_cases_04072020_imagefiles" --fps=5
Open ``covid19_conus_cases_04072020.mp4`` with a video viewer. This is 5 frames per second.

.. _`demo_create_movie_sequence.py`: https://gist.github.com/tanimislam/406a1379e746c9882c101f656a6da949
.. _FFMPEG: https://ffmpeg.org
.. _`this website`: https://hamelot.io/visualization/using-ffmpeg-to-convert-a-set-of-images-into-a-video/
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#!/usr/bin/env python3

"""
I developed this Python CLI script to demonstrate creating an MP4 movie from an image sequence.
I follow instructions from https://hamelot.io/visualization/using-ffmpeg-to-convert-a-set-of-images-into-a-video. I make an MP4 movie that is 5 FPS with psychovisual quality of 25.
"""
import os, sys, numpy, subprocess, glob, re, logging, time
from PIL import Image
from distutils.spawn import find_executable
from argparse import ArgumentParser

def create_movie_from_sequence( prefix, output_file_name, dirname = os.getcwd( ), fps = 5 ):
"""
Creates an MP4 movie from a sequence of PNG images. The output file name must end in .mp4.
:param str prefix: the beginnning base name of all the PNG images. If `foo` is the prefix, then will look for images that are like `foo0001.png`, `foo0002.png` and so forth. To make life simpler, the prefix *MUST* be alphanumeric.
:param str output_file_name: the name of the output file. Must end in .mp4.
:param str dirname: optional argument. The directory that contains the image sequence. Default is the current working directory.
:param int fps: frames per second. Default is 5.
:returns: `True` if successful, `False` otherwise.
:rtype: bool
"""
time0 = time.time( )
assert( os.path.isdir( dirname ) ) # is a directory
assert( os.path.basename( output_file_name ).endswith( '.mp4' ) ) # ends with mp4
assert( re.match( '^[a-zA-Z]+', prefix ) is not None ) # alphanumeric
assert( find_executable( 'ffmpeg' ) is not None )
assert( fps >= 1 ) # fps must be positive
ffmpeg_exec = find_executable( 'ffmpeg' )
#
## now sequence of images
sorted_filenames = sorted(
filter(lambda fname: re.match('.*[0-9]+\.png', os.path.basename( fname ) ) is not None and
os.path.basename( fname ).startswith( prefix ),
glob.glob( os.path.join( dirname,'%s*.png' % prefix ) ) ) )
if len( sorted_filenames ) == 0:
print( 'ERROR, COULD FIND NO IMAGE SEQUENCE.' )
return False
def is_divis_2( fname ):
img = Image.open( fname )
if img.size[0] % 2 != 0: return False
if img.size[1] % 2 != 0: return False
return True
try:
assert(all(filter(is_divis_2, sorted_filenames))) # all widths and heights div by 2
except:
print( "ERROR, NOT ALL IMAGES HAVE WIDTHS + HEIGHTS DIVISIBLE BY 2.")
return False

#
##
num_base_10 = 1 + int( numpy.log10(len(sorted_filenames)))
sequence_ffmpeg = '%%%02dd' % num_base_10 # this is tricky!
input_ffmpeg_image_string = '%s%s.png' % ( os.path.join( dirname, prefix ), sequence_ffmpeg )
logging.info( 'got here, %s.' % input_ffmpeg_image_string )
#
## now run the ffmpeg command
command_to_process = [
ffmpeg_exec, '-y', '-r', '%d' % fps, '-f', 'image2', '-i', input_ffmpeg_image_string,
'-vcodec', 'libx264', '-crf', '25', '-pix_fmt', 'yuv420p',
output_file_name ]
logging.info( 'COMMAND TO RUN: %s.' % ' '.join( command_to_process ) )
proc = subprocess.Popen( command_to_process, stdout = subprocess.PIPE,
stderr = subprocess.STDOUT )
stdout_val, stderr_val = proc.communicate( )
logging.info( 'STDOUT_MESSAGE.' )
logging.info( '%s\n' % stdout_val )
logging.info( 'TOOK %0.3f SECONDS TO RUN TO COMPLETION.' % (
time.time( ) - time0 ) )

if __name__=='__main__':
parser = ArgumentParser( )
parser.add_argument( '--prefix', dest='prefix', type=str, required = True,
help = 'The prefix to the sequence of PNG images.' )
parser.add_argument( '--output', dest='output', type=str, required = True,
help = 'The name of the MP4 output file.' )
parser.add_argument( '--dirname', dest='dirname', type=str, default = os.getcwd( ),
help = 'The directory containing the image sequence. Default is CWD.')
parser.add_argument( '--fps', dest='fps', type=int, default = 5,
help = 'Frames per second of movie. Default is 5.' )
parser.add_argument( '--info', dest='do_info', action='store_true', default = False,
help = 'If chosen, then print out INFO debug logging.' )
args = parser.parse_args( )
logger = logging.getLogger( )
#
if args.do_info: logger.setLevel( logging.INFO )
status = create_movie_from_sequence(
args.prefix, args.output, dirname = args.dirname, fps = args.fps )

Loading

0 comments on commit 9f7e891

Please sign in to comment.