Commit
·
3cb8cb6
1
Parent(s):
9d13d0d
performance optimization
Browse files- app.py +13 -20
- example_pyav.md +91 -0
app.py
CHANGED
@@ -39,8 +39,7 @@ from tqdm import tqdm
|
|
39 |
import imageio
|
40 |
import av
|
41 |
import uuid
|
42 |
-
import
|
43 |
-
import shutil
|
44 |
from pathlib import Path
|
45 |
from typing import Dict, Any, List, Optional, Tuple, Union
|
46 |
|
@@ -171,7 +170,7 @@ if not APP_STATE["torch_compile_applied"] and ENABLE_TORCH_COMPILATION:
|
|
171 |
|
172 |
def frames_to_mp4_base64(frames, fps = 15):
|
173 |
"""
|
174 |
-
Convert frames directly to base64 data URI using PyAV.
|
175 |
|
176 |
Args:
|
177 |
frames: List of numpy arrays (HWC, RGB, uint8)
|
@@ -185,14 +184,12 @@ def frames_to_mp4_base64(frames, fps = 15):
|
|
185 |
|
186 |
height, width = frames[0].shape[:2]
|
187 |
|
188 |
-
# Create
|
189 |
-
|
190 |
-
temp_filepath = temp_file.name
|
191 |
-
temp_file.close()
|
192 |
|
193 |
try:
|
194 |
-
# Create container for MP4 format
|
195 |
-
container = av.open(
|
196 |
|
197 |
# Add video stream with fast settings
|
198 |
stream = container.add_stream('h264', rate=fps)
|
@@ -222,18 +219,14 @@ def frames_to_mp4_base64(frames, fps = 15):
|
|
222 |
finally:
|
223 |
container.close()
|
224 |
|
225 |
-
#
|
226 |
-
|
227 |
-
|
228 |
-
|
229 |
-
return f"data:video/mp4;base64,{base64_data}"
|
230 |
|
231 |
-
|
232 |
-
|
233 |
-
|
234 |
-
os.unlink(temp_filepath)
|
235 |
-
|
236 |
-
return "data:video/mp4;base64,"
|
237 |
|
238 |
# note: we set use_taehv to be able to use other resolutions
|
239 |
# this might impact performance
|
|
|
39 |
import imageio
|
40 |
import av
|
41 |
import uuid
|
42 |
+
import io
|
|
|
43 |
from pathlib import Path
|
44 |
from typing import Dict, Any, List, Optional, Tuple, Union
|
45 |
|
|
|
170 |
|
171 |
def frames_to_mp4_base64(frames, fps = 15):
|
172 |
"""
|
173 |
+
Convert frames directly to base64 data URI using PyAV with in-memory file.
|
174 |
|
175 |
Args:
|
176 |
frames: List of numpy arrays (HWC, RGB, uint8)
|
|
|
184 |
|
185 |
height, width = frames[0].shape[:2]
|
186 |
|
187 |
+
# Create BytesIO "in memory file"
|
188 |
+
output_memory_file = io.BytesIO()
|
|
|
|
|
189 |
|
190 |
try:
|
191 |
+
# Create container for MP4 format using in-memory file
|
192 |
+
container = av.open(output_memory_file, mode='w', format='mp4')
|
193 |
|
194 |
# Add video stream with fast settings
|
195 |
stream = container.add_stream('h264', rate=fps)
|
|
|
219 |
finally:
|
220 |
container.close()
|
221 |
|
222 |
+
# Get video data from in-memory file and encode to base64
|
223 |
+
video_data = output_memory_file.getbuffer()
|
224 |
+
base64_data = base64.b64encode(video_data).decode('utf-8')
|
225 |
+
return f"data:video/mp4;base64,{base64_data}"
|
|
|
226 |
|
227 |
+
except Exception as e:
|
228 |
+
print(f"Error encoding video: {e}")
|
229 |
+
return "data:video/mp4;base64,"
|
|
|
|
|
|
|
230 |
|
231 |
# note: we set use_taehv to be able to use other resolutions
|
232 |
# this might impact performance
|
example_pyav.md
ADDED
@@ -0,0 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
We may use [PyAV](https://github.com/PyAV-Org/PyAV) for encoding "in memory file".
|
2 |
+
|
3 |
+
PyAV is a Pythonic binding for the [FFmpeg](https://ffmpeg.org/) libraries.
|
4 |
+
The interface is relatively low level, but it allows us to do things that are not possible using other FFmpeg bindings.
|
5 |
+
|
6 |
+
Here are the main stages for creating MP4 in memory using PyAV:
|
7 |
+
|
8 |
+
* Create BytesIO "in memory file":
|
9 |
+
|
10 |
+
output_memory_file = io.BytesIO()
|
11 |
+
|
12 |
+
|
13 |
+
* Use PyAV to open "in memory file" as MP4 video output file:
|
14 |
+
|
15 |
+
output = av.open(output_memory_file, 'w', format="mp4")
|
16 |
+
|
17 |
+
|
18 |
+
* Add H.264 video stream to the MP4 container, and set codec parameters:
|
19 |
+
|
20 |
+
stream = output.add_stream('h264', str(fps))
|
21 |
+
stream.width = width
|
22 |
+
stream.height = height
|
23 |
+
stream.pix_fmt = 'yuv444p'
|
24 |
+
stream.options = {'crf': '17'}
|
25 |
+
|
26 |
+
|
27 |
+
* Iterate the OpenCV images, convert image to PyAV `VideoFrame`, encode, and "Mux":
|
28 |
+
|
29 |
+
for i in range(n_frmaes):
|
30 |
+
img = make_sample_image(i) # Create OpenCV image for testing (resolution 192x108, pixel format BGR).
|
31 |
+
frame = av.VideoFrame.from_ndarray(img, format='bgr24')
|
32 |
+
packet = stream.encode(frame)
|
33 |
+
output.mux(packet)
|
34 |
+
|
35 |
+
|
36 |
+
* Flush the encoder and close the "in memory" file:
|
37 |
+
|
38 |
+
packet = stream.encode(None)
|
39 |
+
output.mux(packet)
|
40 |
+
output.close()
|
41 |
+
|
42 |
+
|
43 |
+
|
44 |
+
* * *
|
45 |
+
|
46 |
+
The following code samples encode 100 synthetic images to "in memory" MP4 memory file.
|
47 |
+
Each synthetic image applies OpenCV image, with sequential blue frame number (used for testing).
|
48 |
+
At the end, the memory file is written to `output.mp4` file for testing.
|
49 |
+
|
50 |
+
import numpy as np
|
51 |
+
import cv2
|
52 |
+
import av
|
53 |
+
import io
|
54 |
+
|
55 |
+
n_frmaes = 100 # Select number of frames (for testing).
|
56 |
+
width, height, fps = 192, 108, 23.976 # Select video resolution and framerate.
|
57 |
+
|
58 |
+
output_memory_file = io.BytesIO() # Create BytesIO "in memory file".
|
59 |
+
|
60 |
+
output = av.open(output_memory_file, 'w', format="mp4") # Open "in memory file" as MP4 video output
|
61 |
+
stream = output.add_stream('h264', str(fps)) # Add H.264 video stream to the MP4 container, with framerate = fps.
|
62 |
+
stream.width = width # Set frame width
|
63 |
+
stream.height = height # Set frame height
|
64 |
+
stream.pix_fmt = 'yuv444p' # Select yuv444p pixel format (better quality than default yuv420p).
|
65 |
+
stream.options = {'crf': '17'} # Select low crf for high quality (the price is larger file size).
|
66 |
+
|
67 |
+
|
68 |
+
def make_sample_image(i):
|
69 |
+
""" Build synthetic "raw BGR" image for testing """
|
70 |
+
p = width//60
|
71 |
+
img = np.full((height, width, 3), 60, np.uint8)
|
72 |
+
cv2.putText(img, str(i+1), (width//2-p*10*len(str(i+1)), height//2+p*10), cv2.FONT_HERSHEY_DUPLEX, p, (255, 30, 30), p*2) # Blue number
|
73 |
+
return img
|
74 |
+
|
75 |
+
|
76 |
+
# Iterate the created images, encode and write to MP4 memory file.
|
77 |
+
for i in range(n_frmaes):
|
78 |
+
img = make_sample_image(i) # Create OpenCV image for testing (resolution 192x108, pixel format BGR).
|
79 |
+
frame = av.VideoFrame.from_ndarray(img, format='bgr24') # Convert image from NumPy Array to frame.
|
80 |
+
packet = stream.encode(frame) # Encode video frame
|
81 |
+
output.mux(packet) # "Mux" the encoded frame (add the encoded frame to MP4 file).
|
82 |
+
|
83 |
+
# Flush the encoder
|
84 |
+
packet = stream.encode(None)
|
85 |
+
output.mux(packet)
|
86 |
+
|
87 |
+
output.close()
|
88 |
+
|
89 |
+
# Write BytesIO from RAM to file, for testing
|
90 |
+
with open("output.mp4", "wb") as f:
|
91 |
+
f.write(output_memory_file.getbuffer())
|