|
1
|
|
|
""" |
|
2
|
|
|
The video module contains the :class:`Video` class, which represents video |
|
3
|
|
|
objects (AVI, MOV, etc.). |
|
4
|
|
|
|
|
5
|
|
|
.. moduleauthor:: Jaisen Mathai <[email protected]> |
|
6
|
|
|
""" |
|
7
|
|
|
from __future__ import print_function |
|
8
|
|
|
from __future__ import absolute_import |
|
9
|
|
|
from __future__ import division |
|
10
|
|
|
|
|
11
|
|
|
# load modules |
|
12
|
|
|
from datetime import datetime |
|
13
|
|
|
|
|
14
|
|
|
import os |
|
15
|
|
|
import re |
|
16
|
|
|
import time |
|
17
|
|
|
|
|
18
|
|
|
from .media import Media |
|
19
|
|
|
|
|
20
|
|
|
|
|
21
|
|
|
class Video(Media): |
|
22
|
|
|
|
|
23
|
|
|
"""A video object. |
|
24
|
|
|
|
|
25
|
|
|
:param str source: The fully qualified path to the video file. |
|
26
|
|
|
""" |
|
27
|
|
|
|
|
28
|
|
|
__name__ = 'Video' |
|
29
|
|
|
|
|
30
|
|
|
#: Valid extensions for video files. |
|
31
|
|
|
extensions = ('avi', 'm4v', 'mov', 'mp4', 'mpg', 'mpeg', '3gp', 'mts') |
|
32
|
|
|
|
|
33
|
|
|
def __init__(self, source=None): |
|
34
|
|
|
super(Video, self).__init__(source) |
|
35
|
|
|
self.exif_map['date_taken'] = [ |
|
36
|
|
|
'QuickTime:CreationDate', |
|
37
|
|
|
'QuickTime:CreateDate', |
|
38
|
|
|
'QuickTime:CreationDate-und-US', |
|
39
|
|
|
'QuickTime:MediaCreateDate', |
|
40
|
|
|
'H264:DateTimeOriginal' |
|
41
|
|
|
] |
|
42
|
|
|
self.title_key = 'XMP:DisplayName' |
|
43
|
|
|
self.latitude_keys = [ |
|
44
|
|
|
'XMP:GPSLatitude', |
|
45
|
|
|
# 'QuickTime:GPSLatitude', |
|
46
|
|
|
'Composite:GPSLatitude' |
|
47
|
|
|
] |
|
48
|
|
|
self.longitude_keys = [ |
|
49
|
|
|
'XMP:GPSLongitude', |
|
50
|
|
|
# 'QuickTime:GPSLongitude', |
|
51
|
|
|
'Composite:GPSLongitude' |
|
52
|
|
|
] |
|
53
|
|
|
self.latitude_ref_key = 'EXIF:GPSLatitudeRef' |
|
54
|
|
|
self.longitude_ref_key = 'EXIF:GPSLongitudeRef' |
|
55
|
|
|
self.set_gps_ref = False |
|
56
|
|
|
|
|
57
|
|
|
def get_date_taken(self): |
|
58
|
|
|
"""Get the date which the photo was taken. |
|
59
|
|
|
|
|
60
|
|
|
The date value returned is defined by the min() of mtime and ctime. |
|
61
|
|
|
|
|
62
|
|
|
:returns: time object or None for non-photo files or 0 timestamp |
|
63
|
|
|
""" |
|
64
|
|
|
if(not self.is_valid()): |
|
65
|
|
|
return None |
|
66
|
|
|
|
|
67
|
|
|
source = self.source |
|
68
|
|
|
seconds_since_epoch = min(os.path.getmtime(source), os.path.getctime(source)) # noqa |
|
69
|
|
|
|
|
70
|
|
|
exif = self.get_exiftool_attributes() |
|
71
|
|
|
for date_key in self.exif_map['date_taken']: |
|
72
|
|
|
if date_key in exif: |
|
73
|
|
|
# Example date strings we want to parse |
|
74
|
|
|
# 2015:01:19 12:45:11-08:00 |
|
75
|
|
|
# 2013:09:30 07:06:05 |
|
76
|
|
|
date = re.search('([0-9: ]+)([-+][0-9:]+)?', exif[date_key]) |
|
77
|
|
|
if(date is not None): |
|
78
|
|
|
date_string = date.group(1) |
|
79
|
|
|
date_offset = date.group(2) |
|
80
|
|
|
try: |
|
81
|
|
|
exif_seconds_since_epoch = time.mktime( |
|
82
|
|
|
datetime.strptime( |
|
83
|
|
|
date_string, |
|
84
|
|
|
'%Y:%m:%d %H:%M:%S' |
|
85
|
|
|
).timetuple() |
|
86
|
|
|
) |
|
87
|
|
|
if(exif_seconds_since_epoch < seconds_since_epoch): |
|
88
|
|
|
seconds_since_epoch = exif_seconds_since_epoch |
|
89
|
|
|
if date_offset is not None: |
|
90
|
|
|
offset_parts = date_offset[1:].split(':') |
|
91
|
|
|
offset_seconds = int(offset_parts[0]) * 3600 |
|
92
|
|
|
offset_seconds = offset_seconds + int(offset_parts[1]) * 60 # noqa |
|
93
|
|
|
if date_offset[0] == '-': |
|
94
|
|
|
seconds_since_epoch - offset_seconds |
|
95
|
|
|
elif date_offset[0] == '+': |
|
96
|
|
|
seconds_since_epoch + offset_seconds |
|
97
|
|
|
except: |
|
98
|
|
|
pass |
|
99
|
|
|
|
|
100
|
|
|
if(seconds_since_epoch == 0): |
|
101
|
|
|
return None |
|
102
|
|
|
|
|
103
|
|
|
return time.gmtime(seconds_since_epoch) |
|
104
|
|
|
|