Skip to content

Commit

Permalink
Add AllInOnePlayer
Browse files Browse the repository at this point in the history
  • Loading branch information
BetaDoggo committed Aug 5, 2024
1 parent 9821069 commit 5119e07
Show file tree
Hide file tree
Showing 4 changed files with 268 additions and 3 deletions.
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
# ComfyUI Video Player
1 step closer to replacing all software with comfy
![preview](https://github.com/BetaDoggo/ComfyUI-VideoPlayer/blob/main/ComfyApple.gif)
# Usage

Install this node as well as [ComfyUI-Custom-Scripts](https://github.com/pythongosssss/ComfyUI-Custom-Scripts)
# Usage (automatic)
1. Load allinoneworkflow.json
2. Enter the full video path
3. Check "Extra options" -> "Auto Queue"
4. Click "Queue Prompt"
# Usage (manual)
1. Convert your file using the included convert-video.py or convert-video-color.py/convert-video-color-cuda.py (requires cuda-requirements.txt)
2. Load the example workflow
3. Set the framerate of the video
Expand Down
157 changes: 157 additions & 0 deletions allinoneworkflow.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
{
"last_node_id": 16,
"last_link_id": 11,
"nodes": [
{
"id": 7,
"type": "ShowText|pysssss",
"pos": [
1214,
338
],
"size": {
"0": 1404.90625,
"1": 1109.595703125
},
"flags": {},
"order": 2,
"mode": 0,
"inputs": [
{
"name": "text",
"type": "STRING",
"link": 10,
"widget": {
"name": "text"
}
}
],
"outputs": [
{
"name": "STRING",
"type": "STRING",
"links": null,
"shape": 6
}
],
"properties": {
"Node name for S&R": "ShowText|pysssss"
},
"widgets_values": [
"",
"Failed to load frame. Either the video is over, the video path is wrong or there's another error. \nMake sure that you entered a direct path and that there are no \"s in the path."
]
},
{
"id": 15,
"type": "AllInOnePlayer",
"pos": [
882,
341
],
"size": [
315,
130
],
"flags": {},
"order": 1,
"mode": 0,
"inputs": [
{
"name": "frame",
"type": "INT",
"link": 11,
"widget": {
"name": "frame"
}
}
],
"outputs": [
{
"name": "STRING",
"type": "STRING",
"links": [
10
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "AllInOnePlayer"
},
"widgets_values": [
1,
"",
100,
30
]
},
{
"id": 16,
"type": "PrimitiveNode",
"pos": [
883,
515
],
"size": [
267.27526413341457,
82
],
"flags": {},
"order": 0,
"mode": 0,
"outputs": [
{
"name": "INT",
"type": "INT",
"links": [
11
],
"slot_index": 0,
"widget": {
"name": "frame"
}
}
],
"title": "Frame Counter\n",
"properties": {
"Run widget replace on values": false
},
"widgets_values": [
1,
"increment"
]
}
],
"links": [
[
10,
15,
0,
7,
0,
"STRING"
],
[
11,
16,
0,
15,
0,
"INT"
]
],
"groups": [],
"config": {},
"extra": {
"ds": {
"scale": 0.7513148009015777,
"offset": [
-552.0969407948444,
-147.57064944942996
]
}
},
"version": 0.4
}
105 changes: 103 additions & 2 deletions nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
import cv2
import time
import torch
import shutil
import numpy as np
from PIL import Image
from comfy.utils import ProgressBar

class LoadFrame:
@classmethod
Expand All @@ -21,6 +23,7 @@ def INPUT_TYPES(s):
CATEGORY = "VideoPlayer"
def Loadframe(self, frame, frameRate, path):
try:
path = path.replace('"', '') #make "copy as path" faster
with open((path + str(frame) + ".txt"), 'r', encoding='utf-8') as file:
content = file.read()
timestamp = 0.00
Expand All @@ -45,8 +48,10 @@ def INPUT_TYPES(s):
RETURN_TYPES = ("IMAGE", "FLOAT",)
FUNCTION = "load_jpg_frame"
CATEGORY = "VideoPlayer"

def load_jpg_frame(self, frame, frameRate, path):
try:
path = path.replace('"', '') #make "copy as path" faster
image_path = os.path.join(path, f"{frame:05d}.jpg")
with Image.open(image_path) as img:
img = img.convert("RGB")
Expand Down Expand Up @@ -75,8 +80,10 @@ def INPUT_TYPES(s):
RETURN_TYPES = ("IMAGE", "FLOAT",)
FUNCTION = "LoadVideoFrame"
CATEGORY = "VideoPlayer"

def LoadVideoFrame(self, video_path, frame, frameRate):
try:
video_path = video_path.replace('"', '') #make "copy as path" faster
cap = cv2.VideoCapture(video_path)
cap.set(cv2.CAP_PROP_POS_FRAMES, frame - 1) # Subtract 1 because frame count starts at 0
ret, img = cap.read()
Expand Down Expand Up @@ -123,17 +130,111 @@ def ImageToEmoji(self, image, width):
emoji_array = np.where(pixels > threshold, "⬜", "⬛")
ascii_image = '\n'.join([''.join(row) for row in emoji_array])
return(ascii_image,)

class AllInOnePlayer:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"frame": ("INT", {"default": 1, "min": 1, "max": 100000, "step": 1, "forceInput": True}),
"video_path": ("STRING", {"forceInput": False}),
"width": ("INT", {"default": 100, "min": 10, "max": 200, "step": 1}),
"framerate": ("INT", {"default": 30, "min": 0, "max": 500, "step": 1}),
},
}

RETURN_TYPES = ("STRING",)
FUNCTION = "PlayVideo"
CATEGORY = "VideoPlayer"

def __init__(self):
self.node_dir = os.path.dirname(os.path.abspath(__file__))

def ImageToEmoji(self, image, width):
if len(image.shape) == 3:
image = np.mean(image, axis=2).astype(np.uint8)
else:
image = image.astype(np.uint8)
img = Image.fromarray(image)
pixels = np.array(img)
threshold = np.mean(pixels)
emoji_array = np.where(pixels > threshold, "⬜", "⬛")
ascii_image = '\n'.join([''.join(row) for row in emoji_array])
return ascii_image

def get_video_prefix(self, video_path):
return os.path.splitext(os.path.basename(video_path))[0]

def resize_frame(self, frame, target_width):
height, width = frame.shape[:2]
aspect_ratio = height / width
new_height = int(target_width * aspect_ratio)
return cv2.resize(frame, (target_width, new_height), interpolation=cv2.INTER_AREA)

def ExtractFrames(self, video_path, target_width):
temp_frames_dir = os.path.join(self.node_dir, "temp_frames")
shutil.rmtree(temp_frames_dir, ignore_errors=True)
os.makedirs(temp_frames_dir)
video_prefix = self.get_video_prefix(video_path)
cap = cv2.VideoCapture(video_path)
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
#Initialize progress bar
progress = ProgressBar(10)
frame_count = 0
next_progress_update = total_frames // 10 # Calculate frames per 10%

while True:
ret, frame = cap.read()
if not ret:
break
resized_frame = self.resize_frame(frame, target_width)
emoji_frame = self.ImageToEmoji(resized_frame, target_width)

frame_filename = os.path.join(temp_frames_dir, f"{video_prefix}_frame_{frame_count:04d}.txt")
with open(frame_filename, 'w', encoding='utf-8') as f:
f.write(emoji_frame)

frame_count += 1
#Update progress bar every 10% of frames processed
if frame_count >= next_progress_update:
progress.update(1)
next_progress_update += total_frames // 10

cap.release()

def PlayVideo(self, frame, video_path, width, framerate):
video_path = video_path.replace('"', '') #make "copy as path" faster
progress = ProgressBar(10)
progress.update
temp_frames_dir = os.path.join(self.node_dir, "temp_frames")
video_prefix = self.get_video_prefix(video_path)

if not os.path.exists(temp_frames_dir) or not any(f.startswith(video_prefix) for f in os.listdir(temp_frames_dir)):
self.ExtractFrames(video_path, width)

frame_path = os.path.join(temp_frames_dir, f"{video_prefix}_frame_{frame:04d}.txt")
if os.path.exists(frame_path):
with open(frame_path, 'r', encoding='utf-8') as f:
emoji_frame = f.read()

if framerate != 0:
time.sleep(1/framerate)
return (emoji_frame,)
else:
return ("Failed to load frame. Either the video is over, the video path is wrong, or there's another error.",)

NODE_CLASS_MAPPINGS = {
"LoadFrame": LoadFrame,
"LoadJPGFrame": LoadJPGFrame,
"LoadVideoFrame": LoadVideoFrame,
"ImageToEmoji": ImageToEmoji
"ImageToEmoji": ImageToEmoji,
"AllInOnePlayer": AllInOnePlayer,
}

NODE_DISPLAY_NAME_MAPPINGS = {
"LoadFrame": "LoadFrame",
"LoadJPGFrame": "LoadJPGFrame",
"LoadVideoFrame": "Load Video Frame",
"ImageToEmoji": "Image To Emoji"
"ImageToEmoji": "Image To Emoji",
"AllInOnePlayer": "AllInOnePlayer",
}
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
opencv-python

0 comments on commit 5119e07

Please sign in to comment.