Compare commits
7 Commits
v2.0-alpha
...
v2.0-alpha
| Author | SHA1 | Date | |
|---|---|---|---|
| 0040572ac8 | |||
| 9ef72f4567 | |||
| 7d53803c7d | |||
| 801ca04299 | |||
| c64d303fe3 | |||
| cff5d49833 | |||
| ab42d4f8e4 |
@@ -1,7 +1,7 @@
|
||||
# Localization file for video2geoframes.py script
|
||||
# English (US / World)
|
||||
#
|
||||
# Last edition : 2024-06-22
|
||||
# Last edition : 2024-06-23
|
||||
|
||||
[ui]
|
||||
|
||||
@@ -12,6 +12,7 @@ This script is designed to create geotagged frames from video and GPX track."""
|
||||
end = "End of program, press Enter to quit."
|
||||
paths_title = "## Paths"
|
||||
parameters_title = "## Process parameters"
|
||||
tags_title = "## Additional tags"
|
||||
metadata = """{} ({} {}B)\n
|
||||
- Duration : {} s\n
|
||||
- Start time : {}.{}\n
|
||||
@@ -21,16 +22,16 @@ metadata = """{} ({} {}B)\n
|
||||
cv2_tqdm = 'frame(s)'
|
||||
|
||||
[ui.parameters]
|
||||
toml_setting² = "Setting with TOML file ({}/{}) ? "
|
||||
toml_setting = "Setting with TOML file ({}/{}) ? "
|
||||
|
||||
timelapse = "Timelapse video ({}/{}) ? "
|
||||
timelapse_fps = "Timelapse framerate (frame/s) [{}-{}] : "
|
||||
timelapse_fps_err = "Error... please enter a decimal between {} et {}."
|
||||
|
||||
frame_samp = "Enter the frame sampling in seconds [{}-{}] : "
|
||||
frame_samp = "Enter frame sampling in seconds [{}-{}] : "
|
||||
frame_samp_err = "Error... please enter a decimal between {} and {}."
|
||||
|
||||
frame_height = "Enter frame height in pixels (ratio unchanged) [{}-{}] : "
|
||||
frame_height = "Enter output frame height in pixels (ratio unchanged) [{}-{}] : "
|
||||
frame_height_err = "Error... please enter an integer between {} and {}."
|
||||
|
||||
video_start_datetime = "Enter video start datetime following ISO format (exemple : 2023-09-18T22:00:02.000) : "
|
||||
@@ -38,7 +39,7 @@ video_start_datetime_err = "Error... entered datetime is not valid."
|
||||
|
||||
rec_timezone = "Enter time offset related to UTC (example for CEST : +02:00) : "
|
||||
|
||||
time_offset = "Enter time offset between video and GPX in seconds [{}-{}] : "
|
||||
time_offset = "Enter time offset with GPS track in seconds [{}-{}] : "
|
||||
time_offset_err = "Error... please enter a decimal between {} and {}."
|
||||
|
||||
[ui.paths]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Localization file for video2geoframes.py script
|
||||
# French (France)
|
||||
#
|
||||
# Last edition : 2024-06-22
|
||||
# Last edition : 2024-06-23
|
||||
|
||||
[ui]
|
||||
|
||||
@@ -11,6 +11,7 @@ intro = """Bienvenue dans le script video2geoframes.py !
|
||||
Ce script permet, à partir d'une vidéo et d'une trace GPS, de créer un ensemble de photos géotaguées."""
|
||||
end = "Fin du programme, appuyez sur Entrée pour fermer."
|
||||
paths_title = "## Chemins"
|
||||
tags_title = "## Tags additionnels"
|
||||
parameters_title = "## Paramètres du traitement"
|
||||
metadatas = """{} ({} {}B)
|
||||
- Durée : {} s
|
||||
@@ -27,10 +28,10 @@ timelapse = "Vidéo timelapse ({}/{}) ? "
|
||||
timelapse_fps = "Débit d'image du timelapse (image/s) [{}-{}] : "
|
||||
timelapse_fps_err = "Erreur... entrez un entier entre {} et {}."
|
||||
|
||||
frame_samp = "Entrez l'espacement temporel des images en secondes [{}-{}] : "
|
||||
frame_samp = "Entrez l'espacement temporel en secondes entre les images [{}-{}] : "
|
||||
frame_samp_err = "'Erreur... veuillez entrer un nombre décimal entre {} et {}."
|
||||
|
||||
frame_height = "Entrez la hauteur des images en pixels (ratio inchangé) [{}-{}] : "
|
||||
frame_height = "Entrez la hauteur en pixels des images en sortie (ratio inchangé) [{}-{}] : "
|
||||
frame_height_err = "Erreur... veuillez entrer un nombre entier entre {} et {}."
|
||||
|
||||
video_start_datetime = "Entrez l'horodatage du début de la vidéo au format ISO (exemple : 2023-09-18T22:00:02.000) : "
|
||||
@@ -38,7 +39,7 @@ video_start_datetime_err = "Erreur... l'horodatage entré est invalide."
|
||||
|
||||
rec_timezone = "Entrez le décalage horaire par rapport à UTC (exemple pour CEST : +02:00) : "
|
||||
|
||||
time_offset = "Entrez le décalage temporel entre la vidéo et le GPX en secondes [{}-{}] : "
|
||||
time_offset = "Entrez le décalage temporel avec la trace GPS en secondes [{}-{}] : "
|
||||
time_offset_err = "Erreur... veuillez entrer un nombre décimal entre {} et {}."
|
||||
|
||||
[ui.paths]
|
||||
|
||||
@@ -8,7 +8,7 @@ Designed for contribution to street-level imagery projects like Mapillary or Pan
|
||||
|
||||
__author__ = "Lucas MATHIEU (@campanu)"
|
||||
__license__ = "AGPL-3.0-or-later"
|
||||
__version__ = "2.0-alpha2"
|
||||
__version__ = "2.0-alpha7"
|
||||
__maintainer__ = "Lucas MATHIEU (@campanu)"
|
||||
__email__ = "campanu@luc-geo.fr"
|
||||
|
||||
@@ -18,6 +18,7 @@ import platform
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import cv2
|
||||
import piexif
|
||||
from tomlkit import dumps, loads
|
||||
from tqdm import tqdm
|
||||
from exif import Image, GpsAltitudeRef
|
||||
@@ -49,7 +50,7 @@ def byte_multiple(size):
|
||||
|
||||
|
||||
# Start
|
||||
print('# video2geoframes.py')
|
||||
print('# video2geoframes.py ({})'.format(__version__))
|
||||
|
||||
# Configuration settings
|
||||
base_path = unix_path(os.path.dirname(__file__))
|
||||
@@ -59,9 +60,13 @@ ini_file_err = False
|
||||
## Default values
|
||||
locale = 'en_us'
|
||||
min_frame_samp = 0.5
|
||||
max_frame_samp = float(60)
|
||||
max_frame_samp = 60.0
|
||||
min_timelapse_fps = 1
|
||||
max_timelapse_fps = 15
|
||||
min_frame_height = 480
|
||||
max_frame_height = 9000
|
||||
min_time_offset = -10.0
|
||||
max_time_offset = 10.0
|
||||
|
||||
## Platform-dependent commands
|
||||
if platform.system() == 'Windows':
|
||||
@@ -153,6 +158,13 @@ else:
|
||||
print('{}\n'.format(locale_toml['ui']['paths']['path_err']))
|
||||
True
|
||||
|
||||
### Video metadatas extraction
|
||||
video = cv2.VideoCapture(video_path)
|
||||
video_fps = video.get(cv2.CAP_PROP_FPS)
|
||||
video_width = video.get(cv2.CAP_PROP_FRAME_WIDTH)
|
||||
video_height = video.get(cv2.CAP_PROP_FRAME_HEIGHT)
|
||||
video_total_frames = int(video.get(cv2.CAP_PROP_FRAME_COUNT))
|
||||
|
||||
### GPS track file
|
||||
while True:
|
||||
try:
|
||||
@@ -194,7 +206,6 @@ else:
|
||||
|
||||
else:
|
||||
### Frame sampling parameter
|
||||
|
||||
while True:
|
||||
try:
|
||||
frame_sampling = float(input(locale_toml['ui']['parameters']['frame_samp'].format(min_frame_samp,
|
||||
@@ -210,8 +221,8 @@ else:
|
||||
True
|
||||
|
||||
## Frame height parameter
|
||||
min_frame_height = 480
|
||||
max_frame_height = 6000
|
||||
if video_height <= max_frame_height:
|
||||
max_frame_height = int(round(video_height, 0))
|
||||
|
||||
while True:
|
||||
try:
|
||||
@@ -220,6 +231,8 @@ else:
|
||||
|
||||
if max_frame_height >= frame_height >= min_frame_height:
|
||||
break
|
||||
elif frame_height == 0:
|
||||
break
|
||||
else:
|
||||
print(locale_toml['ui']['parameters']['frame_height_err'].format(min_frame_height, max_frame_height))
|
||||
True
|
||||
@@ -241,12 +254,10 @@ else:
|
||||
video_rec_timezone = input(locale_toml['ui']['parameters']['rec_timezone'])
|
||||
|
||||
### Time offset parameter
|
||||
min_time_offset = -10.0
|
||||
max_time_offset = 10.0
|
||||
|
||||
while True:
|
||||
try:
|
||||
time_offset = float(input(locale_toml['ui']['parameters']['time_offset'].format(min_time_offset, max_time_offset)))
|
||||
time_offset = float(input(locale_toml['ui']['parameters']['time_offset'].format(min_time_offset,
|
||||
max_time_offset)))
|
||||
|
||||
if max_time_offset >= frame_sampling >= min_time_offset:
|
||||
break
|
||||
@@ -257,33 +268,29 @@ else:
|
||||
print(locale_toml['ui']['parameters']['time_offset_err'].format(min_time_offset, max_time_offset))
|
||||
True
|
||||
|
||||
### User-defined metadata
|
||||
## User-defined metadata
|
||||
print('\n{}'.format(locale_toml['ui']['info']['tags_title']))
|
||||
|
||||
make = input(locale_toml['ui']['metadatas']['make'])
|
||||
model = input(locale_toml['ui']['metadatas']['model'])
|
||||
author = input(locale_toml['ui']['metadatas']['author'])
|
||||
|
||||
# Video metadatas extraction
|
||||
# Video metadatas formatting
|
||||
print('\n{}'.format(locale_toml['processing']['reading_metadatas']))
|
||||
|
||||
video = cv2.VideoCapture(video_path)
|
||||
video_fps = video.get(cv2.CAP_PROP_FPS)
|
||||
video_width = video.get(cv2.CAP_PROP_FRAME_WIDTH)
|
||||
video_height = video.get(cv2.CAP_PROP_FRAME_HEIGHT)
|
||||
video_total_frames = int(video.get(cv2.CAP_PROP_FRAME_COUNT))
|
||||
|
||||
video_file_name = os.path.basename(video_path)
|
||||
video_file_size = byte_multiple(os.stat(video_path).st_size)
|
||||
video_duration = video_total_frames / video_fps
|
||||
|
||||
video_start_datetime_obj = video_start_datetime_obj + timedelta(seconds=time_offset)
|
||||
video_start_datetime = video_start_datetime_obj.strftime('%Y:%m:%d %H:%M:%S')
|
||||
video_start_subsectime = video_start_datetime_obj.strftime('%f')
|
||||
video_start_datetime = video_start_datetime_obj.strftime('%Y-%m-%d %H:%M:%S')
|
||||
video_start_subsectime = int(video_start_datetime_obj.strftime('%f') / 1000)
|
||||
|
||||
# Metadata recap
|
||||
print('\n{}'.format(locale_toml['ui']['info']['metadatas'].format(video_file_name,
|
||||
round(video_file_size[0], 3), video_file_size[1],
|
||||
video_duration, video_start_datetime,
|
||||
int(int(video_start_subsectime) / 1000),
|
||||
'{:03d}'.format(video_start_subsectime),
|
||||
video_rec_timezone)))
|
||||
|
||||
# Output folder creation
|
||||
@@ -291,7 +298,9 @@ output_folder = '{}/{}'.format(output_folder, video_file_name)
|
||||
existing_path(output_folder)
|
||||
|
||||
# Processes
|
||||
## Frame sampling + tagging (OpenCV + exif)
|
||||
## Frame sampling + tagging (OpenCV + piexif)
|
||||
print('\n{}'.format(locale_toml['processing']['sampling']))
|
||||
|
||||
i = 0
|
||||
|
||||
if timelapse == user_agree:
|
||||
@@ -299,7 +308,7 @@ if timelapse == user_agree:
|
||||
else:
|
||||
frame_interval = frame_sampling
|
||||
|
||||
cv2_tqdm_unit = " {}".format(locale_toml['ui']['units']['cv2_tqdm'])
|
||||
cv2_tqdm_unit = locale_toml['ui']['units']['cv2_tqdm']
|
||||
cv2_tqdm_range = int(video_duration / frame_interval)
|
||||
|
||||
for i in tqdm(range(cv2_tqdm_range), unit=cv2_tqdm_unit):
|
||||
@@ -307,37 +316,72 @@ for i in tqdm(range(cv2_tqdm_range), unit=cv2_tqdm_unit):
|
||||
video.set(cv2.CAP_PROP_POS_MSEC, t)
|
||||
ret, frame = video.read()
|
||||
|
||||
### Image resizing
|
||||
if frame_height != 0:
|
||||
resize_factor = video_height / frame_height
|
||||
image_height = frame_height
|
||||
image_width = int(round(video_height * resize_factor), 0)
|
||||
|
||||
frame = cv2.resize(frame, (image_width, image_height), interpolation=cv2.INTER_LANCZOS4)
|
||||
|
||||
frame_name = '{:05d}'.format(i)
|
||||
image_name = "{}_f{}.jpg".format(video_file_name.split('.')[0], frame_name)
|
||||
image_path = "{}/{}".format(output_folder, image_name)
|
||||
|
||||
cv2.imwrite(image_path, frame, [cv2.IMWRITE_JPEG_QUALITY, 88, cv2.IMWRITE_JPEG_PROGRESSIVE, 1, cv2.IMWRITE_JPEG_SAMPLING_FACTOR, 0x411111])
|
||||
|
||||
## Time tags preparation
|
||||
## Time tags formatting
|
||||
time_shift = i * frame_sampling
|
||||
current_datetime_obj = video_start_datetime_obj + timedelta(seconds=time_shift)
|
||||
current_datetime = current_datetime_obj.strftime('%Y:%m:%d %H:%M:%S')
|
||||
current_subsec_time = int(int(current_datetime_obj.strftime('%f')) / 1000)
|
||||
|
||||
with open(image_path, 'rb') as image_file:
|
||||
image = Image(image_file)
|
||||
image.make = make
|
||||
image.model = model
|
||||
image.author = author
|
||||
image.copyright = "{}, {}".format(author, video_start_datetime_obj.strftime('%Y'))
|
||||
image.datetime_original = current_datetime
|
||||
#image.offset_time_original = video_rec_timezone
|
||||
# exif code
|
||||
# with open(image_path, 'rb') as image_file:
|
||||
# image = Image(image_file)
|
||||
# image.make = make
|
||||
# image.model = model
|
||||
# image.author = author
|
||||
# image.copyright = "{}, {}".format(author, video_start_datetime_obj.strftime('%Y'))
|
||||
# image.datetime_original = current_datetime
|
||||
# #image.offset_time_original = video_rec_timezone
|
||||
#
|
||||
# if current_subsec_time > 0:
|
||||
# image.subsec_time_original = str(current_subsec_time)
|
||||
#
|
||||
# with open(image_path, 'wb') as tagged_image_file:
|
||||
# tagged_image_file.write(image.get_file())
|
||||
|
||||
# piexif code
|
||||
image_exif = piexif.load(image_path)
|
||||
|
||||
image_tags = {
|
||||
piexif.ImageIFD.Make: make,
|
||||
piexif.ImageIFD.Model: model,
|
||||
piexif.ImageIFD.Artist: author,
|
||||
piexif.ImageIFD.Copyright: "{}, {}".format(author, video_start_datetime_obj.strftime('%Y')),
|
||||
piexif.ImageIFD.Software: 'video2geoframes.py (v{})'.format(__version__)
|
||||
}
|
||||
|
||||
exif_tags = {
|
||||
piexif.ExifIFD.DateTimeOriginal: current_datetime,
|
||||
piexif.ExifIFD.OffsetTimeOriginal: video_rec_timezone
|
||||
}
|
||||
|
||||
if current_subsec_time > 0:
|
||||
image.subsec_time_original = str(current_subsec_time)
|
||||
exif_tags[piexif.ExifIFD.SubSecTime] = str(current_subsec_time)
|
||||
|
||||
with open(image_path, 'wb') as tagged_image_file:
|
||||
tagged_image_file.write(image.get_file())
|
||||
image_exif['0th'] = image_tags
|
||||
image_exif['Exif'] = exif_tags
|
||||
|
||||
image_exif_bytes = piexif.dump(image_exif)
|
||||
piexif.insert(image_exif_bytes, image_path)
|
||||
|
||||
i += 1
|
||||
|
||||
# Geo-tagging (ExifTool)
|
||||
print('\n{}'.format(locale_toml['processing']['geotagging']))
|
||||
|
||||
geotagging_cmd = '{} -P -geotag "{}" "-geotime<SubSecDateTimeOriginal" -overwrite_original "{}/{}_f*.jpg"'\
|
||||
.format(exiftool_path, gps_track_path, output_folder, video_file_name.split('.')[0])
|
||||
geotagging = os.system(geotagging_cmd)
|
||||
|
||||
Reference in New Issue
Block a user