Source code for data_juicer.ops.mapper.video_audio_speech_emotion_mapper
import gc
from data_juicer.utils.constant import Fields, MetaKeys
from data_juicer.utils.lazy_loader import LazyLoader
from data_juicer.utils.mm_utils import extract_audio_from_video
from data_juicer.utils.model_utils import get_model, prepare_model
from ..base_op import OPERATORS, Mapper
librosa = LazyLoader("librosa")
torch = LazyLoader("torch")
OP_NAME = "video_audio_speech_emotion_mapper"
[docs]
@OPERATORS.register_module(OP_NAME)
class VideoAudioSpeechEmotionMapper(Mapper):
"""Mapper to generate video tags from audio streams extracted by video
using the Audio Spectrogram Transformer.
Source: This operator is a part of HumanVBench (CVPR 2026).
"""
_accelerator = "cuda"
_batched_op = True
[docs]
def __init__(
self, model_dir_emo="FunAudioLLM/SenseVoiceSmall", speech_Emo: str = MetaKeys.speech_emotion, *args, **kwargs
):
"""
Initialization method.
:param args: extra args
:param kwargs: extra args
"""
kwargs.setdefault("mem_required", "20GB")
super().__init__(*args, **kwargs)
self._batched_op = True
self._model_sampling_rate = 16000
self.model_dir_emo = model_dir_emo
self.model_key = prepare_model(
model_type="SenseVoiceSmall",
pretrained_model_name_or_path=self.model_dir_emo,
)
self.speech_Emo = speech_Emo
self._no_audio_label = "EMPTY"
[docs]
def process_single(self, sample, rank=None):
# check if it's generated already
if Fields.meta not in sample:
sample[Fields.meta] = {}
if self.speech_Emo in sample[Fields.meta]:
return sample
# there is no video in this sample
if self.video_key not in sample or not sample[self.video_key]:
sample[Fields.meta][self.speech_Emo] = []
return sample
if MetaKeys.video_audio_tags not in sample[Fields.meta]:
raise ValueError("video_active_speaker_mapper must be operated after video_tagging_from_audio_mapper.")
# load video paths
loaded_video_keys = sample[self.video_key]
audio_tags = sample[Fields.meta][MetaKeys.video_audio_tags]
Emo_model, kwargs1 = get_model(self.model_key, rank=rank)
video_audio_tags = []
for id, video_path in enumerate(loaded_video_keys):
if audio_tags[id] == "Speech":
# only extract audio data and sr for index 0 for now
ys, srs, valid_indexes = extract_audio_from_video(video_path, stream_indexes=[0])
if len(valid_indexes) == 0:
# there is no valid audio streams. Skip!
video_audio_tags.append(self._no_audio_label)
continue
# inference
y = ys[0]
sr = srs[0]
# check if it meets the sampling rate condition of the model
if sr != self._model_sampling_rate:
y = librosa.resample(y, orig_sr=sr, target_sr=self._model_sampling_rate)
sr = self._model_sampling_rate
inputs = torch.tensor(y).to(next(Emo_model.parameters()).device)
with torch.no_grad():
output_emo = Emo_model.inference(
data_in=inputs,
language="auto", # "zn", "en", "yue", "ja", "ko", "nospeech"
use_itn=False,
**kwargs1,
)
video_audio_tags.append(output_emo[0][0]["text"].split("<|", 2)[-1].split("|>")[0])
else:
video_audio_tags.append("")
sample[Fields.meta][self.speech_Emo] = video_audio_tags
gc.collect()
torch.cuda.empty_cache()
return sample