Skip to content

Commit b59d30e

Browse files
committed
add reversing videos tutorial
1 parent 9cff058 commit b59d30e

File tree

5 files changed

+87
-0
lines changed

5 files changed

+87
-0
lines changed

Diff for: README.md

+1
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ This is a repository of all the tutorials of [The Python Code](https://www.thepy
173173
- [How to Concatenate Video Files in Python](https://www.thepythoncode.com/article/concatenate-video-files-in-python). ([code](python-for-multimedia/combine-video))
174174
- [How to Concatenate Audio Files in Python](https://www.thepythoncode.com/article/concatenate-audio-files-in-python). ([code](python-for-multimedia/combine-audio))
175175
- [How to Extract Frames from Video in Python](https://www.thepythoncode.com/article/extract-frames-from-videos-in-python). ([code](python-for-multimedia/extract-frames-from-video))
176+
- [How to Reverse Videos in Python](https://www.thepythoncode.com/article/reverse-video-in-python). ([code](python-for-multimedia/reverse-video))
176177

177178

178179
For any feedback, please consider pulling requests.

Diff for: python-for-multimedia/reverse-video/README.md

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# [How to Reverse Videos in Python](https://www.thepythoncode.com/article/reverse-video-in-python)
2+
To run this:
3+
- `pip3 install -r requirements.txt`
4+
-
5+
```
6+
$ python reverse_video.py Tenet-the-breach-scene-in-forward.mp4
7+
```
Binary file not shown.

Diff for: python-for-multimedia/reverse-video/requirements.txt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
tqdm
2+
moviepy
3+
numpy

Diff for: python-for-multimedia/reverse-video/reverse_video.py

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
from moviepy.editor import VideoFileClip, ImageSequenceClip
2+
import numpy as np
3+
import os
4+
from datetime import timedelta, datetime
5+
from glob import glob
6+
from tqdm import tqdm
7+
import shutil
8+
9+
# i.e if video of duration 30 seconds, saves 10 frame per second = 300 frames saved in total
10+
SAVING_FRAMES_PER_SECOND = 30
11+
12+
def format_timedelta(td):
13+
"""Utility function to format timedelta objects in a cool way (e.g 00:00:20.05)
14+
omitting microseconds and retaining milliseconds"""
15+
result = str(td)
16+
try:
17+
result, ms = result.split(".")
18+
except ValueError:
19+
return result + ".00".replace(":", "-")
20+
ms = int(ms)
21+
ms = round(ms / 1e4)
22+
return f"{result}.{ms:02}".replace(":", "-")
23+
24+
25+
def extract_frames(video_file, verbose=1):
26+
# load the video clip
27+
video_clip = VideoFileClip(video_file)
28+
# make a folder by the name of the video file
29+
filename, _ = os.path.splitext(video_file)
30+
if not os.path.isdir(filename):
31+
os.mkdir(filename)
32+
# if the SAVING_FRAMES_PER_SECOND is above video FPS, then set it to FPS (as maximum)
33+
saving_frames_per_second = min(video_clip.fps, SAVING_FRAMES_PER_SECOND)
34+
# if SAVING_FRAMES_PER_SECOND is set to 0, step is 1/fps, else 1/SAVING_FRAMES_PER_SECOND
35+
step = 1 / video_clip.fps if saving_frames_per_second == 0 else 1 / saving_frames_per_second
36+
iteration = np.arange(0, video_clip.duration, step)
37+
if verbose:
38+
iteration = tqdm(iteration, desc="Extracting video frames")
39+
# iterate over each possible frame
40+
for current_duration in iteration:
41+
# format the file name and save it
42+
frame_duration_formatted = format_timedelta(timedelta(seconds=current_duration)).replace(":", "-")
43+
frame_filename = os.path.join(filename, f"frame{frame_duration_formatted}.jpg")
44+
# save the frame with the current duration
45+
video_clip.save_frame(frame_filename, current_duration)
46+
return filename, video_clip.fps
47+
48+
49+
50+
def reverse_video(frames_path, video_fps, remove_extracted_frames=True):
51+
frame_files = glob(os.path.join(frames_path, "*"))
52+
# sort by duration in descending order
53+
frame_files = sorted(frame_files, key=lambda d: datetime.strptime(d.split("frame")[1], "%H-%M-%S.%f.jpg"), reverse=True)
54+
# calculate the FPS, getting the minimum between the original FPS and the parameter we set
55+
saving_frames_per_second = min(video_fps, SAVING_FRAMES_PER_SECOND)
56+
if saving_frames_per_second == 0:
57+
# if the parameter is set to 0, automatically set it to the original video fps
58+
saving_frames_per_second = video_fps
59+
print("Saving the video with FPS:", saving_frames_per_second)
60+
# load the frames into a image sequence clip (MoviePy)
61+
image_sequence_clip = ImageSequenceClip(frame_files, fps=saving_frames_per_second)
62+
# write the video file to disk
63+
output_filename = f"{frames_path}-inverted.mp4"
64+
image_sequence_clip.write_videofile(output_filename)
65+
if remove_extracted_frames:
66+
# if set to True, then remove the folder that contain the extracted frames
67+
shutil.rmtree(frames_path)
68+
69+
70+
71+
if __name__ == "__main__":
72+
import sys
73+
video_file = sys.argv[1]
74+
frames_folder_path, video_fps = extract_frames(video_file)
75+
reverse_video(frames_folder_path, video_fps=video_fps)
76+

0 commit comments

Comments
 (0)