1212# See the License for the specific language governing permissions and
1313# limitations under the License.
1414
15- """Shared video utilities: VideoReaderWrapper, read_video_decord , sample_frames, read_frames_decord ."""
15+ """Shared video utilities: VideoReaderWrapper, read_video_paddlecodec , sample_frames, read_frames_paddlecodec ."""
1616
1717import datetime
1818import hashlib
2626from typing import Optional , Union
2727
2828import numpy as np
29+ import paddle
2930from PIL import Image
3031
3132from fastdeploy .input .image_processors .common import ceil_by_factor , floor_by_factor
32- from fastdeploy .utils import data_processor_logger
33+ from fastdeploy .utils import data_processor_logger , get_logger
34+
35+ logger = get_logger ("video_utils" )
3336
3437__all__ = [
3538 "VideoReaderWrapper" ,
36- "read_video_decord " ,
39+ "read_video_paddlecodec " ,
3740 "sample_frames" ,
3841 "sample_frames_qwen" ,
3942 "sample_frames_paddleocr" ,
4043 "get_frame_indices" ,
41- "read_frames_decord " ,
44+ "read_frames_paddlecodec " ,
4245 "EXTRACTED_FRAME_DIR" ,
4346 "get_filename" ,
4447]
@@ -54,15 +57,20 @@ def _is_gif(data: bytes) -> bool:
5457 return data [:6 ] in (b"GIF87a" , b"GIF89a" )
5558
5659
57- class VideoReaderWrapper :
58- """decord.VideoReader wrapper that fixes a memory leak and adds GIF support.
60+ class _NumpyFrame :
61+ """Wrapper so that frame[idx].asnumpy() keeps working with paddlecodec."""
5962
60- Reference: https://github.com/dmlc/decord/issues/208
61- """
63+ def __init__ (self , array ):
64+ self ._array = array
65+
66+ def asnumpy (self ):
67+ return self ._array
6268
63- def __init__ (self , video_path , * args , ** kwargs ):
64- import decord
6569
70+ class VideoReaderWrapper :
71+ """paddlecodec VideoDecoder wrapper with GIF support."""
72+
73+ def __init__ (self , video_path , * args , ** kwargs ):
6674 try :
6775 # moviepy 1.0
6876 import moviepy .editor as mp
@@ -101,22 +109,53 @@ def __init__(self, video_path, *args, **kwargs):
101109 video_path = mp4_path
102110 self .original_file = video_path # temp mp4, cleaned up in __del__
103111
104- self ._reader = decord .VideoReader (video_path , * args , ** kwargs )
105- self ._reader .seek (0 )
112+ with paddle .use_compat_guard (enable = True , scope = {"torchcodec" }):
113+ try :
114+ import sys
115+
116+ from torchcodec .decoders import VideoDecoder
117+
118+ sys .modules ["torchcodec" ] = None
119+ except (ImportError , RuntimeError ) as e :
120+ logger .error (
121+ f"Failed to load 'torchcodec' backend via Paddle proxy.\n "
122+ f" - Common Causes:\n "
123+ f" 1. Conflict with official 'torch' or 'torchcodec' packages.\n "
124+ f" 2. Missing FFmpeg libraries or System library mismatch (CXXABI).\n "
125+ f" - Recommended Fix Steps:\n "
126+ f" 1. Install dependencies: `conda install ffmpeg -c conda-forge` or `apt-get update && apt-get install ffmpeg` \n "
127+ f" 2. Uninstall conflicts: `pip uninstall torchcodec paddlecodec -y`\n "
128+ f" 3. Reinstall packages: `pip install paddlecodec --force-reinstall`\n "
129+ f" - If you encounter 'CXXABI' or 'libstdc++' errors, your system libraries might be outdated.\n "
130+ f" Try prioritizing Conda libraries by running: `LD_LIBRARY_PATH=$CONDA_PREFIX/lib:$LD_LIBRARY_PATH python your_script.py`\n "
131+ f" - Original Error: { e } "
132+ )
133+ raise
134+ PADDLECODEC_NUM_THREADS = int (os .environ .get ("PADDLECODEC_NUM_THREADS" , 0 ))
135+ self ._decoder = VideoDecoder (
136+ video_path ,
137+ seek_mode = "exact" ,
138+ num_ffmpeg_threads = PADDLECODEC_NUM_THREADS ,
139+ device = kwargs .get ("device" , "cpu" ),
140+ dimension_order = "NHWC" ,
141+ )
106142
107143 def __len__ (self ):
108- return len ( self ._reader )
144+ return self ._decoder . metadata . num_frames
109145
110146 def __getitem__ (self , key ):
111- frames = self ._reader [key ]
112- self ._reader .seek (0 )
113- return frames
147+ if isinstance (key , (int , np .integer )):
148+ frame = self ._decoder .get_frames_at (indices = [int (key )]).data [0 ]
149+ return _NumpyFrame (frame .numpy ())
150+ if isinstance (key , slice ):
151+ indices = list (range (* key .indices (len (self ))))
152+ else :
153+ indices = list (key ) if not isinstance (key , list ) else key
154+ frames = self ._decoder .get_frames_at (indices = indices ).data
155+ return _NumpyFrame (frames .numpy ())
114156
115157 def get_avg_fps (self ):
116- return self ._reader .get_avg_fps ()
117-
118- def seek (self , pos ):
119- return self ._reader .seek (pos )
158+ return self ._decoder .metadata .average_fps
120159
121160 def __del__ (self ):
122161 original_file = getattr (self , "original_file" , None )
@@ -128,11 +167,11 @@ def __del__(self):
128167
129168
130169# ---------------------------------------------------------------------------
131- # read_video_decord
170+ # read_video_paddlecodec
132171# ---------------------------------------------------------------------------
133172
134173
135- def read_video_decord (video_path , save_to_disk : bool = False ):
174+ def read_video_paddlecodec (video_path , save_to_disk : bool = False ):
136175 """Load a video file and return (video_reader, video_meta, video_path).
137176
138177 video_meta contains keys: "fps", "duration", "num_of_frame".
@@ -306,7 +345,7 @@ def get_filename(url=None):
306345
307346
308347# ---------------------------------------------------------------------------
309- # get_frame_indices / read_frames_decord
348+ # get_frame_indices / read_frames_paddlecodec
310349# (migrated from ernie4_5_vl_processor/process_video.py)
311350# ---------------------------------------------------------------------------
312351
@@ -376,7 +415,7 @@ def get_frame_indices(
376415 return frame_indices
377416
378417
379- def read_frames_decord (
418+ def read_frames_paddlecodec (
380419 video_path ,
381420 video_reader ,
382421 video_meta ,
@@ -389,7 +428,7 @@ def read_frames_decord(
389428 frame_indices = None ,
390429 tol = 10 ,
391430):
392- """Read frames from a video using decord , with retry logic for corrupt frames."""
431+ """Read frames from a video using paddlecodec , with retry logic for corrupt frames."""
393432 if cache_dir is None :
394433 cache_dir = EXTRACTED_FRAME_DIR
395434
0 commit comments