Skip to content

Commit 6d1a33b

Browse files
No public description
PiperOrigin-RevId: 756455149
1 parent c59931b commit 6d1a33b

File tree

2 files changed

+185
-0
lines changed

2 files changed

+185
-0
lines changed
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
# Copyright 2024 The TensorFlow Authors. All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""This script is designed to handle video and image processing tasks.
16+
17+
The script relies heavily on the ffmpeg library for video and image
18+
processing and ffprobe for metadata extraction.
19+
20+
It focuses on three primary functionalities:
21+
1) Splitting a video into individual frames, and
22+
2) Extracting the creation time of the video from its metadata.
23+
3) Extracting the creation time of an image from its metadata.
24+
"""
25+
26+
import datetime
27+
import os
28+
import ffmpeg
29+
import PIL
30+
from PIL import Image
31+
32+
33+
def split_video_to_frames(
34+
video_name: str, folder_name: str, fps: int = 30
35+
) -> None:
36+
"""Split the video into frames using ffmpeg-python.
37+
38+
Args:
39+
video_name: The name/path of the video file.
40+
folder_name: The name/path of the folder to store frames.
41+
fps: Frames per second to extract from the video.
42+
"""
43+
# Ensure the folder exists
44+
if not os.path.exists(folder_name):
45+
os.makedirs(folder_name)
46+
47+
(
48+
ffmpeg.input(video_name)
49+
.filter('fps', fps=fps)
50+
.output(os.path.join(folder_name, 'frame_%06d.png'))
51+
.run(capture_stdout=True, capture_stderr=True)
52+
)
53+
54+
55+
def find_creation_time(video: str) -> str:
56+
"""Find the creation time of a video file.
57+
58+
Args:
59+
video: A string path to the video file.
60+
61+
Returns:
62+
A string representing the formatted creation time of the video in
63+
"YYYY-MM-DD HH:MM:SS" format.
64+
"""
65+
metadata = ffmpeg.probe(video)['streams']
66+
timestamp_str = metadata[0]['tags']['creation_time']
67+
return datetime.datetime.strptime(
68+
timestamp_str, '%Y-%m-%dT%H:%M:%S.%fZ'
69+
).strftime('%Y-%m-%d %H:%M:%S')
70+
71+
72+
def get_image_creation_time(image_path):
73+
"""Retrieves the creation time of an image, trying multiple methods.
74+
75+
Args:
76+
image_path: The path to the image file.
77+
78+
Returns:
79+
A string representing the creation time in the format "%Y-%m-%d %H:%M:%S" if
80+
found, otherwise returns "Creation time not found".
81+
"""
82+
83+
try:
84+
# 1. Try EXIF data (if available)
85+
image = Image.open(image_path)
86+
exif_data = image.getexif()
87+
if exif_data:
88+
datetime_tag_id = 36867 # Tag ID for "DateTimeOriginal"
89+
datetime_str = exif_data.get(datetime_tag_id)
90+
if datetime_str:
91+
return datetime.datetime.strptime(
92+
datetime_str, '%Y:%m:%d %H:%M:%S'
93+
).strftime('%Y-%m-%d %H:%M:%S')
94+
95+
# 2. Try file modification time (less accurate, but better than nothing)
96+
file_modified_time = os.path.getmtime(image_path)
97+
return datetime.datetime.fromtimestamp(
98+
file_modified_time
99+
).strftime('%Y-%m-%d %H:%M:%S')
100+
101+
except FileNotFoundError:
102+
return 'Image not found'
103+
except PIL.UnidentifiedImageError as e:
104+
return f'Error: {e}'
105+
except Exception as e: # pylint: disable=broad-exception-caught
106+
return f'An unexpected error occurred: {e}'
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# Copyright 2024 The TensorFlow Authors. All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import datetime
16+
import time
17+
import unittest
18+
from unittest import mock
19+
import ffmpeg
20+
import PIL
21+
from official.projects.waste_identification_ml.Triton_TF_Cloud_Deployment.client import ffmpeg_ops
22+
23+
24+
class TestVideoImageProcessing(unittest.TestCase):
25+
26+
@mock.patch.object(ffmpeg, "probe", autospec=True)
27+
def test_find_creation_time(self, mock_ffprobe):
28+
mock_ffprobe.return_value = {
29+
"streams": [{"tags": {"creation_time": "2024-02-25T15:30:45.123Z"}}]
30+
}
31+
expected_time = "2024-02-25 15:30:45"
32+
self.assertEqual(ffmpeg_ops.find_creation_time("test.mp4"), expected_time)
33+
34+
@mock.patch("PIL.Image.open")
35+
def test_get_image_creation_time_exif(self, mock_open):
36+
mock_image = mock.Mock()
37+
mock_exif = mock.Mock()
38+
mock_exif.get.return_value = "2024:02:25 15:30:45"
39+
mock_image.getexif.return_value = mock_exif
40+
mock_open.return_value = mock_image
41+
42+
result = ffmpeg_ops.get_image_creation_time("dummy.jpg")
43+
self.assertEqual(result, "2024-02-25 15:30:45")
44+
45+
@mock.patch("os.path.getmtime")
46+
@mock.patch("PIL.Image.open")
47+
def test_get_image_creation_time_no_exif(self, mock_open, mock_getmtime):
48+
# Mock image and EXIF returning nothing
49+
mock_image = mock.Mock()
50+
mock_image.getexif.return_value = {} # No EXIF data
51+
mock_open.return_value = mock_image
52+
53+
# Return fixed timestamp (e.g., 2024-02-25 15:30:45)
54+
dt = datetime.datetime(2024, 2, 25, 15, 30, 45)
55+
mock_getmtime.return_value = time.mktime(dt.timetuple())
56+
57+
result = ffmpeg_ops.get_image_creation_time("dummy.jpg")
58+
self.assertEqual(result, "2024-02-25 15:30:45")
59+
60+
@mock.patch.object(PIL.Image, "open", autospec=True)
61+
def test_get_image_creation_time_file_not_found(self, mock_image_open):
62+
mock_image_open.side_effect = FileNotFoundError
63+
self.assertEqual(
64+
ffmpeg_ops.get_image_creation_time("missing.jpg"), "Image not found"
65+
)
66+
67+
@mock.patch.object(PIL.Image, "open", autospec=True)
68+
def test_get_image_creation_time_unidentified_image(self, mock_image_open):
69+
mock_image_open.side_effect = PIL.UnidentifiedImageError(
70+
"Cannot identify image"
71+
)
72+
self.assertEqual(
73+
ffmpeg_ops.get_image_creation_time("corrupt.jpg"),
74+
"Error: Cannot identify image",
75+
)
76+
77+
78+
if __name__ == "__main__":
79+
unittest.main()

0 commit comments

Comments
 (0)