├── 2024-04-21 092318.png ├── LICENSE .txt ├── __init__.py ├── imageeffectcontrast.py ├── imageeffectgamma.py ├── imageeffectbrightness.py ├── imageeffectsaturation.py ├── imageeffectblue.py ├── imageeffectsharpness.py ├── imageeffecthue.py ├── imageeffectred.py ├── README.md ├── imageeffectgreen.py └── audiofeatures_anodes.ipynb /2024-04-21 092318.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hannahun1/anodes/HEAD/2024-04-21 092318.png -------------------------------------------------------------------------------- /LICENSE .txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 hanna_hun 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | from .imageeffectcontrast import ImageEffectContrast 2 | from .imageeffectbrightness import ImageEffectBrightness 3 | from .imageeffectsaturation import ImageEffectSaturation 4 | from .imageeffecthue import ImageEffectHue 5 | from .imageeffectgamma import ImageEffectGamma 6 | from .imageeffectsharpness import ImageEffectSharpness 7 | from .imageeffectred import ImageEffectRed 8 | from .imageeffectgreen import ImageEffectGreen 9 | from .imageeffectblue import ImageEffectBlue 10 | 11 | NODE_CLASS_MAPPINGS = { 12 | "ImageEffectContrast": ImageEffectContrast, 13 | "ImageEffectBrightness": ImageEffectBrightness, 14 | "ImageEffectSaturation": ImageEffectSaturation, 15 | "ImageEffectHue": ImageEffectHue, 16 | "ImageEffectGamma": ImageEffectGamma, 17 | "ImageEffectSharpness": ImageEffectSharpness, 18 | "ImageEffectRed": ImageEffectRed, 19 | "ImageEffectGreen": ImageEffectGreen, 20 | "ImageEffectBlue": ImageEffectBlue, 21 | } 22 | 23 | NODE_DISPLAY_NAME_MAPPINGS = { 24 | "ImageEffectContrast": "ImageEffectContrast", 25 | "ImageEffectBrightness": "ImageEffectBrightness", 26 | "ImageEffectSaturation": "ImageEffectSaturation", 27 | "ImageEffectHue": "ImageEffectHue", 28 | "ImageEffectGamma": "ImageEffectGamma", 29 | "ImageEffectSharpness": "ImageEffectSharpness", 30 | "ImageEffectRed": "ImageEffectRed", 31 | "ImageEffectGreen": "ImageEffectGreen", 32 | "ImageEffectBlue": "ImageEffectBlue" 33 | } 34 | 35 | __all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS'] -------------------------------------------------------------------------------- /imageeffectcontrast.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torchvision.transforms.functional as F 3 | import numpy as np 4 | import einops 5 | 6 | def preprocess_frames(frames): 7 | return einops.rearrange(frames, "n h w c -> n c h w") 8 | 9 | def postprocess_frames(frames): 10 | return einops.rearrange(frames, "n c h w -> n h w c").cpu() 11 | 12 | class ImageEffectContrast: 13 | RETURN_TYPES = ("IMAGE",) 14 | FUNCTION = "apply_contrast" 15 | CATEGORY = "ImageEffects" 16 | 17 | @classmethod 18 | def INPUT_TYPES(cls): 19 | return { 20 | "required": { 21 | "images": ("IMAGE",), 22 | "contrast_values": ("STRING", { 23 | "default": "0:(1.0),\n7:(0.5),\n15:(1.0)\n", 24 | "multiline": True 25 | }), 26 | "contrast_multiplier": ("FLOAT", { 27 | "default": 1.0, 28 | "step": 0.01 29 | }), 30 | }, 31 | } 32 | 33 | def apply_contrast(self, images, contrast_values, contrast_multiplier): 34 | points = [] 35 | contrast_values = contrast_values.rstrip(',\n') 36 | for point_str in contrast_values.split(','): 37 | frame_str, contrast_str = point_str.split(':') 38 | frame = int(frame_str.strip()) 39 | contrast = float(contrast_str.strip()[1:-1]) 40 | points.append((frame, contrast)) 41 | 42 | points.sort(key=lambda x: x[0]) 43 | 44 | images = preprocess_frames(images) 45 | out = [] 46 | for idx, image in enumerate(images): 47 | current_frame_index = idx % len(points) 48 | contrast_value = np.interp(current_frame_index, [p[0] for p in points], [p[1] for p in points]) 49 | contrast_value *= contrast_multiplier 50 | img = F.adjust_contrast(image, contrast_value) 51 | out.append(img) 52 | 53 | return (postprocess_frames(torch.stack(out)),) 54 | -------------------------------------------------------------------------------- /imageeffectgamma.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torchvision.transforms.functional as F 3 | import numpy as np 4 | import einops 5 | 6 | def preprocess_frames(frames): 7 | return einops.rearrange(frames, "n h w c -> n c h w") 8 | 9 | def postprocess_frames(frames): 10 | return einops.rearrange(frames, "n c h w -> n h w c").cpu() 11 | 12 | class ImageEffectGamma: 13 | RETURN_TYPES = ("IMAGE",) 14 | FUNCTION = "apply_gamma" 15 | CATEGORY = "ImageEffects" 16 | 17 | @classmethod 18 | def INPUT_TYPES(cls): 19 | return { 20 | "required": { 21 | "images": ("IMAGE",), 22 | "gamma_values": ("STRING", { 23 | "default": "0:(1.0),\n7:(0.8),\n15:(2.2)\n", 24 | "multiline": True 25 | }), 26 | "gamma_multiplier": ("FLOAT", { 27 | "default": 1.0, 28 | "min": 0.1, 29 | "max": 10.0, 30 | "step": 0.1 31 | }), 32 | }, 33 | } 34 | 35 | def apply_gamma(self, images, gamma_values, gamma_multiplier): 36 | points = [] 37 | gamma_values = gamma_values.rstrip(',\n') 38 | for point_str in gamma_values.split(','): 39 | frame_str, gamma_str = point_str.split(':') 40 | frame = int(frame_str.strip()) 41 | gamma = float(gamma_str.strip()[1:-1]) 42 | points.append((frame, gamma)) 43 | 44 | points.sort(key=lambda x: x[0]) 45 | 46 | images = preprocess_frames(images) 47 | out = [] 48 | for idx, image in enumerate(images): 49 | current_frame_index = idx % len(points) 50 | gamma_value = np.interp(current_frame_index, [p[0] for p in points], [p[1] for p in points]) 51 | gamma_value *= gamma_multiplier 52 | img = F.adjust_gamma(image, gamma_value) 53 | out.append(img) 54 | 55 | return (postprocess_frames(torch.stack(out)),) 56 | -------------------------------------------------------------------------------- /imageeffectbrightness.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torchvision.transforms.functional as F 3 | import numpy as np 4 | import einops 5 | 6 | def preprocess_frames(frames): 7 | return einops.rearrange(frames, "n h w c -> n c h w") 8 | 9 | def postprocess_frames(frames): 10 | return einops.rearrange(frames, "n c h w -> n h w c").cpu() 11 | 12 | class ImageEffectBrightness: 13 | RETURN_TYPES = ("IMAGE",) 14 | FUNCTION = "apply_brightness" 15 | CATEGORY = "ImageEffects_Anodes" 16 | 17 | @classmethod 18 | def INPUT_TYPES(cls): 19 | return { 20 | "required": { 21 | "images": ("IMAGE",), 22 | "brightness_values": ("STRING", { 23 | "default": "0:(1.0),\n7:(1.5),\n15:(1.0)\n", 24 | "multiline": True 25 | }), 26 | "brightness_multiplier": ("FLOAT", { 27 | "default": 1.0, 28 | "step": 0.01 29 | }), 30 | }, 31 | } 32 | 33 | def apply_brightness(self, images, brightness_values, brightness_multiplier): 34 | points = [] 35 | brightness_values = brightness_values.rstrip(',\n') 36 | for point_str in brightness_values.split(','): 37 | frame_str, brightness_str = point_str.split(':') 38 | frame = int(frame_str.strip()) 39 | brightness = float(brightness_str.strip()[1:-1]) 40 | points.append((frame, brightness)) 41 | 42 | points.sort(key=lambda x: x[0]) 43 | 44 | images = preprocess_frames(images) 45 | out = [] 46 | for idx, image in enumerate(images): 47 | current_frame_index = idx % len(points) 48 | brightness_value = np.interp(current_frame_index, [p[0] for p in points], [p[1] for p in points]) 49 | brightness_value *= brightness_multiplier 50 | img = F.adjust_brightness(image, brightness_value) 51 | out.append(img) 52 | 53 | return (postprocess_frames(torch.stack(out)),) 54 | -------------------------------------------------------------------------------- /imageeffectsaturation.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torchvision.transforms.functional as F 3 | import numpy as np 4 | import einops 5 | 6 | def preprocess_frames(frames): 7 | return einops.rearrange(frames, "n h w c -> n c h w") 8 | 9 | def postprocess_frames(frames): 10 | return einops.rearrange(frames, "n c h w -> n h w c").cpu() 11 | 12 | class ImageEffectSaturation: 13 | RETURN_TYPES = ("IMAGE",) 14 | FUNCTION = "apply_saturation" 15 | CATEGORY = "ImageEffects_Anodes" 16 | 17 | @classmethod 18 | def INPUT_TYPES(cls): 19 | return { 20 | "required": { 21 | "images": ("IMAGE",), 22 | "saturation_values": ("STRING", { 23 | "default": "0:(1.0),\n7:(1.5),\n15:(1.0)\n", 24 | "multiline": True 25 | }), 26 | "saturation_multiplier": ("FLOAT", { 27 | "default": 1.0, 28 | "step": 0.01 29 | }), 30 | }, 31 | } 32 | 33 | def apply_saturation(self, images, saturation_values, saturation_multiplier): 34 | points = [] 35 | saturation_values = saturation_values.rstrip(',\n') 36 | for point_str in saturation_values.split(','): 37 | frame_str, saturation_str = point_str.split(':') 38 | frame = int(frame_str.strip()) 39 | saturation = float(saturation_str.strip()[1:-1]) 40 | points.append((frame, saturation)) 41 | 42 | points.sort(key=lambda x: x[0]) 43 | 44 | images = preprocess_frames(images) 45 | out = [] 46 | for idx, image in enumerate(images): 47 | current_frame_index = idx % len(points) 48 | saturation_value = np.interp(current_frame_index, [p[0] for p in points], [p[1] for p in points]) 49 | saturation_value *= saturation_multiplier 50 | img = F.adjust_saturation(image, saturation_value) 51 | out.append(img) 52 | 53 | return (postprocess_frames(torch.stack(out)),) 54 | -------------------------------------------------------------------------------- /imageeffectblue.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torchvision.transforms.functional as F 3 | import numpy as np 4 | import einops 5 | 6 | def preprocess_frames(frames): 7 | return einops.rearrange(frames, "n h w c -> n c h w") 8 | 9 | def postprocess_frames(frames): 10 | return einops.rearrange(frames, "n c h w -> n h w c").cpu() 11 | 12 | class ImageEffectBlue: 13 | RETURN_TYPES = ("IMAGE",) 14 | FUNCTION = "apply_blue" 15 | CATEGORY = "ImageEffects_Anodes" 16 | 17 | @classmethod 18 | def INPUT_TYPES(cls): 19 | return { 20 | "required": { 21 | "images": ("IMAGE",), 22 | "blue_values": ("STRING", { 23 | "default": "0:(1.0),\n7:(0.5),\n15:(1.0)\n", 24 | "multiline": True 25 | }), 26 | "blue_multiplier": ("FLOAT", { 27 | "default": 1.0, # Default multiplier value 28 | "step": 0.01 29 | }), 30 | }, 31 | } 32 | 33 | def apply_blue(self, images, blue_values, blue_multiplier): 34 | points = [] 35 | blue_values = blue_values.rstrip(',\n') 36 | for point_str in blue_values.split(','): 37 | frame_str, blue_str = point_str.split(':') 38 | frame = int(frame_str.strip()) 39 | blue = float(blue_str.strip()[1:-1]) 40 | points.append((frame, blue)) 41 | 42 | points.sort(key=lambda x: x[0]) 43 | 44 | images = preprocess_frames(images) 45 | out = [] 46 | 47 | for idx, image in enumerate(images): 48 | current_frame_index = idx % len(points) 49 | blue_value = np.interp(current_frame_index, [p[0] for p in points], [p[1] for p in points]) 50 | blue_value *= blue_multiplier 51 | 52 | img = image.clone() 53 | r, g, b = img[0], img[1], img[2] * blue_value 54 | modified_img = torch.stack([r, g, b], dim=0) 55 | 56 | out.append(modified_img) 57 | 58 | return (postprocess_frames(torch.stack(out)),) 59 | -------------------------------------------------------------------------------- /imageeffectsharpness.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torchvision.transforms.functional as F 3 | import numpy as np 4 | import einops 5 | 6 | def preprocess_frames(frames): 7 | return einops.rearrange(frames, "n h w c -> n c h w") 8 | 9 | def postprocess_frames(frames): 10 | return einops.rearrange(frames, "n c h w -> n h w c").cpu() 11 | 12 | class ImageEffectSharpness: 13 | RETURN_TYPES = ("IMAGE",) 14 | FUNCTION = "apply_sharpness" 15 | CATEGORY = "ImageEffects" 16 | 17 | @classmethod 18 | def INPUT_TYPES(cls): 19 | return { 20 | "required": { 21 | "images": ("IMAGE",), 22 | "sharpness_values": ("STRING", { 23 | "default": "0:(1.0),\n7:(2.0),\n15:(0.5)\n", 24 | "multiline": True 25 | }), 26 | "sharpness_multiplier": ("FLOAT", { 27 | "default": 1.0, # Default multiplier value 28 | "min": 0.0, # Minimum sharpness multiplier 29 | "max": 3.0, # Maximum sharpness multiplier 30 | "step": 0.1 31 | }), 32 | }, 33 | } 34 | 35 | def apply_sharpness(self, images, sharpness_values, sharpness_multiplier): 36 | points = [] 37 | sharpness_values = sharpness_values.rstrip(',\n') 38 | for point_str in sharpness_values.split(','): 39 | frame_str, sharpness_str = point_str.split(':') 40 | frame = int(frame_str.strip()) 41 | sharpness = float(sharpness_str.strip()[1:-1]) 42 | points.append((frame, sharpness)) 43 | 44 | points.sort(key=lambda x: x[0]) 45 | 46 | images = preprocess_frames(images) 47 | out = [] 48 | for idx, image in enumerate(images): 49 | current_frame_index = idx % len(points) 50 | sharpness_value = np.interp(current_frame_index, [p[0] for p in points], [p[1] for p in points]) 51 | sharpness_value *= sharpness_multiplier 52 | img = F.adjust_sharpness(image, sharpness_value) 53 | out.append(img) 54 | 55 | return (postprocess_frames(torch.stack(out)),) -------------------------------------------------------------------------------- /imageeffecthue.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torchvision.transforms.functional as F 3 | import numpy as np 4 | import einops 5 | 6 | def preprocess_frames(frames): 7 | return einops.rearrange(frames, "n h w c -> n c h w") 8 | 9 | def postprocess_frames(frames): 10 | return einops.rearrange(frames, "n c h w -> n h w c").cpu() 11 | 12 | class ImageEffectHue: 13 | RETURN_TYPES = ("IMAGE",) 14 | FUNCTION = "apply_hue" 15 | CATEGORY = "ImageEffects_Anodes" 16 | 17 | @classmethod 18 | def INPUT_TYPES(cls): 19 | return { 20 | "required": { 21 | "images": ("IMAGE",), 22 | "hue_values": ("STRING", { 23 | "default": "0:(0.0),\n7:(0.5),\n15:(1.0)\n", 24 | "multiline": True 25 | }), 26 | "hue_multiplier": ("FLOAT", { 27 | "default": 1.0, 28 | "step": 0.01 29 | }), 30 | }, 31 | } 32 | 33 | def apply_hue(self, images, hue_values, hue_multiplier): 34 | points = [] 35 | hue_values = hue_values.rstrip(',\n') 36 | for point_str in hue_values.split(','): 37 | frame_str, hue_str = point_str.split(':') 38 | frame = int(frame_str.strip()) 39 | # Scale input to range -0.5 to 0.5 40 | hue = (float(hue_str.strip()[1:-1]) - 0.5) * 2 * 0.5 41 | points.append((frame, hue)) 42 | 43 | points.sort(key=lambda x: x[0]) # Make sure points are in order 44 | images = preprocess_frames(images) 45 | out = [] 46 | 47 | for idx, image in enumerate(images): 48 | current_frame_index = idx % len(points) 49 | hue_value = np.interp(current_frame_index, [p[0] for p in points], [p[1] for p in points]) 50 | # Apply the multiplier and ensure the value is within the -0.5 to 0.5 range 51 | hue_value = (hue_value * hue_multiplier) * 0.5 52 | hue_value = max(-0.5, min(0.5, hue_value)) 53 | 54 | img = image.clone() # Clone to avoid modifying the original in-place 55 | img = F.adjust_hue(img, hue_value) # Apply hue adjustment 56 | 57 | out.append(img) 58 | 59 | return (postprocess_frames(torch.stack(out)),) 60 | -------------------------------------------------------------------------------- /imageeffectred.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torchvision.transforms.functional as F 3 | import numpy as np 4 | import einops 5 | 6 | def preprocess_frames(frames): 7 | return einops.rearrange(frames, "n h w c -> n c h w") 8 | 9 | def postprocess_frames(frames): 10 | return einops.rearrange(frames, "n c h w -> n h w c").cpu() 11 | 12 | class ImageEffectRed: 13 | RETURN_TYPES = ("IMAGE",) 14 | FUNCTION = "apply_red" 15 | CATEGORY = "ImageEffects" 16 | 17 | @classmethod 18 | def INPUT_TYPES(cls): 19 | return { 20 | "required": { 21 | "images": ("IMAGE",), 22 | "red_values": ("STRING", { 23 | "default": "0:(1.0),\n7:(0.5),\n15:(1.0)\n", 24 | "multiline": True 25 | }), 26 | "red_multiplier": ("FLOAT", { 27 | "default": 1.0, # Default multiplier value 28 | "step": 0.01 29 | }), 30 | }, 31 | } 32 | 33 | def apply_red(self, images, red_values, red_multiplier): 34 | points = [] 35 | red_values = red_values.rstrip(',\n') 36 | for point_str in red_values.split(','): 37 | frame_str, red_str = point_str.split(':') 38 | frame = int(frame_str.strip()) 39 | red = float(red_str.strip()[1:-1]) 40 | points.append((frame, red)) 41 | 42 | points.sort(key=lambda x: x[0]) 43 | 44 | images = preprocess_frames(images) 45 | out = [] 46 | for idx, image in enumerate(images): 47 | current_frame_index = idx % len(points) 48 | red_value = np.interp(current_frame_index, [p[0] for p in points], [p[1] for p in points]) 49 | red_value *= red_multiplier # Multiply the red value by the multiplier 50 | 51 | # Adjust the red channel specifically 52 | img = image.clone() # Clone to avoid modifying the original in-place 53 | r = img[0] * red_value 54 | g = img[1] # Green channel remains the same 55 | b = img[2] # Blue channel remains the same 56 | 57 | # Stack channels back together 58 | modified_img = torch.stack([r, g, b], dim=0) 59 | out.append(modified_img) 60 | 61 | return (postprocess_frames(torch.stack(out)),) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Image Effect Scheduler Node Set for ComfyUI 2 | 3 | ![image](https://github.com/hannahunter88/anodes/blob/master/2024-04-21%20092318.png) 4 | 5 | ImageEffect nodes were created to provide complete control over visual effects by allowing users to specify individual values for each frame. For example it can be useful in creating visual effects that are synchronized with extracted music features. 6 | 7 | ImageEffect nodes were also created to enable the creation of smoother, more consistent visual effects by automatically interpolating values between frames when users provide fewer specific values. 8 | 9 | *Installation* 10 | 11 | Clone this repo into custom_nodes folder. 12 | 13 | *How to use these nodes?* 14 | 15 | ImageEffect nodes should be applied after KSampler and VAE decode and before Preview Images and Video Combine nodes. 16 | It is also recommended to experiment with multiple ImageEffect nodes simultaneously, while keeping the original preview images and video for comparison. 17 | 18 | Values in the list should be specified as floating-point numbers between 0 and 1. Use the multiplier parameter if you need higher values. For ImageEffectHue, the allowed range is -0.5 to 0.5, but these values are also rescaled to fit with the other ImageEffect Nodes, so you can still work with floating-point values between 0 and 1. 19 | 20 | **audiofeatures_anodes.ipynb** 21 | 22 | If you're feeling adventurous, you can play around with audiofeatures_anodes.ipynb. In addition to the standard beat times lists, I have added a new set of lists generated using librosa for advanced audio feature extraction in the notebook. These lists include Mel-Frequency Cepstral Coefficients (MFCC), their deltas, and Chroma features, all resampled to a frame rate of 10 FPS to seamlessly integrate with the existing setup. You can use the values from these lists with the ImageEffect nodes after KSampler (and VAE Decode) or with Kijai's CreateFadeMaskAdvanced node before KSampler, for example, to manipulate the IPAdapter. 23 | 24 | **The video combine node requires a frame rate of 10 FPS when using these lists!** 25 | 26 | I just shared this as an example, but there are many other ways to create lists. I encourage you to experiment and explore! 27 | 28 | [![ImageEffectHue Node with Beattimes_Switch List](https://img.youtube.com/vi/xSIVtJ7xsHY/maxresdefault.jpg)](https://www.youtube.com/watch?v=xSIVtJ7xsHY) 29 | -------------------------------------------------------------------------------- /imageeffectgreen.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torchvision.transforms.functional as F 3 | import numpy as np 4 | import einops 5 | 6 | # Preprocess and Postprocess functions as defined 7 | def preprocess_frames(frames): 8 | return einops.rearrange(frames, "n h w c -> n c h w") 9 | 10 | def postprocess_frames(frames): 11 | return einops.rearrange(frames, "n c h w -> n h w c").cpu() 12 | 13 | class ImageEffectGreen: 14 | RETURN_TYPES = ("IMAGE",) 15 | FUNCTION = "apply_green" 16 | CATEGORY = "ImageEffects" 17 | 18 | @classmethod 19 | def INPUT_TYPES(cls): 20 | return { 21 | "required": { 22 | "images": ("IMAGE",), 23 | "green_values": ("STRING", { 24 | "default": "0:(1.0),\n7:(0.5),\n15:(1.0)\n", 25 | "multiline": True 26 | }), 27 | "green_multiplier": ("FLOAT", { 28 | "default": 1.0, # Default multiplier value 29 | "step": 0.01 30 | }), 31 | }, 32 | } 33 | 34 | def apply_green(self, images, green_values, green_multiplier): 35 | points = [] 36 | green_values = green_values.rstrip(',\n') 37 | for point_str in green_values.split(','): 38 | frame_str, green_str = point_str.split(':') 39 | frame = int(frame_str.strip()) 40 | green = float(green_str.strip()[1:-1]) 41 | points.append((frame, green)) 42 | 43 | points.sort(key=lambda x: x[0]) 44 | 45 | images = preprocess_frames(images) 46 | out = [] 47 | for idx, image in enumerate(images): 48 | current_frame_index = idx % len(points) 49 | green_value = np.interp(current_frame_index, [p[0] for p in points], [p[1] for p in points]) 50 | green_value *= green_multiplier # Apply the green multiplier 51 | 52 | # Adjust the green channel specifically 53 | img = image.clone() # Clone to avoid modifying the original in-place 54 | r = img[0] # Red channel remains the same 55 | g = img[1] * green_value # Apply green multiplier 56 | b = img[2] # Blue channel remains the same 57 | 58 | # Stack channels back together 59 | modified_img = torch.stack([r, g, b], dim=0) 60 | out.append(modified_img) 61 | 62 | return (postprocess_frames(torch.stack(out)),) -------------------------------------------------------------------------------- /audiofeatures_anodes.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "nbformat": 4, 3 | "nbformat_minor": 0, 4 | "metadata": { 5 | "colab": { 6 | "provenance": [] 7 | }, 8 | "kernelspec": { 9 | "name": "python3", 10 | "display_name": "Python 3" 11 | }, 12 | "language_info": { 13 | "name": "python" 14 | } 15 | }, 16 | "cells": [ 17 | { 18 | "cell_type": "markdown", 19 | "source": [ 20 | "# MFCC, Delta MFCC, and Chroma Lists\n" 21 | ], 22 | "metadata": { 23 | "id": "Tg7oc1ng2m4C" 24 | } 25 | }, 26 | { 27 | "cell_type": "code", 28 | "source": [ 29 | "import librosa\n", 30 | "import numpy as np\n", 31 | "import pandas as pd\n", 32 | "\n", 33 | "#@markdown ### Path to your audio file\n", 34 | "audio_path = '/content/WingsofSorrow.mp3' #@param {type:\"string\"}\n", 35 | "y, sr = librosa.load(audio_path, sr=None)\n", 36 | "\n", 37 | "# Calculate MFCC, Delta MFCC, and Chroma features\n", 38 | "mfcc = librosa.feature.mfcc(y=y, sr=sr, hop_length=512, n_mfcc=13)\n", 39 | "mfcc_delta = librosa.feature.delta(mfcc)\n", 40 | "chroma = librosa.feature.chroma_cqt(y=y, sr=sr)\n", 41 | "\n", 42 | "# Determine the total number of frames at 10 frames per second\n", 43 | "duration_sec = librosa.get_duration(y=y, sr=sr)\n", 44 | "total_frames = int(duration_sec * 10)\n", 45 | "\n", 46 | "# Resample each feature individually\n", 47 | "features = {'MFCC': mfcc, 'MFCC_delta': mfcc_delta, 'Chroma': chroma}\n", 48 | "resampled_features = {}\n", 49 | "for feature_name, feature_data in features.items():\n", 50 | " resampled = np.zeros((total_frames, feature_data.shape[0]))\n", 51 | " for i in range(feature_data.shape[0]):\n", 52 | " resampled[:, i] = np.interp(np.linspace(0, len(feature_data[i])-1, total_frames), np.arange(len(feature_data[i])), feature_data[i])\n", 53 | " resampled_features[feature_name] = resampled\n", 54 | "\n", 55 | "# Combine all features into a single DataFrame and save it\n", 56 | "all_features = np.hstack([resampled_features[feature_name] for feature_name in ['Chroma', 'MFCC', 'MFCC_delta']])\n", 57 | "feature_columns = ['Chroma_' + str(i) for i in range(chroma.shape[0])] + ['MFCC_' + str(i) for i in range(13)] + ['MFCC_delta_' + str(i) for i in range(13)]\n", 58 | "features_df = pd.DataFrame(all_features, columns=feature_columns)\n", 59 | "features_df.to_csv('/content/continuous_mfcc_features.csv', index=False)\n", 60 | "print(\"Continuous MFCC, Delta MFCC, and Chroma features extracted and saved.\")\n", 61 | "\n", 62 | "# Load the DataFrame from the saved CSV file\n", 63 | "features_df = pd.read_csv('/content/continuous_mfcc_features.csv')\n", 64 | "\n", 65 | "# Function to normalize and save each feature column as a list in a text file\n", 66 | "def save_and_normalize_features(df, prefix='/content/'):\n", 67 | " for column in df.columns:\n", 68 | " # Select and normalize the column\n", 69 | " data = df[column]\n", 70 | " normalized_data = (data - data.min()) / (data.max() - data.min())\n", 71 | "\n", 72 | " # Round the normalized data to two decimal places\n", 73 | " normalized_data = normalized_data.round(2)\n", 74 | "\n", 75 | " # Write formatted data to a file\n", 76 | " output_path = f\"{prefix}{column}.txt\"\n", 77 | " with open(output_path, 'w') as file:\n", 78 | " formatted_data = ',\\n'.join([f\"{i + 1}:({value})\" for i, value in enumerate(normalized_data)])\n", 79 | " file.write(formatted_data)\n", 80 | " print(f\"Data for {column} saved to: {output_path}\")\n", 81 | "\n", 82 | "# Normalize and save all columns as separate lists in text files\n", 83 | "save_and_normalize_features(features_df)" 84 | ], 85 | "metadata": { 86 | "colab": { 87 | "base_uri": "https://localhost:8080/" 88 | }, 89 | "id": "pTU0VuUY13Bx", 90 | "outputId": "d8d96ded-245c-40c1-f0b4-dd778e31ad32" 91 | }, 92 | "execution_count": 1, 93 | "outputs": [ 94 | { 95 | "output_type": "stream", 96 | "name": "stdout", 97 | "text": [ 98 | "Continuous MFCC, Delta MFCC, and Chroma features extracted and saved.\n", 99 | "Data for Chroma_0 saved to: /content/Chroma_0.txt\n", 100 | "Data for Chroma_1 saved to: /content/Chroma_1.txt\n", 101 | "Data for Chroma_2 saved to: /content/Chroma_2.txt\n", 102 | "Data for Chroma_3 saved to: /content/Chroma_3.txt\n", 103 | "Data for Chroma_4 saved to: /content/Chroma_4.txt\n", 104 | "Data for Chroma_5 saved to: /content/Chroma_5.txt\n", 105 | "Data for Chroma_6 saved to: /content/Chroma_6.txt\n", 106 | "Data for Chroma_7 saved to: /content/Chroma_7.txt\n", 107 | "Data for Chroma_8 saved to: /content/Chroma_8.txt\n", 108 | "Data for Chroma_9 saved to: /content/Chroma_9.txt\n", 109 | "Data for Chroma_10 saved to: /content/Chroma_10.txt\n", 110 | "Data for Chroma_11 saved to: /content/Chroma_11.txt\n", 111 | "Data for MFCC_0 saved to: /content/MFCC_0.txt\n", 112 | "Data for MFCC_1 saved to: /content/MFCC_1.txt\n", 113 | "Data for MFCC_2 saved to: /content/MFCC_2.txt\n", 114 | "Data for MFCC_3 saved to: /content/MFCC_3.txt\n", 115 | "Data for MFCC_4 saved to: /content/MFCC_4.txt\n", 116 | "Data for MFCC_5 saved to: /content/MFCC_5.txt\n", 117 | "Data for MFCC_6 saved to: /content/MFCC_6.txt\n", 118 | "Data for MFCC_7 saved to: /content/MFCC_7.txt\n", 119 | "Data for MFCC_8 saved to: /content/MFCC_8.txt\n", 120 | "Data for MFCC_9 saved to: /content/MFCC_9.txt\n", 121 | "Data for MFCC_10 saved to: /content/MFCC_10.txt\n", 122 | "Data for MFCC_11 saved to: /content/MFCC_11.txt\n", 123 | "Data for MFCC_12 saved to: /content/MFCC_12.txt\n", 124 | "Data for MFCC_delta_0 saved to: /content/MFCC_delta_0.txt\n", 125 | "Data for MFCC_delta_1 saved to: /content/MFCC_delta_1.txt\n", 126 | "Data for MFCC_delta_2 saved to: /content/MFCC_delta_2.txt\n", 127 | "Data for MFCC_delta_3 saved to: /content/MFCC_delta_3.txt\n", 128 | "Data for MFCC_delta_4 saved to: /content/MFCC_delta_4.txt\n", 129 | "Data for MFCC_delta_5 saved to: /content/MFCC_delta_5.txt\n", 130 | "Data for MFCC_delta_6 saved to: /content/MFCC_delta_6.txt\n", 131 | "Data for MFCC_delta_7 saved to: /content/MFCC_delta_7.txt\n", 132 | "Data for MFCC_delta_8 saved to: /content/MFCC_delta_8.txt\n", 133 | "Data for MFCC_delta_9 saved to: /content/MFCC_delta_9.txt\n", 134 | "Data for MFCC_delta_10 saved to: /content/MFCC_delta_10.txt\n", 135 | "Data for MFCC_delta_11 saved to: /content/MFCC_delta_11.txt\n", 136 | "Data for MFCC_delta_12 saved to: /content/MFCC_delta_12.txt\n" 137 | ] 138 | } 139 | ] 140 | }, 141 | { 142 | "cell_type": "markdown", 143 | "source": [ 144 | "# Simple Beat Times Lists" 145 | ], 146 | "metadata": { 147 | "id": "QKPymmEf2FyP" 148 | } 149 | }, 150 | { 151 | "cell_type": "code", 152 | "source": [ 153 | "import os\n", 154 | "import librosa\n", 155 | "import numpy as np\n", 156 | "\n", 157 | "#@markdown ### Path to your audio file\n", 158 | "audio_path = \"/content/Mechanical_Mirage.mp3\" #@param {type:\"string\"}\n", 159 | "\n", 160 | "# Load the audio file\n", 161 | "y, sr = librosa.load(audio_path, sr=None)\n", 162 | "# Extract tempo and beats\n", 163 | "tempo, beats = librosa.beat.beat_track(y=y, sr=sr)\n", 164 | "beat_times = librosa.frames_to_time(beats, sr=sr)\n", 165 | "\n", 166 | "# Round beat times to hundredth of a second\n", 167 | "beat_times_rounded = [round(time, 2) for time in beat_times]\n", 168 | "# Calculate frame numbers at 10 fps based on beat times\n", 169 | "frame_numbers_10fps = [int(time * 10) for time in beat_times_rounded]\n", 170 | "max_frame = max(frame_numbers_10fps)\n", 171 | "\n", 172 | "# Initialize switch and inverse switch frames\n", 173 | "current_state = 0\n", 174 | "switch_frames = []\n", 175 | "invert_switch_frames = []\n", 176 | "for i in range(max_frame + 1):\n", 177 | " if i in frame_numbers_10fps:\n", 178 | " current_state = 1 - current_state # Toggle between 0 and 1\n", 179 | " switch_frames.append(f\"{i + 1}:({current_state})\")\n", 180 | " invert_switch_frames.append(f\"{i + 1}:({1 - current_state})\")\n", 181 | "\n", 182 | "# Save the switch effect frames\n", 183 | "output_path_switch = '/content/beattimes_switch.txt'\n", 184 | "with open(output_path_switch, 'w') as file:\n", 185 | " file.write(',\\n'.join(switch_frames))\n", 186 | "print(f\"Switch effect frame list saved to: {output_path_switch}\")\n", 187 | "\n", 188 | "# Save the inverse switch effect frames\n", 189 | "output_path_invert_switch = '/content/invert_beattimes_switch.txt'\n", 190 | "with open(output_path_invert_switch, 'w') as file:\n", 191 | " file.write(',\\n'.join(invert_switch_frames))\n", 192 | "print(f\"Inverse switch effect frame list saved to: {output_path_invert_switch}\")\n", 193 | "\n", 194 | "# Function to interpolate beat changes with smooth gradient\n", 195 | "def interpolate_beat_changes(beat_times, fps=10):\n", 196 | " frame_numbers = [int(time * fps) for time in beat_times]\n", 197 | " beat_wave = np.zeros(max(frame_numbers) + 1)\n", 198 | " for i in range(len(frame_numbers) - 1):\n", 199 | " start_frame = frame_numbers[i]\n", 200 | " end_frame = frame_numbers[i + 1]\n", 201 | " beat_wave[start_frame:end_frame] = np.linspace(0, 1, end_frame - start_frame, endpoint=False)\n", 202 | " return beat_wave\n", 203 | "\n", 204 | "# Generate and save the continuous beat wave\n", 205 | "beat_wave = interpolate_beat_changes(beat_times_rounded)\n", 206 | "output_path_wave = '/content/beatwaves.txt'\n", 207 | "with open(output_path_wave, 'w') as file:\n", 208 | " formatted_data_wave = ',\\n'.join([f\"{i + 1}:({value:.2f})\" for i, value in enumerate(beat_wave)])\n", 209 | " file.write(formatted_data_wave)\n", 210 | "print(f\"Continuous beat wave data saved to: {output_path_wave}\")\n", 211 | "\n", 212 | "# Generate and save the inverse continuous beat wave\n", 213 | "inverse_beat_wave = 1 - beat_wave\n", 214 | "output_path_inverse_wave = '/content/invert_beatwaves.txt'\n", 215 | "with open(output_path_inverse_wave, 'w') as file:\n", 216 | " formatted_data_inverse_wave = ',\\n'.join([f\"{i + 1}:({value:.2f})\" for i, value in enumerate(inverse_beat_wave)])\n", 217 | " file.write(formatted_data_inverse_wave)\n", 218 | "print(f\"Inverse continuous beat wave data saved to: {output_path_inverse_wave}\")" 219 | ], 220 | "metadata": { 221 | "colab": { 222 | "base_uri": "https://localhost:8080/" 223 | }, 224 | "id": "Oz8JVYDwy-rw", 225 | "outputId": "4f6572a2-679b-4573-9208-c38fcd98a143" 226 | }, 227 | "execution_count": null, 228 | "outputs": [ 229 | { 230 | "output_type": "stream", 231 | "name": "stdout", 232 | "text": [ 233 | "Switch effect frame list saved to: /content/beattimes_switch.txt\n", 234 | "Inverse switch effect frame list saved to: /content/invert_beattimes_switch.txt\n", 235 | "Continuous beat wave data saved to: /content/beatwaves.txt\n", 236 | "Inverse continuous beat wave data saved to: /content/invert_beatwaves.txt\n" 237 | ] 238 | } 239 | ] 240 | } 241 | ] 242 | } --------------------------------------------------------------------------------