Compare commits

...
Author SHA1 Message Date
shadowcz007 5d4567b134 Lama 改成手动安装,新增JsonRepair 2024-08-06 11:08:50 +08:00
shadowcz007 d110a08889 Update __init__.py 2024-08-06 00:23:34 +08:00
shadowcz007 e0157293cb Update P5.py 2024-08-06 00:21:51 +08:00
shadowcz007 0d985b3b65 update 2024-08-06 00:14:05 +08:00
shadowcz007 a65ade9fda updage 2024-08-05 21:30:30 +08:00
shadowcz007 874d6c8cb1 1 2024-08-05 21:16:19 +08:00
shadowcz007 f70ba2afa3 update 2024-08-05 21:08:32 +08:00
shadowcz007 e9f821e578 update 2024-08-05 20:49:06 +08:00
shadowcz007 77201a457d 基本打通 2024-08-04 23:48:06 +08:00
shadowcz007 076e3b1178 test 2024-08-04 22:28:16 +08:00
shadowcz007 6b13fa64dc update 2024-08-04 20:44:56 +08:00
shadowcz007 846671a890 preview audio 2024-08-04 18:06:37 +08:00
shadowcz007 05b3088b75 0.35.1 2024-08-04 18:02:13 +08:00
37 changed files with 165351 additions and 438 deletions
+18 -5
View File
@@ -1,6 +1,6 @@
![](https://img.shields.io/github/release/shadowcz007/comfyui-mixlab-nodes)
> 适配了最新版 comfyui 的 py3.11 ,torch 2.1.2+cu121
> 适配了最新版 comfyui 的 py3.11 ,torch 2.3.1+cu121
> [Mixlab nodes discord](https://discord.gg/cXs9vZSqeK)
@@ -10,10 +10,14 @@
![alt text](./assets/1722517810720.png)
- 增加 API Key Input 节点,用于管理LLM的Key,同时优化LLM相关节点,为后续agent模式做准备
- 增加 SiliconflowLLM,可以使用由Siliconflow提供的免费LLM
- 增加 Edit Mask,方便在生成的时候手动绘制 mask [workflow](./workflow/edit-mask-workflow.json)
- LaMaInpainting 调整为手动安装
<!-- - ChatGPT 节点支持 Local LLM(llama.cpp),Phi3、llama3 都可以直接一个节点运行了。模型下载后,放置到 `models/llamafile/` -->
<!-- - 右键菜单支持 text-to-text,方便对 prompt 词补全 -->
@@ -112,13 +116,18 @@ https://github.com/shadowcz007/comfyui-mixlab-nodes/assets/12645064/e7e77f90-e43
[Voice + Real-time Face Swap Workflow](./workflow/语音+实时换脸workflow.json)
- Preview Audio
[text-to-audio](./workflow/text-to-audio-base-workflow.json)
### GPT
> Support for calling multiple GPTs.Local LLM(llama.cpp)、 ChatGPT、ChatGLM3 、ChatGLM4 , Some code provided by rui. If you are using OpenAI's service, fill in https://api.openai.com/v1 . If you are using a local LLM service, fill in http://127.0.0.1:xxxx/v1 . Azure OpenAI:https://xxxx.openai.azure.com
> Support for calling multiple GPTs.Local LLM 、 ChatGPT、ChatGLM3 、ChatGLM4 , Some code provided by rui. If you are using OpenAI's service, fill in https://api.openai.com/v1 . If you are using a local LLM service, fill in http://127.0.0.1:xxxx/v1 . Azure OpenAI:https://xxxx.openai.azure.com
![gpt-workflow.svg](./assets/gpt-workflow.svg)
[LLM_base_workflow](./workflow/LLM_base_workflow.json)
[workflow-5](./workflow/5-gpt-workflow.json)
- SiliconflowLLM
- ChatGPTOpenAI
<!-- 最新:ChatGPT 节点支持 Local LLM(llama.cpp),Phi3、llama3 都可以直接一个节点运行了。
@@ -252,10 +261,14 @@ Add edges to an image.
![FeatheredMask](./assets/FlVou_Y6kaGWYoEj1Tn0aTd4AjMI.jpg)
> LaMaInpainting
> LaMaInpainting(需要手动安装)
* simple-lama-inpainting 里的pillow造成冲突,暂时从依赖里移除,如果有安装 simple-lama-inpainting ,节点会自动添加,没有,则不会自动添加。
from [simple-lama-inpainting](https://github.com/enesmsahin/simple-lama-inpainting)
* [问题汇总](https://github.com/shadowcz007/comfyui-mixlab-nodes/issues/294)
> rembgNode
"briarmbg","u2net","u2netp","u2net_human_seg","u2net_cloth_seg","silueta","isnet-general-use","isnet-anime"
+34 -16
View File
@@ -46,26 +46,35 @@ except ImportError:
print("or")
print("pip install -r requirements.txt")
sys.exit()
def is_installed(package, package_overwrite=None):
def is_installed(package, package_overwrite=None,auto_install=True):
is_has=False
try:
spec = importlib.util.find_spec(package)
is_has=spec is not None
except ModuleNotFoundError:
pass
package = package_overwrite or package
if spec is None:
print(f"Installing {package}...")
# 清华源 -i https://pypi.tuna.tsinghua.edu.cn/simple
command = f'"{python}" -m pip install {package}'
result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, env=os.environ)
if auto_install==True:
print(f"Installing {package}...")
# 清华源 -i https://pypi.tuna.tsinghua.edu.cn/simple
command = f'"{python}" -m pip install {package}'
result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, env=os.environ)
if result.returncode != 0:
print(f"Couldn't install\nCommand: {command}\nError code: {result.returncode}")
is_has=True
if result.returncode != 0:
print(f"Couldn't install\nCommand: {command}\nError code: {result.returncode}")
is_has=False
else:
print(package+'## OK')
return is_has
try:
import OpenSSL
@@ -88,7 +97,6 @@ except ImportError:
sys.exit()
def install_openai():
# Helper function to install the OpenAI module if not already installed
try:
@@ -1011,6 +1019,7 @@ from .nodes.Mask import PreviewMask_,MaskListReplace,MaskListMerge,OutlineMask,F
from .nodes.Style import ApplyVisualStylePrompting,StyleAlignedReferenceSampler,StyleAlignedBatchAlign,StyleAlignedSampleReferenceLatents
from .nodes.P5 import P5Input
# 要导出的所有节点及其名称的字典
@@ -1094,7 +1103,9 @@ NODE_CLASS_MAPPINGS = {
"MaskListReplace_":MaskListReplace,
"IncrementingListNode_":IncrementingListNode,
"PreviewMask_":PreviewMask_,
"AudioPlay":AudioPlayNode
"AudioPlay":AudioPlayNode,
"P5Input":P5Input
}
# 一个包含节点友好/可读的标题的字典
@@ -1106,6 +1117,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"Color":"Color Input ♾️MixlabApp",
"TextInput_":"Text Input ♾️MixlabApp",
"KeyInput":"API Key Input ♾️MixlabApp",
"FloatSlider":"Float Slider Input ♾️MixlabApp",
"IntNumber":"Int Input ♾️MixlabApp",
"ImagesPrompt_":"Images Input ♾️MixlabApp",
@@ -1156,6 +1168,8 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"AudioPlay":"Preview Audio ♾️Mixlab",
"MultiplicationNode":"Math Operation ♾️Mixlab",
"P5Input":"P5 Input ♾️Mixlab for test"
}
# web ui的节点功能
@@ -1166,7 +1180,7 @@ logging.info('\033[91m ### Mixlab Nodes: \033[93mLoaded')
# print('\033[91m ### Mixlab Nodes: \033[93mLoaded')
try:
from .nodes.ChatGPT import ChatGPTNode,ShowTextForGPT,CharacterInText,TextSplitByDelimiter,SiliconflowFreeNode
from .nodes.ChatGPT import JsonRepair,ChatGPTNode,ShowTextForGPT,CharacterInText,TextSplitByDelimiter,SiliconflowFreeNode
logging.info('ChatGPT.available True')
NODE_CLASS_MAPPINGS_V = {
@@ -1175,6 +1189,7 @@ try:
"ShowTextForGPT":ShowTextForGPT,
"CharacterInText":CharacterInText,
"TextSplitByDelimiter":TextSplitByDelimiter,
"JsonRepair":JsonRepair
}
# 一个包含节点友好/可读的标题的字典
@@ -1184,6 +1199,7 @@ try:
"ShowTextForGPT":"Show Text ♾️MixlabApp",
"CharacterInText":"Character In Text",
"TextSplitByDelimiter":"Text Split By Delimiter",
"JsonRepair":"Json Repair"
}
@@ -1203,10 +1219,12 @@ except Exception as e:
logging.info('edit_mask.available False')
try:
from .nodes.Lama import LaMaInpainting
logging.info('LaMaInpainting.available {}'.format(LaMaInpainting.available))
if LaMaInpainting.available:
NODE_CLASS_MAPPINGS['LaMaInpainting']=LaMaInpainting
is_has=is_installed('simple_lama_inpainting',None,False)
if is_has:
from .nodes.Lama import LaMaInpainting
logging.info('LaMaInpainting.available {}'.format(LaMaInpainting.available))
if LaMaInpainting.available:
NODE_CLASS_MAPPINGS['LaMaInpainting']=LaMaInpainting
except Exception as e:
logging.info('LaMaInpainting.available False')
+170 -40
View File
@@ -6,14 +6,69 @@ import folder_paths
import hashlib
import codecs,sys
import importlib.util
import subprocess
python = sys.executable
# 从文本中提取json
def extract_json_strings(text):
json_strings = []
brace_level = 0
json_str = ''
in_json = False
for char in text:
if char == '{':
brace_level += 1
in_json = True
if in_json:
json_str += char
if char == '}':
brace_level -= 1
if in_json and brace_level == 0:
json_strings.append(json_str)
json_str = ''
in_json = False
return json_strings[0] if len(json_strings)>0 else "{}"
def is_installed(package):
def is_installed(package, package_overwrite=None,auto_install=True):
is_has=False
try:
spec = importlib.util.find_spec(package)
is_has=spec is not None
except ModuleNotFoundError:
return False
return spec is not None
pass
package = package_overwrite or package
if spec is None:
if auto_install==True:
print(f"Installing {package}...")
# 清华源 -i https://pypi.tuna.tsinghua.edu.cn/simple
command = f'"{python}" -m pip install {package}'
result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, env=os.environ)
is_has=True
if result.returncode != 0:
print(f"Couldn't install\nCommand: {command}\nError code: {result.returncode}")
is_has=False
else:
print(package+'## OK')
return is_has
# def is_installed(package):
# try:
# spec = importlib.util.find_spec(package)
# except ModuleNotFoundError:
# return False
# return spec is not None
def get_unique_hash(string):
@@ -59,24 +114,8 @@ def openai_client(key,url):
return client
def ZhipuAI_client(key):
try:
if is_installed('zhipuai')==False:
import subprocess
# 安装
print('#pip install zhipuai')
result = subprocess.run([sys.executable, '-s', '-m', 'pip', 'install', 'zhipuai'], capture_output=True, text=True)
#检查命令执行结果
if result.returncode == 0:
print("#install success")
from zhipuai import ZhipuAI
else:
print("#install error")
else:
if is_installed('zhipuai')==True:
from zhipuai import ZhipuAI
except:
print("#install zhipuai error")
@@ -160,7 +199,9 @@ def get_llama_path():
# return llm
if is_installed('json_repair'):
from json_repair import repair_json
def chat(client, model_name,messages ):
@@ -207,6 +248,36 @@ def chat(client, model_name,messages ):
return content
llm_apis=[
{
"value": "https://api.openai.com/v1",
"label": "openai"
},
{
"value": "https://openai.api2d.net/v1",
"label": "api2d"
},
# {
# "value": "https://docs-test-001.openai.azure.com",
# "label": "https://docs-test-001.openai.azure.com"
# },
{
"value": "https://api.moonshot.cn/v1",
"label": "Kimi"
},
{
"value": "https://api.deepseek.com/v1",
"label": "DeepSeek-V2"
},
{
"value": "https://api.siliconflow.cn/v1",
"label": "SiliconCloud"
}]
llm_apis_dict = {api["label"]: api["value"] for api in llm_apis}
class ChatGPTNode:
def __init__(self):
# self.__client = OpenAI()
@@ -216,8 +287,9 @@ class ChatGPTNode:
@classmethod
def INPUT_TYPES(cls):
model_list=[
"gpt-3.5-turbo",
"gpt-3.5-turbo",
"gpt-3.5-turbo-16k",
"gpt-4o",
"gpt-4o-2024-05-13",
@@ -243,25 +315,32 @@ class ChatGPTNode:
"01-ai/Yi-1.5-9B-Chat-16K",
"meta-llama/Meta-Llama-3.1-8B-Instruct"
]
return {
"required": {
"api_key":("KEY", {"default": "", "multiline": True,"dynamicPrompts": False}),
"api_url":("URL", {"default": "", "multiline": True,"dynamicPrompts": False}),
# "api_key":("KEY", {"default": "", "multiline": True,"dynamicPrompts": False}),
# "api_key":("STRING", {"forceInput": True,}),
"prompt": ("STRING", {"multiline": True,"dynamicPrompts": False}),
"system_content": ("STRING",
{
"default": "You are ChatGPT, a large language model trained by OpenAI. Answer as concisely as possible.",
"multiline": True,"dynamicPrompts": False
}),
"model": ( model_list,
{"default": model_list[0]}),
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "step": 1}),
"context_size":("INT", {"default": 1, "min": 0, "max":30, "step": 1}),
"api_url":(list(llm_apis_dict.keys()),
{"default": list(llm_apis_dict.keys())[0]}),
},
"hidden": {
"unique_id": "UNIQUE_ID",
"extra_pnginfo": "EXTRA_PNGINFO",
},
"optional":{
"api_key":("STRING", {"forceInput": True,}),
"custom_model_name":("STRING", {"forceInput": True,}), #适合自定义model
"custom_api_url":("STRING", {"forceInput": True,}), #适合自定义model
},
}
RETURN_TYPES = ("STRING","STRING","STRING",)
@@ -273,12 +352,29 @@ class ChatGPTNode:
def generate_contextual_text(self,
api_key,
api_url,
# api_key,
prompt,
system_content,
model,
seed,context_size,unique_id = None, extra_pnginfo=None):
model,
seed,
context_size,
api_url,
api_key=None,
custom_model_name=None,
custom_api_url=None,
):
if custom_model_name!=None:
model=custom_model_name
api_url=llm_apis_dict[api_url] if api_url in llm_apis_dict else ""
if custom_api_url!=None:
api_url=custom_api_url
if api_key==None:
api_key="lm_studio"
# print(api_key!='',api_url,prompt,system_content,model,seed)
# 可以选择保留会话历史以维持上下文记忆
# 或者在此处清除会话历史 self.session_history.clear()
@@ -291,7 +387,7 @@ class ChatGPTNode:
self.system_content=system_content
# self.session_history=[]
# self.session_history.append({"role": "system", "content": system_content})
print("api_key,api_url",api_key,api_url)
#
if is_azure_url(api_url):
client=azure_client(api_key,api_url)
@@ -359,7 +455,7 @@ class SiliconflowFreeNode:
]
return {
"required": {
"api_key":("KEY", {"default": "", "multiline": True,"dynamicPrompts": False}),
"api_key":("STRING", {"forceInput": True,}),
"prompt": ("STRING", {"multiline": True,"dynamicPrompts": False}),
"system_content": ("STRING",
{
@@ -371,10 +467,9 @@ class SiliconflowFreeNode:
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "step": 1}),
"context_size":("INT", {"default": 1, "min": 0, "max":30, "step": 1}),
},
"hidden": {
"unique_id": "UNIQUE_ID",
"extra_pnginfo": "EXTRA_PNGINFO",
},
"optional":{
"custom_model_name":("STRING", {"forceInput": True,}), #适合自定义model
},
}
RETURN_TYPES = ("STRING","STRING","STRING",)
@@ -390,8 +485,11 @@ class SiliconflowFreeNode:
prompt,
system_content,
model,
seed,context_size,unique_id = None, extra_pnginfo=None):
seed,context_size,custom_model_name=None):
if custom_model_name!=None:
model=custom_model_name
api_url="https://api.siliconflow.cn/v1"
# 把系统信息和初始信息添加到会话历史中
@@ -588,3 +686,35 @@ class TextSplitByDelimiter:
arr= arr[start_index:start_index + max_count * (skip_every+1):(skip_every+1)]
return (arr,)
class JsonRepair:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"json_string":("STRING", {"forceInput": True,}),
}
}
INPUT_IS_LIST = False
RETURN_TYPES = ("STRING",)
FUNCTION = "run"
# OUTPUT_NODE = True
OUTPUT_IS_LIST = (False,)
CATEGORY = "♾️Mixlab/GPT"
def run(self, json_string):
json_string=extract_json_strings(json_string)
# print(json_string)
good_json_string = repair_json(json_string)
# 将 JSON 字符串解析为 Python 对象
data = json.loads(good_json_string)
# 将 Python 对象转换回 JSON 字符串,确保中文字符不被转义
json_str_with_chinese = json.dumps(data, ensure_ascii=False)
return (json_str_with_chinese,)
+8 -8
View File
@@ -1515,29 +1515,29 @@ class TextImage:
"font": (get_files_with_extension(FONT_PATH,['.ttf','.otf']),),#后缀为 ttf
"font_size": ("INT",{
"default":100,
"min": 100, #Minimum value
"max": 1000, #Maximum value
"min": 1, #Minimum value
"max": 10000000, #Maximum value
"step": 1, #Slider's step
"display": "number" # Cosmetic only: display as "number" or "slider"
}),
"spacing": ("INT",{
"default":12,
"min": -200, #Minimum value
"max": 200, #Maximum value
"min": -2000000000, #Minimum value
"max": 2000000000, #Maximum value
"step": 1, #Slider's step
"display": "number" # Cosmetic only: display as "number" or "slider"
}),
"line_spacing": ("INT",{
"default":12,
"min": -200, #Minimum value
"max": 200, #Maximum value
"min": -2000000000, #Minimum value
"max": 2000000000, #Maximum value
"step": 1, #Slider's step
"display": "number" # Cosmetic only: display as "number" or "slider"
}),
"padding": ("INT",{
"default":8,
"min": 0, #Minimum value
"max": 200, #Maximum value
"max": 2000000000, #Maximum value
"step": 1, #Slider's step
"display": "number" # Cosmetic only: display as "number" or "slider"
}),
@@ -1598,7 +1598,7 @@ class LoadImagesFromURL:
def run(self,url,seed=0):
global urls_image
print(urls_image)
# print(urls_image)
def filter_http_urls(urls):
filtered_urls = []
for url in urls.split('\n'):
+104
View File
@@ -0,0 +1,104 @@
import torch
import numpy as np
from PIL import Image,ImageSequence,ImageOps
import base64
import io
import comfy.utils
import folder_paths
import node_helpers
# Tensor to PIL
def tensor2pil(image):
return Image.fromarray(np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8))
# Convert PIL to Tensor
def pil2tensor(image):
return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0)
def load_image( image):
image_path = folder_paths.get_annotated_filepath(image)
img = node_helpers.pillow(Image.open, image_path)
output_images = []
output_masks = []
w, h = None, None
excluded_formats = ['MPO']
for i in ImageSequence.Iterator(img):
i = node_helpers.pillow(ImageOps.exif_transpose, i)
if i.mode == 'I':
i = i.point(lambda i: i * (1 / 255))
image = i.convert("RGB")
if len(output_images) == 0:
w = image.size[0]
h = image.size[1]
if image.size[0] != w or image.size[1] != h:
continue
image = np.array(image).astype(np.float32) / 255.0
image = torch.from_numpy(image)[None,]
if 'A' in i.getbands():
mask = np.array(i.getchannel('A')).astype(np.float32) / 255.0
mask = 1. - torch.from_numpy(mask)
else:
mask = torch.zeros((64,64), dtype=torch.float32, device="cpu")
output_images.append(image)
output_masks.append(mask.unsqueeze(0))
if len(output_images) > 1 and img.format not in excluded_formats:
output_image = torch.cat(output_images, dim=0)
output_mask = torch.cat(output_masks, dim=0)
else:
output_image = output_images[0]
output_mask = output_masks[0]
return (output_image, output_mask)
class P5Input:
@classmethod
def INPUT_TYPES(s):
return {"required": {
"frames":("IMAGEBASE64",),
},
}
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("frames",)
FUNCTION = "run"
CATEGORY = "♾️Mixlab/Input"
OUTPUT_NODE = True
INPUT_IS_LIST = False
OUTPUT_IS_LIST = (False,)
def run(self, frames):
ims=[]
for im in frames['images']:
# print(im)
if 'type' in im and (not f"[{im['type']}]" in im['name']):
im['name']=im['name']+" "+f"[{im['type']}]"
output_image, output_mask = load_image(im['name'])
ims.append(output_image)
if len(ims)==0:
image1 = Image.new('RGB', (512, 512), color='black')
return (pil2tensor(image1),)
image1 = ims[0]
for image2 in ims[1:]:
if image1.shape[1:] != image2.shape[1:]:
image2 = comfy.utils.common_upscale(image2.movedim(-1, 1), image1.shape[2], image1.shape[1], "bilinear", "center").movedim(1, -1)
image1 = torch.cat((image1, image2), dim=0)
# 用于节点提示:p5节点提示有多少帧
return {"ui": {"_info": [len(frames['images'])]}, "result": (image1,)}
+2 -2
View File
@@ -1,8 +1,8 @@
[project]
name = "comfyui-mixlab-nodes"
description = "3D, ScreenShareNode & FloatingVideoNode, SpeechRecognition & SpeechSynthesis, GPT, LoadImagesFromLocal, Layers, Other Nodes, ..."
version = "0.34.0"
license = { text = "MIT License" }
version = "0.35.2"
license = "MIT"
dependencies = ["numpy", "pyOpenSSL", "watchdog", "opencv-python-headless", "matplotlib", "openai", "simple-lama-inpainting", "clip-interrogator==0.6.0", "transformers>=4.36.0", "lark-parser", "imageio-ffmpeg", "rembg[gpu]", "omegaconf==2.3.0", "Pillow>=9.5.0", "einops==0.7.0", "trimesh>=4.0.5", "huggingface-hub", "scikit-image"]
[project.urls]
+3 -2
View File
@@ -4,7 +4,7 @@ watchdog
opencv-python-headless
matplotlib
openai
simple-lama-inpainting
# simple-lama-inpainting
clip-interrogator==0.6.0
transformers>=4.36.0
lark-parser
@@ -17,4 +17,5 @@ trimesh>=4.0.5
huggingface-hub
scikit-image
torchaudio
soundfile>=0.12.1
soundfile>=0.12.1
json-repair
+1 -1
View File
@@ -3,7 +3,7 @@ import { app } from '../../../scripts/app.js'
const repoOwner = 'shadowcz007' // 替换为仓库的所有者
const repoName = 'comfyui-mixlab-nodes' // 替换为仓库的名称
const version = 'v0.34.0'
const version = 'v0.35.1'
fetch(`https://api.github.com/repos/${repoOwner}/${repoName}/releases/latest`)
.then(response => response.json())
+1 -334
View File
@@ -1,319 +1,5 @@
import { app } from '../../../scripts/app.js'
// import { api } from '../../../scripts/api.js'
import { ComfyWidgets } from '../../../scripts/widgets.js'
import { $el } from '../../../scripts/ui.js'
async function getConfig () {
let api_host = `${window.location.hostname}:${window.location.port}`
let api_base = ''
let url = `${window.location.protocol}//${api_host}${api_base}`
const res = await fetch(`${url}/mixlab`, {
method: 'POST'
})
return await res.json()
}
function get_position_style (ctx, widget_width, y, node_height) {
const MARGIN = 4 // the margin around the html element
/* Create a transform that deals with all the scrolling and zooming */
const elRect = ctx.canvas.getBoundingClientRect()
const transform = new DOMMatrix()
.scaleSelf(
elRect.width / ctx.canvas.width,
elRect.height / ctx.canvas.height
)
.multiplySelf(ctx.getTransform())
.translateSelf(MARGIN, MARGIN + y)
return {
transformOrigin: '0 0',
transform: transform,
left: `0`,
top: `0`,
cursor: 'pointer',
position: 'absolute',
maxWidth: `${widget_width - MARGIN * 2}px`,
// maxHeight: `${node_height - MARGIN * 2}px`, // we're assuming we have the whole height of the node
width: `${widget_width - MARGIN * 2}px`,
// height: `${node_height * 0.3 - MARGIN * 2}px`,
// background: '#EEEEEE',
display: 'flex',
flexDirection: 'column',
// alignItems: 'center',
justifyContent: 'space-around'
}
}
const getLocalData = key => {
let data = {}
try {
data = JSON.parse(localStorage.getItem(key)) || {}
} catch (error) {
return {}
}
return data
}
app.registerExtension({
name: 'Mixlab.GPT.ChatGPTOpenAI',
async getCustomWidgets (app) {
return {
KEY (node, inputName, inputData, app) {
// console.log('##inputData', inputData)
const widget = {
type: inputData[0], // the type, CHEESE
name: inputName, // the name, slice
size: [128, 32], // a default size
draw (ctx, node, width, y) {},
computeSize (...args) {
return [128, 32] // a method to compute the current size of the widget
},
async serializeValue (nodeId, widgetIndex) {
let data = getLocalData('_mixlab_api_key')
return data[node.id] || 'by Mixlab'
}
}
// widget.something = something; // maybe adds stuff to it
node.addCustomWidget(widget) // adds it to the node
return widget // and returns it.
},
URL (node, inputName, inputData, app) {
// console.log('node', inputName, inputData[0])
const widget = {
type: inputData[0], // the type, CHEESE
name: inputName, // the name, slice
size: [128, 32], // a default size
draw (ctx, node, width, y) {
// a method to draw the widget (ctx is a CanvasRenderingContext2D)
},
computeSize (...args) {
return [128, 32] // a method to compute the current size of the widget
},
async serializeValue (nodeId, widgetIndex) {
let data = getLocalData('_mixlab_api_url')
return data[node.id] || 'https://api.openai.com/v1'
}
}
// widget.something = something; // maybe adds stuff to it
node.addCustomWidget(widget) // adds it to the node
return widget // and returns it.
}
}
},
async beforeRegisterNodeDef (nodeType, nodeData, app) {
if (nodeType.comfyClass == 'ChatGPTOpenAI') {
const orig_nodeCreated = nodeType.prototype.onNodeCreated
nodeType.prototype.onNodeCreated = function () {
orig_nodeCreated?.apply(this, arguments)
const api_key = this.widgets.filter(w => w.name == 'api_key')[0]
const api_url = this.widgets.filter(w => w.name == 'api_url')[0]
console.log('ChatGPTOpenAI nodeData', this.widgets)
const widget = {
type: 'div',
name: 'chatgptdiv',
draw (ctx, node, widget_width, y, widget_height) {
Object.assign(
this.div.style,
get_position_style(ctx, widget_width, api_key.y, node.size[1])
)
}
}
widget.div = $el('div', {})
document.body.appendChild(widget.div)
const inputDiv = (key, placeholder) => {
let div = document.createElement('div')
const ip = document.createElement('input')
ip.type = placeholder === 'Key' ? 'password' : 'text'
ip.className = `${'comfy-multiline-input'} ${placeholder}`
div.style = `display: flex;
align-items: center;
margin: 6px 8px;
margin-top: 0;`
ip.placeholder = placeholder
ip.value = placeholder
ip.style = `margin-left: 24px;
outline: none;
border: none;
padding: 4px;width: 100%;`
const label = document.createElement('label')
label.style = 'font-size: 10px;min-width:32px'
label.innerText = placeholder
div.appendChild(label)
div.appendChild(ip)
ip.addEventListener('change', () => {
let data = getLocalData(key)
data[this.id] = ip.value.trim()
localStorage.setItem(key, JSON.stringify(data))
console.log(this.id, key)
})
return div
}
let inputKey = inputDiv('_mixlab_api_key', 'Key')
let inputUrl = inputDiv('_mixlab_api_url', 'URL')
widget.div.appendChild(inputKey)
widget.div.appendChild(inputUrl)
this.addCustomWidget(widget)
const onRemoved = this.onRemoved
this.onRemoved = () => {
inputUrl.remove()
inputKey.remove()
widget.div.remove()
return onRemoved?.()
}
this.serialize_widgets = true //需要保存参数
}
}
},
async loadedGraphNode (node, app) {
// Fires every time a node is constructed
// You can modify widgets/add handlers/etc here
if (node.type === 'ChatGPTOpenAI') {
let widget = node.widgets.filter(w => w.div)[0]
let apiKey = getLocalData('_mixlab_api_key'),
url = getLocalData('_mixlab_api_url')
let id = node.id
// console.log('ChatGPTOpenAI serialize_widgets', this)
widget.div.querySelector('.Key').value = apiKey[id] || 'by Mixlab'
widget.div.querySelector('.URL').value =
url[id] || 'https://api.openai.com/v1'
}
}
});
app.registerExtension({
name: 'Mixlab.GPT.SiliconflowLLM',
async getCustomWidgets (app) {
return {
KEY (node, inputName, inputData, app) {
// console.log('##inputData', inputData)
const widget = {
type: inputData[0], // the type, CHEESE
name: inputName, // the name, slice
size: [128, 32], // a default size
draw (ctx, node, width, y) {},
computeSize (...args) {
return [128, 32] // a method to compute the current size of the widget
},
async serializeValue (nodeId, widgetIndex) {
let data = getLocalData('_mixlab_api_key')
return data[node.id] || 'by Mixlab'
}
}
// widget.something = something; // maybe adds stuff to it
node.addCustomWidget(widget) // adds it to the node
return widget // and returns it.
},
}
},
async beforeRegisterNodeDef (nodeType, nodeData, app) {
if (nodeType.comfyClass == 'SiliconflowLLM') {
const orig_nodeCreated = nodeType.prototype.onNodeCreated
nodeType.prototype.onNodeCreated = function () {
orig_nodeCreated?.apply(this, arguments)
const api_key = this.widgets.filter(w => w.name == 'api_key')[0]
const widget = {
type: 'div',
name: 'chatgptdiv',
draw (ctx, node, widget_width, y, widget_height) {
Object.assign(
this.div.style,
get_position_style(ctx, widget_width, api_key.y, node.size[1])
)
}
}
widget.div = $el('div', {})
document.body.appendChild(widget.div)
const inputDiv = (key, placeholder) => {
let div = document.createElement('div')
const ip = document.createElement('input')
ip.type = placeholder === 'Key' ? 'password' : 'text'
ip.className = `${'comfy-multiline-input'} ${placeholder}`
div.style = `display: flex;
align-items: center;
margin: 6px 8px;
margin-top: 0;`
ip.placeholder = placeholder
ip.value = placeholder
ip.style = `margin-left: 24px;
outline: none;
border: none;
padding: 4px;width: 100%;`
const label = document.createElement('label')
label.style = 'font-size: 10px;min-width:32px'
label.innerText = placeholder
div.appendChild(label)
div.appendChild(ip)
ip.addEventListener('change', () => {
let data = getLocalData(key)
data[this.id] = ip.value.trim()
localStorage.setItem(key, JSON.stringify(data))
console.log(this.id, key)
})
return div
}
let inputKey = inputDiv('_mixlab_api_key', 'Key')
widget.div.appendChild(inputKey)
this.addCustomWidget(widget)
const onRemoved = this.onRemoved
this.onRemoved = () => {
inputKey.remove()
widget.div.remove()
return onRemoved?.()
}
this.serialize_widgets = true //需要保存参数
}
}
},
async loadedGraphNode (node, app) {
// Fires every time a node is constructed
// You can modify widgets/add handlers/etc here
if (node.type === 'SiliconflowLLM') {
let widget = node.widgets.filter(w => w.div)[0]
let apiKey = getLocalData('_mixlab_api_key');
let id = node.id
widget.div.querySelector('.Key').value = apiKey[id] || 'by Mixlab'
}
}
})
app.registerExtension({
name: 'Mixlab.GPT.ShowTextForGPT',
@@ -328,7 +14,7 @@ app.registerExtension({
for (let i = 0; i < this.widgets.length; i++) {
if (this.widgets[i].name == 'show_text')
this.widgets[i].onRemove?.()
console.log('#ShowTextForGPT', this.widgets[i])
}
this.widgets.length = 2
}
@@ -399,24 +85,5 @@ app.registerExtension({
this.serialize_widgets = true //需要保存参数
}
},
async loadedGraphNode (node, app) {
if (node.type === 'ShowTextForGPT') {
let widget = node.widgets.filter(w => w.name == 'show_text')[0]
// if (widget.value) {
// let [url, prompt] = widget.value
// this[`wavesurfer_${node.id}`] = updateWaveWidgetValue(
// node.widgets,
// node.id,
// url,
// prompt,
// this[`wavesurfer_${node.id}`]
// )
// }
console.log('#loadedGraphNode', node)
}
}
})
+1 -1
View File
@@ -861,7 +861,7 @@ app.registerExtension({
nodeCreated (node, app) {
//数据延迟??
setTimeout(() => {
console.log('#LoadImagesToBatch', node.type)
// console.log('#LoadImagesToBatch', node.type)
if (node.type === 'LoadImagesToBatch') {
let imagesWidget = node.widgets.filter(w => w.name === 'images')[0]
let imagePreview = node.widgets.filter(w => w.name == 'image_base64')[0]
+214
View File
@@ -0,0 +1,214 @@
import { app } from '../../../scripts/app.js'
import { api } from '../../../scripts/api.js'
import { $el } from '../../../scripts/ui.js'
function base64ToBlobFromURL (base64URL, contentType) {
return fetch(base64URL).then(response => response.blob())
}
async function uploadImage (blob, fileType = '.svg', filename) {
// const blob = await (await fetch(src)).blob();
const body = new FormData()
body.append(
'image',
new File([blob], (filename || new Date().getTime()) + fileType)
)
const resp = await api.fetchApi('/upload/image', {
method: 'POST',
body
})
// console.log(resp)
let data = await resp.json()
let { name, subfolder } = data
// let src = api.apiURL(
// `/view?filename=${encodeURIComponent(
// name
// )}&type=input&subfolder=${subfolder}${app.getPreviewFormatParam()}${app.getRandParam()}`
// )
return data
}
// 上传得到url
async function uploadBase64ToFile (base64) {
let bg_blob = await base64ToBlobFromURL(base64)
let url = await uploadImage(bg_blob, '.png')
return url
}
function get_position_style (ctx, widget_width, y, node_height) {
const MARGIN = 4 // the margin around the html element
/* Create a transform that deals with all the scrolling and zooming */
const elRect = ctx.canvas.getBoundingClientRect()
const transform = new DOMMatrix()
.scaleSelf(
elRect.width / ctx.canvas.width,
elRect.height / ctx.canvas.height
)
.multiplySelf(ctx.getTransform())
.translateSelf(MARGIN, MARGIN + y)
return {
transformOrigin: '0 0',
transform: transform,
left: `0`,
top: `0`,
cursor: 'pointer',
position: 'absolute',
maxWidth: `${widget_width - MARGIN * 2}px`,
// maxHeight: `${node_height - MARGIN * 2}px`, // we're assuming we have the whole height of the node
width: `${widget_width - MARGIN * 2}px`,
// height: `${node_height * 0.3 - MARGIN * 2}px`,
// background: '#EEEEEE',
display: 'flex',
flexDirection: 'column',
// alignItems: 'center',
justifyContent: 'space-around'
}
}
const p5InputNode = {
name: 'Mixlab.Comfy.P5Input',
async getCustomWidgets (app) {
return {
IMAGEBASE64 (node, inputName, inputData, app) {
const widget = {
value: {
images: []
}, // 不能[x,x,x]
type: inputData[0], // the type
name: inputName, // the name, slice
size: [320, 120], // a default size
draw (ctx, node, width, y) {},
computeSize (...args) {
return [128, 32] // a method to compute the current size of the widget
}
}
node.addCustomWidget(widget)
return widget
}
}
},
async beforeRegisterNodeDef (nodeType, nodeData, app) {
if (nodeType.comfyClass == 'P5Input') {
console.log('P5Input')
const orig_nodeCreated = nodeType.prototype.onNodeCreated
nodeType.prototype.onNodeCreated = function () {
orig_nodeCreated?.apply(this, arguments)
const widget = {
type: 'div',
name: 'image_base64',
draw (ctx, node, widget_width, y, widget_height) {
Object.assign(
this.div.style,
get_position_style(ctx, widget_width - 24, 44, node.size[1])
)
},
serialize: false
}
widget.div = $el('div', {})
widget.div.style = `margin:12px;width:400px;height:480px;background:white`
document.body.appendChild(widget.div)
this.addCustomWidget(widget)
// document.addEventListener('wheel', handleMouseWheel)
const onRemoved = this.onRemoved
this.onRemoved = () => {
widget.div.remove()
// window.removeEventListener('message', ms)
return onRemoved?.()
}
// 节点的大小控制
this.setSize([480, 560])
app.canvas.draw(true, true)
const onResize = this.onResize
this.onResize = () => {
// 设置最小尺寸
if (
Math.max(this.size[0], 480) != this.size[0] &&
Math.max(this.size[1], 560) != this.size[1]
) {
this.setSize([
Math.max(this.size[0], 480),
Math.max(this.size[1], 560)
])
}
return onResize?.apply(this, arguments)
}
this.serialize_widgets = true //需要保存参数
}
const onExecuted = nodeType.prototype.onExecuted
nodeType.prototype.onExecuted = function (message) {
onExecuted?.apply(this, arguments)
// console.log('##onExecuted', this, message._info)
// app.graph.getNodeById(8).widgets[1].div.querySelector('iframe').contentWindow.postMessage('Hello from parent', '*');
}
}
},
async loadedGraphNode (node, app) {
if (node.type === 'P5Input') {
}
},
nodeCreated (node, app) {
//数据延迟??
setTimeout(() => {
let widget = node.widgets?.filter(w => w.name == 'image_base64')[0]
let framesWidget = node.widgets?.filter(w => w.name == 'frames')[0]
if (node.type === 'P5Input' && widget) {
if (framesWidget && !framesWidget.value)
framesWidget.value = { images: [] }
framesWidget.value._seed = Math.random()
let nodeId = node.id
//延迟才能获得this.id
widget.div.innerHTML = `<iframe src="extensions/comfyui-mixlab-nodes/p5_export/p5.html?id=${nodeId}"
style="border:0;width:100%;height:100%;"
></iframe>`
// 监听来自iframe的消息
const ms = async event => {
const data = event.data
if (
data.from === 'p5.widget' &&
data.status === 'save' &&
data.frames &&
data.frames.length > 0 &&
data.nodeId == nodeId
) {
const frames = data.frames
console.log(frames.length, nodeId)
//workflow会存储到local,会卡死
framesWidget.value.images = []
for (const f of frames) {
let file = await uploadBase64ToFile(f)
framesWidget.value.images.push(file)
}
// framesWidget.value.base64 = frames
framesWidget.value._seed = Math.random()
node.title = 'P5 Input #' + frames.length
}
}
window.addEventListener('message', ms)
}
}, 1000)
}
}
app.registerExtension(p5InputNode)
+46 -29
View File
@@ -357,7 +357,7 @@ app.registerExtension({
const widget = {
type: inputData[0], // the type, CHEESE
name: inputName, // the name, slice
size: [128, 32], // a default size
size: [128, 24], // a default size
draw (ctx, node, width, y) {},
computeSize (...args) {
return [128, 32] // a method to compute the current size of the widget
@@ -382,57 +382,61 @@ app.registerExtension({
const widget = {
type: 'div',
name: 'key_inp',
name: 'input_key',
draw (ctx, node, widget_width, y, widget_height) {
Object.assign(
this.div.style,
get_position_style(ctx, widget_width, 44, node.size[1])
)
get_position_style(ctx, widget_width, 24, node.size[1])
)
}
}
widget.div = $el('div', {})
document.body.appendChild(widget.div)
const inputDiv = (key, placeholder) => {
let div = document.createElement('div')
const ip = document.createElement('input')
ip.type = placeholder === 'Key' ? 'password' : 'text'
ip.className = `${'comfy-multiline-input'} ${placeholder}`
div.style = `display: flex;
div.style = `
display: flex;
align-items: center;
margin: 6px 8px;
margin-top: 0;`
ip.placeholder = placeholder
ip.value = placeholder
margin-top:0px;
height:44px;
width:220px;
`
ip.style = `margin-left: 24px;
const ip = document.createElement('input')
ip.type = 'password'
ip.className = `${'comfy-multiline-input'} ${placeholder}`
ip.placeholder = placeholder
// ip.value = placeholder
ip.style = `margin-left:8px;
outline: none;
border: none;
padding: 4px;width: 100%;`
const label = document.createElement('label')
label.style = 'font-size: 10px;min-width:32px'
label.innerText = placeholder
div.appendChild(label)
padding:12px;
width: 100%;
`
div.appendChild(ip)
ip.addEventListener('change', () => {
let data = getLocalData(key)
data[this.id] = ip.value.trim()
localStorage.setItem(key, JSON.stringify(data))
console.log(this.id, key)
})
return div
}
let inputKey = inputDiv('_mixlab_api_key', 'Key')
let inputKey = inputDiv('_mixlab_api_key', 'Key')
widget.div.appendChild(inputKey)
widget.div.appendChild(inputKey)
this.addCustomWidget(widget)
console.log('KeyInput nodeData',widget.div)
const onRemoved = this.onRemoved
this.onRemoved = () => {
inputKey.remove()
@@ -445,17 +449,30 @@ app.registerExtension({
}
},
async loadedGraphNode (node, app) {
// Fires every time a node is constructed
// You can modify widgets/add handlers/etc here
if (node.type === 'KeyInput') {
let widget = node.widgets.filter(w => w.div)[0]
let apiKey = getLocalData('_mixlab_api_key');
let apiKey = getLocalData('_mixlab_api_key')
let id = node.id
widget.div.querySelector('.Key').value = apiKey[id] || 'by Mixlab'
if (widget.div.querySelector('.Key'))
widget.div.querySelector('.Key').value = apiKey[id] || 'by Mixlab'
}
},
nodeCreated (node, app) {
//数据延迟??
setTimeout(() => {
// console.log('#LoadImagesToBatch', node.type)
if (node.type === 'KeyInput') {
let widget = node.widgets.filter(w => w.div)[0]
let apiKey = getLocalData('_mixlab_api_key')
let id = node.id
if (widget.div.querySelector('.Key'))
widget.div.querySelector('.Key').value = apiKey[id] || 'by Mixlab'
}
}, 1000)
}
})
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+6
View File
@@ -0,0 +1,6 @@
<!DOCTYPE html>
<meta charset="utf-8">
<link href="https://fonts.googleapis.com/css?family=Montserrat" rel="stylesheet">
<title>p5.js-widget</title>
<div id="app-holder"></div>
<script src="./main.bundle.js"></script>
+240
View File
@@ -0,0 +1,240 @@
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId])
/******/ return installedModules[moduleId].exports;
/******/
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ exports: {},
/******/ id: moduleId,
/******/ loaded: false
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.loaded = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
var defaults = __webpack_require__(1);
var MY_FILENAME = 'p5-widget.js';
var IFRAME_FILENAME = 'p5-widget.html';
var IFRAME_STYLE = [
'width: 100%',
'background-color: white',
'border: 1px solid #ec245e',
'box-sizing: border-box'
];
var AVOID_MIXED_CONTENT_WARNINGS = true;
var myScriptEl = getMyScriptEl();
var myBaseURL = getMyBaseURL(myScriptEl ? myScriptEl.src : "");
var autoload = myScriptEl ? !myScriptEl.hasAttribute('data-manual') : false;
var nextId = 1;
function getMyBaseURL(url) {
var baseURL = url.slice(0, -MY_FILENAME.length);
if (AVOID_MIXED_CONTENT_WARNINGS) {
if (window.location.protocol === 'http:' && /^https:/.test(baseURL)) {
// Our script was loaded over HTTPS, but the embedding page is
// using HTTP. This is likely to result in mixed content warnings
// if e.g. the widget's sketch wants to load resources relative to
// the embedding page's URL, so let's just embed the widget over
// HTTP instead of HTTPS.
baseURL = baseURL.replace('https:', 'http:');
}
}
return baseURL;
}
function getMyScriptEl() {
return (document.currentScript ||
document.querySelectorAll("script[src$='" + MY_FILENAME + "']")[0]);
}
// http://stackoverflow.com/a/7557433/2422398
function isElementInViewport(el) {
var rect = el.getBoundingClientRect();
return (rect.bottom >= 0 &&
rect.right >= 0 &&
rect.top <= (window.innerHeight ||
document.documentElement.clientHeight) &&
rect.left <= (window.innerWidth ||
document.documentElement.clientWidth));
}
function getDataHeight(el) {
var height = parseInt(el.getAttribute('data-height'));
if (isNaN(height))
height = defaults.HEIGHT;
return height;
}
function absoluteURL(url) {
var a = document.createElement('a');
a.setAttribute('href', url);
return a.href;
}
function getSketch(url, cb) {
var error = function (msg) {
var lines = ['// p5.js-widget failed to retrieve ' + url + '.'];
if (msg && typeof (msg) == 'string') {
lines.push('// ' + msg);
}
cb(lines.join('\n'));
};
var req = new XMLHttpRequest();
req.open('GET', url);
req.onload = function () {
if (req.status == 200) {
cb(req.responseText);
}
else {
error('Server returned HTTP ' + req.status + '.');
}
};
req.onerror = error;
req.send(null);
}
function replaceScriptWithWidget(el) {
var iframe = document.createElement('iframe');
var height = getDataHeight(el);
var previewWidth = parseInt(el.getAttribute('data-preview-width'));
var baseSketchURL = absoluteURL(el.getAttribute('data-base-url'));
var p5version = el.getAttribute('data-p5-version');
var maxRunTime = parseInt(el.getAttribute('data-max-run-time'));
var autoplay = el.hasAttribute('data-autoplay');
var url;
var qsArgs = [
'id=' + encodeURIComponent(el.getAttribute('data-id'))
];
var style = IFRAME_STYLE.slice();
function makeWidget(sketch) {
qsArgs.push('sketch=' + encodeURIComponent(sketch));
style.push('min-height: ' + height + 'px');
url = myBaseURL + IFRAME_FILENAME + '?' + qsArgs.join('&');
iframe.setAttribute('src', url);
iframe.setAttribute('style', style.join('; '));
el.parentNode.replaceChild(iframe, el);
}
if (!isNaN(previewWidth) && previewWidth >= 0) {
qsArgs.push('previewWidth=' + previewWidth);
}
if (!isNaN(maxRunTime) && maxRunTime >= 0) {
qsArgs.push('maxRunTime=' + maxRunTime);
}
if (baseSketchURL) {
qsArgs.push('baseSketchURL=' + encodeURIComponent(baseSketchURL));
}
if (p5version) {
qsArgs.push('p5version=' + encodeURIComponent(p5version));
}
if (autoplay) {
qsArgs.push('autoplay=on');
}
if (el.src && el.textContent && el.textContent.trim()) {
return makeWidget([
'// Your widget includes both a "src" attribute and inline script',
'// content, which makes no sense. Please remove one of them.'
].join('\n'));
}
if (el.src) {
getSketch(el.src, makeWidget);
}
else {
makeWidget(el.textContent);
}
}
function whenVisible(el, cb) {
var CHECK_INTERVAL_MS = 1000;
var interval;
function maybeMakeVisible() {
if (!isElementInViewport(el))
return;
clearInterval(interval);
window.removeEventListener('scroll', maybeMakeVisible, false);
window.removeEventListener('resize', maybeMakeVisible, false);
cb(el);
}
// We want to check at a fixed interval as a fallback, to make
// sure that we detect when the element is visible even outside
// of the usual means (e.g., because the user did some
// sort of pinch/zoom gesture).
interval = setInterval(maybeMakeVisible, 1000);
window.addEventListener('scroll', maybeMakeVisible, false);
window.addEventListener('resize', maybeMakeVisible, false);
maybeMakeVisible();
}
function lazilyReplaceScriptWithWidget(el) {
var height = getDataHeight(el);
el.style.display = 'block';
el.style.fontSize = '0';
el.style.width = '100%';
el.style.minHeight = height + 'px';
el.style.background = '#f0f0f0';
if (!el.hasAttribute('data-id')) {
el.setAttribute('data-id', nextId.toString());
nextId++;
}
whenVisible(el, replaceScriptWithWidget);
}
function lazilyReplaceAllScriptsWithWidget() {
var scripts = document.querySelectorAll("script[type='text/p5']");
[].slice.call(scripts).forEach(function (el) {
lazilyReplaceScriptWithWidget(el);
});
}
if (autoload) {
if (document.readyState === 'complete') {
lazilyReplaceAllScriptsWithWidget();
}
else {
window.addEventListener('load', lazilyReplaceAllScriptsWithWidget, false);
}
}
window['p5Widget'] = {
baseURL: myBaseURL,
url: myBaseURL + MY_FILENAME,
replaceScript: lazilyReplaceScriptWithWidget,
replaceAll: lazilyReplaceAllScriptsWithWidget,
defaults: defaults
};
/***/ }),
/* 1 */
/***/ (function(module, exports) {
"use strict";
exports.P5_VERSION = '0.4.23';
exports.PREVIEW_WIDTH = 150;
exports.HEIGHT = 300;
exports.MAX_RUN_TIME = 1000;
/***/ })
/******/ ]);
//# sourceMappingURL=p5-widget.js.map
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,501 @@
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId])
/******/ return installedModules[moduleId].exports;
/******/
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ exports: {},
/******/ id: moduleId,
/******/ loaded: false
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.loaded = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(0);
/******/ })
/************************************************************************/
/******/ ({
/***/ 0:
/***/ (function(module, exports, __webpack_require__) {
"use strict";
__webpack_require__(216);
// @ts-ignore
var global = window;
function loadScript(url, cb) {
var script = document.createElement('script');
cb = cb || (function () { });
script.onload = cb;
script.onerror = function () {
console.log("Failed to load script: " + url);
};
script.setAttribute('src', url);
document.body.appendChild(script);
}
function loadScripts(urls, cb) {
cb = cb || (function () { });
var i = 0;
var loadNextScript = function () {
if (i === urls.length) {
return cb();
}
loadScript(urls[i++], loadNextScript);
};
loadNextScript();
}
function p5url(version) {
return "//cdnjs.cloudflare.com/ajax/libs/p5.js/" + version + "/p5.js";
}
function LoopChecker(sketch, funcName, maxRunTime) {
var self = {
wasTriggered: false,
getLineNumber: function () {
var index = loopCheckFailureRange[0];
var line = 1;
for (var i = 0; i < index; i++) {
if (sketch[i] === '\n')
line++;
}
return line;
}
};
var startTime = Date.now();
var loopCheckFailureRange = null;
global[funcName] = function (range) {
if (Date.now() - startTime > maxRunTime) {
self.wasTriggered = true;
loopCheckFailureRange = range;
throw new Error("Loop took over " + maxRunTime + " ms to run");
}
};
setInterval(function () {
startTime = Date.now();
}, maxRunTime / 2);
return self;
}
function setBaseURL(url) {
var base = document.createElement('base');
base.setAttribute('href', url);
document.head.appendChild(base);
}
function startSketch(sketch, p5version, maxRunTime, loopCheckFuncName, baseURL, errorCb) {
var sketchScript = document.createElement('script');
var loopChecker = LoopChecker(sketch, loopCheckFuncName, maxRunTime);
if (baseURL) {
setBaseURL(baseURL);
}
sketchScript.textContent = sketch;
global.addEventListener('error', function (e) {
var message = e.message;
var line = undefined;
if (loopChecker.wasTriggered) {
message = "Your loop is taking too long to run.";
line = loopChecker.getLineNumber();
}
else if (typeof (e.lineno) === 'number' &&
(e.filename === '' || e.filename === window.location.href)) {
line = e.lineno;
}
// p5 sketches don't actually stop looping if they throw an exception,
// so try to stop the sketch.
try {
global.noLoop();
}
catch (e) { }
errorCb(message, line);
});
loadScripts([
p5url(p5version),
], function () {
document.body.appendChild(sketchScript);
if (document.readyState === 'complete') {
new global.p5();
}
});
}
global.startSketch = startSketch;
/***/ }),
/***/ 210:
/***/ (function(module, exports) {
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
// css base code, injected by the css-loader
module.exports = function() {
var list = [];
// return the list of modules as css string
list.toString = function toString() {
var result = [];
for(var i = 0; i < this.length; i++) {
var item = this[i];
if(item[2]) {
result.push("@media " + item[2] + "{" + item[1] + "}");
} else {
result.push(item[1]);
}
}
return result.join("");
};
// import a list of modules into the list
list.i = function(modules, mediaQuery) {
if(typeof modules === "string")
modules = [[null, modules, ""]];
var alreadyImportedModules = {};
for(var i = 0; i < this.length; i++) {
var id = this[i][0];
if(typeof id === "number")
alreadyImportedModules[id] = true;
}
for(i = 0; i < modules.length; i++) {
var item = modules[i];
// skip already imported module
// this implementation is not 100% perfect for weird media query combinations
// when a module is imported multiple times with different media queries.
// I hope this will never occur (Hey this way we have smaller bundles)
if(typeof item[0] !== "number" || !alreadyImportedModules[item[0]]) {
if(mediaQuery && !item[2]) {
item[2] = mediaQuery;
} else if(mediaQuery) {
item[2] = "(" + item[2] + ") and (" + mediaQuery + ")";
}
list.push(item);
}
}
};
return list;
};
/***/ }),
/***/ 211:
/***/ (function(module, exports, __webpack_require__) {
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
var stylesInDom = {},
memoize = function(fn) {
var memo;
return function () {
if (typeof memo === "undefined") memo = fn.apply(this, arguments);
return memo;
};
},
isOldIE = memoize(function() {
return /msie [6-9]\b/.test(self.navigator.userAgent.toLowerCase());
}),
getHeadElement = memoize(function () {
return document.head || document.getElementsByTagName("head")[0];
}),
singletonElement = null,
singletonCounter = 0,
styleElementsInsertedAtTop = [];
module.exports = function(list, options) {
if(false) {
if(typeof document !== "object") throw new Error("The style-loader cannot be used in a non-browser environment");
}
options = options || {};
// Force single-tag solution on IE6-9, which has a hard limit on the # of <style>
// tags it will allow on a page
if (typeof options.singleton === "undefined") options.singleton = isOldIE();
// By default, add <style> tags to the bottom of <head>.
if (typeof options.insertAt === "undefined") options.insertAt = "bottom";
var styles = listToStyles(list);
addStylesToDom(styles, options);
return function update(newList) {
var mayRemove = [];
for(var i = 0; i < styles.length; i++) {
var item = styles[i];
var domStyle = stylesInDom[item.id];
domStyle.refs--;
mayRemove.push(domStyle);
}
if(newList) {
var newStyles = listToStyles(newList);
addStylesToDom(newStyles, options);
}
for(var i = 0; i < mayRemove.length; i++) {
var domStyle = mayRemove[i];
if(domStyle.refs === 0) {
for(var j = 0; j < domStyle.parts.length; j++)
domStyle.parts[j]();
delete stylesInDom[domStyle.id];
}
}
};
}
function addStylesToDom(styles, options) {
for(var i = 0; i < styles.length; i++) {
var item = styles[i];
var domStyle = stylesInDom[item.id];
if(domStyle) {
domStyle.refs++;
for(var j = 0; j < domStyle.parts.length; j++) {
domStyle.parts[j](item.parts[j]);
}
for(; j < item.parts.length; j++) {
domStyle.parts.push(addStyle(item.parts[j], options));
}
} else {
var parts = [];
for(var j = 0; j < item.parts.length; j++) {
parts.push(addStyle(item.parts[j], options));
}
stylesInDom[item.id] = {id: item.id, refs: 1, parts: parts};
}
}
}
function listToStyles(list) {
var styles = [];
var newStyles = {};
for(var i = 0; i < list.length; i++) {
var item = list[i];
var id = item[0];
var css = item[1];
var media = item[2];
var sourceMap = item[3];
var part = {css: css, media: media, sourceMap: sourceMap};
if(!newStyles[id])
styles.push(newStyles[id] = {id: id, parts: [part]});
else
newStyles[id].parts.push(part);
}
return styles;
}
function insertStyleElement(options, styleElement) {
var head = getHeadElement();
var lastStyleElementInsertedAtTop = styleElementsInsertedAtTop[styleElementsInsertedAtTop.length - 1];
if (options.insertAt === "top") {
if(!lastStyleElementInsertedAtTop) {
head.insertBefore(styleElement, head.firstChild);
} else if(lastStyleElementInsertedAtTop.nextSibling) {
head.insertBefore(styleElement, lastStyleElementInsertedAtTop.nextSibling);
} else {
head.appendChild(styleElement);
}
styleElementsInsertedAtTop.push(styleElement);
} else if (options.insertAt === "bottom") {
head.appendChild(styleElement);
} else {
throw new Error("Invalid value for parameter 'insertAt'. Must be 'top' or 'bottom'.");
}
}
function removeStyleElement(styleElement) {
styleElement.parentNode.removeChild(styleElement);
var idx = styleElementsInsertedAtTop.indexOf(styleElement);
if(idx >= 0) {
styleElementsInsertedAtTop.splice(idx, 1);
}
}
function createStyleElement(options) {
var styleElement = document.createElement("style");
styleElement.type = "text/css";
insertStyleElement(options, styleElement);
return styleElement;
}
function createLinkElement(options) {
var linkElement = document.createElement("link");
linkElement.rel = "stylesheet";
insertStyleElement(options, linkElement);
return linkElement;
}
function addStyle(obj, options) {
var styleElement, update, remove;
if (options.singleton) {
var styleIndex = singletonCounter++;
styleElement = singletonElement || (singletonElement = createStyleElement(options));
update = applyToSingletonTag.bind(null, styleElement, styleIndex, false);
remove = applyToSingletonTag.bind(null, styleElement, styleIndex, true);
} else if(obj.sourceMap &&
typeof URL === "function" &&
typeof URL.createObjectURL === "function" &&
typeof URL.revokeObjectURL === "function" &&
typeof Blob === "function" &&
typeof btoa === "function") {
styleElement = createLinkElement(options);
update = updateLink.bind(null, styleElement);
remove = function() {
removeStyleElement(styleElement);
if(styleElement.href)
URL.revokeObjectURL(styleElement.href);
};
} else {
styleElement = createStyleElement(options);
update = applyToTag.bind(null, styleElement);
remove = function() {
removeStyleElement(styleElement);
};
}
update(obj);
return function updateStyle(newObj) {
if(newObj) {
if(newObj.css === obj.css && newObj.media === obj.media && newObj.sourceMap === obj.sourceMap)
return;
update(obj = newObj);
} else {
remove();
}
};
}
var replaceText = (function () {
var textStore = [];
return function (index, replacement) {
textStore[index] = replacement;
return textStore.filter(Boolean).join('\n');
};
})();
function applyToSingletonTag(styleElement, index, remove, obj) {
var css = remove ? "" : obj.css;
if (styleElement.styleSheet) {
styleElement.styleSheet.cssText = replaceText(index, css);
} else {
var cssNode = document.createTextNode(css);
var childNodes = styleElement.childNodes;
if (childNodes[index]) styleElement.removeChild(childNodes[index]);
if (childNodes.length) {
styleElement.insertBefore(cssNode, childNodes[index]);
} else {
styleElement.appendChild(cssNode);
}
}
}
function applyToTag(styleElement, obj) {
var css = obj.css;
var media = obj.media;
if(media) {
styleElement.setAttribute("media", media)
}
if(styleElement.styleSheet) {
styleElement.styleSheet.cssText = css;
} else {
while(styleElement.firstChild) {
styleElement.removeChild(styleElement.firstChild);
}
styleElement.appendChild(document.createTextNode(css));
}
}
function updateLink(linkElement, obj) {
var css = obj.css;
var sourceMap = obj.sourceMap;
if(sourceMap) {
// http://stackoverflow.com/a/26603875
css += "\n/*# sourceMappingURL=data:application/json;base64," + btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap)))) + " */";
}
var blob = new Blob([css], { type: "text/css" });
var oldSrc = linkElement.href;
linkElement.href = URL.createObjectURL(blob);
if(oldSrc)
URL.revokeObjectURL(oldSrc);
}
/***/ }),
/***/ 216:
/***/ (function(module, exports, __webpack_require__) {
// style-loader: Adds some css to the DOM by adding a <style> tag
// load the styles
var content = __webpack_require__(217);
if(typeof content === 'string') content = [[module.id, content, '']];
// add the styles to the DOM
var update = __webpack_require__(211)(content, {});
if(content.locals) module.exports = content.locals;
// Hot Module Replacement
if(false) {
// When the styles change, update the <style> tags
if(!content.locals) {
module.hot.accept("!!../node_modules/.store/css-loader@0.23.1/node_modules/css-loader/index.js?sourceMap!../node_modules/.store/postcss-loader@0.8.2/node_modules/postcss-loader/index.js?sourceMap!./preview-frame.css", function() {
var newContent = require("!!../node_modules/.store/css-loader@0.23.1/node_modules/css-loader/index.js?sourceMap!../node_modules/.store/postcss-loader@0.8.2/node_modules/postcss-loader/index.js?sourceMap!./preview-frame.css");
if(typeof newContent === 'string') newContent = [[module.id, newContent, '']];
update(newContent);
});
}
// When the module is disposed, remove the <style> tags
module.hot.dispose(function() { update(); });
}
/***/ }),
/***/ 217:
/***/ (function(module, exports, __webpack_require__) {
exports = module.exports = __webpack_require__(210)();
// imports
// module
exports.push([module.id, "html, body {\r\n height: 100%;\r\n}\r\n\r\nbody {\r\n margin: 0;\r\n display: -ms-flexbox;\r\n display: flex;\r\n\r\n /* This centers our sketch horizontally. */\r\n -ms-flex-pack: center;\r\n justify-content: center;\r\n\r\n /* This centers our sketch vertically. */\r\n -ms-flex-align: center;\r\n align-items: center;\r\n}\r\n", "", {"version":3,"sources":["/./css/preview-frame.css"],"names":[],"mappings":"AAAA;EACE,aAAa;CACd;;AAED;EACE,UAAU;EACV,qBAAc;EAAd,cAAc;;EAEd,2CAA2C;EAC3C,sBAAwB;MAAxB,wBAAwB;;EAExB,yCAAyC;EACzC,uBAAoB;MAApB,oBAAoB;CACrB","file":"preview-frame.css","sourcesContent":["html, body {\r\n height: 100%;\r\n}\r\n\r\nbody {\r\n margin: 0;\r\n display: flex;\r\n\r\n /* This centers our sketch horizontally. */\r\n justify-content: center;\r\n\r\n /* This centers our sketch vertically. */\r\n align-items: center;\r\n}\r\n"],"sourceRoot":"webpack://"}]);
// exports
/***/ })
/******/ });
//# sourceMappingURL=preview-frame.bundle.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,43 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>Preview</title>
<body>
<script src="./p5.js"></script>
<script src="./src/CCapture.js"></script>
<!-- <script src="src/gif.js"></script> -->
<!-- <script src="src/gif.worker.js"></script> -->
<!-- <script src="src/download.js"></script> -->
<!-- <script src="src/tar.js"></script> -->
<script>
let capturer = new CCapture({
format: 'png',
framerate: 60,
verbose: true
});
var capturer_start = () => {
if (frameCount === 1) {
capturer.start();
}
}
var capturer_end = (t = 24) => {
if (frameCount < t) {
capturer.capture(canvas, t);
} else if (frameCount === t) {
capturer.save(null);
capturer.stop();
capturer = null;
}
}
</script>
<script src="./preview-frame.bundle.js"></script>
</body>
File diff suppressed because it is too large Load Diff
+585
View File
@@ -0,0 +1,585 @@
/*
var vid = new Whammy.Video();
vid.add(canvas or data url)
vid.compile()
*/
window.Whammy = (function(){
// in this case, frames has a very specific meaning, which will be
// detailed once i finish writing the code
function toWebM(frames, outputAsArray){
var info = checkFrames(frames);
//max duration by cluster in milliseconds
var CLUSTER_MAX_DURATION = 30000;
var EBML = [
{
"id": 0x1a45dfa3, // EBML
"data": [
{
"data": 1,
"id": 0x4286 // EBMLVersion
},
{
"data": 1,
"id": 0x42f7 // EBMLReadVersion
},
{
"data": 4,
"id": 0x42f2 // EBMLMaxIDLength
},
{
"data": 8,
"id": 0x42f3 // EBMLMaxSizeLength
},
{
"data": "webm",
"id": 0x4282 // DocType
},
{
"data": 2,
"id": 0x4287 // DocTypeVersion
},
{
"data": 2,
"id": 0x4285 // DocTypeReadVersion
}
]
},
{
"id": 0x18538067, // Segment
"data": [
{
"id": 0x1549a966, // Info
"data": [
{
"data": 1e6, //do things in millisecs (num of nanosecs for duration scale)
"id": 0x2ad7b1 // TimecodeScale
},
{
"data": "whammy",
"id": 0x4d80 // MuxingApp
},
{
"data": "whammy",
"id": 0x5741 // WritingApp
},
{
"data": doubleToString(info.duration),
"id": 0x4489 // Duration
}
]
},
{
"id": 0x1654ae6b, // Tracks
"data": [
{
"id": 0xae, // TrackEntry
"data": [
{
"data": 1,
"id": 0xd7 // TrackNumber
},
{
"data": 1,
"id": 0x73c5 // TrackUID
},
{
"data": 0,
"id": 0x9c // FlagLacing
},
{
"data": "und",
"id": 0x22b59c // Language
},
{
"data": "V_VP8",
"id": 0x86 // CodecID
},
{
"data": "VP8",
"id": 0x258688 // CodecName
},
{
"data": 1,
"id": 0x83 // TrackType
},
{
"id": 0xe0, // Video
"data": [
{
"data": info.width,
"id": 0xb0 // PixelWidth
},
{
"data": info.height,
"id": 0xba // PixelHeight
}
]
}
]
}
]
},
{
"id": 0x1c53bb6b, // Cues
"data": [
//cue insertion point
]
}
//cluster insertion point
]
}
];
var segment = EBML[1];
var cues = segment.data[2];
//Generate clusters (max duration)
var frameNumber = 0;
var clusterTimecode = 0;
while(frameNumber < frames.length){
var cuePoint = {
"id": 0xbb, // CuePoint
"data": [
{
"data": Math.round(clusterTimecode),
"id": 0xb3 // CueTime
},
{
"id": 0xb7, // CueTrackPositions
"data": [
{
"data": 1,
"id": 0xf7 // CueTrack
},
{
"data": 0, // to be filled in when we know it
"size": 8,
"id": 0xf1 // CueClusterPosition
}
]
}
]
};
cues.data.push(cuePoint);
var clusterFrames = [];
var clusterDuration = 0;
do {
clusterFrames.push(frames[frameNumber]);
clusterDuration += frames[frameNumber].duration;
frameNumber++;
}while(frameNumber < frames.length && clusterDuration < CLUSTER_MAX_DURATION);
var clusterCounter = 0;
var cluster = {
"id": 0x1f43b675, // Cluster
"data": [
{
"data": Math.round(clusterTimecode),
"id": 0xe7 // Timecode
}
].concat(clusterFrames.map(function(webp){
var block = makeSimpleBlock({
discardable: 0,
frame: webp.data.slice(4),
invisible: 0,
keyframe: 1,
lacing: 0,
trackNum: 1,
timecode: Math.round(clusterCounter)
});
clusterCounter += webp.duration;
return {
data: block,
id: 0xa3
};
}))
}
//Add cluster to segment
segment.data.push(cluster);
clusterTimecode += clusterDuration;
}
//First pass to compute cluster positions
var position = 0;
for(var i = 0; i < segment.data.length; i++){
if (i >= 3) {
cues.data[i-3].data[1].data[1].data = position;
}
var data = generateEBML([segment.data[i]], outputAsArray);
position += data.size || data.byteLength || data.length;
if (i != 2) { // not cues
//Save results to avoid having to encode everything twice
segment.data[i] = data;
}
}
return generateEBML(EBML, outputAsArray)
}
// sums the lengths of all the frames and gets the duration, woo
function checkFrames(frames){
var width = frames[0].width,
height = frames[0].height,
duration = frames[0].duration;
for(var i = 1; i < frames.length; i++){
if(frames[i].width != width) throw "Frame " + (i + 1) + " has a different width";
if(frames[i].height != height) throw "Frame " + (i + 1) + " has a different height";
if(frames[i].duration < 0 || frames[i].duration > 0x7fff) throw "Frame " + (i + 1) + " has a weird duration (must be between 0 and 32767)";
duration += frames[i].duration;
}
return {
duration: duration,
width: width,
height: height
};
}
function numToBuffer(num){
var parts = [];
while(num > 0){
parts.push(num & 0xff)
num = num >> 8
}
return new Uint8Array(parts.reverse());
}
function numToFixedBuffer(num, size){
var parts = new Uint8Array(size);
for(var i = size - 1; i >= 0; i--){
parts[i] = num & 0xff;
num = num >> 8;
}
return parts;
}
function strToBuffer(str){
// return new Blob([str]);
var arr = new Uint8Array(str.length);
for(var i = 0; i < str.length; i++){
arr[i] = str.charCodeAt(i)
}
return arr;
// this is slower
// return new Uint8Array(str.split('').map(function(e){
// return e.charCodeAt(0)
// }))
}
//sorry this is ugly, and sort of hard to understand exactly why this was done
// at all really, but the reason is that there's some code below that i dont really
// feel like understanding, and this is easier than using my brain.
function bitsToBuffer(bits){
var data = [];
var pad = (bits.length % 8) ? (new Array(1 + 8 - (bits.length % 8))).join('0') : '';
bits = pad + bits;
for(var i = 0; i < bits.length; i+= 8){
data.push(parseInt(bits.substr(i,8),2))
}
return new Uint8Array(data);
}
function generateEBML(json, outputAsArray){
var ebml = [];
for(var i = 0; i < json.length; i++){
if (!('id' in json[i])){
//already encoded blob or byteArray
ebml.push(json[i]);
continue;
}
var data = json[i].data;
if(typeof data == 'object') data = generateEBML(data, outputAsArray);
if(typeof data == 'number') data = ('size' in json[i]) ? numToFixedBuffer(data, json[i].size) : bitsToBuffer(data.toString(2));
if(typeof data == 'string') data = strToBuffer(data);
if(data.length){
var z = z;
}
/*var len = data.size || data.byteLength || data.length;
var zeroes = Math.ceil(Math.ceil(Math.log(len)/Math.log(2))/8);
var size_str = len.toString(2);
var padded = (new Array((zeroes * 7 + 7 + 1) - size_str.length)).join('0') + size_str;
var size = (new Array(zeroes)).join('0') + '1' + padded;*/
var len = data.size || data.byteLength || data.length;
var zeroes = 0
for( var j = 56; j > 0; j-= 7 ) {
if( len > Math.pow( 2, j ) - 2 ) {
zeroes = j / 7
break
}
}
var size_str = len.toString(2);
var base = ( new Array( 8 * ( zeroes + 1 ) + 1 ) ).join( '0' )
var pre = ( new Array( zeroes + 1 ) ).join( '0' ) + 1;
var padded = base.substr( 0, base.length - size_str.length - pre.length ) + size_str;
var size = pre + padded;
//i actually dont quite understand what went on up there, so I'm not really
//going to fix this, i'm probably just going to write some hacky thing which
//converts that string into a buffer-esque thing
ebml.push(numToBuffer(json[i].id));
ebml.push(bitsToBuffer(size));
ebml.push(data)
}
//output as blob or byteArray
if(outputAsArray){
//convert ebml to an array
var buffer = toFlatArray(ebml)
return new Uint8Array(buffer);
}else{
return new Blob(ebml, {type: "video/webm"});
}
}
function toFlatArray(arr, outBuffer){
if(outBuffer == null){
outBuffer = [];
}
for(var i = 0; i < arr.length; i++){
if(typeof arr[i] == 'object'){
//an array
toFlatArray(arr[i], outBuffer)
}else{
//a simple element
outBuffer.push(arr[i]);
}
}
return outBuffer;
}
//OKAY, so the following two functions are the string-based old stuff, the reason they're
//still sort of in here, is that they're actually faster than the new blob stuff because
//getAsFile isn't widely implemented, or at least, it doesn't work in chrome, which is the
// only browser which supports get as webp
//Converting between a string of 0010101001's and binary back and forth is probably inefficient
//TODO: get rid of this function
function toBinStr_old(bits){
var data = '';
var pad = (bits.length % 8) ? (new Array(1 + 8 - (bits.length % 8))).join('0') : '';
bits = pad + bits;
for(var i = 0; i < bits.length; i+= 8){
data += String.fromCharCode(parseInt(bits.substr(i,8),2))
}
return data;
}
function generateEBML_old(json){
var ebml = '';
for(var i = 0; i < json.length; i++){
var data = json[i].data;
if(typeof data == 'object') data = generateEBML_old(data);
if(typeof data == 'number') data = toBinStr_old(data.toString(2));
var len = data.length;
var zeroes = Math.ceil(Math.ceil(Math.log(len)/Math.log(2))/8);
var size_str = len.toString(2);
var padded = (new Array((zeroes * 7 + 7 + 1) - size_str.length)).join('0') + size_str;
var size = (new Array(zeroes)).join('0') + '1' + padded;
ebml += toBinStr_old(json[i].id.toString(2)) + toBinStr_old(size) + data;
}
return ebml;
}
//woot, a function that's actually written for this project!
//this parses some json markup and makes it into that binary magic
//which can then get shoved into the matroska comtainer (peaceably)
function makeSimpleBlock(data){
var flags = 0;
if (data.keyframe) flags |= 128;
if (data.invisible) flags |= 8;
if (data.lacing) flags |= (data.lacing << 1);
if (data.discardable) flags |= 1;
if (data.trackNum > 127) {
throw "TrackNumber > 127 not supported";
}
var out = [data.trackNum | 0x80, data.timecode >> 8, data.timecode & 0xff, flags].map(function(e){
return String.fromCharCode(e)
}).join('') + data.frame;
return out;
}
// here's something else taken verbatim from weppy, awesome rite?
function parseWebP(riff){
var VP8 = riff.RIFF[0].WEBP[0];
var frame_start = VP8.indexOf('\x9d\x01\x2a'); //A VP8 keyframe starts with the 0x9d012a header
for(var i = 0, c = []; i < 4; i++) c[i] = VP8.charCodeAt(frame_start + 3 + i);
var width, horizontal_scale, height, vertical_scale, tmp;
//the code below is literally copied verbatim from the bitstream spec
tmp = (c[1] << 8) | c[0];
width = tmp & 0x3FFF;
horizontal_scale = tmp >> 14;
tmp = (c[3] << 8) | c[2];
height = tmp & 0x3FFF;
vertical_scale = tmp >> 14;
return {
width: width,
height: height,
data: VP8,
riff: riff
}
}
// i think i'm going off on a riff by pretending this is some known
// idiom which i'm making a casual and brilliant pun about, but since
// i can't find anything on google which conforms to this idiomatic
// usage, I'm assuming this is just a consequence of some psychotic
// break which makes me make up puns. well, enough riff-raff (aha a
// rescue of sorts), this function was ripped wholesale from weppy
function parseRIFF(string){
var offset = 0;
var chunks = {};
while (offset < string.length) {
var id = string.substr(offset, 4);
chunks[id] = chunks[id] || [];
if (id == 'RIFF' || id == 'LIST') {
var len = parseInt(string.substr(offset + 4, 4).split('').map(function(i){
var unpadded = i.charCodeAt(0).toString(2);
return (new Array(8 - unpadded.length + 1)).join('0') + unpadded
}).join(''),2);
var data = string.substr(offset + 4 + 4, len);
offset += 4 + 4 + len;
chunks[id].push(parseRIFF(data));
} else if (id == 'WEBP') {
// Use (offset + 8) to skip past "VP8 "/"VP8L"/"VP8X" field after "WEBP"
chunks[id].push(string.substr(offset + 8));
offset = string.length;
} else {
// Unknown chunk type; push entire payload
chunks[id].push(string.substr(offset + 4));
offset = string.length;
}
}
return chunks;
}
// here's a little utility function that acts as a utility for other functions
// basically, the only purpose is for encoding "Duration", which is encoded as
// a double (considerably more difficult to encode than an integer)
function doubleToString(num){
return [].slice.call(
new Uint8Array(
(
new Float64Array([num]) //create a float64 array
).buffer) //extract the array buffer
, 0) // convert the Uint8Array into a regular array
.map(function(e){ //since it's a regular array, we can now use map
return String.fromCharCode(e) // encode all the bytes individually
})
.reverse() //correct the byte endianness (assume it's little endian for now)
.join('') // join the bytes in holy matrimony as a string
}
function WhammyVideo(speed, quality){ // a more abstract-ish API
this.frames = [];
this.duration = 1000 / speed;
this.quality = quality || 0.8;
}
WhammyVideo.prototype.add = function(frame, duration){
if(typeof duration != 'undefined' && this.duration) throw "you can't pass a duration if the fps is set";
if(typeof duration == 'undefined' && !this.duration) throw "if you don't have the fps set, you need to have durations here.";
if(frame.canvas){ //CanvasRenderingContext2D
frame = frame.canvas;
}
if(frame.toDataURL){
// frame = frame.toDataURL('image/webp', this.quality);
// quickly store image data so we don't block cpu. encode in compile method.
frame = frame.getContext('2d').getImageData(0, 0, frame.width, frame.height);
}else if(typeof frame != "string"){
throw "frame must be a a HTMLCanvasElement, a CanvasRenderingContext2D or a DataURI formatted string"
}
if (typeof frame === "string" && !(/^data:image\/webp;base64,/ig).test(frame)) {
throw "Input must be formatted properly as a base64 encoded DataURI of type image/webp";
}
this.frames.push({
image: frame,
duration: duration || this.duration
});
};
// deferred webp encoding. Draws image data to canvas, then encodes as dataUrl
WhammyVideo.prototype.encodeFrames = function(callback){
if(this.frames[0].image instanceof ImageData){
var frames = this.frames;
var tmpCanvas = document.createElement('canvas');
var tmpContext = tmpCanvas.getContext('2d');
tmpCanvas.width = this.frames[0].image.width;
tmpCanvas.height = this.frames[0].image.height;
var encodeFrame = function(index){
var frame = frames[index];
tmpContext.putImageData(frame.image, 0, 0);
frame.image = tmpCanvas.toDataURL('image/webp', this.quality);
if(index < frames.length-1){
setTimeout(function(){ encodeFrame(index + 1); }, 1);
}else{
callback();
}
}.bind(this);
encodeFrame(0);
}else{
callback();
}
};
WhammyVideo.prototype.compile = function(outputAsArray, callback){
this.encodeFrames(function(){
var webm = new toWebM(this.frames.map(function(frame){
var webp = parseWebP(parseRIFF(atob(frame.image.slice(23))));
webp.duration = frame.duration;
return webp;
}), outputAsArray);
callback(webm);
}.bind(this));
};
return {
Video: WhammyVideo,
fromImageArray: function(images, fps, outputAsArray){
return toWebM(images.map(function(image){
var webp = parseWebP(parseRIFF(atob(image.slice(23))))
webp.duration = 1000 / fps;
return webp;
}), outputAsArray)
},
toWebM: toWebM
// expose methods of madness
}
})()
+334
View File
@@ -0,0 +1,334 @@
(function () {
"use strict";
var lookup = [
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
'w', 'x', 'y', 'z', '0', '1', '2', '3',
'4', '5', '6', '7', '8', '9', '+', '/'
];
function clean(length) {
var i, buffer = new Uint8Array(length);
for (i = 0; i < length; i += 1) {
buffer[i] = 0;
}
return buffer;
}
function extend(orig, length, addLength, multipleOf) {
var newSize = length + addLength,
buffer = clean((parseInt(newSize / multipleOf) + 1) * multipleOf);
buffer.set(orig);
return buffer;
}
function pad(num, bytes, base) {
num = num.toString(base || 8);
return "000000000000".substr(num.length + 12 - bytes) + num;
}
function stringToUint8 (input, out, offset) {
var i, length;
out = out || clean(input.length);
offset = offset || 0;
for (i = 0, length = input.length; i < length; i += 1) {
out[offset] = input.charCodeAt(i);
offset += 1;
}
return out;
}
function uint8ToBase64(uint8) {
var i,
extraBytes = uint8.length % 3, // if we have 1 byte left, pad 2 bytes
output = "",
temp, length;
function tripletToBase64 (num) {
return lookup[num >> 18 & 0x3F] + lookup[num >> 12 & 0x3F] + lookup[num >> 6 & 0x3F] + lookup[num & 0x3F];
};
// go through the array every three bytes, we'll deal with trailing stuff later
for (i = 0, length = uint8.length - extraBytes; i < length; i += 3) {
temp = (uint8[i] << 16) + (uint8[i + 1] << 8) + (uint8[i + 2]);
output += tripletToBase64(temp);
}
// this prevents an ERR_INVALID_URL in Chrome (Firefox okay)
switch (output.length % 4) {
case 1:
output += '=';
break;
case 2:
output += '==';
break;
default:
break;
}
return output;
}
window.utils = {}
window.utils.clean = clean;
window.utils.pad = pad;
window.utils.extend = extend;
window.utils.stringToUint8 = stringToUint8;
window.utils.uint8ToBase64 = uint8ToBase64;
}());
(function () {
"use strict";
/*
struct posix_header { // byte offset
char name[100]; // 0
char mode[8]; // 100
char uid[8]; // 108
char gid[8]; // 116
char size[12]; // 124
char mtime[12]; // 136
char chksum[8]; // 148
char typeflag; // 156
char linkname[100]; // 157
char magic[6]; // 257
char version[2]; // 263
char uname[32]; // 265
char gname[32]; // 297
char devmajor[8]; // 329
char devminor[8]; // 337
char prefix[155]; // 345
// 500
};
*/
var utils = window.utils,
headerFormat;
headerFormat = [
{
'field': 'fileName',
'length': 100
},
{
'field': 'fileMode',
'length': 8
},
{
'field': 'uid',
'length': 8
},
{
'field': 'gid',
'length': 8
},
{
'field': 'fileSize',
'length': 12
},
{
'field': 'mtime',
'length': 12
},
{
'field': 'checksum',
'length': 8
},
{
'field': 'type',
'length': 1
},
{
'field': 'linkName',
'length': 100
},
{
'field': 'ustar',
'length': 8
},
{
'field': 'owner',
'length': 32
},
{
'field': 'group',
'length': 32
},
{
'field': 'majorNumber',
'length': 8
},
{
'field': 'minorNumber',
'length': 8
},
{
'field': 'filenamePrefix',
'length': 155
},
{
'field': 'padding',
'length': 12
}
];
function formatHeader(data, cb) {
var buffer = utils.clean(512),
offset = 0;
headerFormat.forEach(function (value) {
var str = data[value.field] || "",
i, length;
for (i = 0, length = str.length; i < length; i += 1) {
buffer[offset] = str.charCodeAt(i);
offset += 1;
}
offset += value.length - i; // space it out with nulls
});
if (typeof cb === 'function') {
return cb(buffer, offset);
}
return buffer;
}
window.header = {}
window.header.structure = headerFormat;
window.header.format = formatHeader;
}());
(function () {
"use strict";
var header = window.header,
utils = window.utils,
recordSize = 512,
blockSize;
function Tar(recordsPerBlock) {
this.written = 0;
blockSize = (recordsPerBlock || 20) * recordSize;
this.out = utils.clean(blockSize);
this.blocks = [];
this.length = 0;
}
Tar.prototype.append = function (filepath, input, opts, callback) {
var data,
checksum,
mode,
mtime,
uid,
gid,
headerArr;
if (typeof input === 'string') {
input = utils.stringToUint8(input);
} else if (input.constructor !== Uint8Array.prototype.constructor) {
throw 'Invalid input type. You gave me: ' + input.constructor.toString().match(/function\s*([$A-Za-z_][0-9A-Za-z_]*)\s*\(/)[1];
}
if (typeof opts === 'function') {
callback = opts;
opts = {};
}
opts = opts || {};
mode = opts.mode || parseInt('777', 8) & 0xfff;
mtime = opts.mtime || Math.floor(+new Date() / 1000);
uid = opts.uid || 0;
gid = opts.gid || 0;
data = {
fileName: filepath,
fileMode: utils.pad(mode, 7),
uid: utils.pad(uid, 7),
gid: utils.pad(gid, 7),
fileSize: utils.pad(input.length, 11),
mtime: utils.pad(mtime, 11),
checksum: ' ',
type: '0', // just a file
ustar: 'ustar ',
owner: opts.owner || '',
group: opts.group || ''
};
// calculate the checksum
checksum = 0;
Object.keys(data).forEach(function (key) {
var i, value = data[key], length;
for (i = 0, length = value.length; i < length; i += 1) {
checksum += value.charCodeAt(i);
}
});
data.checksum = utils.pad(checksum, 6) + "\u0000 ";
headerArr = header.format(data);
var headerLength = Math.ceil( headerArr.length / recordSize ) * recordSize;
var inputLength = Math.ceil( input.length / recordSize ) * recordSize;
this.blocks.push( { header: headerArr, input: input, headerLength: headerLength, inputLength: inputLength } );
};
Tar.prototype.save = function() {
var buffers = [];
var chunks = [];
var length = 0;
var max = Math.pow( 2, 20 );
var chunk = [];
this.blocks.forEach( function( b ) {
if( length + b.headerLength + b.inputLength > max ) {
chunks.push( { blocks: chunk, length: length } );
chunk = [];
length = 0;
}
chunk.push( b );
length += b.headerLength + b.inputLength;
} );
chunks.push( { blocks: chunk, length: length } );
chunks.forEach( function( c ) {
var buffer = new Uint8Array( c.length );
var written = 0;
c.blocks.forEach( function( b ) {
buffer.set( b.header, written );
written += b.headerLength;
buffer.set( b.input, written );
written += b.inputLength;
} );
buffers.push( buffer );
} );
buffers.push( new Uint8Array( 2 * recordSize ) );
return new Blob( buffers, { type: 'octet/stream' } );
};
Tar.prototype.clear = function () {
this.written = 0;
this.out = utils.clean(blockSize);
};
window.Tar = Tar;
}());
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="250px" height="114px" viewBox="0 0 250 114" enable-background="new 0 0 250 114" xml:space="preserve">
<path fill="#EC245E" d="M16.254,27.631v7.998h0.359c0.715-1.113,1.65-2.248,2.805-3.402c1.155-1.154,2.568-2.188,4.24-3.105
c1.67-0.912,3.561-1.67,5.67-2.268c2.107-0.596,4.477-0.896,7.104-0.896c4.059,0,7.799,0.777,11.223,2.328
c3.422,1.555,6.367,3.684,8.836,6.389c2.465,2.707,4.375,5.891,5.73,9.551c1.352,3.662,2.029,7.602,2.029,11.82
s-0.656,8.179-1.971,11.879c-1.312,3.701-3.184,6.925-5.611,9.67c-2.427,2.746-5.371,4.938-8.834,6.566
c-3.463,1.631-7.385,2.446-11.76,2.446c-4.061,0-7.781-0.836-11.164-2.506c-3.385-1.672-5.99-3.938-7.82-6.807h-0.238v36.295H2.525
V27.631H16.254z M49.684,56.045c0-2.229-0.338-4.438-1.014-6.627c-0.678-2.188-1.693-4.158-3.045-5.91
c-1.354-1.748-3.064-3.162-5.135-4.238c-2.07-1.074-4.496-1.611-7.281-1.611c-2.627,0-4.977,0.557-7.045,1.672
c-2.07,1.115-3.842,2.549-5.312,4.297c-1.475,1.752-2.588,3.742-3.344,5.971c-0.758,2.229-1.133,4.459-1.133,6.686
c0,2.229,0.375,4.438,1.133,6.625c0.756,2.191,1.869,4.16,3.344,5.912c1.471,1.75,3.242,3.164,5.312,4.236
c2.068,1.075,4.418,1.61,7.045,1.61c2.785,0,5.211-0.555,7.281-1.67c2.07-1.115,3.781-2.547,5.135-4.299
c1.352-1.75,2.367-3.74,3.045-5.97C49.346,60.502,49.684,58.273,49.684,56.045z M189.332,24.893v63.505
c0,3.422-0.279,6.666-0.836,9.73c-0.559,3.064-1.611,5.73-3.164,8c-1.551,2.27-3.662,4.078-6.328,5.432
c-2.668,1.354-6.146,2.029-10.445,2.029c-1.193,0-2.389-0.08-3.582-0.238c-1.193-0.16-2.148-0.319-2.865-0.479l1.195-12.178
c0.637,0.16,1.311,0.279,2.027,0.359c0.717,0.077,1.354,0.118,1.91,0.118c1.67,0,3.023-0.317,4.059-0.955
c1.033-0.639,1.83-1.514,2.391-2.627c0.555-1.114,0.914-2.407,1.074-3.881c0.156-1.474,0.236-3.043,0.236-4.715V24.893H189.332z
M238.162,42.912c-1.275-1.672-3.025-3.123-5.254-4.357c-2.229-1.234-4.656-1.852-7.283-1.852c-2.309,0-4.416,0.479-6.326,1.434
c-1.912,0.953-2.863,2.547-2.863,4.775s1.053,3.803,3.16,4.715c2.109,0.916,5.195,1.852,9.256,2.807
c2.146,0.479,4.314,1.115,6.506,1.91c2.189,0.795,4.18,1.85,5.971,3.164c1.789,1.312,3.242,2.945,4.357,4.895
c1.111,1.951,1.672,4.318,1.672,7.104c0,3.504-0.658,6.47-1.973,8.896c-1.311,2.428-3.062,4.397-5.254,5.91
c-2.189,1.512-4.734,2.606-7.641,3.283c-2.906,0.676-5.908,1.014-9.014,1.014c-4.459,0-8.795-0.816-13.014-2.447
c-4.219-1.629-7.721-3.959-10.506-6.982l9.432-8.836c1.592,2.07,3.66,3.781,6.209,5.133c2.547,1.354,5.371,2.029,8.477,2.029
c1.033,0,2.088-0.117,3.164-0.357c1.074-0.237,2.068-0.614,2.984-1.133c0.914-0.518,1.65-1.213,2.209-2.09
c0.555-0.877,0.834-1.949,0.834-3.225c0-2.389-1.094-4.098-3.281-5.133c-2.191-1.035-5.475-2.07-9.85-3.104
c-2.15-0.479-4.24-1.094-6.27-1.853c-2.029-0.756-3.84-1.75-5.432-2.983c-1.596-1.234-2.865-2.764-3.82-4.598
c-0.955-1.83-1.436-4.098-1.436-6.805c0-3.184,0.656-5.928,1.973-8.236c1.311-2.312,3.045-4.197,5.191-5.674
c2.148-1.471,4.576-2.566,7.283-3.281c2.705-0.717,5.492-1.076,8.357-1.076c4.137,0,8.178,0.717,12.117,2.148
c3.939,1.434,7.062,3.625,9.373,6.568L238.162,42.912z M153.559,72.816l8.533-2.576l1.676,5.156l-8.498,2.897l5.275,7.479
l-4.447,3.226l-5.553-7.349l-5.408,7.154l-4.318-3.289l5.275-7.223l-8.564-3.09l1.678-5.16l8.6,2.771v-8.896h5.754v8.897H153.559z
M124.086,45.836c-1.473-3.301-3.52-6.088-6.148-8.357c-2.625-2.268-5.711-4-9.252-5.193c-3.543-1.193-7.383-1.791-11.521-1.791
c-1.512,0-3.203,0.082-5.074,0.238c-1.871,0.162-3.482,0.439-4.834,0.838l0.834-18.268h34.503V0.41H74.481l-1.432,46.201
c1.271-0.635,2.725-1.232,4.357-1.791c1.631-0.555,3.301-1.053,5.014-1.49c1.711-0.438,3.463-0.775,5.254-1.016
c1.791-0.238,3.48-0.357,5.074-0.357c2.307,0,4.576,0.258,6.805,0.775c2.227,0.518,4.238,1.434,6.029,2.746s3.242,3.045,4.357,5.193
c1.113,2.148,1.672,4.855,1.672,8.119c0,2.547-0.418,4.836-1.254,6.865c-0.836,2.026-1.971,3.721-3.402,5.071
c-1.434,1.355-3.104,2.39-5.016,3.104c-1.91,0.719-3.939,1.076-6.088,1.076c-3.82,0-7.125-1.017-9.91-3.046
c-2.787-2.028-4.775-4.715-5.969-8.059l-0.16,0.059l-10.367,9.716c2.096,3.42,4.799,6.28,8.139,8.553
c4.854,3.302,10.824,4.955,17.91,4.955c4.217,0,8.197-0.678,11.938-2.028c3.741-1.352,7.004-3.304,9.791-5.853
c2.786-2.545,4.994-5.67,6.627-9.371c1.629-3.701,2.445-7.897,2.445-12.597C126.295,52.939,125.559,49.141,124.086,45.836z
M131.07,6.842h2.521c0.244,0,0.484,0.029,0.723,0.086c0.236,0.059,0.447,0.152,0.635,0.283c0.186,0.131,0.336,0.301,0.453,0.508
c0.115,0.207,0.172,0.457,0.172,0.749c0,0.365-0.104,0.667-0.311,0.904c-0.207,0.237-0.479,0.407-0.812,0.511v0.02
c0.408,0.055,0.742,0.213,1.006,0.475c0.262,0.262,0.393,0.611,0.393,1.051c0,0.354-0.07,0.65-0.209,0.891
c-0.143,0.24-0.324,0.434-0.555,0.58c-0.229,0.146-0.488,0.251-0.785,0.314c-0.295,0.064-0.596,0.096-0.898,0.096h-2.33V6.842
H131.07z M132.221,9.473h1.023c0.383,0,0.676-0.076,0.877-0.229c0.201-0.153,0.301-0.369,0.301-0.648c0-0.293-0.104-0.5-0.311-0.621
c-0.207-0.122-0.529-0.184-0.969-0.184h-0.924v1.682H132.221z M132.221,12.341h1.031c0.146,0,0.307-0.011,0.477-0.032
s0.328-0.064,0.471-0.133c0.143-0.066,0.262-0.164,0.355-0.292c0.096-0.128,0.143-0.298,0.143-0.511
c0-0.342-0.115-0.579-0.348-0.713c-0.23-0.135-0.582-0.201-1.051-0.201h-1.078V12.341z M136.936,6.842h4.283v1.004h-3.135v1.645
h2.969v0.969h-2.969v1.827h3.299v1.022h-4.447V6.842z M144.088,7.846h-1.982V6.842h5.117v1.004h-1.982v5.463h-1.152V7.846
L144.088,7.846z M149.449,6.842h0.996l2.787,6.467h-1.316l-0.602-1.479h-2.807l-0.584,1.479h-1.289L149.449,6.842z M150.912,10.843
l-0.996-2.631l-1.014,2.631H150.912z"/>
</svg>

After

Width:  |  Height:  |  Size: 5.7 KiB

+17
View File
@@ -0,0 +1,17 @@
html {
min-width: 768px;
}
body {
font-family: Georgia, serif;
max-width: 740px;
margin: 0 auto;
}
h1, h2, h3 {
font-weight: normal;
}
a {
color: inherit;
}
+338
View File
@@ -0,0 +1,338 @@
/* BASICS */
.CodeMirror {
/* Set height, width, borders, and global font properties here */
font-family: monospace;
height: 300px;
color: black;
}
/* PADDING */
.CodeMirror-lines {
padding: 4px 0; /* Vertical padding around content */
}
.CodeMirror pre {
padding: 0 4px; /* Horizontal padding of content */
}
.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
background-color: white; /* The little square between H and V scrollbars */
}
/* GUTTER */
.CodeMirror-gutters {
border-right: 1px solid #ddd;
background-color: #f7f7f7;
white-space: nowrap;
}
.CodeMirror-linenumbers {}
.CodeMirror-linenumber {
padding: 0 3px 0 5px;
min-width: 20px;
text-align: right;
color: #999;
white-space: nowrap;
}
.CodeMirror-guttermarker { color: black; }
.CodeMirror-guttermarker-subtle { color: #999; }
/* CURSOR */
.CodeMirror-cursor {
border-left: 1px solid black;
border-right: none;
width: 0;
}
/* Shown when moving in bi-directional text */
.CodeMirror div.CodeMirror-secondarycursor {
border-left: 1px solid silver;
}
.cm-fat-cursor .CodeMirror-cursor {
width: auto;
border: 0;
background: #7e7;
}
.cm-fat-cursor div.CodeMirror-cursors {
z-index: 1;
}
.cm-animate-fat-cursor {
width: auto;
border: 0;
-webkit-animation: blink 1.06s steps(1) infinite;
-moz-animation: blink 1.06s steps(1) infinite;
animation: blink 1.06s steps(1) infinite;
background-color: #7e7;
}
@-moz-keyframes blink {
0% {}
50% { background-color: transparent; }
100% {}
}
@-webkit-keyframes blink {
0% {}
50% { background-color: transparent; }
100% {}
}
@keyframes blink {
0% {}
50% { background-color: transparent; }
100% {}
}
/* Can style cursor different in overwrite (non-insert) mode */
.CodeMirror-overwrite .CodeMirror-cursor {}
.cm-tab { display: inline-block; text-decoration: inherit; }
.CodeMirror-ruler {
border-left: 1px solid #ccc;
position: absolute;
}
/* DEFAULT THEME */
.cm-s-default .cm-header {color: blue;}
.cm-s-default .cm-quote {color: #090;}
.cm-negative {color: #d44;}
.cm-positive {color: #292;}
.cm-header, .cm-strong {font-weight: bold;}
.cm-em {font-style: italic;}
.cm-link {text-decoration: underline;}
.cm-strikethrough {text-decoration: line-through;}
.cm-s-default .cm-keyword {color: #708;}
.cm-s-default .cm-atom {color: #219;}
.cm-s-default .cm-number {color: #164;}
.cm-s-default .cm-def {color: #00f;}
.cm-s-default .cm-variable,
.cm-s-default .cm-punctuation,
.cm-s-default .cm-property,
.cm-s-default .cm-operator {}
.cm-s-default .cm-variable-2 {color: #05a;}
.cm-s-default .cm-variable-3 {color: #085;}
.cm-s-default .cm-comment {color: #a50;}
.cm-s-default .cm-string {color: #a11;}
.cm-s-default .cm-string-2 {color: #f50;}
.cm-s-default .cm-meta {color: #555;}
.cm-s-default .cm-qualifier {color: #555;}
.cm-s-default .cm-builtin {color: #30a;}
.cm-s-default .cm-bracket {color: #997;}
.cm-s-default .cm-tag {color: #170;}
.cm-s-default .cm-attribute {color: #00c;}
.cm-s-default .cm-hr {color: #999;}
.cm-s-default .cm-link {color: #00c;}
.cm-s-default .cm-error {color: #f00;}
.cm-invalidchar {color: #f00;}
.CodeMirror-composing { border-bottom: 2px solid; }
/* Default styles for common addons */
div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
.CodeMirror-activeline-background {background: #e8f2ff;}
/* STOP */
/* The rest of this file contains styles related to the mechanics of
the editor. You probably shouldn't touch them. */
.CodeMirror {
position: relative;
overflow: hidden;
background: white;
}
.CodeMirror-scroll {
overflow: scroll !important; /* Things will break if this is overridden */
/* 30px is the magic margin used to hide the element's real scrollbars */
/* See overflow: hidden in .CodeMirror */
margin-bottom: -30px; margin-right: -30px;
padding-bottom: 30px;
height: 100%;
outline: none; /* Prevent dragging from highlighting the element */
position: relative;
}
.CodeMirror-sizer {
position: relative;
border-right: 30px solid transparent;
}
/* The fake, visible scrollbars. Used to force redraw during scrolling
before actual scrolling happens, thus preventing shaking and
flickering artifacts. */
.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
position: absolute;
z-index: 6;
display: none;
}
.CodeMirror-vscrollbar {
right: 0; top: 0;
overflow-x: hidden;
overflow-y: scroll;
}
.CodeMirror-hscrollbar {
bottom: 0; left: 0;
overflow-y: hidden;
overflow-x: scroll;
}
.CodeMirror-scrollbar-filler {
right: 0; bottom: 0;
}
.CodeMirror-gutter-filler {
left: 0; bottom: 0;
}
.CodeMirror-gutters {
position: absolute; left: 0; top: 0;
min-height: 100%;
z-index: 3;
}
.CodeMirror-gutter {
white-space: normal;
height: 100%;
display: inline-block;
vertical-align: top;
margin-bottom: -30px;
/* Hack to make IE7 behave */
*zoom:1;
*display:inline;
}
.CodeMirror-gutter-wrapper {
position: absolute;
z-index: 4;
background: none !important;
border: none !important;
}
.CodeMirror-gutter-background {
position: absolute;
top: 0; bottom: 0;
z-index: 4;
}
.CodeMirror-gutter-elt {
position: absolute;
cursor: default;
z-index: 4;
}
.CodeMirror-gutter-wrapper {
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}
.CodeMirror-lines {
cursor: text;
min-height: 1px; /* prevents collapsing before first draw */
}
.CodeMirror pre {
/* Reset some styles that the rest of the page might have set */
-moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
border-width: 0;
background: transparent;
font-family: inherit;
font-size: inherit;
margin: 0;
white-space: pre;
word-wrap: normal;
line-height: inherit;
color: inherit;
z-index: 2;
position: relative;
overflow: visible;
-webkit-tap-highlight-color: transparent;
-webkit-font-variant-ligatures: none;
font-variant-ligatures: none;
}
.CodeMirror-wrap pre {
word-wrap: break-word;
white-space: pre-wrap;
word-break: normal;
}
.CodeMirror-linebackground {
position: absolute;
left: 0; right: 0; top: 0; bottom: 0;
z-index: 0;
}
.CodeMirror-linewidget {
position: relative;
z-index: 2;
overflow: auto;
}
.CodeMirror-widget {}
.CodeMirror-code {
outline: none;
}
/* Force content-box sizing for the elements where we expect it */
.CodeMirror-scroll,
.CodeMirror-sizer,
.CodeMirror-gutter,
.CodeMirror-gutters,
.CodeMirror-linenumber {
-moz-box-sizing: content-box;
box-sizing: content-box;
}
.CodeMirror-measure {
position: absolute;
width: 100%;
height: 0;
overflow: hidden;
visibility: hidden;
}
.CodeMirror-cursor { position: absolute; }
.CodeMirror-measure pre { position: static; }
div.CodeMirror-cursors {
visibility: hidden;
position: relative;
z-index: 3;
}
div.CodeMirror-dragcursors {
visibility: visible;
}
.CodeMirror-focused div.CodeMirror-cursors {
visibility: visible;
}
.CodeMirror-selected { background: #d9d9d9; }
.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
.CodeMirror-crosshair { cursor: crosshair; }
.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
.cm-searching {
background: #ffa;
background: rgba(255, 255, 0, .4);
}
/* IE7 hack to prevent it from returning funny offsetTops on the spans */
.CodeMirror span { *vertical-align: text-bottom; }
/* Used to force a border model for a node */
.cm-force-border { padding-right: .1px; }
@media print {
/* Hide the cursor when printing */
.CodeMirror div.CodeMirror-cursors {
visibility: hidden;
}
}
/* See issue #2901 */
.cm-tab-wrap-hack:after { content: ''; }
/* Help users use markselection to safely style text background */
span.CodeMirror-selectedtext { background: none; }
File diff suppressed because it is too large Load Diff
+123
View File
@@ -0,0 +1,123 @@
/* http://prismjs.com/download.html?themes=prism-okaidia&languages=markup+css+clike+javascript */
/**
* okaidia theme for JavaScript, CSS and HTML
* Loosely based on Monokai textmate theme by http://www.monokai.nl/
* @author ocodia
*/
code[class*="language-"],
pre[class*="language-"] {
color: #f8f8f2;
background: none;
text-shadow: 0 1px rgba(0, 0, 0, 0.3);
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
/* Code blocks */
pre[class*="language-"] {
padding: 1em;
margin: .5em 0;
overflow: auto;
border-radius: 0.3em;
}
:not(pre) > code[class*="language-"],
pre[class*="language-"] {
background: #272822;
}
/* Inline code */
:not(pre) > code[class*="language-"] {
padding: .1em;
border-radius: .3em;
white-space: normal;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: slategray;
}
.token.punctuation {
color: #f8f8f2;
}
.namespace {
opacity: .7;
}
.token.property,
.token.tag,
.token.constant,
.token.symbol,
.token.deleted {
color: #f92672;
}
.token.boolean,
.token.number {
color: #ae81ff;
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
color: #a6e22e;
}
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string,
.token.variable {
color: #f8f8f2;
}
.token.atrule,
.token.attr-value,
.token.function {
color: #e6db74;
}
.token.keyword {
color: #66d9ef;
}
.token.regex,
.token.important {
color: #fd971f;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}
+668
View File
@@ -0,0 +1,668 @@
/* http://prismjs.com/download.html?themes=prism&languages=markup+css+clike+javascript */
var _self = (typeof window !== 'undefined')
? window // if in browser
: (
(typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope)
? self // if in worker
: {} // if in node js
);
/**
* Prism: Lightweight, robust, elegant syntax highlighting
* MIT license http://www.opensource.org/licenses/mit-license.php/
* @author Lea Verou http://lea.verou.me
*/
var Prism = (function(){
// Private helper vars
var lang = /\blang(?:uage)?-(\w+)\b/i;
var uniqueId = 0;
var _ = _self.Prism = {
util: {
encode: function (tokens) {
if (tokens instanceof Token) {
return new Token(tokens.type, _.util.encode(tokens.content), tokens.alias);
} else if (_.util.type(tokens) === 'Array') {
return tokens.map(_.util.encode);
} else {
return tokens.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/\u00a0/g, ' ');
}
},
type: function (o) {
return Object.prototype.toString.call(o).match(/\[object (\w+)\]/)[1];
},
objId: function (obj) {
if (!obj['__id']) {
Object.defineProperty(obj, '__id', { value: ++uniqueId });
}
return obj['__id'];
},
// Deep clone a language definition (e.g. to extend it)
clone: function (o) {
var type = _.util.type(o);
switch (type) {
case 'Object':
var clone = {};
for (var key in o) {
if (o.hasOwnProperty(key)) {
clone[key] = _.util.clone(o[key]);
}
}
return clone;
case 'Array':
// Check for existence for IE8
return o.map && o.map(function(v) { return _.util.clone(v); });
}
return o;
}
},
languages: {
extend: function (id, redef) {
var lang = _.util.clone(_.languages[id]);
for (var key in redef) {
lang[key] = redef[key];
}
return lang;
},
/**
* Insert a token before another token in a language literal
* As this needs to recreate the object (we cannot actually insert before keys in object literals),
* we cannot just provide an object, we need anobject and a key.
* @param inside The key (or language id) of the parent
* @param before The key to insert before. If not provided, the function appends instead.
* @param insert Object with the key/value pairs to insert
* @param root The object that contains `inside`. If equal to Prism.languages, it can be omitted.
*/
insertBefore: function (inside, before, insert, root) {
root = root || _.languages;
var grammar = root[inside];
if (arguments.length == 2) {
insert = arguments[1];
for (var newToken in insert) {
if (insert.hasOwnProperty(newToken)) {
grammar[newToken] = insert[newToken];
}
}
return grammar;
}
var ret = {};
for (var token in grammar) {
if (grammar.hasOwnProperty(token)) {
if (token == before) {
for (var newToken in insert) {
if (insert.hasOwnProperty(newToken)) {
ret[newToken] = insert[newToken];
}
}
}
ret[token] = grammar[token];
}
}
// Update references in other language definitions
_.languages.DFS(_.languages, function(key, value) {
if (value === root[inside] && key != inside) {
this[key] = ret;
}
});
return root[inside] = ret;
},
// Traverse a language definition with Depth First Search
DFS: function(o, callback, type, visited) {
visited = visited || {};
for (var i in o) {
if (o.hasOwnProperty(i)) {
callback.call(o, i, o[i], type || i);
if (_.util.type(o[i]) === 'Object' && !visited[_.util.objId(o[i])]) {
visited[_.util.objId(o[i])] = true;
_.languages.DFS(o[i], callback, null, visited);
}
else if (_.util.type(o[i]) === 'Array' && !visited[_.util.objId(o[i])]) {
visited[_.util.objId(o[i])] = true;
_.languages.DFS(o[i], callback, i, visited);
}
}
}
}
},
plugins: {},
highlightAll: function(async, callback) {
var env = {
callback: callback,
selector: 'code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code'
};
_.hooks.run("before-highlightall", env);
var elements = env.elements || document.querySelectorAll(env.selector);
for (var i=0, element; element = elements[i++];) {
_.highlightElement(element, async === true, env.callback);
}
},
highlightElement: function(element, async, callback) {
// Find language
var language, grammar, parent = element;
while (parent && !lang.test(parent.className)) {
parent = parent.parentNode;
}
if (parent) {
language = (parent.className.match(lang) || [,''])[1];
grammar = _.languages[language];
}
// Set language on the element, if not present
element.className = element.className.replace(lang, '').replace(/\s+/g, ' ') + ' language-' + language;
// Set language on the parent, for styling
parent = element.parentNode;
if (/pre/i.test(parent.nodeName)) {
parent.className = parent.className.replace(lang, '').replace(/\s+/g, ' ') + ' language-' + language;
}
var code = element.textContent;
var env = {
element: element,
language: language,
grammar: grammar,
code: code
};
if (!code || !grammar) {
_.hooks.run('complete', env);
return;
}
_.hooks.run('before-highlight', env);
if (async && _self.Worker) {
var worker = new Worker(_.filename);
worker.onmessage = function(evt) {
env.highlightedCode = evt.data;
_.hooks.run('before-insert', env);
env.element.innerHTML = env.highlightedCode;
callback && callback.call(env.element);
_.hooks.run('after-highlight', env);
_.hooks.run('complete', env);
};
worker.postMessage(JSON.stringify({
language: env.language,
code: env.code,
immediateClose: true
}));
}
else {
env.highlightedCode = _.highlight(env.code, env.grammar, env.language);
_.hooks.run('before-insert', env);
env.element.innerHTML = env.highlightedCode;
callback && callback.call(element);
_.hooks.run('after-highlight', env);
_.hooks.run('complete', env);
}
},
highlight: function (text, grammar, language) {
var tokens = _.tokenize(text, grammar);
return Token.stringify(_.util.encode(tokens), language);
},
tokenize: function(text, grammar, language) {
var Token = _.Token;
var strarr = [text];
var rest = grammar.rest;
if (rest) {
for (var token in rest) {
grammar[token] = rest[token];
}
delete grammar.rest;
}
tokenloop: for (var token in grammar) {
if(!grammar.hasOwnProperty(token) || !grammar[token]) {
continue;
}
var patterns = grammar[token];
patterns = (_.util.type(patterns) === "Array") ? patterns : [patterns];
for (var j = 0; j < patterns.length; ++j) {
var pattern = patterns[j],
inside = pattern.inside,
lookbehind = !!pattern.lookbehind,
greedy = !!pattern.greedy,
lookbehindLength = 0,
alias = pattern.alias;
pattern = pattern.pattern || pattern;
for (var i=0; i<strarr.length; i++) { // Don’t cache length as it changes during the loop
var str = strarr[i];
if (strarr.length > text.length) {
// Something went terribly wrong, ABORT, ABORT!
break tokenloop;
}
if (str instanceof Token) {
continue;
}
pattern.lastIndex = 0;
var match = pattern.exec(str),
delNum = 1;
// Greedy patterns can override/remove up to two previously matched tokens
if (!match && greedy && i != strarr.length - 1) {
// Reconstruct the original text using the next two tokens
var nextToken = strarr[i + 1].matchedStr || strarr[i + 1],
combStr = str + nextToken;
if (i < strarr.length - 2) {
combStr += strarr[i + 2].matchedStr || strarr[i + 2];
}
// Try the pattern again on the reconstructed text
pattern.lastIndex = 0;
match = pattern.exec(combStr);
if (!match) {
continue;
}
var from = match.index + (lookbehind ? match[1].length : 0);
// To be a valid candidate, the new match has to start inside of str
if (from >= str.length) {
continue;
}
var to = match.index + match[0].length,
len = str.length + nextToken.length;
// Number of tokens to delete and replace with the new match
delNum = 3;
if (to <= len) {
if (strarr[i + 1].greedy) {
continue;
}
delNum = 2;
combStr = combStr.slice(0, len);
}
str = combStr;
}
if (!match) {
continue;
}
if(lookbehind) {
lookbehindLength = match[1].length;
}
var from = match.index + lookbehindLength,
match = match[0].slice(lookbehindLength),
to = from + match.length,
before = str.slice(0, from),
after = str.slice(to);
var args = [i, delNum];
if (before) {
args.push(before);
}
var wrapped = new Token(token, inside? _.tokenize(match, inside) : match, alias, match, greedy);
args.push(wrapped);
if (after) {
args.push(after);
}
Array.prototype.splice.apply(strarr, args);
}
}
}
return strarr;
},
hooks: {
all: {},
add: function (name, callback) {
var hooks = _.hooks.all;
hooks[name] = hooks[name] || [];
hooks[name].push(callback);
},
run: function (name, env) {
var callbacks = _.hooks.all[name];
if (!callbacks || !callbacks.length) {
return;
}
for (var i=0, callback; callback = callbacks[i++];) {
callback(env);
}
}
}
};
var Token = _.Token = function(type, content, alias, matchedStr, greedy) {
this.type = type;
this.content = content;
this.alias = alias;
// Copy of the full string this token was created from
this.matchedStr = matchedStr || null;
this.greedy = !!greedy;
};
Token.stringify = function(o, language, parent) {
if (typeof o == 'string') {
return o;
}
if (_.util.type(o) === 'Array') {
return o.map(function(element) {
return Token.stringify(element, language, o);
}).join('');
}
var env = {
type: o.type,
content: Token.stringify(o.content, language, parent),
tag: 'span',
classes: ['token', o.type],
attributes: {},
language: language,
parent: parent
};
if (env.type == 'comment') {
env.attributes['spellcheck'] = 'true';
}
if (o.alias) {
var aliases = _.util.type(o.alias) === 'Array' ? o.alias : [o.alias];
Array.prototype.push.apply(env.classes, aliases);
}
_.hooks.run('wrap', env);
var attributes = '';
for (var name in env.attributes) {
attributes += (attributes ? ' ' : '') + name + '="' + (env.attributes[name] || '') + '"';
}
return '<' + env.tag + ' class="' + env.classes.join(' ') + '" ' + attributes + '>' + env.content + '</' + env.tag + '>';
};
if (!_self.document) {
if (!_self.addEventListener) {
// in Node.js
return _self.Prism;
}
// In worker
_self.addEventListener('message', function(evt) {
var message = JSON.parse(evt.data),
lang = message.language,
code = message.code,
immediateClose = message.immediateClose;
_self.postMessage(_.highlight(code, _.languages[lang], lang));
if (immediateClose) {
_self.close();
}
}, false);
return _self.Prism;
}
//Get current script and highlight
var script = document.currentScript || [].slice.call(document.getElementsByTagName("script")).pop();
if (script) {
_.filename = script.src;
if (document.addEventListener && !script.hasAttribute('data-manual')) {
document.addEventListener('DOMContentLoaded', _.highlightAll);
}
}
return _self.Prism;
})();
if (typeof module !== 'undefined' && module.exports) {
module.exports = Prism;
}
// hack for components to work correctly in node.js
if (typeof global !== 'undefined') {
global.Prism = Prism;
}
;
Prism.languages.markup = {
'comment': /<!--[\w\W]*?-->/,
'prolog': /<\?[\w\W]+?\?>/,
'doctype': /<!DOCTYPE[\w\W]+?>/,
'cdata': /<!\[CDATA\[[\w\W]*?]]>/i,
'tag': {
pattern: /<\/?(?!\d)[^\s>\/=.$<]+(?:\s+[^\s>\/=]+(?:=(?:("|')(?:\\\1|\\?(?!\1)[\w\W])*\1|[^\s'">=]+))?)*\s*\/?>/i,
inside: {
'tag': {
pattern: /^<\/?[^\s>\/]+/i,
inside: {
'punctuation': /^<\/?/,
'namespace': /^[^\s>\/:]+:/
}
},
'attr-value': {
pattern: /=(?:('|")[\w\W]*?(\1)|[^\s>]+)/i,
inside: {
'punctuation': /[=>"']/
}
},
'punctuation': /\/?>/,
'attr-name': {
pattern: /[^\s>\/]+/,
inside: {
'namespace': /^[^\s>\/:]+:/
}
}
}
},
'entity': /&#?[\da-z]{1,8};/i
};
// Plugin to make entity title show the real entity, idea by Roman Komarov
Prism.hooks.add('wrap', function(env) {
if (env.type === 'entity') {
env.attributes['title'] = env.content.replace(/&amp;/, '&');
}
});
Prism.languages.xml = Prism.languages.markup;
Prism.languages.html = Prism.languages.markup;
Prism.languages.mathml = Prism.languages.markup;
Prism.languages.svg = Prism.languages.markup;
Prism.languages.css = {
'comment': /\/\*[\w\W]*?\*\//,
'atrule': {
pattern: /@[\w-]+?.*?(;|(?=\s*\{))/i,
inside: {
'rule': /@[\w-]+/
// See rest below
}
},
'url': /url\((?:(["'])(\\(?:\r\n|[\w\W])|(?!\1)[^\\\r\n])*\1|.*?)\)/i,
'selector': /[^\{\}\s][^\{\};]*?(?=\s*\{)/,
'string': /("|')(\\(?:\r\n|[\w\W])|(?!\1)[^\\\r\n])*\1/,
'property': /(\b|\B)[\w-]+(?=\s*:)/i,
'important': /\B!important\b/i,
'function': /[-a-z0-9]+(?=\()/i,
'punctuation': /[(){};:]/
};
Prism.languages.css['atrule'].inside.rest = Prism.util.clone(Prism.languages.css);
if (Prism.languages.markup) {
Prism.languages.insertBefore('markup', 'tag', {
'style': {
pattern: /(<style[\w\W]*?>)[\w\W]*?(?=<\/style>)/i,
lookbehind: true,
inside: Prism.languages.css,
alias: 'language-css'
}
});
Prism.languages.insertBefore('inside', 'attr-value', {
'style-attr': {
pattern: /\s*style=("|').*?\1/i,
inside: {
'attr-name': {
pattern: /^\s*style/i,
inside: Prism.languages.markup.tag.inside
},
'punctuation': /^\s*=\s*['"]|['"]\s*$/,
'attr-value': {
pattern: /.+/i,
inside: Prism.languages.css
}
},
alias: 'language-css'
}
}, Prism.languages.markup.tag);
};
Prism.languages.clike = {
'comment': [
{
pattern: /(^|[^\\])\/\*[\w\W]*?\*\//,
lookbehind: true
},
{
pattern: /(^|[^\\:])\/\/.*/,
lookbehind: true
}
],
'string': {
pattern: /(["'])(\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,
greedy: true
},
'class-name': {
pattern: /((?:\b(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[a-z0-9_\.\\]+/i,
lookbehind: true,
inside: {
punctuation: /(\.|\\)/
}
},
'keyword': /\b(if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,
'boolean': /\b(true|false)\b/,
'function': /[a-z0-9_]+(?=\()/i,
'number': /\b-?(?:0x[\da-f]+|\d*\.?\d+(?:e[+-]?\d+)?)\b/i,
'operator': /--?|\+\+?|!=?=?|<=?|>=?|==?=?|&&?|\|\|?|\?|\*|\/|~|\^|%/,
'punctuation': /[{}[\];(),.:]/
};
Prism.languages.javascript = Prism.languages.extend('clike', {
'keyword': /\b(as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|var|void|while|with|yield)\b/,
'number': /\b-?(0x[\dA-Fa-f]+|0b[01]+|0o[0-7]+|\d*\.?\d+([Ee][+-]?\d+)?|NaN|Infinity)\b/,
// Allow for all non-ASCII characters (See http://stackoverflow.com/a/2008444)
'function': /[_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*(?=\()/i
});
Prism.languages.insertBefore('javascript', 'keyword', {
'regex': {
pattern: /(^|[^/])\/(?!\/)(\[.+?]|\\.|[^/\\\r\n])+\/[gimyu]{0,5}(?=\s*($|[\r\n,.;})]))/,
lookbehind: true,
greedy: true
}
});
Prism.languages.insertBefore('javascript', 'class-name', {
'template-string': {
pattern: /`(?:\\\\|\\?[^\\])*?`/,
greedy: true,
inside: {
'interpolation': {
pattern: /\$\{[^}]+\}/,
inside: {
'interpolation-punctuation': {
pattern: /^\$\{|\}$/,
alias: 'punctuation'
},
rest: Prism.languages.javascript
}
},
'string': /[\s\S]+/
}
}
});
if (Prism.languages.markup) {
Prism.languages.insertBefore('markup', 'tag', {
'script': {
pattern: /(<script[\w\W]*?>)[\w\W]*?(?=<\/script>)/i,
lookbehind: true,
inside: Prism.languages.javascript,
alias: 'language-javascript'
}
});
}
Prism.languages.js = Prism.languages.javascript;
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+105
View File
@@ -0,0 +1,105 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
body {
height: 600px;
padding: 24px;
}
iframe {
min-height: 600px !important
}
/* 自定义滚动条样式 */
::-webkit-scrollbar {
width: 8px;
/* 滚动条宽度 */
}
::-webkit-scrollbar-track {
background: #f1f1f1;
/* 滚动条轨道颜色 */
}
::-webkit-scrollbar-thumb {
background: #888;
/* 滚动条滑块颜色 */
}
::-webkit-scrollbar-thumb:hover {
background: #555;
/* 滚动条滑块悬停颜色 */
}
</style>
<!-- <script src="./p5.js"></script> -->
<!-- <script src="./sketch.js"></script> -->
<!-- <script src="./src/CCapture.js"></script> -->
<!-- <script src="./src/gif.js"></script>
<script src="./src/gif.worker.js"></script> -->
<!-- <script src="./src/download.js"></script> -->
<!-- <script src="./src/webm-writer-0.2.0.js"></script> -->
</head>
<body>
<script>
function getIdFromUrl(url) {
const urlParams = new URLSearchParams(new URL(url).search);
return urlParams.get('id');
}
// 监听来自iframe的消息
window.addEventListener('message', (event) => {
const data = event.data;
const nodeId = getIdFromUrl(window.location.href);
if (data.from === 'p5.widget' && data.status === 'save') {
const frames = data.frames;
// 示例用法
// const url = 'https://example.com/page?id=12345';
window.parent.postMessage({
frames,
from: 'p5.widget',
status: 'save',
nodeId
}, '*');
window.location.reload()
}
// console.log(data)
// if (data.from === 'p5.widget' && data.status === 'capture') {
// window.parent.postMessage({
// from: 'p5.widget',
// status: 'capture',
// frameCount: data.frameCount,
// maxCount: data.maxCount,
// nodeId
// }, '*');
// }
});
</script>
<script type="text/p5" data-height="500" data-preview-width="300">
function setup() {
createCanvas(100, 100);
}
function draw() {
background(255, 0, 200);
}
</script>
<script src="./p5-widget/p5-widget.js"></script>
</body>
</html>
+70
View File
@@ -0,0 +1,70 @@
function setup () {
createCanvas(400, 400, WEBGL)
angleMode(DEGREES)
}
function draw () {
if (frameCount === 1) {
capturer.start()
}
background(30)
noStroke()
translate(0, 0, sin(frameCount) * 400 - 800)
rotateX(frameCount)
rotateY(frameCount)
rotateZ(frameCount)
var w = 20
randomSeed(1)
for (var x = -width / 2; x <= width / 2; x += w) {
for (var y = -width / 2; y <= width / 2; y += w) {
for (var z = -width / 2; z <= width / 2; z += w) {
var r = random(255)
var g = random(255)
var b = random(255)
fill(r, g, b)
push()
translate(x, y, z)
box(w)
pop()
}
}
}
// console.log(frameRate());
if (frameCount < 60) {
capturer.capture(canvas)
} else if (frameCount === 60) {
capturer.save(function (blob) {
// console.log(blob)
// 示例用法
// const blob = new Blob([/* 数据 */], { type: 'video/webm' });
blobToBase64(blob).then(base64String => {
console.log(base64String);
const video = document.createElement('video');
video.controls = true; // 显示视频控件(播放、暂停等)
video.src = base64String; // 设置视频的 src 属性为 Base64 数据 URL
video.width = 640; // 设置视频宽度
video.height = 360; // 设置视频高度
// 将 video 元素添加到页面中
document.body.appendChild(video)
// 自动播放视频
video.play();
}).catch(error => {
console.error('转换失败:', error);
});
})
capturer.stop()
}
}
+408
View File
@@ -0,0 +1,408 @@
{
"last_node_id": 21,
"last_link_id": 16,
"nodes": [
{
"id": 10,
"type": "ChatGPTOpenAI",
"pos": [
489,
689
],
"size": {
"0": 403.2580261230469,
"1": 309.2166442871094
},
"flags": {},
"order": 5,
"mode": 0,
"inputs": [
{
"name": "api_key",
"type": "STRING",
"link": null,
"widget": {
"name": "api_key"
}
},
{
"name": "custom_model_name",
"type": "STRING",
"link": null,
"widget": {
"name": "custom_model_name"
}
},
{
"name": "custom_api_url",
"type": "STRING",
"link": 13,
"widget": {
"name": "custom_api_url"
},
"slot_index": 2
}
],
"outputs": [
{
"name": "text",
"type": "STRING",
"links": [
12
],
"shape": 3,
"slot_index": 0
},
{
"name": "messages",
"type": "STRING",
"links": null,
"shape": 3
},
{
"name": "session_history",
"type": "STRING",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "ChatGPTOpenAI"
},
"widgets_values": [
"hi",
"You are ChatGPT, a large language model trained by OpenAI. Answer as concisely as possible.",
"gpt-3.5-turbo",
447210757728856,
"randomize",
1,
"openai",
"",
"",
""
]
},
{
"id": 3,
"type": "ShowTextForGPT",
"pos": [
982,
686
],
"size": {
"0": 400,
"1": 200
},
"flags": {},
"order": 7,
"mode": 0,
"inputs": [
{
"name": "text",
"type": "STRING",
"link": 12,
"widget": {
"name": "text"
}
},
{
"name": "output_dir",
"type": "STRING",
"link": null,
"widget": {
"name": "output_dir"
}
}
],
"outputs": [
{
"name": "STRING",
"type": "STRING",
"links": null,
"shape": 6
}
],
"properties": {
"Node name for S&R": "ShowTextForGPT"
},
"widgets_values": [
"",
"",
" Hi there! What can I help you with?"
]
},
{
"id": 11,
"type": "SiliconflowLLM",
"pos": [
489,
318
],
"size": {
"0": 395.197998046875,
"1": 262
},
"flags": {},
"order": 4,
"mode": 0,
"inputs": [
{
"name": "api_key",
"type": "STRING",
"link": 16,
"widget": {
"name": "api_key"
}
},
{
"name": "custom_model_name",
"type": "STRING",
"link": null,
"widget": {
"name": "custom_model_name"
}
}
],
"outputs": [
{
"name": "text",
"type": "STRING",
"links": [
15
],
"shape": 3,
"slot_index": 0
},
{
"name": "messages",
"type": "STRING",
"links": null,
"shape": 3
},
{
"name": "session_history",
"type": "STRING",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "SiliconflowLLM"
},
"widgets_values": [
"",
"",
"You are ChatGPT, a large language model trained by OpenAI. Answer as concisely as possible.",
"Qwen/Qwen2-7B-Instruct",
593422808835285,
"randomize",
1,
""
]
},
{
"id": 17,
"type": "ShowTextForGPT",
"pos": [
975,
329
],
"size": {
"0": 400,
"1": 200
},
"flags": {},
"order": 6,
"mode": 0,
"inputs": [
{
"name": "text",
"type": "STRING",
"link": 15,
"widget": {
"name": "text"
}
},
{
"name": "output_dir",
"type": "STRING",
"link": null,
"widget": {
"name": "output_dir"
}
}
],
"outputs": [
{
"name": "STRING",
"type": "STRING",
"links": null,
"shape": 6
}
],
"properties": {
"Node name for S&R": "ShowTextForGPT"
},
"widgets_values": [
"",
"",
"Hello! How can I assist you today?"
]
},
{
"id": 18,
"type": "KeyInput",
"pos": [
46,
319
],
"size": {
"0": 315,
"1": 70
},
"flags": {},
"order": 0,
"mode": 0,
"outputs": [
{
"name": "key",
"type": "STRING",
"links": [
16
],
"shape": 3
}
],
"properties": {
"Node name for S&R": "KeyInput"
},
"widgets_values": [
null,
null
]
},
{
"id": 12,
"type": "TextInput_",
"pos": [
31,
871
],
"size": [
407.9377612789413,
76
],
"flags": {},
"order": 1,
"mode": 0,
"outputs": [
{
"name": "STRING",
"type": "STRING",
"links": [
13
],
"shape": 3
}
],
"properties": {
"Node name for S&R": "TextInput_"
},
"widgets_values": [
"http://127.0.0.1:8000/v1"
]
},
{
"id": 20,
"type": "Note",
"pos": [
35,
664
],
"size": [
350.04604707424306,
116.54209784249178
],
"flags": {},
"order": 2,
"mode": 0,
"properties": {
"text": ""
},
"widgets_values": [
"api_key 填写对应平台的Key\ncustom model和api 根据需要自行填写\n\n如果不填写custome,则按照model和api_url选择的选项"
],
"color": "#432",
"bgcolor": "#653"
},
{
"id": 21,
"type": "Note",
"pos": [
42,
438
],
"size": {
"0": 350.0460510253906,
"1": 116.54209899902344
},
"flags": {},
"order": 3,
"mode": 0,
"properties": {
"text": ""
},
"widgets_values": [
"API key节点不会保存到workflow的json文件。\n\n::会保存到appinfo导出的app.json里\n\n\n注册https://cloud.siliconflow.cn/account/ak 领取免费的API"
],
"color": "#432",
"bgcolor": "#653"
}
],
"links": [
[
12,
10,
0,
3,
0,
"STRING"
],
[
13,
12,
0,
10,
2,
"STRING"
],
[
15,
11,
0,
17,
0,
"STRING"
],
[
16,
18,
0,
11,
0,
"STRING"
]
],
"groups": [],
"config": {},
"extra": {
"ds": {
"scale": 0.9646149645000006,
"offset": [
170.81398913081276,
-128.0066534315481
]
}
},
"version": 0.4
}
+415
View File
@@ -0,0 +1,415 @@
{
"last_node_id": 20,
"last_link_id": 28,
"nodes": [
{
"id": 11,
"type": "EmptyLatentAudio",
"pos": [
576,
480
],
"size": {
"0": 240,
"1": 58
},
"flags": {},
"order": 0,
"mode": 0,
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [
12
],
"shape": 3
}
],
"properties": {
"Node name for S&R": "EmptyLatentAudio"
},
"widgets_values": [
47.6
]
},
{
"id": 7,
"type": "CLIPTextEncode",
"pos": [
384,
288
],
"size": {
"0": 432,
"1": 144
},
"flags": {},
"order": 4,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": 26
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [
6
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [
""
],
"color": "#322",
"bgcolor": "#533"
},
{
"id": 3,
"type": "KSampler",
"pos": [
864,
96
],
"size": {
"0": 315,
"1": 262
},
"flags": {},
"order": 5,
"mode": 0,
"inputs": [
{
"name": "model",
"type": "MODEL",
"link": 18
},
{
"name": "positive",
"type": "CONDITIONING",
"link": 4
},
{
"name": "negative",
"type": "CONDITIONING",
"link": 6
},
{
"name": "latent_image",
"type": "LATENT",
"link": 12,
"slot_index": 3
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [
13
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "KSampler"
},
"widgets_values": [
173246216057483,
"randomize",
50,
4.98,
"dpmpp_3m_sde_gpu",
"exponential",
1
]
},
{
"id": 6,
"type": "CLIPTextEncode",
"pos": [
384,
96
],
"size": {
"0": 432,
"1": 144
},
"flags": {},
"order": 3,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": 25
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [
4
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [
"heaven church electronic dance music"
],
"color": "#232",
"bgcolor": "#353"
},
{
"id": 12,
"type": "VAEDecodeAudio",
"pos": [
1200,
96
],
"size": {
"0": 210,
"1": 46
},
"flags": {},
"order": 6,
"mode": 0,
"inputs": [
{
"name": "samples",
"type": "LATENT",
"link": 13
},
{
"name": "vae",
"type": "VAE",
"link": 14,
"slot_index": 1
}
],
"outputs": [
{
"name": "AUDIO",
"type": "AUDIO",
"links": [
28
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "VAEDecodeAudio"
}
},
{
"id": 19,
"type": "AudioPlay",
"pos": [
1482,
96
],
"size": [
210,
280
],
"flags": {},
"order": 7,
"mode": 0,
"inputs": [
{
"name": "audio",
"type": "AUDIO",
"link": 28
}
],
"properties": {
"Node name for S&R": "AudioPlay"
},
"widgets_values": [
[
"/view?filename=._00001_.wav&type=temp&subfolder=&rand=0.5323617364976663",
null
]
]
},
{
"id": 4,
"type": "CheckpointLoaderSimple",
"pos": [
-56,
304
],
"size": [
390.12250902572447,
98
],
"flags": {},
"order": 1,
"mode": 0,
"outputs": [
{
"name": "MODEL",
"type": "MODEL",
"links": [
18
],
"slot_index": 0
},
{
"name": "CLIP",
"type": "CLIP",
"links": [],
"slot_index": 1
},
{
"name": "VAE",
"type": "VAE",
"links": [
14
],
"slot_index": 2
}
],
"properties": {
"Node name for S&R": "CheckpointLoaderSimple"
},
"widgets_values": [
"stable_audio_open_1.0.safetensors"
]
},
{
"id": 10,
"type": "CLIPLoader",
"pos": [
-18,
93
],
"size": {
"0": 335.6534118652344,
"1": 82
},
"flags": {},
"order": 2,
"mode": 0,
"outputs": [
{
"name": "CLIP",
"type": "CLIP",
"links": [
25,
26
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "CLIPLoader"
},
"widgets_values": [
"t5_base.safetensors",
"stable_audio"
]
}
],
"links": [
[
4,
6,
0,
3,
1,
"CONDITIONING"
],
[
6,
7,
0,
3,
2,
"CONDITIONING"
],
[
12,
11,
0,
3,
3,
"LATENT"
],
[
13,
3,
0,
12,
0,
"LATENT"
],
[
14,
4,
2,
12,
1,
"VAE"
],
[
18,
4,
0,
3,
0,
"MODEL"
],
[
25,
10,
0,
6,
0,
"CLIP"
],
[
26,
10,
0,
7,
0,
"CLIP"
],
[
28,
12,
0,
19,
0,
"AUDIO"
]
],
"groups": [],
"config": {},
"extra": {
"ds": {
"scale": 0.7972024500000005,
"offset": {
"0": 206.23990767458133,
"1": 402.8380652485712
}
}
},
"version": 0.4
}