Use other timestamp for videos.
Easier to manipulate timestamp via exiftool this way.
This commit is contained in:
parent
1cc1ec02aa
commit
5cbc3f31e3
@ -149,7 +149,7 @@ class Photo(object):
|
||||
else:
|
||||
self.is_valid = False
|
||||
return
|
||||
|
||||
|
||||
def _photo_metadata(self, image):
|
||||
self._attributes["size"] = image.size
|
||||
self._orientation = 1
|
||||
@ -238,15 +238,15 @@ class Photo(object):
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except TypeError:
|
||||
self._attributes["dateTimeOriginal"] = exif["DateTimeOriginal"]
|
||||
self._attributes["dateTimeOriginal"] = exif["DateTimeOriginal"]
|
||||
if "DateTime" in exif:
|
||||
try:
|
||||
self._attributes["dateTime"] = datetime.strptime(exif["DateTime"], '%Y:%m:%d %H:%M:%S')
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except TypeError:
|
||||
self._attributes["dateTime"] = exif["DateTime"]
|
||||
|
||||
self._attributes["dateTime"] = exif["DateTime"]
|
||||
|
||||
_photo_metadata.flash_dictionary = {0x0: "No Flash", 0x1: "Fired",0x5: "Fired, Return not detected",0x7: "Fired, Return detected",0x8: "On, Did not fire",0x9: "On, Fired",0xd: "On, Return not detected",0xf: "On, Return detected",0x10: "Off, Did not fire",0x14: "Off, Did not fire, Return not detected",0x18: "Auto, Did not fire",0x19: "Auto, Fired",0x1d: "Auto, Fired, Return not detected",0x1f: "Auto, Fired, Return detected",0x20: "No flash function",0x30: "Off, No flash function",0x41: "Fired, Red-eye reduction",0x45: "Fired, Red-eye reduction, Return not detected",0x47: "Fired, Red-eye reduction, Return detected",0x49: "On, Red-eye reduction",0x4d: "On, Red-eye reduction, Return not detected",0x4f: "On, Red-eye reduction, Return detected",0x50: "Off, Red-eye reduction",0x58: "Auto, Did not fire, Red-eye reduction",0x59: "Auto, Fired, Red-eye reduction",0x5d: "Auto, Fired, Red-eye reduction, Return not detected",0x5f: "Auto, Fired, Red-eye reduction, Return detected"}
|
||||
_photo_metadata.light_source_dictionary = {0: "Unknown", 1: "Daylight", 2: "Fluorescent", 3: "Tungsten (incandescent light)", 4: "Flash", 9: "Fine weather", 10: "Cloudy weather", 11: "Shade", 12: "Daylight fluorescent (D 5700 - 7100K)", 13: "Day white fluorescent (N 4600 - 5400K)", 14: "Cool white fluorescent (W 3900 - 4500K)", 15: "White fluorescent (WW 3200 - 3700K)", 17: "Standard light A", 18: "Standard light B", 19: "Standard light C", 20: "D55", 21: "D65", 22: "D75", 23: "D50", 24: "ISO studio tungsten"}
|
||||
_photo_metadata.metering_list = ["Unknown", "Average", "Center-weighted average", "Spot", "Multi-spot", "Multi-segment", "Partial"]
|
||||
@ -258,26 +258,39 @@ class Photo(object):
|
||||
|
||||
|
||||
def _video_metadata(self, path, original=True):
|
||||
p = VideoProbeWrapper().call('-show_format', '-show_streams', '-of', 'json', '-loglevel', '0', path)
|
||||
if p == False:
|
||||
self.is_valid = False
|
||||
return
|
||||
info = json.loads(p)
|
||||
for s in info["streams"]:
|
||||
if 'codec_type' in s and s['codec_type'] == 'video':
|
||||
self._attributes["mediaType"] = "video"
|
||||
self._attributes["size"] = (int(s["width"]), int(s["height"]))
|
||||
if "duration" in s:
|
||||
p = VideoProbeWrapper().call('-show_format', '-show_streams', '-of', 'json', '-loglevel', '0', path)
|
||||
if p == False:
|
||||
self.is_valid = False
|
||||
return
|
||||
info = json.loads(p)
|
||||
for s in info["streams"]:
|
||||
if 'codec_type' in s and s['codec_type'] == 'video':
|
||||
self._attributes["mediaType"] = "video"
|
||||
self._attributes["size"] = (int(s["width"]), int(s["height"]))
|
||||
if "duration" in s:
|
||||
self._attributes["duration"] = s["duration"]
|
||||
if "tags" in s and "rotate" in s["tags"]:
|
||||
self._attributes["rotate"] = s["tags"]["rotate"]
|
||||
if original:
|
||||
self._attributes["originalSize"] = (int(s["width"]), int(s["height"]))
|
||||
# we break, because a video can contain several streams
|
||||
# this way we only get/use values from the first stream
|
||||
break
|
||||
|
||||
|
||||
def _photo_thumbnail(self, original_path, thumb_path, size, square=False):
|
||||
try:
|
||||
|
||||
# use time from EXIF (rather than file creation)
|
||||
if info['format']['tags']['creation_time']:
|
||||
# we have time modifiable via exif
|
||||
# lets use this
|
||||
|
||||
try:
|
||||
self._attributes["videoCreateDate"] = datetime.strptime(info['format']['tags']['creation_time'], '%Y-%m-%d %H:%M:%S')
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
def _photo_thumbnail(self, original_path, thumb_path, size, square=False):
|
||||
try:
|
||||
image = Image.open(original_path)
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
@ -305,13 +318,13 @@ class Photo(object):
|
||||
# Vertical Mirror + Rotation 270
|
||||
mirror = image.transpose(Image.FLIP_LEFT_RIGHT).transpose(Image.ROTATE_270)
|
||||
elif self._orientation == 8:
|
||||
# Rotation 90
|
||||
# Rotation 90
|
||||
mirror = image.transpose(Image.ROTATE_90)
|
||||
|
||||
image = mirror
|
||||
self._thumbnail(image, original_path, thumb_path, size, square)
|
||||
|
||||
def _thumbnail(self, image, original_path, thumb_path, size, square):
|
||||
|
||||
image = mirror
|
||||
self._thumbnail(image, original_path, thumb_path, size, square)
|
||||
|
||||
def _thumbnail(self, image, original_path, thumb_path, size, square):
|
||||
thumb_path = os.path.join(thumb_path, image_cache(self._path, size, square))
|
||||
info_string = "%s -> %spx" % (os.path.basename(original_path), str(size))
|
||||
if square:
|
||||
@ -361,55 +374,55 @@ class Photo(object):
|
||||
os.unlink(thumb_path)
|
||||
except:
|
||||
pass
|
||||
|
||||
def _photo_thumbnails(self, original_path, thumb_path):
|
||||
# get number of cores on the system, and use all minus one
|
||||
num_of_cores = os.sysconf('SC_NPROCESSORS_ONLN') - 1
|
||||
pool = Pool(processes=num_of_cores)
|
||||
|
||||
try:
|
||||
for size in Photo.thumb_sizes:
|
||||
pool.apply_async(make_photo_thumbs, args = (self, original_path, thumb_path, size))
|
||||
except:
|
||||
pool.terminate()
|
||||
|
||||
pool.close()
|
||||
pool.join()
|
||||
|
||||
def _photo_thumbnails(self, original_path, thumb_path):
|
||||
# get number of cores on the system, and use all minus one
|
||||
num_of_cores = os.sysconf('SC_NPROCESSORS_ONLN') - 1
|
||||
pool = Pool(processes=num_of_cores)
|
||||
|
||||
try:
|
||||
for size in Photo.thumb_sizes:
|
||||
pool.apply_async(make_photo_thumbs, args = (self, original_path, thumb_path, size))
|
||||
except:
|
||||
pool.terminate()
|
||||
|
||||
pool.close()
|
||||
pool.join()
|
||||
|
||||
def _video_thumbnails(self, thumb_path, original_path):
|
||||
(tfd, tfn) = tempfile.mkstemp();
|
||||
p = VideoTranscodeWrapper().call(
|
||||
'-i', original_path, # original file to extract thumbs from
|
||||
'-f', 'image2', # extract image
|
||||
'-vsync', '1', # CRF
|
||||
'-vframes', '1', # extrat 1 single frame
|
||||
'-an', # disable audio
|
||||
'-loglevel', 'quiet', # don't display anything
|
||||
'-y', # don't prompt for overwrite
|
||||
tfn # temporary file to store extracted image
|
||||
)
|
||||
p = VideoTranscodeWrapper().call(
|
||||
'-i', original_path, # original file to extract thumbs from
|
||||
'-f', 'image2', # extract image
|
||||
'-vsync', '1', # CRF
|
||||
'-vframes', '1', # extrat 1 single frame
|
||||
'-an', # disable audio
|
||||
'-loglevel', 'quiet', # don't display anything
|
||||
'-y', # don't prompt for overwrite
|
||||
tfn # temporary file to store extracted image
|
||||
)
|
||||
if p == False:
|
||||
message("couldn't extract video frame", os.path.basename(original_path))
|
||||
try:
|
||||
os.unlink(tfn)
|
||||
except:
|
||||
pass
|
||||
os.unlink(tfn)
|
||||
except:
|
||||
pass
|
||||
self.is_valid = False
|
||||
return
|
||||
try:
|
||||
image = Image.open(tfn)
|
||||
except KeyboardInterrupt:
|
||||
try:
|
||||
os.unlink(tfn)
|
||||
except:
|
||||
pass
|
||||
try:
|
||||
os.unlink(tfn)
|
||||
except:
|
||||
pass
|
||||
raise
|
||||
except:
|
||||
message("couldn't open video thumbnail", tfn)
|
||||
try:
|
||||
os.unlink(tfn)
|
||||
except:
|
||||
pass
|
||||
os.unlink(tfn)
|
||||
except:
|
||||
pass
|
||||
self.is_valid = False
|
||||
return
|
||||
mirror = image
|
||||
@ -424,14 +437,14 @@ class Photo(object):
|
||||
if size[1]:
|
||||
self._thumbnail(mirror, original_path, thumb_path, size[0], size[1])
|
||||
try:
|
||||
os.unlink(tfn)
|
||||
except:
|
||||
pass
|
||||
|
||||
os.unlink(tfn)
|
||||
except:
|
||||
pass
|
||||
|
||||
def _video_transcode(self, transcode_path, original_path):
|
||||
transcode_path = os.path.join(transcode_path, video_cache(self._path))
|
||||
# get number of cores on the system, and use all minus one
|
||||
num_of_cores = os.sysconf('SC_NPROCESSORS_ONLN') - 1
|
||||
# get number of cores on the system, and use all minus one
|
||||
num_of_cores = os.sysconf('SC_NPROCESSORS_ONLN') - 1
|
||||
transcode_cmd = [
|
||||
'-i', original_path, # original file to be encoded
|
||||
'-c:v', 'libx264', # set h264 as videocodec
|
||||
@ -470,30 +483,30 @@ class Photo(object):
|
||||
if len(filters):
|
||||
transcode_cmd.append('-vf')
|
||||
transcode_cmd.append(','.join(filters))
|
||||
|
||||
tmp_transcode_cmd = transcode_cmd[:]
|
||||
|
||||
tmp_transcode_cmd = transcode_cmd[:]
|
||||
transcode_cmd.append(transcode_path)
|
||||
p = VideoTranscodeWrapper().call(*transcode_cmd)
|
||||
if p == False:
|
||||
# add another option, try transcoding again
|
||||
# done to avoid this error;
|
||||
# x264 [error]: baseline profile doesn't support 4:2:2
|
||||
message("transcoding failure, trying yuv420p", os.path.basename(original_path))
|
||||
tmp_transcode_cmd.append('-pix_fmt')
|
||||
tmp_transcode_cmd.append('yuv420p')
|
||||
tmp_transcode_cmd.append(transcode_path)
|
||||
p = VideoTranscodeWrapper().call(*tmp_transcode_cmd)
|
||||
|
||||
if p == False:
|
||||
message("transcoding failure", os.path.basename(original_path))
|
||||
try:
|
||||
os.unlink(transcode_path)
|
||||
except:
|
||||
pass
|
||||
self.is_valid = False
|
||||
return
|
||||
self._video_metadata(transcode_path, False)
|
||||
|
||||
# add another option, try transcoding again
|
||||
# done to avoid this error;
|
||||
# x264 [error]: baseline profile doesn't support 4:2:2
|
||||
message("transcoding failure, trying yuv420p", os.path.basename(original_path))
|
||||
tmp_transcode_cmd.append('-pix_fmt')
|
||||
tmp_transcode_cmd.append('yuv420p')
|
||||
tmp_transcode_cmd.append(transcode_path)
|
||||
p = VideoTranscodeWrapper().call(*tmp_transcode_cmd)
|
||||
|
||||
if p == False:
|
||||
message("transcoding failure", os.path.basename(original_path))
|
||||
try:
|
||||
os.unlink(transcode_path)
|
||||
except:
|
||||
pass
|
||||
self.is_valid = False
|
||||
return
|
||||
self._video_metadata(transcode_path, False)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return os.path.basename(self._path)
|
||||
@ -518,7 +531,9 @@ class Photo(object):
|
||||
correct_date = None;
|
||||
if not self.is_valid:
|
||||
correct_date = datetime(1900, 1, 1)
|
||||
if "dateTimeOriginal" in self._attributes:
|
||||
if "videoCreateDate" in self._attributes:
|
||||
correct_date = self._attributes["videoCreateDate"]
|
||||
elif "dateTimeOriginal" in self._attributes:
|
||||
correct_date = self._attributes["dateTimeOriginal"]
|
||||
elif "dateTime" in self._attributes:
|
||||
correct_date = self._attributes["dateTime"]
|
||||
|
@ -6,15 +6,15 @@ class VideoToolWrapper(object):
|
||||
def call(self, *args):
|
||||
path = args[-1]
|
||||
for tool in self.wrappers:
|
||||
try:
|
||||
if self.check_output:
|
||||
p = subprocess.check_output((tool,) + args)
|
||||
else:
|
||||
p = subprocess.call((tool,) + args)
|
||||
if p > 0:
|
||||
return False
|
||||
else:
|
||||
return "SUCCESS"
|
||||
try:
|
||||
if self.check_output:
|
||||
p = subprocess.check_output((tool,) + args)
|
||||
else:
|
||||
p = subprocess.call((tool,) + args)
|
||||
if p > 0:
|
||||
return False
|
||||
else:
|
||||
return "SUCCESS"
|
||||
except KeyboardInterrupt:
|
||||
if self.cleanup:
|
||||
self.remove(path)
|
||||
@ -37,11 +37,11 @@ class VideoToolWrapper(object):
|
||||
class VideoTranscodeWrapper(VideoToolWrapper):
|
||||
def __init__(self):
|
||||
self.wrappers = ['avconv', 'ffmpeg']
|
||||
self.check_output = False
|
||||
self.check_output = False
|
||||
self.cleanup = True
|
||||
|
||||
class VideoProbeWrapper(VideoToolWrapper):
|
||||
def __init__(self):
|
||||
self.wrappers = ['avprobe', 'ffprobe']
|
||||
self.check_output = True
|
||||
self.check_output = True
|
||||
self.cleanup = False
|
||||
|
Loading…
Reference in New Issue
Block a user