|
24 | 24 | # %% |
25 | 25 | # First, some boilerplate: we'll download a short video from the web, and |
26 | 26 | # use ffmpeg to create a longer version by repeating it multiple times. We'll end up |
27 | | -# with two videos: a short one of approximately 3 minutes and a long one of about 13 minutes. |
| 27 | +# with two videos: a short one of approximately 14 seconds and a long one of about 14 minutes. |
28 | 28 | # You can ignore this part and skip below to :ref:`frame_mappings_creation`. |
29 | 29 |
|
30 | 30 | import tempfile |
31 | 31 | from pathlib import Path |
32 | 32 | import subprocess |
33 | 33 | import requests |
34 | 34 |
|
35 | | -url = "https://download.pytorch.org/torchaudio/tutorial-assets/stream-api/NASAs_Most_Scientifically_Complex_Space_Observatory_Requires_Precision-MP4_small.mp4" |
| 35 | +# Video source: https://www.pexels.com/video/dog-eating-854132/ |
| 36 | +# License: CC0. Author: Coverr. |
| 37 | +url = "https://videos.pexels.com/video-files/854132/854132-sd_640_360_25fps.mp4" |
36 | 38 | response = requests.get(url, headers={"User-Agent": ""}) |
37 | 39 | if response.status_code != 200: |
38 | 40 | raise RuntimeError(f"Failed to download video. {response.status_code = }.") |
|
46 | 48 | long_video_path = Path(temp_dir) / "long_video.mp4" |
47 | 49 | ffmpeg_command = [ |
48 | 50 | "ffmpeg", |
49 | | - "-stream_loop", "3", # repeat video 3 times to get a ~13 min video |
| 51 | + "-stream_loop", "80", # repeat video 80 times to get a ~18 min video |
50 | 52 | "-i", f"{short_video_path}", |
51 | 53 | "-c", "copy", |
52 | 54 | f"{long_video_path}" |
|
77 | 79 | from time import perf_counter_ns |
78 | 80 | import json |
79 | 81 |
|
| 82 | + |
| 83 | +# Lets define a simple function to run ffprobe on a video's first stream index, then writes the results in output_json_path. |
| 84 | +def generate_frame_mappings(video_path, output_json_path, stream_index): |
| 85 | + ffprobe_cmd = ["ffprobe", "-i", f"{video_path}", "-select_streams", f"{stream_index}", "-show_frames", "-show_entries", "frame=pts,duration,key_frame", "-of", "json"] |
| 86 | + print(f"Running ffprobe:\n{' '.join(ffprobe_cmd)}") |
| 87 | + ffprobe_result = subprocess.run(ffprobe_cmd, check=True, capture_output=True, text=True) |
| 88 | + with open(output_json_path, "w") as f: |
| 89 | + f.write(ffprobe_result.stdout) |
| 90 | + |
| 91 | + |
80 | 92 | stream_index = 0 |
81 | 93 | long_json_path = Path(temp_dir) / "long_custom_frame_mappings.json" |
82 | 94 | short_json_path = Path(temp_dir) / "short_custom_frame_mappings.json" |
83 | 95 |
|
84 | | -ffprobe_cmd = ["ffprobe", "-i", f"{long_video_path}", "-select_streams", f"{stream_index}", "-show_frames", "-show_entries", "frame=pts,duration,key_frame", "-of", "json"] |
85 | | -ffprobe_result = subprocess.run(ffprobe_cmd, check=True, capture_output=True, text=True) |
86 | | -with open(long_json_path, "w") as f: |
87 | | - f.write(ffprobe_result.stdout) |
88 | | - |
89 | | -ffprobe_cmd = ["ffprobe", "-i", f"{short_video_path}", "-select_streams", f"{stream_index}", "-show_frames", "-show_entries", "frame=pts,duration,key_frame", "-of", "json"] |
90 | | -ffprobe_result = subprocess.run(ffprobe_cmd, check=True, capture_output=True, text=True) |
91 | | -with open(short_json_path, "w") as f: |
92 | | - f.write(ffprobe_result.stdout) |
93 | | - |
94 | | -sample_data = json.loads(ffprobe_result.stdout) |
95 | | -print("Data structure of custom frame mappings:") |
| 96 | +generate_frame_mappings(long_video_path, long_json_path, stream_index) |
| 97 | +generate_frame_mappings(short_video_path, short_json_path, stream_index) |
| 98 | +with open(short_json_path) as f: |
| 99 | + sample_data = json.loads(f.read()) |
| 100 | +print("Sample of fields in custom frame mappings:") |
96 | 101 | for frame in sample_data["frames"][:3]: |
97 | | - print(f"{frame}") |
| 102 | + print(f"{frame['key_frame'] = }, {frame['pts'] = }, {frame['duration'] = }") |
98 | 103 |
|
99 | 104 | # %% |
100 | 105 | # .. _custom_frame_mappings_perf_creation: |
|
103 | 108 | # -------------------------------------- |
104 | 109 | # |
105 | 110 | # Custom frame mappings affect the **creation** of a :class:`~torchcodec.decoders.VideoDecoder` |
106 | | -# object. As video length increases, the performance gain compared to exact mode increases. |
| 111 | +# object. As video length or resolution increases, the performance gain compared to exact mode increases. |
107 | 112 | # |
108 | 113 |
|
109 | 114 | import torch |
110 | 115 |
|
111 | 116 |
|
| 117 | +# Here, we define a benchmarking function, with the option to seek to the start of a file_like. |
112 | 118 | def bench(f, file_like=False, average_over=50, warmup=2, **f_kwargs): |
113 | 119 | for _ in range(warmup): |
114 | 120 | f(**f_kwargs) |
@@ -145,9 +151,9 @@ def bench(f, file_like=False, average_over=50, warmup=2, **f_kwargs): |
145 | 151 | # Performance: Frame decoding with custom frame mappings |
146 | 152 | # ------------------------------------------------------ |
147 | 153 | # |
148 | | -# Although using custom_frame_mappings only impacts the initialization speed of |
| 154 | +# Although using ``custom_frame_mappings`` only impacts the initialization speed of |
149 | 155 | # :class:`~torchcodec.decoders.VideoDecoder`, decoding workflows |
150 | | -# usually involve creating a :class:`~torchcodec.decoders.VideoDecoder` instance, |
| 156 | +# involve creating a :class:`~torchcodec.decoders.VideoDecoder` instance, |
151 | 157 | # so the performance benefits are realized. |
152 | 158 |
|
153 | 159 |
|
|
0 commit comments