Download tests/make_workflows.py from supermind/OGG-Comfy: direct link, hf CLI and curl.
- Browser
- Download file 15.3 kB
-
https://huggingface.co/supermind/OGG-Comfy/resolve/main/tests/make_workflows.py
- Command line
-
hf download hf://supermind/OGG-Comfy/tests/make_workflows.py
-
curl -L -o make_workflows.py https://huggingface.co/supermind/OGG-Comfy/resolve/main/tests/make_workflows.py
15.3 kB
| import os as _os | |
| HERE = _os.path.dirname(_os.path.abspath(__file__)) | |
| ROOT = _os.path.dirname(HERE) | |
| import json, uuid | |
| def inp(name, typ, widget=False, optional=False): | |
| return {"name": name, "type": typ, "widget": widget, "optional": optional} | |
| # node definitions: inputs in the order of the schemas in nodes.py, outputs | |
| DEFS = { | |
| "OggLoader": ([inp("model_name","COMBO",True), inp("dtype","COMBO",True)], [("OGG_MODEL","OGG_MODEL")]), | |
| "OggCodecLoader": ([inp("codec_name","COMBO",True)], [("OGG_CODEC","OGG_CODEC")]), | |
| "OggGenerate": ([inp("model","OGG_MODEL"), inp("text","STRING",True), inp("language","COMBO",True), inp("seed","INT",True), | |
| inp("steps","INT",True), inp("cfg","FLOAT",True), inp("temperature","FLOAT",True), inp("split_lines","BOOLEAN",True), | |
| inp("max_frames","INT",True), inp("speaker","STRING",True), inp("quality","STRING",True), | |
| inp("mtp_temperature","FLOAT",True), inp("mtp_top_k","INT",True), inp("mtp_rounds","INT",True), | |
| inp("voice","OGG_VOICE",optional=True)], [("OGG_CODES","OGG_CODES")]), | |
| "OggDialogue": ([inp("model","OGG_MODEL"), inp("script","STRING",True), inp("language","COMBO",True), inp("mode","COMBO",True), | |
| inp("seed","INT",True), inp("steps","INT",True), inp("cfg","FLOAT",True), inp("temperature","FLOAT",True), | |
| inp("max_frames","INT",True)] + [inp(f"name_{i}","STRING",True) for i in range(1,7)] + | |
| [inp("quality","STRING",True), inp("mtp_temperature","FLOAT",True), inp("mtp_top_k","INT",True), inp("mtp_rounds","INT",True), inp("dialogue_layout","COMBO",True,optional=True)] + [inp(f"speaker_{i}","STRING",True,optional=True) for i in range(1,7)] + | |
| [inp(f"voice_{i}","OGG_VOICE",optional=True) for i in range(1,7)], [("OGG_CODES","OGG_CODES")]), | |
| "OggVoiceFromTake": ([inp("codes","OGG_CODES"), inp("take","INT",True), inp("strip_tags","BOOLEAN",True)], [("OGG_VOICE","OGG_VOICE")]), | |
| "OggDecode": ([inp("codec","OGG_CODEC"), inp("codes","OGG_CODES"), inp("shorten_long_pauses","BOOLEAN",True)], [("AUDIO","AUDIO")]), | |
| "SaveAudio": ([inp("audio","AUDIO"), inp("filename_prefix","STRING",True), inp("audioUI","AUDIO_UI",True)], [("audio","AUDIO")]), | |
| "LoadAudio": ([inp("audio","COMBO",True), inp("audioUI","AUDIO_UI",True), inp("upload","AUDIOUPLOAD",True)], [("AUDIO","AUDIO")]), | |
| "OggQwen3ASRLoader": ([inp("model_name","COMBO",True)], [("OGG_ASR_MODEL","OGG_ASR_MODEL")]), | |
| "OggQwen3ASRTranscribe": ([inp("model","OGG_ASR_MODEL"), inp("audio","AUDIO"), inp("language","COMBO",True), | |
| inp("context","STRING",True), inp("max_new_tokens","INT",True)], | |
| [("text","STRING"),("language","STRING")]), | |
| "OggEncodeVoice": ([inp("codec","OGG_CODEC"), inp("audio","AUDIO"), inp("transcript","STRING",True)], [("OGG_VOICE","OGG_VOICE")]), | |
| "OggSaveVoice": ([inp("voice","OGG_VOICE"), inp("name","STRING",True)], []), | |
| "MarkdownNote": ([], []), | |
| } | |
| def build(nodes, links, last_note=True): | |
| """nodes: [(id, type, pos, size, widgets_values, mode)], links: [(origin_id, origin_slot, target_id, input_name)]""" | |
| out_nodes, by_id = [], {} | |
| for order, (nid, typ, pos, size, wv, mode) in enumerate(nodes): | |
| ins, outs = DEFS[typ] | |
| n = {"id": nid, "type": typ, "pos": pos, "size": size, "flags": {}, "order": order, "mode": mode, | |
| "inputs": [], "outputs": [], "properties": {} if typ in ("SaveAudio","MarkdownNote") else {"Node name for S&R": typ}, | |
| "widgets_values": wv} | |
| for i in ins: | |
| e = {"localized_name": i["name"], "name": i["name"], "type": i["type"]} | |
| if i["widget"]: e["widget"] = {"name": i["name"]} | |
| if i["optional"] and not i["widget"]: e["shape"] = 7 | |
| e["link"] = None | |
| n["inputs"].append(e) | |
| for name, typ_ in outs: | |
| n["outputs"].append({"localized_name": name, "name": name, "type": typ_, "links": None}) | |
| out_nodes.append(n); by_id[nid] = n | |
| out_links = [] | |
| for lid, (o, oslot, t, tname) in enumerate(links, 1): | |
| on, tn = by_id[o], by_id[t] | |
| tslot = next(k for k, e in enumerate(tn["inputs"]) if e["name"] == tname) | |
| typ = on["outputs"][oslot]["type"] | |
| assert tn["inputs"][tslot]["type"] == typ, (o, t, tname, typ, tn["inputs"][tslot]["type"]) | |
| tn["inputs"][tslot]["link"] = lid | |
| on["outputs"][oslot]["links"] = (on["outputs"][oslot]["links"] or []) + [lid] | |
| out_links.append([lid, o, oslot, t, tslot, typ]) | |
| for n in out_nodes: | |
| for o in n["outputs"]: | |
| if o["links"] is None: o["links"] = None | |
| return {"id": str(uuid.uuid4()), "revision": 0, "last_node_id": max(n["id"] for n in out_nodes), "last_link_id": len(out_links), | |
| "nodes": out_nodes, "links": out_links, "groups": [], "config": {}, "extra": {"ds": {"scale": 0.75, "offset": [0, 0]}}, "version": 0.4} | |
| FILES = ("| File | Folder |\n|---|---|\n" | |
| "| ogg_tts.safetensors (FashionFlora/ogg-tts `model.safetensors`, renamed, about 3 GB) | models/diffusion_models |\n" | |
| "| drama3_codec.safetensors (the Fish codec, 1.6 GB) | models/vae |\n") | |
| NOTE_TTS = ("## OGG TTS · text to speech (no reference voice)\n\n**Files**\n\n" + FILES + | |
| "\nGet them from [FashionFlora/ogg-tts](https://huggingface.co/FashionFlora/ogg-tts) and " | |
| "[Drama-3-ComfyUI](https://huggingface.co/PedroMarinhoDev/Drama-3-ComfyUI) (the codec only). OmniVoice's config.json and tokenizer " | |
| "files are read from the pack's own `omnivoice` folder and fetched there on first use.\n\n" | |
| "**How to use:** write the text in **OGG TTS Generate**, one line per take: each line hears the two lines before and after it as context. " | |
| "Emotion and vocal tags go inline, e.g. [sighing], [whispering], [excited], [pause] (the node's description lists all 24). " | |
| "Pick the language; there are 8: en, pl, es, de, ru, fr, pt, it.\n\n" | |
| "**Settings:** cfg 1-1.5 keeps the natural tempo, 2 speaks faster and more pointedly. Without a reference voice a take varies with the seed: " | |
| "queue again for another.\n\n**Longer takes:** OGG is still training; this is a preview release, so expect some takes to be better than others.") | |
| NOTE_CLONE = ("## OGG TTS · voice cloning\n\n**Files**\n\n" + FILES + | |
| "| qwen3_asr_1.7b_bf16.safetensors (4.1 GB)<br>or qwen3_asr_0.6b_bf16.safetensors (1.6 GB) | models/asr |\n\n" | |
| "Get them from [FashionFlora/ogg-tts](https://huggingface.co/FashionFlora/ogg-tts), " | |
| "[Drama-3-ComfyUI](https://huggingface.co/PedroMarinhoDev/Drama-3-ComfyUI) (the codec) and " | |
| "[Qwen3-ASR-ComfyUI](https://huggingface.co/PedroMarinhoDev/Qwen3-ASR-ComfyUI). OmniVoice's config.json and tokenizer files are read from " | |
| "the pack's own `omnivoice` folder and fetched there on first use.\n\n" | |
| "**How to use:** pick a voice in **Load Audio** (a few seconds of clean speech, cut between sentences: choose a clip of your own). " | |
| "**Qwen3-ASR Transcribe** writes what it says into **OGG Encode Voice**; to type the transcript yourself, delete that link. " | |
| "Write your text in **OGG TTS Generate**, one line per take; tags such as [sighing] or [excited] go where the delivery should change.\n\n" | |
| "**Keep a voice:** un-mute **Save OGG Voice** (select it, Ctrl+M) and queue once: the voice goes to output/ogg_voices. Next time use " | |
| "**Load OGG Voice** in place of Load Audio, Qwen3-ASR and Encode Voice.\n\n" | |
| "**Settings:** cfg 1-1.5 keeps the voice's natural tempo, 2 speaks faster and more pointedly. Do not clone the voice of a real person " | |
| "without their consent.") | |
| def gen_widgets(text, seed=1, language="en", speaker="", quality=""): | |
| # text, language, seed, control_after_generate, steps, cfg, temperature, split_lines, max_frames, speaker, quality, mtp_temperature, mtp_top_k, mtp_rounds | |
| return [text, language, seed, "randomize", 6, 1.5, 0.8, True, 0, speaker, quality, 0, 0, 4] | |
| # ------------------------------------------------------------------ text to speech | |
| tts = build([ | |
| (1, "MarkdownNote", [20, 20], [560, 520], [NOTE_TTS], 0), | |
| (2, "OggLoader", [620, 20], [360, 82], ["ogg_tts.safetensors", "float32"], 0), | |
| (3, "OggCodecLoader", [620, 160], [360, 58], ["drama3_codec.safetensors"], 0), | |
| (4, "OggGenerate", [1020, 20], [400, 330], | |
| gen_widgets("[excited] Hello! I am OGG, a text to speech model.\n[sighing] It has been a long day.\n[whispering] But I am glad you are here."), 0), | |
| (5, "OggDecode", [1460, 20], [260, 78], [False], 0), | |
| (6, "SaveAudio", [1460, 140], [360, 112], ["ogg/ogg_tts"], 0), | |
| ], [(2, 0, 4, "model"), (3, 0, 5, "codec"), (4, 0, 5, "codes"), (5, 0, 6, "audio")]) | |
| # ------------------------------------------------------------------ voice cloning | |
| clone = build([ | |
| (1, "MarkdownNote", [20, 20], [560, 660], [NOTE_CLONE], 0), | |
| (2, "OggLoader", [620, 20], [360, 82], ["ogg_tts.safetensors", "float32"], 0), | |
| (3, "OggCodecLoader", [620, 160], [360, 58], ["drama3_codec.safetensors"], 0), | |
| (4, "LoadAudio", [620, 380], [360, 136], ["", ""], 0), | |
| (5, "OggEncodeVoice", [1020, 610], [400, 108], ["Hello! This is a short clip of my voice."], 0), | |
| (6, "OggGenerate", [1020, 20], [400, 330], | |
| gen_widgets("[frustrated] I told you this would happen.\n[sighing] We should have left an hour ago.\n[playful] But fine, a little adventure never hurt anyone."), 0), | |
| (7, "OggDecode", [1460, 20], [260, 78], [False], 0), | |
| (8, "SaveAudio", [1460, 140], [360, 112], ["ogg/ogg_clone"], 0), | |
| (9, "OggQwen3ASRLoader", [620, 560], [360, 58], ["qwen3_asr_1.7b_bf16.safetensors"], 0), | |
| (10, "OggQwen3ASRTranscribe", [1020, 380], [400, 180], ["auto", "", 4096], 0), | |
| (11, "OggSaveVoice", [1460, 610], [360, 58], ["ogg/voice"], 2), | |
| ], [(3, 0, 5, "codec"), (4, 0, 5, "audio"), (3, 0, 7, "codec"), (6, 0, 7, "codes"), (7, 0, 8, "audio"), | |
| (2, 0, 6, "model"), (5, 0, 6, "voice"), (9, 0, 10, "model"), (4, 0, 10, "audio"), (10, 0, 5, "transcript"), (5, 0, 11, "voice")]) | |
| NOTE_DIALOGUE = ("## OGG TTS · dialogue, a voice for every speaker\n\n**Files**\n\n" + FILES + | |
| "| qwen3_asr_1.7b_bf16.safetensors (4.1 GB)<br>or qwen3_asr_0.6b_bf16.safetensors (1.6 GB) | models/asr |\n\n" | |
| "**How to use:** pick one clip per speaker in the two **Load Audio** nodes (a few seconds of clean speech each; your own clips). Each " | |
| "**Qwen3-ASR Transcribe** writes what its clip says into **OGG Encode Voice**. In **OGG TTS Dialogue** the speakers are named in " | |
| "`name_1`, `name_2`... (without case) and their voices connect to `voice_1`, `voice_2`...; the script is one turn per line, `Name: [tag] text`. " | |
| "A line with no name goes on with the speaker before it. Tags as in OGG TTS Generate.\n\n" | |
| "**Modes:** *line by line* generates every line from its speaker's voice and lets it hear the two lines before and after it; it is the safe one. " | |
| "*one pass* gives the model the whole scene and all the voices at once (its dialogue mode): one take, no line timing in the editor, " | |
| "and less tried; it is experimental: the authors document only line by line. Up to 6 speakers; a speaker used in the script needs a voice.\n\nDo not clone the voice of a real person without their consent.") | |
| dialogue = build([ | |
| (1, "MarkdownNote", [20, 20], [560, 620], [NOTE_DIALOGUE], 0), | |
| (2, "OggLoader", [620, 20], [360, 82], ["ogg_tts.safetensors", "float32"], 0), | |
| (3, "OggCodecLoader", [620, 160], [360, 58], ["drama3_codec.safetensors"], 0), | |
| (4, "OggQwen3ASRLoader", [620, 280], [360, 58], ["qwen3_asr_1.7b_bf16.safetensors"], 0), | |
| (5, "LoadAudio", [620, 400], [360, 136], ["", ""], 0), | |
| (6, "LoadAudio", [620, 600], [360, 136], ["", ""], 0), | |
| (7, "OggQwen3ASRTranscribe", [1020, 400], [400, 180], ["auto", "", 4096], 0), | |
| (8, "OggQwen3ASRTranscribe", [1020, 620], [400, 180], ["auto", "", 4096], 0), | |
| (9, "OggEncodeVoice", [1460, 400], [400, 108], ["Hello! This is the first speaker."], 0), | |
| (10, "OggEncodeVoice", [1460, 620], [400, 108], ["Hello! This is the second speaker."], 0), | |
| (11, "OggDialogue", [1020, 20], [400, 330], | |
| ["Жан: [excited] Привет, меня зовут Жан и я — авантюрист.\nМария: [sighing] Ты так говоришь каждому встречному.\nЖан: [laughing] Только тем, кто спрашивает.", | |
| "ru", "line by line", 1, "randomize", 6, 1.5, 0.8, 0, "Жан", "Мария", "", "", "", "", "", 0, 0, 4, "as trained", "", "", "", "", "", ""], 0), | |
| (12, "OggDecode", [1900, 20], [260, 78], [False], 0), | |
| (13, "SaveAudio", [1900, 140], [360, 112], ["ogg/ogg_dialogue"], 0), | |
| ], [(3, 0, 9, "codec"), (3, 0, 10, "codec"), (5, 0, 9, "audio"), (6, 0, 10, "audio"), (4, 0, 7, "model"), (4, 0, 8, "model"), | |
| (5, 0, 7, "audio"), (6, 0, 8, "audio"), (7, 0, 9, "transcript"), (8, 0, 10, "transcript"), (2, 0, 11, "model"), | |
| (9, 0, 11, "voice_1"), (10, 0, 11, "voice_2"), (3, 0, 12, "codec"), (11, 0, 12, "codes"), (12, 0, 13, "audio")]) | |
| json.dump(dialogue, open(ROOT + "/example_workflows/ogg_dialogue.json", "w"), indent=2, ensure_ascii=False) | |
| NOTE_DESIGN = ("## OGG TTS · find a voice\n\nOGG cannot be told a voice in words. What it has: **3157 trained speakers** (`spk_0` ... `spk_3156`, anonymous), " | |
| "**599 recordings** (`q0` ... `q598`, each a book or a game, with its room and manner) and an unknown-speaker token per language (`ru_x`).\n\n" | |
| "**Audition:** `speaker` in OGG TTS Generate is `random`: every run takes another seed, so another speaker (the console says which: `speaker token spk_N`). " | |
| "Every run saves the take (output/ogg/design) and, beside it, its voice (output/ogg_voices/ogg/design): matching numbers. Listen, keep the ones you like, delete the others.\n\n" | |
| "**Keep a voice:** the saved voice is a clip of that take with its transcript. Load it with **Load OGG Voice** in OGG TTS Generate or OGG TTS Dialogue: the speaker then " | |
| "stays the same for any text. To explore recordings instead of speakers, fill `quality` (e.g. q35) and keep `speaker` as it is.\n\n" | |
| "Files: see the other OGG workflows. This one needs no Qwen3-ASR: the transcript is the text you wrote.") | |
| design_wf = build([ | |
| (1, "MarkdownNote", [20, 20], [560, 520], [NOTE_DESIGN], 0), | |
| (2, "OggLoader", [620, 20], [360, 82], ["ogg_tts.safetensors", "float32"], 0), | |
| (3, "OggCodecLoader", [620, 160], [360, 58], ["drama3_codec.safetensors"], 0), | |
| (4, "OggGenerate", [1020, 20], [400, 330], gen_widgets("[excited] Hello! This is a short line to hear how I sound.", speaker="random"), 0), | |
| (5, "OggDecode", [1460, 20], [260, 78], [False], 0), | |
| (6, "SaveAudio", [1460, 140], [360, 112], ["ogg/design/take"], 0), | |
| (7, "OggVoiceFromTake", [1020, 400], [400, 100], [0, True], 0), | |
| (8, "OggSaveVoice", [1460, 400], [360, 80], ["ogg/design/voice"], 0), | |
| ], [(2, 0, 4, "model"), (3, 0, 5, "codec"), (4, 0, 5, "codes"), (5, 0, 6, "audio"), (4, 0, 7, "codes"), (7, 0, 8, "voice")]) | |
| json.dump(design_wf, open(ROOT + "/example_workflows/ogg_voice_design.json", "w"), indent=2, ensure_ascii=False) | |
| json.dump(tts, open(ROOT + "/example_workflows/ogg_text_to_speech.json", "w"), indent=2, ensure_ascii=False) | |
| json.dump(clone, open(ROOT + "/example_workflows/ogg_voice_clone.json", "w"), indent=2, ensure_ascii=False) | |
| print("workflows written", len(tts["nodes"]), len(tts["links"]), len(clone["nodes"]), len(clone["links"])) | |