Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Timelapse update #591

Merged
merged 4 commits into from
Sep 15, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 49 additions & 22 deletions pocs/utils/images/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,10 @@
from matplotlib import pyplot as plt
from warnings import warn

from astropy import units as u
from astropy.wcs import WCS
from astropy.io.fits import open as open_fits
from astropy.visualization import (PercentileInterval, LogStretch, ImageNormalize)

from ffmpy import FFmpeg
from glob import glob
from copy import copy

Expand Down Expand Up @@ -201,7 +199,14 @@ def _make_pretty_from_cr2(fname, timeout=15, **kwargs): # pragma: no cover
return fname.replace('cr2', 'jpg')


def create_timelapse(directory, fn_out=None, file_type='jpg', **kwargs):
def create_timelapse(
directory,
fn_out=None,
file_type='jpg',
overwrite=False,
timeout=60,
verbose=False,
**kwargs):
"""Create a timelapse

A timelapse is created from all the jpg images in a given `directory`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

jpg is the default type of file, but we allow for others. What other types would ffmpeg support? Probably gif and png. Speaking of which, I've wondered about using png in place of jpg to avoid lossy artifacts.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

jpgs come directly out of the CR2 without us having to create/convert anything so it's the easiest. Would be open to another solution if practical.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated docs to not be jpg specific.

Expand All @@ -211,6 +216,9 @@ def create_timelapse(directory, fn_out=None, file_type='jpg', **kwargs):
fn_out (str, optional): Full path to output file name, if not provided,
defaults to `directory` basename.
file_type (str, optional): Type of file to search for, default 'jpg'.
overwrite (bool, optional): Overwrite timelapse if exists, default False.
timeout (int): Timeout for making movie, default 60 seconds.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious, how long does this take on the RPi?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I haven't tried. I changed it slightly so on PAN008 the observation directories are moved off the Pi, mostly for storage size issues. I'll try it out tonight when I still have an image sequence on the Pi.

i actually thought it might be better to do this as a Cloud Function that responds the the PubSub notifications when images are uploaded. Then it could just make a movie and post it to youtube.

verbose (bool, optional): Show output, default False.
**kwargs (dict): Valid keywords: verbose

Returns:
Expand All @@ -224,29 +232,48 @@ def create_timelapse(directory, fn_out=None, file_type='jpg', **kwargs):
field_name = head.split('/')[-2]
cam_name = head.split('/')[-1]
fname = '{}_{}_{}.mp4'.format(field_name, cam_name, tail)
fn_out = os.path.join(os.getenv('PANDIR'), 'images', 'timelapse', fname)
fn_out = os.path.normpath(os.path.join(directory, fname))

fn_dir = os.path.dirname(fn_out)
os.makedirs(fn_dir, exist_ok=True)
if verbose:
print("Timelapse file: {}".format(fn_out))

if os.path.exists(fn_out) and not overwrite:
raise FileExistsError("Timelapse exists. Set overwrite=True if needed")

ffmpeg = shutil.which('ffmpeg')
wtgee marked this conversation as resolved.
Show resolved Hide resolved
inputs_glob = os.path.join(directory, '*.{}'.format(file_type))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is it that orders the files added to the video? Just the lexical order?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think ffmpeg is intelligent about ordering them but worth checking on. I usually sort after the glob but here we're just passing the glob.


try:
ff = FFmpeg(
global_options='-r 3 -pattern_type glob',
inputs={inputs_glob: None},
outputs={
fn_out: '-s hd1080 -vcodec libx264'
})

if 'verbose' in kwargs:
out = None
err = None
print("Timelapse command: ", ff.cmd)
else:
out = open(os.devnull, 'w')
err = open(os.devnull, 'w')

ff.run(stdout=out, stderr=err)
ffmpeg_cmd = [
ffmpeg,
'-r', '3',
'-pattern_type', 'glob',
'-i', inputs_glob,
'-s', 'hd1080',
'-vcodec', 'libx264',
]

if overwrite:
ffmpeg_cmd.append('-y')

ffmpeg_cmd.append(fn_out)

if verbose:
print(ffmpeg_cmd)

proc = subprocess.Popen(ffmpeg_cmd, universal_newlines=True,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
try:
# Don't wait forever
outs, errs = proc.communicate(timeout=timeout)
except subprocess.TimeoutExpired:
proc.kill()
outs, errs = proc.communicate()
finally:
if verbose:
print(outs)
print(errs)
fn_out = None
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suspect this belongs in the except block above, along with the proc.kill(). Otherwise fn_out is always None when returning.

except Exception as e:
warn("Problem creating timelapse in {}: {!r}".format(fn_out, e))
fn_out = None
Expand Down
1 change: 0 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ ccdproc
codecov
coveralls
dateparser
ffmpy
gcloud
google-cloud-storage
matplotlib >= 2.0.0
Expand Down