Source code for neuralset.events.transforms.audio
# Copyright (c) Meta Platforms, Inc. and affiliates.
# All rights reserved.
#
# This source code is licensed under the license found in the
# LICENSE file in the root directory of this source tree.
from pathlib import Path
import exca
import pandas as pd
from tqdm import tqdm
from .. import etypes as ev
from ..study import EventsTransform
[docs]
class ExtractAudioFromVideo(EventsTransform):
"""
Extract audio tracks from Video events and add them as separate Audio events.
This transform iterates over events of type "Video", extracts the audio
track, saves it as a `.wav` file (if it does not already exist), and adds
a corresponding event of type "Audio" to the DataFrame.
"""
overwrite: bool = False
def _run(self, events: pd.DataFrame) -> pd.DataFrame:
video_events = events.loc[events.type == "Video"]
if self.overwrite:
for filepath in video_events.filepath.unique():
audio_filepath = Path(filepath).with_suffix(".wav")
if audio_filepath.exists():
audio_filepath.unlink()
if len(video_events) == 0:
return events
frequencies: dict[str, float] = {}
unique_videos = video_events.drop_duplicates("filepath")
# write audio file per video file
for video_event in tqdm(
unique_videos.itertuples(),
total=len(unique_videos),
desc="Extract audio from videos",
):
# timeline is unused but required by events; wavs are shared across timelines
filepath, timeline = str(video_event.filepath), str(video_event.timeline)
audio_filepath = Path(filepath).with_suffix(".wav")
if not audio_filepath.exists():
# whole file: Video events may only cover part of it
video = ev.Video(start=0, timeline=timeline, filepath=filepath)
with video.read() as video_clip:
if video_clip.audio is None:
continue
with exca.utils.temporary_save_path(audio_filepath) as tmp_filepath:
video_clip.audio.write_audiofile(tmp_filepath)
audio = ev.Audio(start=0, timeline=timeline, filepath=str(audio_filepath))
frequencies[filepath] = audio.frequency
events_to_add = []
# add audio events per video event (may be multiple per audio/video file)
for video_event in video_events.itertuples(index=False):
filepath = str(video_event.filepath)
if filepath not in frequencies:
continue
# Audio overwrites duration with length of wav file if frequency is missing
audio_event = video_event._replace(
type="Audio",
filepath=str(Path(filepath).with_suffix(".wav")),
frequency=frequencies[filepath],
) # type: ignore
events_to_add.append(audio_event)
events = pd.concat([events, pd.DataFrame(events_to_add)], ignore_index=True)
events = events.reset_index(drop=True)
return events