Files
TashaSkyUp-EternalKernelPyt…/audio.py
T
Tasha Upchurch 35b3ec4f66 Add nodes for flexible typing and conversion to Any
This commit adds several new nodes to enable flexible typing and conversion between types:

- ValueToAny - Converts primitive values like ints, floats, strings, bools to Any type
- IterableToAny - Converts iterable values like lists, dicts to Any
- CallableToAny - Converts callables like functions to Any
- ObjectToAny - Converts tensor objects like images, latents etc to Any

The motivation is to allow passing different types flexibly into functions using Any typing. This enables writing functions in a more generic way by accepting Any instead of specific types.

The nodes convert the input values to Any and return debug info on what was converted. This allows tracing what types entered a function even if the function uses generic Any types.

No breaking changes, new nodes added:
- ValueToAny
- IterableToAny
- CallableToAny
- ObjectToAny

These provide building blocks for more flexible typing, opening up possibilities like:

- Writing utility and processing functions accepting Any instead of specific types
- Calling functions dynamically with values of different types
- Tracing and logging what types passed through a function

Next steps could include AnyToValue nodes, more specialized Any converters, and exploring uses for generic functions.
2023-09-18 17:18:35 -04:00

177 lines
5.1 KiB
Python

import copy
NODE_CLASS_MAPPINGS = {}
NODE_DISPLAY_NAME_MAPPINGS = {}
def ETK_audio_base(cls):
cls.CATEGORY = "ETK/audio"
NODE_CLASS_MAPPINGS[cls.__name__] = cls
return cls
@ETK_audio_base
class GetAudioMetaData:
""" Returns a dictionary of audio metadata """
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"path": ("STRING", {"multiline": False, "default": ""}),
}
}
RETURN_TYPES = ("DICT",)
FUNCTION = 'get_audio_metadata'
CATEGORY = 'ETK/audio'
def get_audio_metadata(self, **kwargs):
import audioread
kwargs = copy.deepcopy(kwargs)
path = kwargs.get('path')
metadata = {}
with audioread.audio_open(path) as f:
metadata['duration'] = f.duration
metadata['samplerate'] = f.samplerate
metadata['channels'] = f.channels
return (metadata,)
@ETK_audio_base
class AudioFileListFromPathPattern:
""" Returns a list of audio files from a path pattern """
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"path": ("STRING", {"multiline": False, "default": ""}),
}
}
RETURN_TYPES = ("LIST", "LIST", "LIST",)
RETURN_NAMES = ("list_of_fpaths", "list_of_bytes", "list_of_metadata")
FUNCTION = 'get_audio_file_list_from_path_pattern'
CATEGORY = 'ETK/audio'
def get_audio_file_list_from_path_pattern(self, **kwargs):
import glob
kwargs = copy.deepcopy(kwargs)
path = kwargs.get('path')
l_fpaths = glob.glob(path)
l_bytes = []
l_metadata = []
for fpath in l_fpaths:
with open(fpath, "rb") as f:
l_bytes.append(f.read())
l_metadata.append(GetAudioMetaData().get_audio_metadata(path=fpath)[0])
return (l_fpaths, l_bytes, l_metadata,)
import librosa
import soundfile as sf
import numpy as np
@ETK_audio_base
class JoinAudioFiles:
""" Joins a list of audio files into a single audio file """
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"paths": ("LIST", {"default": []}),
"output_path": ("STRING", {"multiline": False, "default": ""}),
}
}
RETURN_TYPES = ("STRING", "BYTES", "DICT",)
FUNCTION = 'join_audio_files'
CATEGORY = 'ETK/audio'
def join_audio_files(self, **kwargs):
kwargs = copy.deepcopy(kwargs)
paths = kwargs['paths']
output_path = kwargs['output_path']
# Read the first file to get metadata
audio_data_list = []
for path in paths:
audio_data, sr = librosa.load(path, sr=None)
audio_data_list.append(audio_data)
# Concatenate all audio data
joined_audio_data = np.concatenate(audio_data_list)
# Save the concatenated audio
sf.write(output_path, joined_audio_data, sr)
# Get metadata
metadata = {
'duration': librosa.get_duration(y=joined_audio_data, sr=sr),
'samplerate': sr
}
# Get bytes
with open(output_path, "rb") as f:
file_bytes = f.read()
return (output_path, file_bytes, metadata,)
@ETK_audio_base
class ExtractAudioFromVideo:
""" Extracts audio from a video file and saves it as an audio file with a specified format and bitrate """
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"video_path": ("STRING", {"multiline": False, "default": ""}),
"format":(["wav", "mp3"], {"default": "wav"}),
"output_path": ("STRING", {"multiline": False, "default": ""}),
},
"optional": {
"bitrate (MP3)": ("INT", {"default": 192}) # Default bitrate value (change as needed)
}
}
RETURN_TYPES = ("STRING", "BYTES")
RETURN_NAMES = ("output_wave_file_path", "audio_bytes")
FUNCTION = 'extract_audio_from_video'
CATEGORY = 'ETK/audio'
def extract_audio_from_video(self, **kwargs):
import moviepy.editor as mp
import io
kwargs = copy.deepcopy(kwargs)
video_path = kwargs.get('video_path')
output_path = kwargs.get('output_path',"tmp.wav")
audio_format = kwargs.get('format', 'wav')
bitrate = kwargs.get('bitrate', None) # Default bitrate is None
# Load the video clip
video_clip = mp.VideoFileClip(video_path)
# Determine the appropriate codec based on the audio format
if audio_format == 'wav':
codec = "pcm_s16le" # For WAV format
elif audio_format == 'mp3':
codec = "mp3" # For MP3 format
# Extract audio and save it with the specified format and bitrate
audio_clip = video_clip.audio
audio_clip.write_audiofile(output_path, codec=codec, bitrate=bitrate)
# Read the generated audio file as bytes
audio_bytes = None
with open(output_path, 'rb') as audio_file:
audio_bytes = audio_file.read()
return (output_path, audio_bytes,)