Skip to content

Commit 9d3dc0e

Browse files
BingooYangYour Name
andauthored
[Others] Decord remove (#7383)
* replace decord to paddlecodec * modify format * modify data format to fix accurate issue * add test * fix test --------- Co-authored-by: Your Name <you@example.com>
1 parent 7bc29b5 commit 9d3dc0e

12 files changed

Lines changed: 281 additions & 106 deletions

fastdeploy/input/encodings/ernie_encoding.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -176,9 +176,12 @@ def add_processed_video(self, frames_cache, outputs, uuid, token_len=None):
176176

177177
def load_video(self, url, item):
178178
from fastdeploy.input.utils.render_timestamp import render_frame_timestamp
179-
from fastdeploy.input.utils.video import read_frames_decord, read_video_decord
179+
from fastdeploy.input.utils.video import (
180+
read_frames_paddlecodec,
181+
read_video_paddlecodec,
182+
)
180183

181-
reader, meta, path = read_video_decord(url, save_to_disk=False)
184+
reader, meta, path = read_video_paddlecodec(url, save_to_disk=False)
182185

183186
video_frame_args = {
184187
"fps": item.get("fps", self.fps),
@@ -189,7 +192,7 @@ def load_video(self, url, item):
189192
}
190193
video_frame_args = self.set_video_frame_args(video_frame_args, meta)
191194

192-
frames_data, _, timestamps = read_frames_decord(
195+
frames_data, _, timestamps = read_frames_paddlecodec(
193196
path,
194197
reader,
195198
meta,

fastdeploy/input/encodings/paddleocr_encoding.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
from fastdeploy.input.encodings.registry import EncodingRegistry
2323
from fastdeploy.input.mm_model_config import PADDLEOCR_VL
2424
from fastdeploy.input.utils import IDS_TYPE_FLAG
25-
from fastdeploy.input.utils.video import read_video_decord
25+
from fastdeploy.input.utils.video import read_video_paddlecodec
2626
from fastdeploy.input.utils.video import sample_frames_paddleocr as _sample_paddleocr
2727
from fastdeploy.multimodal.hasher import MultimodalHasher
2828

@@ -154,7 +154,7 @@ def add_processed_video(self, frames_cache, outputs, uuid, token_len=None):
154154
outputs["vit_position_ids"].append(np.arange(numel) % numel)
155155

156156
def load_video(self, url, item):
157-
reader, meta, _ = read_video_decord(url, save_to_disk=False)
157+
reader, meta, _ = read_video_paddlecodec(url, save_to_disk=False)
158158

159159
fps = item.get("fps", self.fps)
160160
num_frames = item.get("target_frames", self.target_frames)

fastdeploy/input/encodings/qwen_encoding.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
from fastdeploy.input.encodings.registry import EncodingRegistry
2525
from fastdeploy.input.mm_model_config import QWEN3_VL, QWEN_VL
2626
from fastdeploy.input.utils import IDS_TYPE_FLAG
27-
from fastdeploy.input.utils.video import read_video_decord
27+
from fastdeploy.input.utils.video import read_video_paddlecodec
2828
from fastdeploy.input.utils.video import sample_frames_qwen as _sample_qwen
2929
from fastdeploy.multimodal.hasher import MultimodalHasher
3030

@@ -152,7 +152,7 @@ def add_processed_video(self, frames_cache, outputs, uuid, token_len=None):
152152
outputs["fps"].append(fps)
153153

154154
def load_video(self, url, item):
155-
reader, meta, _ = read_video_decord(url, save_to_disk=False)
155+
reader, meta, _ = read_video_paddlecodec(url, save_to_disk=False)
156156

157157
fps = item.get("fps", self.fps)
158158
num_frames = item.get("target_frames", self.target_frames)

fastdeploy/input/utils/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
)
2323
from fastdeploy.input.utils.video import (
2424
VideoReaderWrapper,
25-
read_video_decord,
25+
read_video_paddlecodec,
2626
sample_frames,
2727
sample_frames_paddleocr,
2828
sample_frames_qwen,
@@ -34,7 +34,7 @@
3434
"process_stop_token_ids",
3535
"validate_model_path",
3636
"VideoReaderWrapper",
37-
"read_video_decord",
37+
"read_video_paddlecodec",
3838
"sample_frames",
3939
"sample_frames_paddleocr",
4040
"sample_frames_qwen",

fastdeploy/input/utils/video.py

Lines changed: 64 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
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

1717
import datetime
1818
import hashlib
@@ -26,19 +26,22 @@
2626
from typing import Optional, Union
2727

2828
import numpy as np
29+
import paddle
2930
from PIL import Image
3031

3132
from 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

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ xlwt
2323
visualdl
2424
setuptools-scm>=8
2525
prometheus-client
26-
decord
26+
paddlecodec
2727
moviepy
2828
triton
2929
crcmod

requirements_dcu.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ xlwt
2222
visualdl
2323
setuptools-scm>=8
2424
prometheus-client
25-
decord
25+
paddlecodec
2626
moviepy
2727
use-triton-in-paddle
2828
crcmod

requirements_iluvatar.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ xlwt
2222
visualdl
2323
setuptools-scm>=8
2424
prometheus-client
25-
decord
25+
paddlecodec
2626
moviepy
2727
triton
2828
crcmod

requirements_metaxgpu.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ xlwt
2323
visualdl
2424
setuptools-scm>=8
2525
prometheus-client
26-
decord
26+
paddlecodec
2727
moviepy
2828
triton
2929
use-triton-in-paddle

0 commit comments

Comments
 (0)