From 32a8bfa511f294f252b43858983507d1c8cc526a Mon Sep 17 00:00:00 2001 From: CGlide Date: Tue, 2 Jun 2026 12:22:23 +0200 Subject: [PATCH] Add files via upload --- LICENSE | 1348 +- README.md | 476 +- __init__.py | 98 +- __pycache__/__init__.cpython-312.pyc | Bin 1813 -> 1902 bytes __pycache__/latent_slice.cpython-312.pyc | Bin 0 -> 3052 bytes __pycache__/load_audio_ui.cpython-312.pyc | Bin 6254 -> 6254 bytes __pycache__/load_video_ui.cpython-312.pyc | Bin 17175 -> 17175 bytes __pycache__/ltx_director.cpython-312.pyc | Bin 45146 -> 47311 bytes .../ltx_director_guide.cpython-312.pyc | Bin 5903 -> 5903 bytes __pycache__/ltx_keyframer.cpython-312.pyc | Bin 5039 -> 5039 bytes __pycache__/ltx_sequencer.cpython-312.pyc | Bin 8348 -> 8348 bytes .../multi_image_loader.cpython-312.pyc | Bin 8446 -> 8446 bytes __pycache__/patches.cpython-312.pyc | Bin 8657 -> 8657 bytes __pycache__/prompt_relay.cpython-312.pyc | Bin 9764 -> 9764 bytes .../speech_length_calculator.cpython-312.pyc | Bin 2748 -> 2748 bytes ...LTX Director Example Workflow (Fixed).json | 4672 ++--- ...irector Example Workflow Subgraphs v2.json | 7326 +++---- .../LTX Director Workflow_cs.json | 1 + ... SUPPORTS LATEST COMFYUI VERSION - V3.json | 15026 +++++++-------- ... First Last Frame 2 Stage Workflow v6.json | 15828 +++++++-------- ... First Last Frame 3 Stage Workflow v6.json | 15830 ++++++++-------- js/load_audio_ui.js | 1042 +- js/load_video_ui.js | 3026 +-- js/ltx_director.js | 7932 ++++---- js/ltx_director_guide.js | 32 +- js/ltx_keyframer.js | 836 +- js/ltx_sequencer.js | 794 +- js/multi_image_loader.js | 1374 +- js/speech_length_calculator.js | 670 +- latent_slice.py | 53 +- load_audio_ui.py | 280 +- load_video_ui.py | 822 +- ltx_director.py | 1705 +- ltx_director_guide.py | 178 +- ltx_keyframer.py | 232 +- ltx_sequencer.py | 346 +- multi_image_loader.py | 362 +- patches.py | 334 +- prompt_relay.py | 402 +- pyproject.toml | 72 +- speech_length_calculator.py | 100 +- 41 files changed, 40726 insertions(+), 40471 deletions(-) create mode 100644 __pycache__/latent_slice.cpython-312.pyc create mode 100644 example_workflows/LTX Director Workflow_cs.json diff --git a/LICENSE b/LICENSE index e62ec04..18b1482 100644 --- a/LICENSE +++ b/LICENSE @@ -1,674 +1,674 @@ -GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. +GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/README.md b/README.md index e75f10c..4ad24a4 100644 --- a/README.md +++ b/README.md @@ -1,238 +1,238 @@ -# Overview - -This will be a collection of free resources for ComfyUI. - -Hopefully it will make creating cool stuff easier. - -All of my nodes are created with the help of AI, so there may or may not be redundant, messy code. - -## ▶️ YouTube Tutorial Videos - - - - - - -
-

LTX Director Trailer

- - LTX Director Trailer - -
-

LTX Director Tutorial

- - LTX Director Tutorial - -
- -## ❓ How to install nodes - -- Navigate to your `/ComfyUI/custom_nodes/ folder` -- Run `git clone https://github.com/WhatDreamscost/WhatDreamsCost-ComfyUI` -- Or download through the ComfyUI Manager. - -**❗❗IMPORTANT❗❗** - -If you don't see the latest version (v1.3.5) yet in the manager then just downloaded the nightly version (or fetch the updates to update the list to see the latest version). -Also you will need to update ComfyUI-LTXVideo and ComfyUI-KJNodes to the latest version as well. You cannot use this node without updating ComfyUI-LTXVideo! - -# 🔄 Recent Updates - -**v1.3.3** - * **LTX Director Hotfix 2** - - Fixed duration_seconds input issue. - - Made both duration widgets visible at all times now - - Implemented audio latent fix to improve compatibility - - -**v1.3.2** - * **LTX Director Hotfix** - - Fixed epsilon input overlapping custom_width input - - Fixed invisible widgets in nodes 2.0 when toggling widget visibility through settings menu - -If anyone finds anymore bugs or has idea for improvements please let me know! - - -**v1.3.1** - * **LTX Director Example Workflow Fix** - - Minor fix to the example workflow (i forgot to set the clip loader type to ltxv lol) - - **v1.3.0** - * **New nodes: LTX Director and LTX Director Guide** - - A complete timeline editor that can do almost everything. It's my most ambitious node so far and the successor to LTX Sequencer/Multi Image Loader. - - **v1.2.9** - * **Fixed every known issue with Multi Image Loader and added text output to Speech Length Calculator** - - - Removed the completely useless drag and drop animations (now it's snappy and no longer finicky) - - Fixed the node resizing on nodes 2.0 - - Updated grid logic to fit images better - - Added ablity to right click images to copy/open/save images - - Fixed the "invisible hitbox" underneath node issue (actually this time). - - Also added a text output to the Speech Length Calculator node (can't believe i didn't do this initially) - -
- Click to view older Updates - - **v1.2.8** - * **Updated Load Video UI and Color Conversion** - * Added crop mode, a simple interface to crop videos. It also include various aspect ratio presets. - * Updated color conversion to ensure colors are as accurate as possible. Will first check metadata for colorspace, and if metadata is missing then it will guess the colorspace based on video dimensions. - * Updated display mode toggle UI to be more understandable - - **v1.2.7** - * **New Node: Load Video UI** - -Custom Node to Trim, Resize, and Preview Videos in Realtime - - **v1.2.6** - * **Updated Speech Length Calculator UI** - -Also added duration output to the Load Audio UI node - - **v1.2.5** - * **Updated Load Audio UI Node** - * Added Duration Setting - * Made the whole selection bar draggable - * Fixed Trimmed UI to show centiseconds - - **v1.2.4** - * **New Node: Load Audio UI** - -Overhaul of the load audio node. Features a simple interface to easily trim audio. Also allows dragging and dropping files (fixes the original node that doesn't allow dropping in videos). Also compatible with nodes 2.0. - - **v1.2.3** - * **Workflow Update + Minor Bug Fix** - * Added new workflow that is compatible with the latest ComfyUI version (as of 4/27/26). The new workflow also included an option to include custom audio, and has minor improvements of the previous workflows. - * Fixed minor bug with Multi Image Loader that blocked mouse input in a small area under the node 🤷‍♂️ - -**v1.2.0** - * **New Node: Speech Length Calculator** - - Automatically output in realtime how long a video should be based on the dialouge. - -**v1.1.0** - * Added resize_method to the Multi Image Loader node for more resize options - * Added insert_mode which allows you to enter in seconds instead of frames on the LTX Sequencer node - * Updated workflows with more notes - * Re-added tiny vae to workflows - * Fixed various bugs - * more things i can't rememeber - -**This update will change the node layouts, so be sure to update your workflows or else they won't work properly.** - -❗❗❗ **New Tutorial on using these nodes available: https://www.youtube.com/watch?v=aXDIr8eNovI** ❗❗❗ -
- -# ⚙️ Custom Nodes - - -## LTX Director -Clipboard Image (2) - -A Complete Timeline Editor For LTX 2.3. This is the sucessor of my previous nodes, and has loads of features in it. It was originally based off of [Kijai's Prompt Relay node](https://github.com/kijai/ComfyUI-PromptRelay) and my LTX Sequencer/Multi Image Loader nodes. - -**Main Features:** -- **Fully Functional Timeline Editor:** I spent hours studying various video editors and ended up with this design. If anyone has ideas for improvements let me know! I will adding documentation on all the functions soon. -- **Prompt Relay integrated:** This unlocks the ability to have granular control over video generation. For more information on Prompt Relay go here, https://gordonchen19.github.io/Prompt-Relay/ -- **First, Middle, Last Frame Support:** This has by far the easiest method of creating first/last frames videos. It supports any number of keyframes, and will be the successor of my previous nodes. -- **Custom Audio Support:** Import, trim, and combine your own audio clips in this node. Enabling custom audio is as simple as clicking 1 button. It is also compatible with every other feature in the node, include first/last frames, t2v, i2v, and prompt relay. -- **Image to Video:** Part of the goal of this node was to make it easier to do everything, including Image to Video. It has built in resize functionality, and of course all the benifits of the prompt relay and custom audio integration. -- **Text to Video:** Use text segments to create T2V videos. Compatible with all other features of the node. - -Download workflows here: https://github.com/WhatDreamsCost/WhatDreamsCost-ComfyUI/tree/main/example_workflows - -**Tutorial videos and documentation coming soon** - - -## Multi Image Loader -Multi_Image_Loader_Wide_Gif - -An Image loader that features a built in gallery, allowing your to easily rearrange images and output them seperately or batched together. It also combines the image resize node and LTXVPreprocess node to reduce clutter in LTX workflows. - -## LTX Sequencer -![LTX_Sequencer_GIF](https://github.com/user-attachments/assets/88f27155-f50e-4cb2-b937-ab173e6bdf0b) - -An overhaul of the LTXVAddGuideMulti node. It allows you to quickly create FFLF (First Frame Last Frame) videos, shot sequences, supports any number of middle frames. - -Connect the Multi Image Loader node's multi_output to automatically update the node's widgets. - -It also has a sync feature that syncs all LTX Sequencer nodes together in realtime, removing the need to edit every single node manually every time you want to make a change to something. - - -## LTX Keyframer -LTX Keyframer Wide - -An overhaul of the LTXVImgToVideoInplaceKJ node. It allows you to quickly create FFLF (First Frame Last Frame) videos and shot sequences. Also upports any number of middle frames. - -Connect the Multi Image Loader node's multi_output to automatically update the node's widgets. - -It also has a sync feature that syncs all LTX Keyframer nodes together in realtime, removing the need to edit every single node manually every time you want to make a change to something. - -**I would recommend using the LTX Sequencer Node over this node, after further testing it seems superior in at pretty much everything. I'll leave it in just in case more people want to test it** - -## Speech Length Calculator -Speech Length Calculator v2 Gif -
-
-This node calculates in realtime how long a video should be based on the dialogue. Any words in quotations will be considered as speech. The node updates in realtime without having to run the workflow, and outputs the length depending on how fast the speech is. - -If you connect another string/text node to the text_input, it will still update in the length in realtime. - -I kept having to play the guessing game on my own generations so I made this node to make it easier :man_shrugging: - -## Load Video UI - - - - - -
-

Simple Controls

- -
-

New Crop Mode!

- -
- -
-
-An upgraded Load Video node. It has the following features: - -* Simple interface to quickly trim videos and preview them in realtime. -* Ability to load any length of video into the node (the default load video node was limited to 100MB files) -* Easily switch between showing seconds and frames with a toggle button. This will change the widgets as well as the interface. -* Multiple options for resizing the video (maintain aspect ratio, crop, stretch to fit, pad) -* Allows dragging and dropping files into the node -* Progress bar -* Optimized to use less RAM (still very limited due to ComfyUI limitations, but at least a little more efficient) - -Please note that due to ComfyUI limitations (and the fact that this node doesn't use any addtional libraries), this node will not work well for outputting large videos. You can trim any length of video without a problem, but if the output is still large it will end up using a lot of RAM. I have implemented various optimizations though to make it use less memory. - -## Load Audio UI -Load_Audio_UI_V2 -
-
-An upgraded Load Audio node. Features a simple interface to easily trim audio. Also allows dragging and dropping files (fixes the original node that doesn't allow dropping in videos). Also compatible with nodes 2.0. - -# 💡 Workflows -LTX I2V First Last Frame 3 Stage Workflow v6 -
-
-This is a compact LTX 2.3 workflow for I2V and First Frame, Middle Frame, Last frame video generation. -I seperated and organized everything into subraphs to make things as clean as possible, and added toggles to customize the workflow quickly. - -Download workflows here: https://github.com/WhatDreamsCost/WhatDreamsCost-ComfyUI/tree/main/example_workflows - -Or drag and drop the image into ComfyUI to import workflow. - -# ❗ Known Issues - -Fixed everything so far. If there are any other issue or bugs you find please let me know! - -# 💡 Additional Info - -I made these nodes knowing little about python and a beginner level understanding of javascript. Feel free to suggest improvements, and if you run into any bugs let me know. - -For those asking, I mainly used gemini to create these nodes. +# Overview + +This will be a collection of free resources for ComfyUI. + +Hopefully it will make creating cool stuff easier. + +All of my nodes are created with the help of AI, so there may or may not be redundant, messy code. + +## ▶️ YouTube Tutorial Videos + + + + + + +
+

LTX Director Trailer

+ + LTX Director Trailer + +
+

LTX Director Tutorial

+ + LTX Director Tutorial + +
+ +## ❓ How to install nodes + +- Navigate to your `/ComfyUI/custom_nodes/ folder` +- Run `git clone https://github.com/WhatDreamscost/WhatDreamsCost-ComfyUI` +- Or download through the ComfyUI Manager. + +**❗❗IMPORTANT❗❗** + +If you don't see the latest version (v1.3.5) yet in the manager then just downloaded the nightly version (or fetch the updates to update the list to see the latest version). +Also you will need to update ComfyUI-LTXVideo and ComfyUI-KJNodes to the latest version as well. You cannot use this node without updating ComfyUI-LTXVideo! + +# 🔄 Recent Updates + +**v1.3.3** + * **LTX Director Hotfix 2** + - Fixed duration_seconds input issue. + - Made both duration widgets visible at all times now + - Implemented audio latent fix to improve compatibility + + +**v1.3.2** + * **LTX Director Hotfix** + - Fixed epsilon input overlapping custom_width input + - Fixed invisible widgets in nodes 2.0 when toggling widget visibility through settings menu + +If anyone finds anymore bugs or has idea for improvements please let me know! + + +**v1.3.1** + * **LTX Director Example Workflow Fix** + - Minor fix to the example workflow (i forgot to set the clip loader type to ltxv lol) + + **v1.3.0** + * **New nodes: LTX Director and LTX Director Guide** + - A complete timeline editor that can do almost everything. It's my most ambitious node so far and the successor to LTX Sequencer/Multi Image Loader. + + **v1.2.9** + * **Fixed every known issue with Multi Image Loader and added text output to Speech Length Calculator** + + - Removed the completely useless drag and drop animations (now it's snappy and no longer finicky) + - Fixed the node resizing on nodes 2.0 + - Updated grid logic to fit images better + - Added ablity to right click images to copy/open/save images + - Fixed the "invisible hitbox" underneath node issue (actually this time). + + Also added a text output to the Speech Length Calculator node (can't believe i didn't do this initially) + +
+ Click to view older Updates + + **v1.2.8** + * **Updated Load Video UI and Color Conversion** + * Added crop mode, a simple interface to crop videos. It also include various aspect ratio presets. + * Updated color conversion to ensure colors are as accurate as possible. Will first check metadata for colorspace, and if metadata is missing then it will guess the colorspace based on video dimensions. + * Updated display mode toggle UI to be more understandable + + **v1.2.7** + * **New Node: Load Video UI** + +Custom Node to Trim, Resize, and Preview Videos in Realtime + + **v1.2.6** + * **Updated Speech Length Calculator UI** + +Also added duration output to the Load Audio UI node + + **v1.2.5** + * **Updated Load Audio UI Node** + * Added Duration Setting + * Made the whole selection bar draggable + * Fixed Trimmed UI to show centiseconds + + **v1.2.4** + * **New Node: Load Audio UI** + +Overhaul of the load audio node. Features a simple interface to easily trim audio. Also allows dragging and dropping files (fixes the original node that doesn't allow dropping in videos). Also compatible with nodes 2.0. + + **v1.2.3** + * **Workflow Update + Minor Bug Fix** + * Added new workflow that is compatible with the latest ComfyUI version (as of 4/27/26). The new workflow also included an option to include custom audio, and has minor improvements of the previous workflows. + * Fixed minor bug with Multi Image Loader that blocked mouse input in a small area under the node 🤷‍♂️ + +**v1.2.0** + * **New Node: Speech Length Calculator** + + Automatically output in realtime how long a video should be based on the dialouge. + +**v1.1.0** + * Added resize_method to the Multi Image Loader node for more resize options + * Added insert_mode which allows you to enter in seconds instead of frames on the LTX Sequencer node + * Updated workflows with more notes + * Re-added tiny vae to workflows + * Fixed various bugs + * more things i can't rememeber + +**This update will change the node layouts, so be sure to update your workflows or else they won't work properly.** + +❗❗❗ **New Tutorial on using these nodes available: https://www.youtube.com/watch?v=aXDIr8eNovI** ❗❗❗ +
+ +# ⚙️ Custom Nodes + + +## LTX Director +Clipboard Image (2) + +A Complete Timeline Editor For LTX 2.3. This is the sucessor of my previous nodes, and has loads of features in it. It was originally based off of [Kijai's Prompt Relay node](https://github.com/kijai/ComfyUI-PromptRelay) and my LTX Sequencer/Multi Image Loader nodes. + +**Main Features:** +- **Fully Functional Timeline Editor:** I spent hours studying various video editors and ended up with this design. If anyone has ideas for improvements let me know! I will adding documentation on all the functions soon. +- **Prompt Relay integrated:** This unlocks the ability to have granular control over video generation. For more information on Prompt Relay go here, https://gordonchen19.github.io/Prompt-Relay/ +- **First, Middle, Last Frame Support:** This has by far the easiest method of creating first/last frames videos. It supports any number of keyframes, and will be the successor of my previous nodes. +- **Custom Audio Support:** Import, trim, and combine your own audio clips in this node. Enabling custom audio is as simple as clicking 1 button. It is also compatible with every other feature in the node, include first/last frames, t2v, i2v, and prompt relay. +- **Image to Video:** Part of the goal of this node was to make it easier to do everything, including Image to Video. It has built in resize functionality, and of course all the benifits of the prompt relay and custom audio integration. +- **Text to Video:** Use text segments to create T2V videos. Compatible with all other features of the node. + +Download workflows here: https://github.com/WhatDreamsCost/WhatDreamsCost-ComfyUI/tree/main/example_workflows + +**Tutorial videos and documentation coming soon** + + +## Multi Image Loader +Multi_Image_Loader_Wide_Gif + +An Image loader that features a built in gallery, allowing your to easily rearrange images and output them seperately or batched together. It also combines the image resize node and LTXVPreprocess node to reduce clutter in LTX workflows. + +## LTX Sequencer +![LTX_Sequencer_GIF](https://github.com/user-attachments/assets/88f27155-f50e-4cb2-b937-ab173e6bdf0b) + +An overhaul of the LTXVAddGuideMulti node. It allows you to quickly create FFLF (First Frame Last Frame) videos, shot sequences, supports any number of middle frames. + +Connect the Multi Image Loader node's multi_output to automatically update the node's widgets. + +It also has a sync feature that syncs all LTX Sequencer nodes together in realtime, removing the need to edit every single node manually every time you want to make a change to something. + + +## LTX Keyframer +LTX Keyframer Wide + +An overhaul of the LTXVImgToVideoInplaceKJ node. It allows you to quickly create FFLF (First Frame Last Frame) videos and shot sequences. Also upports any number of middle frames. + +Connect the Multi Image Loader node's multi_output to automatically update the node's widgets. + +It also has a sync feature that syncs all LTX Keyframer nodes together in realtime, removing the need to edit every single node manually every time you want to make a change to something. + +**I would recommend using the LTX Sequencer Node over this node, after further testing it seems superior in at pretty much everything. I'll leave it in just in case more people want to test it** + +## Speech Length Calculator +Speech Length Calculator v2 Gif +
+
+This node calculates in realtime how long a video should be based on the dialogue. Any words in quotations will be considered as speech. The node updates in realtime without having to run the workflow, and outputs the length depending on how fast the speech is. + +If you connect another string/text node to the text_input, it will still update in the length in realtime. + +I kept having to play the guessing game on my own generations so I made this node to make it easier :man_shrugging: + +## Load Video UI + + + + + +
+

Simple Controls

+ +
+

New Crop Mode!

+ +
+ +
+
+An upgraded Load Video node. It has the following features: + +* Simple interface to quickly trim videos and preview them in realtime. +* Ability to load any length of video into the node (the default load video node was limited to 100MB files) +* Easily switch between showing seconds and frames with a toggle button. This will change the widgets as well as the interface. +* Multiple options for resizing the video (maintain aspect ratio, crop, stretch to fit, pad) +* Allows dragging and dropping files into the node +* Progress bar +* Optimized to use less RAM (still very limited due to ComfyUI limitations, but at least a little more efficient) + +Please note that due to ComfyUI limitations (and the fact that this node doesn't use any addtional libraries), this node will not work well for outputting large videos. You can trim any length of video without a problem, but if the output is still large it will end up using a lot of RAM. I have implemented various optimizations though to make it use less memory. + +## Load Audio UI +Load_Audio_UI_V2 +
+
+An upgraded Load Audio node. Features a simple interface to easily trim audio. Also allows dragging and dropping files (fixes the original node that doesn't allow dropping in videos). Also compatible with nodes 2.0. + +# 💡 Workflows +LTX I2V First Last Frame 3 Stage Workflow v6 +
+
+This is a compact LTX 2.3 workflow for I2V and First Frame, Middle Frame, Last frame video generation. +I seperated and organized everything into subraphs to make things as clean as possible, and added toggles to customize the workflow quickly. + +Download workflows here: https://github.com/WhatDreamsCost/WhatDreamsCost-ComfyUI/tree/main/example_workflows + +Or drag and drop the image into ComfyUI to import workflow. + +# ❗ Known Issues + +Fixed everything so far. If there are any other issue or bugs you find please let me know! + +# 💡 Additional Info + +I made these nodes knowing little about python and a beginner level understanding of javascript. Feel free to suggest improvements, and if you run into any bugs let me know. + +For those asking, I mainly used gemini to create these nodes. diff --git a/__init__.py b/__init__.py index b11048f..9efbaaf 100644 --- a/__init__.py +++ b/__init__.py @@ -1,50 +1,50 @@ -from .ltx_keyframer import LTXKeyframer -from .multi_image_loader import MultiImageLoader -from .ltx_sequencer import LTXSequencer -from .speech_length_calculator import SpeechLengthCalculator -from .load_audio_ui import LoadAudioUI -from .load_video_ui import LoadVideoUI -from .ltx_director import LTXDirector -from .ltx_director_guide import LTXDirectorGuide -from comfy_api.latest import ComfyExtension, io -from typing_extensions import override -from .latent_slice import CleanLatentSlice - -class PromptRelay(ComfyExtension): - @override - async def get_node_list(self) -> list[type[io.ComfyNode]]: - return [ - LTXDirector, - LTXDirectorGuide - ] - -async def comfy_entrypoint() -> PromptRelay: - return PromptRelay() - -NODE_CLASS_MAPPINGS = { - "LTXKeyframer": LTXKeyframer, - "MultiImageLoader": MultiImageLoader, - "LTXSequencer": LTXSequencer, - "SpeechLengthCalculator": SpeechLengthCalculator, - "LoadAudioUI": LoadAudioUI, - "LoadVideoUI": LoadVideoUI, - "LTXDirector": LTXDirector, - "LTXDirectorGuide": LTXDirectorGuide, - "CleanLatentSlice": CleanLatentSlice, -} - -NODE_DISPLAY_NAME_MAPPINGS = { - "LTXKeyframer": "LTX Keyframer", - "MultiImageLoader": "Multi Image Loader", - "LTXSequencer": "LTX Sequencer", - "SpeechLengthCalculator": "Speech Length Calculator", - "LoadAudioUI": "Load Audio UI", - "LoadVideoUI": "Load Video UI", - "LTXDirector": "LTX Director", - "LTXDirectorGuide": "LTX Director Guide", - "CleanLatentSlice": "Clean Latent Slice", -} - -WEB_DIRECTORY = "./js" - +from .ltx_keyframer import LTXKeyframer +from .multi_image_loader import MultiImageLoader +from .ltx_sequencer import LTXSequencer +from .speech_length_calculator import SpeechLengthCalculator +from .load_audio_ui import LoadAudioUI +from .load_video_ui import LoadVideoUI +from .ltx_director import LTXDirector +from .ltx_director_guide import LTXDirectorGuide +from comfy_api.latest import ComfyExtension, io +from typing_extensions import override +from .latent_slice import CleanLatentSlice + +class PromptRelay(ComfyExtension): + @override + async def get_node_list(self) -> list[type[io.ComfyNode]]: + return [ + LTXDirector, + LTXDirectorGuide + ] + +async def comfy_entrypoint() -> PromptRelay: + return PromptRelay() + +NODE_CLASS_MAPPINGS = { + "LTXKeyframer": LTXKeyframer, + "MultiImageLoader": MultiImageLoader, + "LTXSequencer": LTXSequencer, + "SpeechLengthCalculator": SpeechLengthCalculator, + "LoadAudioUI": LoadAudioUI, + "LoadVideoUI": LoadVideoUI, + "LTXDirector": LTXDirector, + "LTXDirectorGuide": LTXDirectorGuide, + "CleanLatentSlice": CleanLatentSlice, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "LTXKeyframer": "LTX Keyframer", + "MultiImageLoader": "Multi Image Loader", + "LTXSequencer": "LTX Sequencer", + "SpeechLengthCalculator": "Speech Length Calculator", + "LoadAudioUI": "Load Audio UI", + "LoadVideoUI": "Load Video UI", + "LTXDirector": "LTX Director", + "LTXDirectorGuide": "LTX Director Guide", + "CleanLatentSlice": "Clean Latent Slice", +} + +WEB_DIRECTORY = "./js" + __all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS', 'WEB_DIRECTORY'] \ No newline at end of file diff --git a/__pycache__/__init__.cpython-312.pyc b/__pycache__/__init__.cpython-312.pyc index e93ac256b7db423a7aa1c81b0fb28a6d6043e546..aa15cf205c098f6cb22849a82b0fe1fbad303984 100644 GIT binary patch delta 523 zcmbQr_l{5fG%qg~0}%8Yi)PJYV_QUX&Q$JH-c$^N*E|0;GC11nCFvNlA2c%oRgWn`2eFeBO~ABKTJi^;y^ts89syDQzQl=1Yrd4 zkWvH+i%vFS zQDEep9Ll1>RK!2Iie-Y35ZFBm5C}ZJ~1aJKE4ReDI7Mr`6;D2sdhz%KxLqCDV{R9iA|09E`!$O z6>LV1HzYJZ3Ui4wU0@LYAjKf6azjGrhKTYFIg1;@N?@c25|PjWDv|>#lKLRQASm9z M`-m%`ksa)10GE_}^#A|> delta 419 zcmaFIHLD6v$=ROVFHRQ6QPRPI#XRQ?o!IUG^qDS|0NDZ(itDWWN2DdH&- zU{Q$_$tcNmh7_qqj8RgR(wZ`p#Ta*Qe#&UY$jCccfw@Rp3~0ehhR+~tibO$#0F2<7 zyq4LBTMou85}f>s*~Um1$k$|qsFDTAffays-V#X8&rPe0Pt7YSsw~LQ1X2n>VUfuZ zEDDSqlPg&?CNE>@o$Su)$tXQ}BI^q`h#?>~MT#Io2}CFZiCgUP@rgM(@$p4yHgMSF z=BJeAq}mnf1C=oXadG$LOKfVBf3q1`e-z{tXS%>3{6T_2RON<*&J7Xe8*&ymgq6TZ b5hwywAqP|;^+Ak5P`rWn5m!JXJJ|gI15{p4 diff --git a/__pycache__/latent_slice.cpython-312.pyc b/__pycache__/latent_slice.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..82b1fb0af231a173a988883f63c40d33497515b4 GIT binary patch literal 3052 zcmbVOTTC0-89sCMl>`C=upya{8;-Va*;TVmg2+;vBoN0j*z6W>s_DdY42JR8nHfUt zH4&Av6_44p5>$%RC{Luk_>o?uvdv13)E8UqO5V{vtkk~n<}8(3ZD0DI@t6?W-Kss7 z|2dcM{Fn1z&iQjoiyJ{Zr*_Rf^&<2aGH5o|?(F^mI;%)VGNqs-qR~|((+`l$Y|srv zl9CxrWvC=AGcpTbM&@LGhEB2tD&W{5plGTAZBTn3+C2~BRfG|6KuH=iGW7s$*vKR+ z(@9QdlDy0&9Ws}6%6!r#J075CB{d=$ST*8GCWTYfJ|V;|2L8Km0s-wQ z+0ssCiEgq(c0T6PWQKjEp)Xq%|pvg^ViWdxDV@bfOtd=CZ;-URjtC&MKD1qP0P^ zaA$>Ctl>Z~z?lvir=3>S(_!Rtijm0|FB56%LiRS+gj~8IPA8J) zG0rz-q{z{Twj@g-+FIWGQt{`vU(_|8{cVSo7NGtgZYB#8H>iI5@KRHHX1MX9&}l7-zpDs z_4j}*qdJdTx@x}DHQ({|iSnJTqm_xtIzm68=IECQ_0Q8V@F{f7FRV+O)Yh5GWE^HB z>NZ(EnIg-6nSSYDdk)nZzSUhvyvw}{tdZsb5}>qJ_?TYHIrcG`hhDd+_%JS|u~KMw zP!}ZOZbrsAp<&>De}ii!6?~D_v0*ztozq~0p2w+7I+GF_2OJcpbWB{OrDlVwq-nWD z;GtgzZC zq>V2*_N+JxdcyBRxMN+ir|qz@gyG<5wIOp_zg zt-*VdT15AlX|zZMoKcfi>)(N1N2MTc7$bR$>z?kuQ3B z*OKeq1R5~KH&Yrc2 zN7MiFTAf2jkJr0Vhi~P^KmOK^I{Lmsj#sj3{u576Je^qgmP=c6)wg4nfmo$KR`pMO z)-hp=1jv&&%Dr3e>fm_AfBmzL>)*Jr%Mf9_CG4N|b|U9%{fBUvA7APXb)r9ZTlD*c zKJRpe2bkx_I>RTK=WkJ@f58j=PdL*5#AEflto}(3W=w}JW%CNwO}CoM=vd53`aImN zY$}&81b7HMtfZ|QNNWRu+7T-XRJ>^4MB4X=Pf6u##EBIqS~rY~ecu`%N69`B$TF&R z`d8oo=>4Dnc*Xq{+w1bxhmp5)&0Y2Me|{>sUfc{+zZ>4dFW%igl~@^nSg3kWz*%^^ z*Dh8)r$0YCSU$ITtNP~ni_uDadi!h=Af>AJ)R(Qkm8FWaSL*^9(SXNvilPdy9Z@vh zqL|Ie3kvCbMez>AzrEr~DR8}Iu`!#IHKL*Eog7U}O-37Ug1u36+bY7W?h7w~p z;q?`Q7uVIBlhdXrJQ@#8j>X8dbqpH0T`VOUM?>;mBZL$K{cRu*P>pMSNjo?<_?d63 zlV1G@>Nvg}skI*ia=35#TJ7-h&+kvJqbmlcq8hN7rl9#JiSXD?EHFAVb2MHaK;H5 zuLP+iY!y_*&{|)B(vG*6+819dz3ug08^nIbV{OseujTV`gL1!5y{+E0&dG#CwC(-c z^CkbRz4qE`zt&!R@3p1Ns$X4I#lIXE7sJ5!g_!DjC81SJj1izyv{uMjq&FD7IZFP8E2knEtZ`7dDhQOW6IO<6}+41O_@n8v8PxikDoWp zoSGKvwDR%J65i-Ecy7P3WRIFR4XZoL_yo*HEy;<_C43V6o1Hd38GhxGbc#)mO##XB zX~~3a^X6#gSE$wUX&_v|r-PCVkjr#df})ZhJwG4#OZhAqiYh)Euv*2eV)&e4hPOC5 zPan0{C0RK|V`nY!YTayS9iPkR!EgOuHlJ}8E?`J@XOT0@vkelzB`Q?L5nD zg8}X=x~=HYHjHXB^*pPgSF6k`wyGHBC?hfbd*gU5Xvao2N7w=A{HMR2$0W~9P zc&*1k$Lf-(sn!tgQEh_$u<#$!4KP;S;4N$XyWK%|w|B24IAHG!S_1uorP4Nz<(BpB7Ty_j5O<&84)}wm5@S^j z#?I0+sKrE>*yi%f6+S7S%dVJaS4gZl$XP^-RU_$$v(IaHI3-Q64=RNp0jiU<_P#!+ zpO@60fZK14mEv69K)2o7)kgxpK0%7}1{^b~;3i3fU9%}a<;qWmq;mC19Q06P`=nU8 zK(Nc}4hoV9v^bo>pp)-X*pkZI7mNi!dMpwEN)R$>h|donxKj z-1wGD$wgx-sM%ZsHJeNRVUZ~4Gp<_wYkmGyGGok`N@M25ALu_kI5Ieq zQFzH%G!a`gp)UILqbw$=nGI@T2!EZC12r)*3^m37uZARmvPisf z8LJ_AK++?oD)w&rOH(szqcI65vX=mplp`z!2;%NwktInrko35zIOhmvm;r{EC!9~W zJm2D@k8SJ3GSk^;KX+^xm# zNNMzhDJ_=t0=)+QgO!mt&988lbtlcoTAogLKKYsCr+1$nxMXcQUwz5C_K1Dld?02b zd0ph^X{ju`p8htyF1%EIL7jSYo%-jA`8PMIemSpflZE+Zea5B)^?#(O5i?rifc0{L zWmAg!KXuC&L*kXPj7^#9U&W{q<6BdJ^=eb>rg+VzI2JL%2%Jk6!)85q$(p>G<6h%5 zKwDXf8*p|HW|et`Jzcz;I2}UdXog4KjAMY4#*hsFgQ|TYg8d=5Wf0uKqS#Is@Mj~%^8$YyUB@G~RH-NioG~7}m?p1YeG#catw#W^ zd;#!TJos*>;DF5}VCiuS1olrDl0G|64%0K`7Iu`rQto6Qrj-@t>-Ncce&>Ls>9Yr6 zOkV&7`4Pe+2#+EhM|ce31b{V#oI?6E!Wo3;5T2*ssYqha(F+x~YM%l|Fa^ddJwslz zQXEc@d(heC_Q8~sw;=z+$d<}66vU$G-Hj11j?o%r2;mo-7@H#Ukqi8*X9>BGdUw6$@S z2|F!uu){0nk^tQ|qq&@IOx7B}#&g-o%Z(nDR$f1ABlRu$F(UG5 zzG=Poy{PA1(1=Tucl#s*?8m-Mf}IGY5$m+k57*U&n^2-anKXGS)jqq>E2$k$w^y0A zaTJzkQC?XqF^S7dUMM$MCT2W@a1>!b!u2s#6DQb^>_AUq&Nmq_Z1Mq+k`7JI?hBG- zn2K-d=iPyuok6>=&+8=QQ)KF47M1j8SXlPRXj2mBuure^_b>y;oP3UO0>GL_Vqom9 zFJ2f3J8?OMt{{=(=%V(_qI*%+2_VgbLF*c@?{@YCh>tjsrS0zT>47GE8-)&1Z+pIt zMSXBeVHco^$@$yNpMO@96jIuq}BywFc0Q5kD7ROZXPO6?&|J$d-*O$ zz}M~e!!gE={U#NlYeOW`akHV|yC4R3ic@1%kz28l#<>R$e?b+{mZ9k#jTUdUv2W2G zTl2K?TCSx{52n+{w?Zq{+Pq6AG zF4*rQt&k(z2&|z%zn>>BBV!+}+vZ)BjU;wXQuFSf9!V1*ypu@mPNlUvpM6i4o8LpK zpbAol^%y~T=6r@kb2C`^2iq)~HIV)9k;KkSZLI8e6`x9L?)ZdFpf-EINjCQfq5Ks! zR%@l-v!7{Qf#l4#tTc8W@?>+sp;LAN)J}Am)FYssNNTqq=6X4Ovb#R~7v#nPSaT$e zF93&U@)F9xY7(5VmCR}qO0L5^wI8SXdzh97B1dN0iKADF>9d0m?Lx0bnz8D22rU4T zNeJw8`h#74PNEo!6yIm3cR1p-1*oZs1|6lbIN78M!9`Cydedc#iR*Iu9ndszhp@&8 z9#!9ul5a^!;RO)XvFsfgRk>-l*2-*&q5l!i8XYU6I3 z=G-3TByXSQ>_86afX_9bPPuaE7k)FRIo(Ak{q@`(@S75-*V*~*(~eUrniiZm zbid$%W8?cmsTR);gRS(cP(WApXK8Wjh?jc$PsYmo`65i$M^bh#W=$UK->6E9-Egj-JTM!6cR~ zq@DX}!*d%?cA|hH1(cK^Ux2!1-PQ(oZS?lLEN;KW4wpE=?)EzQ*|#|h*zfEsp`}bo z*-9OQ;kIYmEgR4Z{5UJnqsDyN<)n7WIMpYvw(_W7+y69U7O}+(_8P(Da#S##Q0Ed z$O6HSJTXN~71Me!9#aeD^2Pii_K;RU&GIMa(_IIX>5F%pxdmb##w9fQ;sS92eedpe zZlSo4o?l?58-|U>Mbm+nZJ=e5xJc&jA5LZ!>hpy$>R`e&`@e=$r`ewlXBij4ZSFR> z%@shg0#5?o=4j15iR@y!aa9T(D%Ml?JqwN2Y4ydR-YQy;6qx0#@84r&XA&Q_>gfmf z=$aOXtO7b>r2_@gz}E_9l-+J;nA>4MLWQ9sA$>-sC@K>w7K`N$U%pqPlF3X%X$ZoD z!%V1*K6o%^O)-w1XcSFR{}(Fu?-S#A^-kDhh)ui^YVQeiVy^&=S};NcPMSX}Mfwj7ltGp@93E{E3UB!?_cNnrOtuVj+$- z1PT*F%;C-HbeBJM-mKmf(eQzEbq=2_=g{qkV%c)~lLP6Sa`+@v z7W{^o7raljOb?Grw1E1QP=%N~!^s6sYN%4I;M3$qkcPtp5|wk(l|nB4aY2@b6+x1= zkCbprl_t{9@0$k=keil9&yQe2ZcQ5f)5ttSMaUvnhH_z1{AHveGcVd5*~-8aWGh<` zsuruS+Y^$CJwar5+McR!u&0`Tl08-5U{5vve~vwUmFC&@R4snlo@&sZj5V|DsYV%O zOHFj(zz(v-1?auNXW>NE&e%nQSUXEj>@@4dx*4Yl4N#os!45HL=%7#{TQ)W>4Qy_fJM1GK zz_l4#DX#P^lRf#5@7HrHJxwxwe#D>+tqe!AKEGd|+JbfA$1fCG6^dcPD`C>i^fI{q z+QUX$Ta-VN$S;ZxH_01G^sI{JT>k~_Yvjrf9X2mrERqW*1?9vvEfx{NV*~{Bg!7g~WcY%GiNU>0MA7HS{MjBPtL-4#WKJ~@`yuqoO*m@%~g2N>lW znR(6soPLbRI(M1=v!RPh8Je@ztkmPEDU4mNKDsvEAc{=3oB}uc7Z{ zWzr9iSi|QV67bR_e;)ZgG1Vr{ zDkWCk#r{?4im#F0^*{wgkPp+G+&Zyp?qI+}om~H+x2HA#;Q^bq8=4?TMV0s{9Ecc# z-ieeV+>byJY;eCXvDlET0i*fK5cl9h`r*;6l4V}Iuba0w-VNJz5Lf;X^ECT(!L!QY zlhmRPIpBzQYkKHt%X|zOkO;!R0w{q7ixOx!N3R@R#2%)d59Kz2(WNyM723;{gDl0Redb&=)A9$^!| zjY^jR141L~kS?JgKD;Tm0!5A^jMMdxRI(D?|48FP9tH5W2X9Eip50wur{5*?$|E8N zBZh{CAPVvBBP&!Ujut*TtOQcx=nIdwn4W^NSH6Ic#G{sZRCBy06@z_BbL1v)(@ltM zKAy!U`!O?sAP4w%Aw}rFJhnYtsRSb$QPF$ImIGPn2gynVIkvVBDfEEyOM?>JSdK{y z?#j_mVn7AxR=*skTaCP71UU$XS7dS;h0h?=J%`wikdHYSL68GZ5Xyisp&TX2+=aWAZKp*ligprUUMk8~TO5 ze!;qdJc5-fH++_WJ9W`+R$+!`Lor9dB=!P4_0Kd-j8-@=hul zzx2dOc3>MjQP>~{3%fARyC0b^AjHw}@r;}USmy}B0K#sBg9s6j32vZojIXETW)0U2 z7QgspZhW^2*bjnl{cMPtN};!&e1KgWdH-akDhHkb6EcrAT{IQFZYsZMDu1EkZ17z5 zcf?B-D_%3TU8W6Br0TY?OxBXA+atF<@hg_q(xNAKY8#lmWmC)P@h5*+4L9zRjqK#S z^kWSZh4m8+9TOYvA9Jdbn2UPrgg)(9*)=_rTRJ%}VYKpC%0#a1Y}5G-6I*VN?0iaM z=Pwu!ywHEH^HTNdUldH5E#oC;vnI{C<4tEboZmt1&zv!aw3@W|Pd_pdrn|nK3gY+Up_dM?Hvb1g;_l`~j z^gBjF_fqbiw6yLe+&fD&Nb@xs2djIhK?jO{uQoW!xZlU6ISRSo7ixgEnw2*(FHVXa zed@#6`zC_yFJ+f1J587xrlVu!%8rf>#mAbki~{t;spjybSO^yx`3X{&5adlkcF=j4 zVMiW90Z#NSz#3-WP&+ScOjlJ)H8CG^j5c8ksVP`o+JwuUJ0?r3CtEuwH*cM6SU&0Y zPIhd(YFw@cfq3Q?b}Re(mhOvNx<5izl2ZJt3bU1$Bs=w;ZrZ_3H!|8>>oQ3P&uT7s z2~i&DPGXMS@NXfNhK-b6$B%WcP-iO47o!-foCV>+J48uORdhH+}p};cfS!HrZRckop`&A%^V`B<*I1 z&IX9$ZDn744@$g+Ap2c;i@`8GS%ZL1R5G*%d_8;7&k;La@Z8e;xsxF2mqARq-|K87 z?}Iq}F~OIp_?%1su{M`2{%AEz|M1*r6?dxHn9;nW#bd?S7^J6G#4wt~(fTP)m8zOx ulBOVOnaFK{bhhdulkv&aUYdB;mfFD0}XTo-83OUntuWW2;m=+;8`&fqA?_-ZFmpTq<={DBaq~E zAR3J^I0-2=i5ewwbkIbNw(CZn<2t)K$Dcj^OgwA$&A2AIXLsB=$Blny)R`Uk-q$oC z(cL+F&b$NPyLIc|E*}d4*f*Ar7`Z{h=&7D5g0G zivdD7LML5<<$P9@QBDG^H^WW}*m%ZGB#*t5aYL4(_l%5)0f9O}O3{Tzd|nA&67`O- zW!CbNTx4rQK`JT6&hY_pMDhj$igq~Y4Y=}^gdtzB-{W(S(4c=rVz*j7NlJn*I55u& zvENxs%nBJ%Oe#C%_IX1RbJ&K=d8j$g>mPE5#Gw#PW%t_p^*C0=%E~jWCb|XaeE1Jl zMx2>%^Y*rrmSe56$xo*}o;JJVwEbM+c~|QZ&y3~H_;=IVB8iR+LfTnD&Wed@?Q7bM zb@kd`rR1$^(!8;#yeEfyqam}$tbH?GixLfd2aX^ZE zlryYtX@HSaGeR4{F3s*RU5Ow6X(BvP|tr zb|7@Jry#YyGK)QuZ=GRTEfEq;`gmqPPW7I2&)Qy~k z;ZQK(rebI$7zl~99(lYkI7EXg1&i)VRSZPKlGfIJs$wp=`uXCvG zl;ZXUJ%Zco_Y8?Jm;g94oq<+oBcbx7IOzSh+5++}d!ja1Ux3Q)Wbf75OankF{D?=O zx3jNno#YOdUMG@6Os-qz-mcaK#4$xT;t7FypTKqzgu@8a2uBbY!aV@4bb1o$QwWbB z{0!ky0LAED>-BZv@D$DZRrD#AP`^Qc0%}6(>}{JZQ{BNz0zCvnfan4f z2+d9Xdyx8XE|S_YtkJ7`sxflgs=Ro;YJfY~nYF7rRENQ>rf3I!(uktRRT6_PK00%~ z&$APIQ9uH>u;BG8Mu>U-9&mYymH?YN*z%vOm?%Yw!sr64%dQ2l-l}K^M6WNp#P^}F zy5h@`sztzEsqU{II4V|5A>55HhH%B=S}KCSRUq>y!Z9ud@Dm^v1ME)G;}6jyM%G!n z{tJ0=U=xpj#3$0rC|!<#4yW(~p1F8%33<2#^eY6_6BkpC03Ed)+p?~>IE;0h0F*_z zO~*VtMBKh~BeL}Uql1GYr4OLcBztaMUgaN<{01wH^FyHDVmT>R7Tkl>;}63b+wC_!SvnVtpg8i!o^{5;S&#rfMB} zFR;h@7Dsn!A$z;8gv_!(_GPMH4fw)=#PU}_amWY_znSFDt$L;M#V(4Q+7+kpp;14r z23lQA*b2c>`0V`>8Qa;dTYP0}kn971(B~Z-RCGZqh*TllqJuK{J>zb#FiuO*dKE0& zZH<1eGMRHrrk*wf`;kbmJ4tK`OHzbYi92_;P?s*WnpK7rxo&9<6$@Ur~ z)fKNqN*&)Kq%I+#QK%CEwNnF9aR}Wb&^&}b z_SM#{Hg$U@xW&K#bO_-Ib``x5TD~3`^**m5H?tC7IqUPy#uq{x9{#^fj3oJgqt^`C z*vnFl>E_t6N8%W1va=5*i*Dl?;IhyB`B*aa3Y*SsKCOwBllp`$SO!(_NDj7k)Xuxl z+{}JE+Q{E>W;4s)(O~ch{c4+$?NJ>Ag0Qgc6otVnI);pAhE8M9gr|pdZSAHTB7fMC zMf9uC0xvS__%oz|y*WOv-;9ivY|T!8x*N$~LW`&?X(70b(9P_vong|(x^{KzRe{ay z#I9jI+I-hi77Q20m8qZdUF}eCF>!a69#?_(vpaU5G&Ld#yPc!1$TE2;G5-$fs2=RC zlMROlNHcpoERq&hy{9j`6$D&bI)HL#5uQPaAPlf4_q32lBcJW@5|gT96YAI;>7Pu+ z)8vl5_4cdhKs_iBl>&-sZriQj9&K~mZ7&kCoi*M5BJAC7Z!gvB!Th1pNXZ>dyy0?H zrX}&KI(G5Er43!A+#G+ID>ufgTdtel3S$|_1c*K8pQ;!cOB(qtw7M7 z!P;fpLG`paSIc^rS9?U7vmJ{T}up?|A;=(y{x||_f2l)=JuO8YtW#=TB(o3jW zePk!QG-F}A4yN(Bat@m|S@g0~&Xsf7nSz*?5v9f<^(L8-J zWg}Z#WMS{R;#kq$`AG$HJ;7HdTp$mP2Lef4iT*7WTlGEiv z^U6uG`Lr&oBJ2w6mJI(%dO0UZpv4f#$MnG#ATds^%ip++6w>G$&nEUo001$qRdClPt~Vl9T7> z;gSnsBuinAj(IZ~_!td#Dz(dn5?-j(N6wF#bsLzR>g0U65N#eHLQ1$?&Sp*1S!P#E zhH!GWV6m}jf5xH(wpz+s4%@WZLYi8`9QT+=Ih#Ce>&X^UQCaA2IVW^bw$GVJBiliJ zdbmP%%yS&T$p}}<6@pdWDONNQNK{@`u9R|F#dXj)0)HJ3`!UT6J*-;3*28Mh!;)$icvy{U@xs(BITwQ*gf?8N<>(o#W!h$59y6-y zE%QE+W5e}_*cmdY>yDJ>FqtZpYgAilOrAF6UxpqFf z!Vsd-^{y^Cb@DE$RP}Bgo)!FVfltkEl)9^A9M{U=nS=}1M;*>85FtmdmFuHYa$WR2 zq+`Bg4XMs~ohyV~d4-pR8<_8S&cI^1POd-DCD;Dg3}Rns!UZ&PYPdnFjg43)vPN!@ zdD$Sx$?>Q0ganW3nJdGMQhl@xp-F$Bi#6S6uWIsg;pPJ=hsoY!cc$EFy0dGL7xMPS zgXT7E8R4sK$xpiB`6^#ll{>&&0O^4OLFTwhhKZ zb0xgKtB;T`wZ>==!mm+IOB{0?O@b6f*-=|YXRP+hd!luCH6zUyMvtb}v;{H_qzwP4 z6pU;sj!bYpIG=+Gw@tJScR?yEGkz|fnWSd+uSatrT?Z;P6Kyz#IH4FD%4&gi9P=C7 z_$$*O|-<4fMJxc zH2owtJDF#*$DP^gA{kH5rNhDA8bK4HsS`uNj69=(W#82RnCv z!^G;V(|i&hwbW-=h*4MG@=Cc89I;u>mf+kJeGZsnCX$=vEIIQuUQFQWfrAEt|8U+| zj4Bx8@;C{vD(V9n$6QUgirw{K&gyKrDmEQ8*T{vd_Ea6#PW`)}QdhE#*C?=ut0efe zMW10cTiaZ@P?ubFEBXKHuvPtUI@q7Pt!(5$8^Lks=2Ts6KFE@)Oz>t z{*NHw1U|(>EyZjV9YP-7@#q~08IcnYrIEkf(@FOA!_E}!>C;Jo#|ixco3};^XYMB{ z$53ezFyMZyUd%Gt`I9{+CyE?FxQ|tys)iKS;Hf5kFA6uZCr&xwxc*M7_q=VQnRXdflx;Y6jsv!uJU3?Ke6jEKVcTBj6V-n!$Ji zsfV$*8HAdXi0ue?@q~nydas3ij+*PBc)eD1(kLWJ6vI|e2r`nC{+{i9EN>!+nzkTN zgb;#+a7}J;40+=S@Y&>)_-?rC-Y_bSj7qNS=-t?9^s0yw>cwA8tn_0=8}ix_IuJ0N zN0X;;+w0+|fHD|QX$wLt!lMXYtWX!S52*nJAA%ntfDl9&K^R9ELl|YZ|9ts`dP%<# z)#M>;Kv1_?Do_fK8H|m{JBjcX!kY+B0f423ApH}4ZKb`)R~P(itTYLue*ez792~}7 zB|Zc>Js;lu2bFjL%&xK(vo%Gkm#)S!tw&f3pcp-)f;Z^Kv>Qf9`f~)H{bDvVXAgEX zf#65*Axt7X1~Q@R*w?dv!?(~Stn2aI#CqJ{K?tPZO>&nlZ2a+i$m&SK6V)2~1#{-H z=J(9SADEZDXI}PP)!ERw+GphRRUPk}J1?+X&SV&FAe_D8@=cLFXWk(sj&(n^P2bEd zUU|8L{rywFNc}fw>4infhpUgJ&)ClDAue8{1=c@Tcb0oB+;8)Z#!`)##9JH1_B5AM36C4*p|@4(S^U zbp7RqkBbeU=;LL^0XzS3gLS~le`3`E4cS;TTDcz~1z`}^& ztGc_psf&FTX_@#bO5u*6&m#3If*O`FibZpF8<2;<60%bbs2VoKKWwrmJ4OI7utI-tiSm}W7`F<@AHIRJk&1YD3Qnq+xNV;ea{yv>r37Y z4L;XkwVG_88`!U&YaVV;4Dfr#5O`(u*O8+*7Il!vkxIk?seAkdEFMBo!_6m1J%^x0 zP$K|F1tlF)&x1S5)P$Pb?+HMT-jC_DhuP%cZYp~X^L`Yx1sM0wr!G41LrmHqj439R9e_%k1~h4{5&8v);e^E_J(>#2;RK tv}C&ED-P+)!$vlAE(d;8+<&ezqoKK@iF>t4*O|+|##=hG`PZ_M{vYBMi_ic7 diff --git a/__pycache__/ltx_director_guide.cpython-312.pyc b/__pycache__/ltx_director_guide.cpython-312.pyc index 9a569e75df5a5293cb738bfea23bbc67dbf57190..1f9e4ce4ec499615db6ba7e36d695096bcef5fcb 100644 GIT binary patch delta 23 ccmeCz>(}Eu&CAQh00h0pqFKR08~GT;0YUf#^8f$< delta 23 dcmeCz>(}Eu&CAQh00gBm0$INVH}Wxx0{})g1*HH0 diff --git a/__pycache__/ltx_keyframer.cpython-312.pyc b/__pycache__/ltx_keyframer.cpython-312.pyc index f1d489600bb52197cd2dca338423a6efdbce5fcf..4e83936a1df39ca80eab30a1117d0e15dc9281fe 100644 GIT binary patch delta 23 dcmZ3lzFwX0G%qg~0}%8Yi)JwiZ{%Aj3;;!~1#tiX delta 23 dcmZ3lzFwX0G%qg~0}zzP2xRpMZRA@h3;;<<1_%HE diff --git a/__pycache__/ltx_sequencer.cpython-312.pyc b/__pycache__/ltx_sequencer.cpython-312.pyc index e255d36c019430bcb6b3e8c4a357f2702cb765ec..a48a14904516d5d4ee3a38c16d56a23599c3f920 100644 GIT binary patch delta 23 dcmbQ^ILDFiG%qg~0}%8Yi)NWBY~<@x002k_1-SqK delta 23 dcmbQ^ILDFiG%qg~0}!lF5XkC~-^kae002z222}t6 diff --git a/__pycache__/multi_image_loader.cpython-312.pyc b/__pycache__/multi_image_loader.cpython-312.pyc index 9ba2190da6fd51d2b05d447be741687020e6db9d..35480cbd398cab8431a2cca50983ec73f7897b12 100644 GIT binary patch delta 23 dcmez8_|K8=G%qg~0}%8Yi)N+DZRGo)003IK2MquK delta 23 dcmez8_|K8=G%qg~0}zzP2xM)L-N^Sr0RUXj2V(#L diff --git a/__pycache__/patches.cpython-312.pyc b/__pycache__/patches.cpython-312.pyc index 40abc67103c1c82e5eb92c910a58514ab6d6a7a9..aa8d0ac375652cc91c32276c01480355ffe2ccbf 100644 GIT binary patch delta 22 ccmccUe9@WrG%qg~0}%8Yi)O9b$a_Q)08ZWpu>b%7 delta 22 ccmccUe9@WrG%qg~0}zzP2xKvD delta 22 ccmdlZx<{1vG%qg~0}zzP2xR4L\n\n**Where do I download these models??**\n\nThere is a good list of models and download links here: \n\nAlso the latest v1.1 spatial upscaler can be found here: \n\nThe tiny vae (taeltx2_3.safetensors) is found here:\n\n\n**I'm getting errors when running this workflow!**\n\nDouble check that you have all of the correct models loaded. This is using the newest version of 2x upscaler (v1.1). You can change it to the older one under the Model Loader.\n\n**How can I support you?**\n\nHelp me get a job. I'm unemployed and literally broke. (seriously, i'll even scrub toilets for minimum wage)\n\nOr follow me on my socials :)\n\n\n\n\n\n\n" - ], - "color": "#322", - "bgcolor": "#533" - }, - { - "id": 84, - "type": "DualCLIPLoader", - "pos": [ - 1530, - 4290 - ], - "size": [ - 270, - 130 - ], - "flags": {}, - "order": 9, - "mode": 0, - "inputs": [], - "outputs": [ - { - "name": "CLIP", - "type": "CLIP", - "links": [ - 211 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.19.3", - "Node name for S&R": "DualCLIPLoader" - }, - "widgets_values": [ - "gemma_3_12B_it_fp4_mixed.safetensors", - "ltx-2.3_text_projection_bf16.safetensors", - "ltxv", - "default" - ], - "color": "#222", - "bgcolor": "#000" - } - ], - "links": [ - [ - 6, - 5, - 0, - 8, - 0, - "CONDITIONING" - ], - [ - 7, - 5, - 1, - 8, - 1, - "CONDITIONING" - ], - [ - 10, - 8, - 0, - 9, - 1, - "CONDITIONING" - ], - [ - 11, - 8, - 1, - 9, - 2, - "CONDITIONING" - ], - [ - 12, - 9, - 0, - 10, - 1, - "GUIDER" - ], - [ - 14, - 7, - 0, - 10, - 4, - "LATENT" - ], - [ - 16, - 3, - 0, - 8, - 2, - "VAE" - ], - [ - 24, - 15, - 0, - 17, - 0, - "IMAGE" - ], - [ - 25, - 16, - 0, - 17, - 1, - "AUDIO" - ], - [ - 50, - 26, - 0, - 5, - 1, - "CONDITIONING" - ], - [ - 51, - 28, - 0, - 10, - 0, - "NOISE" - ], - [ - 52, - 11, - 0, - 10, - 3, - "SIGMAS" - ], - [ - 53, - 29, - 0, - 10, - 2, - "SAMPLER" - ], - [ - 54, - 8, - 0, - 14, - 0, - "CONDITIONING" - ], - [ - 55, - 8, - 1, - 14, - 1, - "CONDITIONING" - ], - [ - 56, - 17, - 0, - 30, - 0, - "VIDEO" - ], - [ - 58, - 4, - 0, - 16, - 1, - "VAE" - ], - [ - 59, - 3, - 0, - 15, - 1, - "VAE" - ], - [ - 108, - 8, - 2, - 7, - 0, - "LATENT" - ], - [ - 113, - 4, - 0, - 46, - 2, - "VAE" - ], - [ - 114, - 46, - 0, - 9, - 0, - "MODEL" - ], - [ - 115, - 46, - 0, - 11, - 0, - "MODEL" - ], - [ - 116, - 46, - 1, - 5, - 0, - "CONDITIONING" - ], - [ - 117, - 46, - 1, - 26, - 0, - "CONDITIONING" - ], - [ - 118, - 46, - 2, - 8, - 3, - "LATENT" - ], - [ - 119, - 46, - 3, - 7, - 1, - "LATENT" - ], - [ - 120, - 46, - 5, - 5, - 2, - "FLOAT" - ], - [ - 121, - 46, - 5, - 17, - 2, - "FLOAT" - ], - [ - 122, - 46, - 4, - 8, - 4, - "GUIDE_DATA" - ], - [ - 124, - 49, - 0, - 47, - 1, - "GUIDER" - ], - [ - 125, - 53, - 0, - 47, - 2, - "SAMPLER" - ], - [ - 126, - 54, - 0, - 47, - 3, - "SIGMAS" - ], - [ - 127, - 50, - 0, - 47, - 4, - "LATENT" - ], - [ - 135, - 55, - 2, - 52, - 0, - "LATENT" - ], - [ - 136, - 48, - 0, - 14, - 2, - "LATENT" - ], - [ - 142, - 13, - 0, - 55, - 2, - "LATENT" - ], - [ - 143, - 13, - 1, - 50, - 1, - "LATENT" - ], - [ - 144, - 57, - 0, - 52, - 1, - "LATENT_UPSCALE_MODEL" - ], - [ - 145, - 3, - 0, - 52, - 2, - "VAE" - ], - [ - 146, - 55, - 0, - 58, - 0, - "CONDITIONING" - ], - [ - 147, - 55, - 1, - 58, - 1, - "CONDITIONING" - ], - [ - 149, - 3, - 0, - 58, - 2, - "VAE" - ], - [ - 150, - 52, - 0, - 58, - 3, - "LATENT" - ], - [ - 151, - 46, - 4, - 58, - 4, - "GUIDE_DATA" - ], - [ - 152, - 58, - 0, - 49, - 1, - "CONDITIONING" - ], - [ - 153, - 58, - 1, - 49, - 2, - "CONDITIONING" - ], - [ - 154, - 58, - 2, - 50, - 0, - "LATENT" - ], - [ - 156, - 10, - 0, - 13, - 0, - "LATENT" - ], - [ - 157, - 28, - 0, - 47, - 0, - "NOISE" - ], - [ - 158, - 46, - 0, - 49, - 0, - "MODEL" - ], - [ - 159, - 46, - 0, - 54, - 0, - "MODEL" - ], - [ - 180, - 47, - 0, - 48, - 0, - "LATENT" - ], - [ - 188, - 8, - 0, - 55, - 0, - "CONDITIONING" - ], - [ - 189, - 8, - 1, - 55, - 1, - "CONDITIONING" - ], - [ - 196, - 78, - 0, - 79, - 2, - "VAE" - ], - [ - 197, - 79, - 0, - 80, - 0, - "MODEL" - ], - [ - 199, - 80, - 0, - 46, - 0, - "MODEL" - ], - [ - 202, - 48, - 1, - 16, - 0, - "LATENT" - ], - [ - 208, - 14, - 2, - 15, - 0, - "LATENT" - ], - [ - 210, - 77, - 0, - 79, - 0, - "MODEL" - ], - [ - 211, - 84, - 0, - 46, - 1, - "CLIP" - ] - ], - "groups": [ - { - "id": 3, - "title": "Models", - "bounding": [ - 1230, - 3800, - 590, - 943.6 - ], - "color": "#3f789e", - "font_size": 24, - "flags": {} - }, - { - "id": 4, - "title": "Stage #1", - "bounding": [ - 3020, - 4320, - 1380, - 423.6 - ], - "color": "#3f789e", - "font_size": 24, - "flags": {} - }, - { - "id": 5, - "title": "Stage #2 Upscale", - "bounding": [ - 3020, - 3860, - 1380, - 430 - ], - "color": "#3f789e", - "font_size": 24, - "flags": {} - }, - { - "id": 6, - "title": "Process Video", - "bounding": [ - 4430, - 4180, - 930, - 563.6 - ], - "color": "#3f789e", - "font_size": 24, - "flags": {} - } - ], - "config": {}, - "extra": { - "ds": { - "scale": 0.5730855330117197, - "offset": [ - -201.37060625041624, - -3463.614317275976 - ] - }, - "frontendVersion": "1.42.14", - "VHS_latentpreview": false, - "VHS_latentpreviewrate": 0, - "VHS_MetadataImage": true, - "VHS_KeepIntermediate": true - }, - "version": 0.4 +{ + "id": "64b81755-dd7d-4e01-9e14-9b2ad6ab4341", + "revision": 0, + "last_node_id": 92, + "last_link_id": 211, + "nodes": [ + { + "id": 80, + "type": "LoraLoaderModelOnly", + "pos": [ + 1530, + 4160 + ], + "size": [ + 280, + 90 + ], + "flags": {}, + "order": 11, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 197 + } + ], + "outputs": [ + { + "name": "MODEL", + "type": "MODEL", + "links": [ + 199 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.75", + "Node name for S&R": "LoraLoaderModelOnly", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65, + "models": [ + { + "name": "ltx-2.3-22b-distilled-lora-384.safetensors", + "url": "https://huggingface.co/Lightricks/LTX-2.3/resolve/main/ltx-2.3-22b-distilled-lora-384.safetensors", + "directory": "loras" + } + ] + }, + "widgets_values": [ + "ltx2\\ltx-2.3-22b-distilled-lora-dynamic_fro09_avg_rank_105_bf16.safetensors", + 0.5 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 4, + "type": "VAELoaderKJ", + "pos": [ + 1530, + 4470 + ], + "size": [ + 260, + 110 + ], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "VAE", + "type": "VAE", + "links": [ + 58, + 113 + ] + } + ], + "title": "Audio VAELoader", + "properties": { + "cnr_id": "comfyui-kjnodes", + "ver": "487c7d86a0230aae3d8c0a37d517159b73834f85", + "Node name for S&R": "VAELoaderKJ" + }, + "widgets_values": [ + "LTX23_audio_vae_bf16.safetensors", + "main_device", + "bf16" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 3, + "type": "VAELoaderKJ", + "pos": [ + 1530, + 4620 + ], + "size": [ + 260, + 110 + ], + "flags": {}, + "order": 1, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "VAE", + "type": "VAE", + "links": [ + 16, + 59, + 145, + 149 + ] + } + ], + "title": "Video VAELoader", + "properties": { + "cnr_id": "comfyui-kjnodes", + "ver": "1.2.5", + "Node name for S&R": "VAELoaderKJ" + }, + "widgets_values": [ + "LTX23_video_vae_bf16.safetensors", + "main_device", + "bf16" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 13, + "type": "LTXVSeparateAVLatent", + "pos": [ + 4190, + 4390 + ], + "size": [ + 200, + 50 + ], + "flags": {}, + "order": 21, + "mode": 0, + "inputs": [ + { + "name": "av_latent", + "type": "LATENT", + "link": 156 + } + ], + "outputs": [ + { + "name": "video_latent", + "type": "LATENT", + "links": [ + 142 + ] + }, + { + "name": "audio_latent", + "type": "LATENT", + "links": [ + 143 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.16.0", + "Node name for S&R": "LTXVSeparateAVLatent" + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 54, + "type": "BasicScheduler", + "pos": [ + 3530, + 4170 + ], + "size": [ + 210, + 110 + ], + "flags": {}, + "order": 14, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 159 + } + ], + "outputs": [ + { + "name": "SIGMAS", + "type": "SIGMAS", + "links": [ + 126 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.16.4", + "Node name for S&R": "BasicScheduler" + }, + "widgets_values": [ + "linear_quadratic", + 4, + 0.42 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 53, + "type": "KSamplerSelect", + "pos": [ + 3530, + 4070 + ], + "size": [ + 210, + 60 + ], + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "SAMPLER", + "type": "SAMPLER", + "links": [ + 125 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.56", + "Node name for S&R": "KSamplerSelect", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [ + "euler" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 47, + "type": "SamplerCustomAdvanced", + "pos": [ + 3750, + 3930 + ], + "size": [ + 230, + 220 + ], + "flags": {}, + "order": 27, + "mode": 0, + "inputs": [ + { + "name": "noise", + "type": "NOISE", + "link": 157 + }, + { + "name": "guider", + "type": "GUIDER", + "link": 124 + }, + { + "name": "sampler", + "type": "SAMPLER", + "link": 125 + }, + { + "name": "sigmas", + "type": "SIGMAS", + "link": 126 + }, + { + "name": "latent_image", + "type": "LATENT", + "link": 127 + } + ], + "outputs": [ + { + "name": "output", + "type": "LATENT", + "links": [ + 180 + ] + }, + { + "name": "denoised_output", + "type": "LATENT", + "links": [] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.60", + "Node name for S&R": "SamplerCustomAdvanced", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 52, + "type": "LTXVLatentUpsampler", + "pos": [ + 3040, + 4160 + ], + "size": [ + 250, + 70 + ], + "flags": {}, + "order": 23, + "mode": 0, + "inputs": [ + { + "name": "samples", + "type": "LATENT", + "link": 135 + }, + { + "name": "upscale_model", + "type": "LATENT_UPSCALE_MODEL", + "link": 144 + }, + { + "name": "vae", + "type": "VAE", + "link": 145 + } + ], + "outputs": [ + { + "name": "LATENT", + "type": "LATENT", + "links": [ + 150 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.14.1", + "Node name for S&R": "LTXVLatentUpsampler", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 50, + "type": "LTXVConcatAVLatent", + "pos": [ + 3300, + 4170 + ], + "size": [ + 210, + 60 + ], + "flags": {}, + "order": 26, + "mode": 0, + "inputs": [ + { + "name": "video_latent", + "type": "LATENT", + "link": 154 + }, + { + "name": "audio_latent", + "type": "LATENT", + "link": 143 + } + ], + "outputs": [ + { + "name": "latent", + "type": "LATENT", + "links": [ + 127 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.7.0", + "Node name for S&R": "LTXVConcatAVLatent", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 79, + "type": "LTX2SamplingPreviewOverride", + "pos": [ + 1530, + 4020 + ], + "size": [ + 270, + 100 + ], + "flags": {}, + "order": 10, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 210 + }, + { + "name": "latent_upscale_model", + "shape": 7, + "type": "LATENT_UPSCALE_MODEL", + "link": null + }, + { + "name": "vae", + "shape": 7, + "type": "VAE", + "link": 196 + } + ], + "outputs": [ + { + "name": "MODEL", + "type": "MODEL", + "links": [ + 197 + ] + } + ], + "properties": { + "cnr_id": "comfyui-kjnodes", + "ver": "1.3.4", + "Node name for S&R": "LTX2SamplingPreviewOverride" + }, + "widgets_values": [ + 24 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 78, + "type": "VAELoaderKJ", + "pos": [ + 1530, + 3870 + ], + "size": [ + 270, + 110 + ], + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "VAE", + "type": "VAE", + "links": [ + 196 + ] + } + ], + "title": "Tiny VAELoader KJ", + "properties": { + "cnr_id": "comfyui-kjnodes", + "ver": "1.2.5", + "Node name for S&R": "VAELoaderKJ" + }, + "widgets_values": [ + "taeltx2_3.safetensors", + "main_device", + "bf16" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 10, + "type": "SamplerCustomAdvanced", + "pos": [ + 3960, + 4390 + ], + "size": [ + 220, + 210 + ], + "flags": {}, + "order": 20, + "mode": 0, + "inputs": [ + { + "name": "noise", + "type": "NOISE", + "link": 51 + }, + { + "name": "guider", + "type": "GUIDER", + "link": 12 + }, + { + "name": "sampler", + "type": "SAMPLER", + "link": 53 + }, + { + "name": "sigmas", + "type": "SIGMAS", + "link": 52 + }, + { + "name": "latent_image", + "type": "LATENT", + "link": 14 + } + ], + "outputs": [ + { + "name": "output", + "type": "LATENT", + "links": [ + 156 + ] + }, + { + "name": "denoised_output", + "type": "LATENT", + "links": [] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.60", + "Node name for S&R": "SamplerCustomAdvanced", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 28, + "type": "RandomNoise", + "pos": [ + 3740, + 4390 + ], + "size": [ + 210, + 90 + ], + "flags": {}, + "order": 4, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "NOISE", + "type": "NOISE", + "links": [ + 51, + 157 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.19.3", + "Node name for S&R": "RandomNoise" + }, + "widgets_values": [ + 12, + "fixed" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 29, + "type": "KSamplerSelect", + "pos": [ + 3740, + 4520 + ], + "size": [ + 210, + 60 + ], + "flags": {}, + "order": 5, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "SAMPLER", + "type": "SAMPLER", + "links": [ + 53 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.56", + "Node name for S&R": "KSamplerSelect", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [ + "euler" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 11, + "type": "BasicScheduler", + "pos": [ + 3740, + 4620 + ], + "size": [ + 210, + 110 + ], + "flags": {}, + "order": 13, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 115 + } + ], + "outputs": [ + { + "name": "SIGMAS", + "type": "SIGMAS", + "links": [ + 52 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.16.4", + "Node name for S&R": "BasicScheduler" + }, + "widgets_values": [ + "linear_quadratic", + 8, + 1 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 9, + "type": "CFGGuider", + "pos": [ + 3520, + 4390 + ], + "size": [ + 210, + 100 + ], + "flags": {}, + "order": 18, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 114 + }, + { + "name": "positive", + "type": "CONDITIONING", + "link": 10 + }, + { + "name": "negative", + "type": "CONDITIONING", + "link": 11 + } + ], + "outputs": [ + { + "name": "GUIDER", + "type": "GUIDER", + "links": [ + 12 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.64", + "Node name for S&R": "CFGGuider", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [ + 1 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 7, + "type": "LTXVConcatAVLatent", + "pos": [ + 3520, + 4530 + ], + "size": [ + 210, + 50 + ], + "flags": {}, + "order": 19, + "mode": 0, + "inputs": [ + { + "name": "video_latent", + "type": "LATENT", + "link": 108 + }, + { + "name": "audio_latent", + "type": "LATENT", + "link": 119 + } + ], + "outputs": [ + { + "name": "latent", + "type": "LATENT", + "links": [ + 14 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.16.0", + "Node name for S&R": "LTXVConcatAVLatent" + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 48, + "type": "LTXVSeparateAVLatent", + "pos": [ + 3990, + 3930 + ], + "size": [ + 200, + 50 + ], + "flags": {}, + "order": 28, + "mode": 0, + "inputs": [ + { + "name": "av_latent", + "type": "LATENT", + "link": 180 + } + ], + "outputs": [ + { + "name": "video_latent", + "type": "LATENT", + "links": [ + 136 + ] + }, + { + "name": "audio_latent", + "type": "LATENT", + "links": [ + 202 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.5.1", + "Node name for S&R": "LTXVSeparateAVLatent", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 30, + "type": "SaveVideo", + "pos": [ + 4670, + 4260 + ], + "size": [ + 680, + 470 + ], + "flags": {}, + "order": 33, + "mode": 0, + "inputs": [ + { + "name": "video", + "type": "VIDEO", + "link": 56 + } + ], + "outputs": [], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.19.3", + "Node name for S&R": "SaveVideo" + }, + "widgets_values": [ + "video/LTX_Director", + "auto", + "auto" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 57, + "type": "LatentUpscaleModelLoader", + "pos": [ + 3040, + 4050 + ], + "size": [ + 250, + 70 + ], + "flags": {}, + "order": 6, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "LATENT_UPSCALE_MODEL", + "type": "LATENT_UPSCALE_MODEL", + "links": [ + 144 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.16.4", + "Node name for S&R": "LatentUpscaleModelLoader" + }, + "widgets_values": [ + "ltx-2.3-spatial-upscaler-x2-1.1.safetensors" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 8, + "type": "LTXDirectorGuide", + "pos": [ + 3290, + 4400 + ], + "size": [ + 210, + 180 + ], + "flags": {}, + "order": 17, + "mode": 0, + "inputs": [ + { + "name": "positive", + "type": "CONDITIONING", + "link": 6 + }, + { + "name": "negative", + "type": "CONDITIONING", + "link": 7 + }, + { + "name": "vae", + "type": "VAE", + "link": 16 + }, + { + "name": "latent", + "type": "LATENT", + "link": 118 + }, + { + "name": "guide_data", + "type": "GUIDE_DATA", + "link": 122 + } + ], + "outputs": [ + { + "name": "positive", + "type": "CONDITIONING", + "links": [ + 10, + 54, + 188 + ] + }, + { + "name": "negative", + "type": "CONDITIONING", + "links": [ + 11, + 55, + 189 + ] + }, + { + "name": "latent", + "type": "LATENT", + "links": [ + 108 + ] + } + ], + "properties": { + "cnr_id": "whatdreamscost-comfyui", + "ver": "5cae3c00186ff2eb79771070f05a2c4e02f329bb", + "Node name for S&R": "LTXDirectorGuide", + "aux_id": "kijai/ComfyUI-PromptRelay" + }, + "widgets_values": [ + 0.5, + "bicubic" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 49, + "type": "CFGGuider", + "pos": [ + 3530, + 3930 + ], + "size": [ + 210, + 100 + ], + "flags": {}, + "order": 25, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 158 + }, + { + "name": "positive", + "type": "CONDITIONING", + "link": 152 + }, + { + "name": "negative", + "type": "CONDITIONING", + "link": 153 + } + ], + "outputs": [ + { + "name": "GUIDER", + "type": "GUIDER", + "links": [ + 124 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.64", + "Node name for S&R": "CFGGuider", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [ + 1 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 55, + "type": "LTXVCropGuides", + "pos": [ + 3040, + 3940 + ], + "size": [ + 250, + 70 + ], + "flags": {}, + "order": 22, + "mode": 0, + "inputs": [ + { + "name": "positive", + "type": "CONDITIONING", + "link": 188 + }, + { + "name": "negative", + "type": "CONDITIONING", + "link": 189 + }, + { + "name": "latent", + "type": "LATENT", + "link": 142 + } + ], + "outputs": [ + { + "name": "positive", + "type": "CONDITIONING", + "links": [ + 146 + ] + }, + { + "name": "negative", + "type": "CONDITIONING", + "links": [ + 147 + ] + }, + { + "name": "latent", + "type": "LATENT", + "links": [ + 135 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.16.4", + "Node name for S&R": "LTXVCropGuides" + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 58, + "type": "LTXDirectorGuide", + "pos": [ + 3300, + 3940 + ], + "size": [ + 210, + 190 + ], + "flags": {}, + "order": 24, + "mode": 0, + "inputs": [ + { + "name": "positive", + "type": "CONDITIONING", + "link": 146 + }, + { + "name": "negative", + "type": "CONDITIONING", + "link": 147 + }, + { + "name": "vae", + "type": "VAE", + "link": 149 + }, + { + "name": "latent", + "type": "LATENT", + "link": 150 + }, + { + "name": "guide_data", + "type": "GUIDE_DATA", + "link": 151 + } + ], + "outputs": [ + { + "name": "positive", + "type": "CONDITIONING", + "links": [ + 152 + ] + }, + { + "name": "negative", + "type": "CONDITIONING", + "links": [ + 153 + ] + }, + { + "name": "latent", + "type": "LATENT", + "links": [ + 154 + ] + } + ], + "properties": { + "cnr_id": "whatdreamscost-comfyui", + "ver": "5cae3c00186ff2eb79771070f05a2c4e02f329bb", + "Node name for S&R": "LTXDirectorGuide", + "aux_id": "kijai/ComfyUI-PromptRelay" + }, + "widgets_values": [ + 1, + "bicubic" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 14, + "type": "LTXVCropGuides", + "pos": [ + 4200, + 3930 + ], + "size": [ + 180, + 70 + ], + "flags": {}, + "order": 29, + "mode": 0, + "inputs": [ + { + "name": "positive", + "type": "CONDITIONING", + "link": 54 + }, + { + "name": "negative", + "type": "CONDITIONING", + "link": 55 + }, + { + "name": "latent", + "type": "LATENT", + "link": 136 + } + ], + "outputs": [ + { + "name": "positive", + "type": "CONDITIONING", + "links": null + }, + { + "name": "negative", + "type": "CONDITIONING", + "links": null + }, + { + "name": "latent", + "type": "LATENT", + "links": [ + 208 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.19.3", + "Node name for S&R": "LTXVCropGuides" + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 16, + "type": "LTXVAudioVAEDecode", + "pos": [ + 4440, + 4260 + ], + "size": [ + 210, + 50 + ], + "flags": {}, + "order": 30, + "mode": 0, + "inputs": [ + { + "name": "samples", + "type": "LATENT", + "link": 202 + }, + { + "label": "Audio VAE", + "name": "audio_vae", + "type": "VAE", + "link": 58 + } + ], + "outputs": [ + { + "name": "Audio", + "type": "AUDIO", + "links": [ + 25 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.16.0", + "Node name for S&R": "LTXVAudioVAEDecode" + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 46, + "type": "LTXDirector", + "pos": [ + 1860, + 3970 + ], + "size": [ + 1120, + 777 + ], + "flags": {}, + "order": 12, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 199 + }, + { + "name": "clip", + "type": "CLIP", + "link": 211 + }, + { + "name": "audio_vae", + "shape": 7, + "type": "VAE", + "link": 113 + }, + { + "name": "optional_latent", + "shape": 7, + "type": "LATENT", + "link": null + } + ], + "outputs": [ + { + "name": "model", + "type": "MODEL", + "links": [ + 114, + 115, + 158, + 159 + ] + }, + { + "name": "positive", + "type": "CONDITIONING", + "links": [ + 116, + 117 + ] + }, + { + "name": "video_latent", + "type": "LATENT", + "links": [ + 118 + ] + }, + { + "name": "audio_latent", + "type": "LATENT", + "links": [ + 119 + ] + }, + { + "name": "guide_data", + "type": "GUIDE_DATA", + "links": [ + 122, + 151 + ] + }, + { + "name": "frame_rate", + "type": "FLOAT", + "links": [ + 120, + 121 + ] + }, + { + "name": "combined_audio", + "type": "AUDIO", + "links": [] + } + ], + "properties": { + "cnr_id": "whatdreamscost-comfyui", + "ver": "5cae3c00186ff2eb79771070f05a2c4e02f329bb", + "Node name for S&R": "LTXDirector", + "aux_id": "kijai/ComfyUI-PromptRelay" + }, + "widgets_values": [ + "", + 120, + 5, + "{\"segments\":[],\"audioSegments\":[]}", + "", + "", + 0.001, + "", + false, + 24, + "seconds", + 0, + 0, + "maintain aspect ratio", + 32, + 18, + "" + ] + }, + { + "id": 77, + "type": "CheckpointLoaderSimple", + "pos": [ + 1240, + 4020 + ], + "size": [ + 270, + 100 + ], + "flags": {}, + "order": 7, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "MODEL", + "type": "MODEL", + "links": [ + 210 + ] + }, + { + "name": "CLIP", + "type": "CLIP", + "links": null + }, + { + "name": "VAE", + "type": "VAE", + "links": null + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.17.2", + "Node name for S&R": "CheckpointLoaderSimple" + }, + "widgets_values": [ + "ltx-2.3-22b-dev-fp8.safetensors" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 15, + "type": "VAEDecode", + "pos": [ + 4440, + 4350 + ], + "size": [ + 210, + 50 + ], + "flags": {}, + "order": 31, + "mode": 0, + "inputs": [ + { + "name": "samples", + "type": "LATENT", + "link": 208 + }, + { + "name": "vae", + "type": "VAE", + "link": 59 + } + ], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 24 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.9.1", + "Node name for S&R": "VAEDecode" + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 17, + "type": "CreateVideo", + "pos": [ + 4440, + 4440 + ], + "size": [ + 210, + 80 + ], + "flags": {}, + "order": 32, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 24 + }, + { + "name": "audio", + "shape": 7, + "type": "AUDIO", + "link": 25 + }, + { + "name": "fps", + "type": "FLOAT", + "widget": { + "name": "fps" + }, + "link": 121 + } + ], + "outputs": [ + { + "name": "VIDEO", + "type": "VIDEO", + "links": [ + 56 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.19.3", + "Node name for S&R": "CreateVideo" + }, + "widgets_values": [ + 24 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 5, + "type": "LTXVConditioning", + "pos": [ + 3040, + 4400 + ], + "size": [ + 240, + 80 + ], + "flags": {}, + "order": 16, + "mode": 0, + "inputs": [ + { + "name": "positive", + "type": "CONDITIONING", + "link": 116 + }, + { + "name": "negative", + "type": "CONDITIONING", + "link": 50 + }, + { + "name": "frame_rate", + "type": "FLOAT", + "widget": { + "name": "frame_rate" + }, + "link": 120 + } + ], + "outputs": [ + { + "name": "positive", + "type": "CONDITIONING", + "links": [ + 6 + ] + }, + { + "name": "negative", + "type": "CONDITIONING", + "links": [ + 7 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.56", + "Node name for S&R": "LTXVConditioning", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [ + 24 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 26, + "type": "ConditioningZeroOut", + "pos": [ + 3040, + 4530 + ], + "size": [ + 240, + 30 + ], + "flags": {}, + "order": 15, + "mode": 0, + "inputs": [ + { + "name": "conditioning", + "type": "CONDITIONING", + "link": 117 + } + ], + "outputs": [ + { + "name": "CONDITIONING", + "type": "CONDITIONING", + "links": [ + 50 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.19.3", + "Node name for S&R": "ConditioningZeroOut" + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 92, + "type": "MarkdownNote", + "pos": [ + 1240, + 4170 + ], + "size": [ + 270, + 560 + ], + "flags": {}, + "order": 8, + "mode": 0, + "inputs": [], + "outputs": [], + "title": "FAQ", + "properties": {}, + "widgets_values": [ + "# FAQ\n\n**How do I use this??**\n\nI'll be posting tutorials on my YouTube channel soon!\n\n\n**Where do I download these models??**\n\nThere is a good list of models and download links here: \n\nAlso the latest v1.1 spatial upscaler can be found here: \n\nThe tiny vae (taeltx2_3.safetensors) is found here:\n\n\n**I'm getting errors when running this workflow!**\n\nDouble check that you have all of the correct models loaded. This is using the newest version of 2x upscaler (v1.1). You can change it to the older one under the Model Loader.\n\n**How can I support you?**\n\nHelp me get a job. I'm unemployed and literally broke. (seriously, i'll even scrub toilets for minimum wage)\n\nOr follow me on my socials :)\n\n\n\n\n\n\n" + ], + "color": "#322", + "bgcolor": "#533" + }, + { + "id": 84, + "type": "DualCLIPLoader", + "pos": [ + 1530, + 4290 + ], + "size": [ + 270, + 130 + ], + "flags": {}, + "order": 9, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "CLIP", + "type": "CLIP", + "links": [ + 211 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.19.3", + "Node name for S&R": "DualCLIPLoader" + }, + "widgets_values": [ + "gemma_3_12B_it_fp4_mixed.safetensors", + "ltx-2.3_text_projection_bf16.safetensors", + "ltxv", + "default" + ], + "color": "#222", + "bgcolor": "#000" + } + ], + "links": [ + [ + 6, + 5, + 0, + 8, + 0, + "CONDITIONING" + ], + [ + 7, + 5, + 1, + 8, + 1, + "CONDITIONING" + ], + [ + 10, + 8, + 0, + 9, + 1, + "CONDITIONING" + ], + [ + 11, + 8, + 1, + 9, + 2, + "CONDITIONING" + ], + [ + 12, + 9, + 0, + 10, + 1, + "GUIDER" + ], + [ + 14, + 7, + 0, + 10, + 4, + "LATENT" + ], + [ + 16, + 3, + 0, + 8, + 2, + "VAE" + ], + [ + 24, + 15, + 0, + 17, + 0, + "IMAGE" + ], + [ + 25, + 16, + 0, + 17, + 1, + "AUDIO" + ], + [ + 50, + 26, + 0, + 5, + 1, + "CONDITIONING" + ], + [ + 51, + 28, + 0, + 10, + 0, + "NOISE" + ], + [ + 52, + 11, + 0, + 10, + 3, + "SIGMAS" + ], + [ + 53, + 29, + 0, + 10, + 2, + "SAMPLER" + ], + [ + 54, + 8, + 0, + 14, + 0, + "CONDITIONING" + ], + [ + 55, + 8, + 1, + 14, + 1, + "CONDITIONING" + ], + [ + 56, + 17, + 0, + 30, + 0, + "VIDEO" + ], + [ + 58, + 4, + 0, + 16, + 1, + "VAE" + ], + [ + 59, + 3, + 0, + 15, + 1, + "VAE" + ], + [ + 108, + 8, + 2, + 7, + 0, + "LATENT" + ], + [ + 113, + 4, + 0, + 46, + 2, + "VAE" + ], + [ + 114, + 46, + 0, + 9, + 0, + "MODEL" + ], + [ + 115, + 46, + 0, + 11, + 0, + "MODEL" + ], + [ + 116, + 46, + 1, + 5, + 0, + "CONDITIONING" + ], + [ + 117, + 46, + 1, + 26, + 0, + "CONDITIONING" + ], + [ + 118, + 46, + 2, + 8, + 3, + "LATENT" + ], + [ + 119, + 46, + 3, + 7, + 1, + "LATENT" + ], + [ + 120, + 46, + 5, + 5, + 2, + "FLOAT" + ], + [ + 121, + 46, + 5, + 17, + 2, + "FLOAT" + ], + [ + 122, + 46, + 4, + 8, + 4, + "GUIDE_DATA" + ], + [ + 124, + 49, + 0, + 47, + 1, + "GUIDER" + ], + [ + 125, + 53, + 0, + 47, + 2, + "SAMPLER" + ], + [ + 126, + 54, + 0, + 47, + 3, + "SIGMAS" + ], + [ + 127, + 50, + 0, + 47, + 4, + "LATENT" + ], + [ + 135, + 55, + 2, + 52, + 0, + "LATENT" + ], + [ + 136, + 48, + 0, + 14, + 2, + "LATENT" + ], + [ + 142, + 13, + 0, + 55, + 2, + "LATENT" + ], + [ + 143, + 13, + 1, + 50, + 1, + "LATENT" + ], + [ + 144, + 57, + 0, + 52, + 1, + "LATENT_UPSCALE_MODEL" + ], + [ + 145, + 3, + 0, + 52, + 2, + "VAE" + ], + [ + 146, + 55, + 0, + 58, + 0, + "CONDITIONING" + ], + [ + 147, + 55, + 1, + 58, + 1, + "CONDITIONING" + ], + [ + 149, + 3, + 0, + 58, + 2, + "VAE" + ], + [ + 150, + 52, + 0, + 58, + 3, + "LATENT" + ], + [ + 151, + 46, + 4, + 58, + 4, + "GUIDE_DATA" + ], + [ + 152, + 58, + 0, + 49, + 1, + "CONDITIONING" + ], + [ + 153, + 58, + 1, + 49, + 2, + "CONDITIONING" + ], + [ + 154, + 58, + 2, + 50, + 0, + "LATENT" + ], + [ + 156, + 10, + 0, + 13, + 0, + "LATENT" + ], + [ + 157, + 28, + 0, + 47, + 0, + "NOISE" + ], + [ + 158, + 46, + 0, + 49, + 0, + "MODEL" + ], + [ + 159, + 46, + 0, + 54, + 0, + "MODEL" + ], + [ + 180, + 47, + 0, + 48, + 0, + "LATENT" + ], + [ + 188, + 8, + 0, + 55, + 0, + "CONDITIONING" + ], + [ + 189, + 8, + 1, + 55, + 1, + "CONDITIONING" + ], + [ + 196, + 78, + 0, + 79, + 2, + "VAE" + ], + [ + 197, + 79, + 0, + 80, + 0, + "MODEL" + ], + [ + 199, + 80, + 0, + 46, + 0, + "MODEL" + ], + [ + 202, + 48, + 1, + 16, + 0, + "LATENT" + ], + [ + 208, + 14, + 2, + 15, + 0, + "LATENT" + ], + [ + 210, + 77, + 0, + 79, + 0, + "MODEL" + ], + [ + 211, + 84, + 0, + 46, + 1, + "CLIP" + ] + ], + "groups": [ + { + "id": 3, + "title": "Models", + "bounding": [ + 1230, + 3800, + 590, + 943.6 + ], + "color": "#3f789e", + "font_size": 24, + "flags": {} + }, + { + "id": 4, + "title": "Stage #1", + "bounding": [ + 3020, + 4320, + 1380, + 423.6 + ], + "color": "#3f789e", + "font_size": 24, + "flags": {} + }, + { + "id": 5, + "title": "Stage #2 Upscale", + "bounding": [ + 3020, + 3860, + 1380, + 430 + ], + "color": "#3f789e", + "font_size": 24, + "flags": {} + }, + { + "id": 6, + "title": "Process Video", + "bounding": [ + 4430, + 4180, + 930, + 563.6 + ], + "color": "#3f789e", + "font_size": 24, + "flags": {} + } + ], + "config": {}, + "extra": { + "ds": { + "scale": 0.5730855330117197, + "offset": [ + -201.37060625041624, + -3463.614317275976 + ] + }, + "frontendVersion": "1.42.14", + "VHS_latentpreview": false, + "VHS_latentpreviewrate": 0, + "VHS_MetadataImage": true, + "VHS_KeepIntermediate": true + }, + "version": 0.4 } \ No newline at end of file diff --git a/example_workflows/LTX Director Example Workflow Subgraphs v2.json b/example_workflows/LTX Director Example Workflow Subgraphs v2.json index 42dfee8..30b5a1a 100644 --- a/example_workflows/LTX Director Example Workflow Subgraphs v2.json +++ b/example_workflows/LTX Director Example Workflow Subgraphs v2.json @@ -1,3664 +1,3664 @@ -{ - "id": "c92620ea-60b4-4f41-a78f-621383a62c2a", - "revision": 0, - "last_node_id": 96, - "last_link_id": 250, - "nodes": [ - { - "id": 46, - "type": "LTXDirector", - "pos": [ - 1860, - 3970 - ], - "size": [ - 1120, - 801 - ], - "flags": {}, - "order": 2, - "mode": 0, - "inputs": [ - { - "name": "model", - "type": "MODEL", - "link": 212 - }, - { - "name": "clip", - "type": "CLIP", - "link": 213 - }, - { - "name": "audio_vae", - "shape": 7, - "type": "VAE", - "link": 215 - }, - { - "name": "optional_latent", - "shape": 7, - "type": "LATENT", - "link": null - } - ], - "outputs": [ - { - "name": "model", - "type": "MODEL", - "links": [ - 220, - 239 - ] - }, - { - "name": "positive", - "type": "CONDITIONING", - "links": [ - 225 - ] - }, - { - "name": "video_latent", - "type": "LATENT", - "links": [ - 223 - ] - }, - { - "name": "audio_latent", - "type": "LATENT", - "links": [ - 221 - ] - }, - { - "name": "guide_data", - "type": "GUIDE_DATA", - "links": [ - 224, - 238 - ] - }, - { - "name": "frame_rate", - "type": "FLOAT", - "links": [ - 226, - 248 - ] - }, - { - "name": "combined_audio", - "type": "AUDIO", - "links": [] - } - ], - "properties": { - "cnr_id": "whatdreamscost-comfyui", - "ver": "5cae3c00186ff2eb79771070f05a2c4e02f329bb", - "Node name for S&R": "LTXDirector", - "aux_id": "kijai/ComfyUI-PromptRelay" - }, - "widgets_values": [ - "", - 120, - 5, - "{\"segments\":[],\"audioSegments\":[]}", - "", - "", - 0.001, - "", - false, - 24, - "seconds", - 0, - 0, - "maintain aspect ratio", - 32, - 18, - "" - ] - }, - { - "id": 30, - "type": "SaveVideo", - "pos": [ - 3230, - 3970 - ], - "size": [ - 690, - 530 - ], - "flags": {}, - "order": 6, - "mode": 0, - "inputs": [ - { - "name": "video", - "type": "VIDEO", - "link": 249 - } - ], - "outputs": [], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.19.3", - "Node name for S&R": "SaveVideo" - }, - "widgets_values": [ - "video/LTX_Director", - "auto", - "auto" - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 96, - "type": "739520a4-718f-490f-87d1-34ffbca98e3f", - "pos": [ - 3000, - 4620 - ], - "size": [ - 210, - 130 - ], - "flags": {}, - "order": 5, - "mode": 0, - "inputs": [ - { - "label": "audio_latent", - "name": "samples", - "type": "LATENT", - "link": 244 - }, - { - "label": "audio_vae", - "name": "audio_vae", - "type": "VAE", - "link": 245 - }, - { - "label": "video_latent", - "name": "samples_1", - "type": "LATENT", - "link": 246 - }, - { - "label": "video_vae", - "name": "vae", - "type": "VAE", - "link": 247 - }, - { - "name": "fps", - "type": "FLOAT", - "widget": { - "name": "fps" - }, - "link": 248 - } - ], - "outputs": [ - { - "name": "VIDEO", - "type": "VIDEO", - "links": [ - 249 - ] - } - ], - "properties": { - "proxyWidgets": [], - "cnr_id": "comfy-core", - "ver": "0.19.3" - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 93, - "type": "dc399e64-01cd-40b2-9337-972428cc66de", - "pos": [ - 1430, - 3970 - ], - "size": [ - 400, - 190 - ], - "flags": {}, - "order": 0, - "mode": 0, - "inputs": [], - "outputs": [ - { - "label": "model", - "name": "MODEL", - "type": "MODEL", - "links": [ - 212 - ] - }, - { - "label": "clip", - "name": "CLIP", - "type": "CLIP", - "links": [ - 213 - ] - }, - { - "label": "audio_vae", - "name": "VAE", - "type": "VAE", - "links": [ - 215, - 245 - ] - }, - { - "label": "video_vae", - "name": "VAE_1", - "type": "VAE", - "links": [ - 222, - 237, - 247 - ] - } - ], - "properties": { - "proxyWidgets": [ - [ - "77", - "ckpt_name" - ], - [ - "84", - "clip_name1" - ], - [ - "4", - "vae_name" - ], - [ - "3", - "vae_name" - ] - ], - "cnr_id": "comfy-core", - "ver": "0.19.3" - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 94, - "type": "4b017e47-bf16-441b-ad03-edce978b3be3", - "pos": [ - 3000, - 3970 - ], - "size": [ - 210, - 260 - ], - "flags": {}, - "order": 3, - "mode": 0, - "inputs": [ - { - "name": "model", - "type": "MODEL", - "link": 220 - }, - { - "name": "audio_latent", - "type": "LATENT", - "link": 221 - }, - { - "name": "vae", - "type": "VAE", - "link": 222 - }, - { - "label": "video_latent", - "name": "latent", - "type": "LATENT", - "link": 223 - }, - { - "name": "guide_data", - "type": "GUIDE_DATA", - "link": 224 - }, - { - "name": "positive", - "type": "CONDITIONING", - "link": 225 - }, - { - "name": "frame_rate", - "type": "FLOAT", - "widget": { - "name": "frame_rate" - }, - "link": 226 - } - ], - "outputs": [ - { - "name": "positive", - "type": "CONDITIONING", - "links": [ - 234 - ] - }, - { - "name": "negative", - "type": "CONDITIONING", - "links": [ - 235 - ] - }, - { - "name": "NOISE", - "type": "NOISE", - "links": [ - 240 - ] - }, - { - "name": "video_latent", - "type": "LATENT", - "links": [ - 236 - ] - }, - { - "name": "audio_latent", - "type": "LATENT", - "links": [ - 241 - ] - } - ], - "properties": { - "proxyWidgets": [ - [ - "-1", - "noise_seed" - ] - ], - "cnr_id": "comfy-core", - "ver": "0.19.3" - }, - "widgets_values": [ - 12 - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 95, - "type": "7cf6430b-4784-4af1-8cde-9268e9465c62", - "pos": [ - 3000, - 4290 - ], - "size": [ - 210, - 270 - ], - "flags": {}, - "order": 4, - "mode": 0, - "inputs": [ - { - "name": "positive", - "type": "CONDITIONING", - "link": 234 - }, - { - "name": "negative", - "type": "CONDITIONING", - "link": 235 - }, - { - "label": "video_latent", - "name": "latent", - "type": "LATENT", - "link": 236 - }, - { - "label": "video_vae", - "name": "vae", - "type": "VAE", - "link": 237 - }, - { - "name": "guide_data", - "type": "GUIDE_DATA", - "link": 238 - }, - { - "name": "model", - "type": "MODEL", - "link": 239 - }, - { - "name": "noise", - "type": "NOISE", - "link": 240 - }, - { - "name": "audio_latent", - "type": "LATENT", - "link": 241 - } - ], - "outputs": [ - { - "name": "audio_latent", - "type": "LATENT", - "links": [ - 244 - ] - }, - { - "name": "latent", - "type": "LATENT", - "links": [ - 246 - ] - } - ], - "properties": { - "proxyWidgets": [], - "cnr_id": "comfy-core", - "ver": "0.19.3" - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 92, - "type": "MarkdownNote", - "pos": [ - 1430, - 4220 - ], - "size": [ - 400, - 530 - ], - "flags": {}, - "order": 1, - "mode": 0, - "inputs": [], - "outputs": [], - "title": "FAQ", - "properties": {}, - "widgets_values": [ - "# FAQ\n\n**How do I use this??**\n\nI'll be posting tutorials on my YouTube channel soon!\n\n\n**Where do I download these models??**\n\nThere is a good list of models and download links here: \n\nAlso the latest v1.1 spatial upscaler can be found here: \n\nThe tiny vae (taeltx2_3.safetensors) is found here:\n\n\n**I'm getting errors when running this workflow!**\n\nDouble check that you have all of the correct models loaded. This is using the newest version of 2x upscaler (v1.1). You can change it to the older one under the Model Loader.\n\n**How can I support you??**\n\nHelp me get a job. I'm unemployed and literally broke. (seriously, i'll even scrub toilets for minimum wage)\n\nOr follow me on my socials :)\n\n\n\n\n\n" - ], - "color": "#322", - "bgcolor": "#533" - } - ], - "links": [ - [ - 212, - 93, - 0, - 46, - 0, - "MODEL" - ], - [ - 213, - 93, - 1, - 46, - 1, - "CLIP" - ], - [ - 215, - 93, - 2, - 46, - 2, - "VAE" - ], - [ - 220, - 46, - 0, - 94, - 0, - "MODEL" - ], - [ - 221, - 46, - 3, - 94, - 1, - "LATENT" - ], - [ - 222, - 93, - 3, - 94, - 2, - "VAE" - ], - [ - 223, - 46, - 2, - 94, - 3, - "LATENT" - ], - [ - 224, - 46, - 4, - 94, - 4, - "GUIDE_DATA" - ], - [ - 225, - 46, - 1, - 94, - 5, - "CONDITIONING" - ], - [ - 226, - 46, - 5, - 94, - 6, - "FLOAT" - ], - [ - 234, - 94, - 0, - 95, - 0, - "CONDITIONING" - ], - [ - 235, - 94, - 1, - 95, - 1, - "CONDITIONING" - ], - [ - 236, - 94, - 3, - 95, - 2, - "LATENT" - ], - [ - 237, - 93, - 3, - 95, - 3, - "VAE" - ], - [ - 238, - 46, - 4, - 95, - 4, - "GUIDE_DATA" - ], - [ - 239, - 46, - 0, - 95, - 5, - "MODEL" - ], - [ - 240, - 94, - 2, - 95, - 6, - "NOISE" - ], - [ - 241, - 94, - 4, - 95, - 7, - "LATENT" - ], - [ - 244, - 95, - 0, - 96, - 0, - "LATENT" - ], - [ - 245, - 93, - 2, - 96, - 1, - "VAE" - ], - [ - 246, - 95, - 1, - 96, - 2, - "LATENT" - ], - [ - 247, - 93, - 3, - 96, - 3, - "VAE" - ], - [ - 248, - 46, - 5, - 96, - 4, - "FLOAT" - ], - [ - 249, - 96, - 0, - 30, - 0, - "VIDEO" - ] - ], - "groups": [], - "definitions": { - "subgraphs": [ - { - "id": "dc399e64-01cd-40b2-9337-972428cc66de", - "version": 1, - "state": { - "lastGroupId": 6, - "lastNodeId": 96, - "lastLinkId": 250, - "lastRerouteId": 0 - }, - "revision": 0, - "config": {}, - "name": "Model Loader", - "inputNode": { - "id": -10, - "bounding": [ - 1060, - 4265, - 120, - 40 - ] - }, - "outputNode": { - "id": -20, - "bounding": [ - 1870, - 4225, - 120, - 120 - ] - }, - "inputs": [], - "outputs": [ - { - "id": "68f91346-0127-4451-a92f-5cfb56fc7f02", - "name": "MODEL", - "type": "MODEL", - "linkIds": [ - 199 - ], - "localized_name": "MODEL", - "label": "model", - "pos": [ - 1890, - 4245 - ] - }, - { - "id": "d50bc485-29c2-44a9-9653-fbb738989bdc", - "name": "CLIP", - "type": "CLIP", - "linkIds": [ - 211 - ], - "localized_name": "CLIP", - "label": "clip", - "pos": [ - 1890, - 4265 - ] - }, - { - "id": "cfbbcf84-8477-40b1-a7ba-318c8d3b03a0", - "name": "VAE", - "type": "VAE", - "linkIds": [ - 58, - 58 - ], - "localized_name": "VAE", - "label": "audio_vae", - "pos": [ - 1890, - 4285 - ] - }, - { - "id": "815c9c85-34bc-49fc-8161-aca22b170346", - "name": "VAE_1", - "type": "VAE", - "linkIds": [ - 16, - 16, - 16, - 16 - ], - "localized_name": "VAE_1", - "label": "video_vae", - "pos": [ - 1890, - 4305 - ] - } - ], - "widgets": [], - "nodes": [ - { - "id": 77, - "type": "CheckpointLoaderSimple", - "pos": [ - 1240, - 4020 - ], - "size": [ - 270, - 100 - ], - "flags": {}, - "order": 0, - "mode": 0, - "inputs": [ - { - "label": "model", - "localized_name": "ckpt_name", - "name": "ckpt_name", - "type": "COMBO", - "widget": { - "name": "ckpt_name" - }, - "link": null - } - ], - "outputs": [ - { - "localized_name": "MODEL", - "name": "MODEL", - "type": "MODEL", - "links": [ - 210 - ] - }, - { - "localized_name": "CLIP", - "name": "CLIP", - "type": "CLIP", - "links": null - }, - { - "localized_name": "VAE", - "name": "VAE", - "type": "VAE", - "links": null - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.17.2", - "Node name for S&R": "CheckpointLoaderSimple" - }, - "widgets_values": [ - "ltx-2.3-22b-dev-fp8.safetensors" - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 80, - "type": "LoraLoaderModelOnly", - "pos": [ - 1530, - 4160 - ], - "size": [ - 280, - 90 - ], - "flags": {}, - "order": 6, - "mode": 0, - "inputs": [ - { - "localized_name": "model", - "name": "model", - "type": "MODEL", - "link": 197 - } - ], - "outputs": [ - { - "localized_name": "MODEL", - "name": "MODEL", - "type": "MODEL", - "links": [ - 199 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.75", - "Node name for S&R": "LoraLoaderModelOnly", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65, - "models": [ - { - "name": "ltx-2.3-22b-distilled-lora-384.safetensors", - "url": "https://huggingface.co/Lightricks/LTX-2.3/resolve/main/ltx-2.3-22b-distilled-lora-384.safetensors", - "directory": "loras" - } - ] - }, - "widgets_values": [ - "ltx2\\ltx-2.3-22b-distilled-lora-dynamic_fro09_avg_rank_105_bf16.safetensors", - 0.5 - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 79, - "type": "LTX2SamplingPreviewOverride", - "pos": [ - 1530, - 4020 - ], - "size": [ - 270, - 100 - ], - "flags": {}, - "order": 5, - "mode": 0, - "inputs": [ - { - "localized_name": "model", - "name": "model", - "type": "MODEL", - "link": 210 - }, - { - "localized_name": "latent_upscale_model", - "name": "latent_upscale_model", - "shape": 7, - "type": "LATENT_UPSCALE_MODEL", - "link": null - }, - { - "localized_name": "vae", - "name": "vae", - "shape": 7, - "type": "VAE", - "link": 196 - } - ], - "outputs": [ - { - "localized_name": "MODEL", - "name": "MODEL", - "type": "MODEL", - "links": [ - 197 - ] - } - ], - "properties": { - "cnr_id": "comfyui-kjnodes", - "ver": "1.3.4", - "Node name for S&R": "LTX2SamplingPreviewOverride" - }, - "widgets_values": [ - 24 - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 78, - "type": "VAELoaderKJ", - "pos": [ - 1530, - 3870 - ], - "size": [ - 270, - 110 - ], - "flags": {}, - "order": 1, - "mode": 0, - "inputs": [], - "outputs": [ - { - "localized_name": "VAE", - "name": "VAE", - "type": "VAE", - "links": [ - 196 - ] - } - ], - "title": "Tiny VAELoader KJ", - "properties": { - "cnr_id": "comfyui-kjnodes", - "ver": "1.2.5", - "Node name for S&R": "VAELoaderKJ" - }, - "widgets_values": [ - "taeltx2_3.safetensors", - "main_device", - "bf16" - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 4, - "type": "VAELoaderKJ", - "pos": [ - 1530, - 4470 - ], - "size": [ - 260, - 110 - ], - "flags": {}, - "order": 2, - "mode": 0, - "inputs": [ - { - "label": "audio_vae", - "localized_name": "vae_name", - "name": "vae_name", - "type": "COMBO", - "widget": { - "name": "vae_name" - }, - "link": null - } - ], - "outputs": [ - { - "localized_name": "VAE", - "name": "VAE", - "type": "VAE", - "links": [ - 58 - ] - } - ], - "title": "Audio VAELoader", - "properties": { - "cnr_id": "comfyui-kjnodes", - "ver": "487c7d86a0230aae3d8c0a37d517159b73834f85", - "Node name for S&R": "VAELoaderKJ" - }, - "widgets_values": [ - "LTX23_audio_vae_bf16.safetensors", - "main_device", - "bf16" - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 3, - "type": "VAELoaderKJ", - "pos": [ - 1530, - 4620 - ], - "size": [ - 260, - 110 - ], - "flags": {}, - "order": 3, - "mode": 0, - "inputs": [ - { - "label": "video_vae", - "localized_name": "vae_name", - "name": "vae_name", - "type": "COMBO", - "widget": { - "name": "vae_name" - }, - "link": null - } - ], - "outputs": [ - { - "localized_name": "VAE", - "name": "VAE", - "type": "VAE", - "links": [ - 16 - ] - } - ], - "title": "Video VAELoader", - "properties": { - "cnr_id": "comfyui-kjnodes", - "ver": "1.2.5", - "Node name for S&R": "VAELoaderKJ" - }, - "widgets_values": [ - "LTX23_video_vae_bf16.safetensors", - "main_device", - "bf16" - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 84, - "type": "DualCLIPLoader", - "pos": [ - 1530, - 4290 - ], - "size": [ - 270, - 130 - ], - "flags": {}, - "order": 4, - "mode": 0, - "inputs": [ - { - "label": "text_encoder", - "localized_name": "clip_name1", - "name": "clip_name1", - "type": "COMBO", - "widget": { - "name": "clip_name1" - }, - "link": null - } - ], - "outputs": [ - { - "localized_name": "CLIP", - "name": "CLIP", - "type": "CLIP", - "links": [ - 211 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.19.3", - "Node name for S&R": "DualCLIPLoader" - }, - "widgets_values": [ - "gemma_3_12B_it_fp4_mixed.safetensors", - "ltx-2.3_text_projection_bf16.safetensors", - "ltxv", - "default" - ], - "color": "#222", - "bgcolor": "#000" - } - ], - "groups": [], - "links": [ - { - "id": 197, - "origin_id": 79, - "origin_slot": 0, - "target_id": 80, - "target_slot": 0, - "type": "MODEL" - }, - { - "id": 210, - "origin_id": 77, - "origin_slot": 0, - "target_id": 79, - "target_slot": 0, - "type": "MODEL" - }, - { - "id": 196, - "origin_id": 78, - "origin_slot": 0, - "target_id": 79, - "target_slot": 2, - "type": "VAE" - }, - { - "id": 199, - "origin_id": 80, - "origin_slot": 0, - "target_id": -20, - "target_slot": 0, - "type": "MODEL" - }, - { - "id": 211, - "origin_id": 84, - "origin_slot": 0, - "target_id": -20, - "target_slot": 1, - "type": "CLIP" - }, - { - "id": 58, - "origin_id": 4, - "origin_slot": 0, - "target_id": -20, - "target_slot": 2, - "type": "VAE" - }, - { - "id": 16, - "origin_id": 3, - "origin_slot": 0, - "target_id": -20, - "target_slot": 3, - "type": "VAE" - } - ], - "extra": { - "workflowRendererVersion": "LG" - } - }, - { - "id": "4b017e47-bf16-441b-ad03-edce978b3be3", - "version": 1, - "state": { - "lastGroupId": 6, - "lastNodeId": 96, - "lastLinkId": 250, - "lastRerouteId": 0 - }, - "revision": 0, - "config": {}, - "name": "Stage #1", - "inputNode": { - "id": -10, - "bounding": [ - 2860, - 4380, - 120, - 200 - ] - }, - "outputNode": { - "id": -20, - "bounding": [ - 4450, - 4360, - 120, - 140 - ] - }, - "inputs": [ - { - "id": "3c716dfb-4465-4519-8705-d5e7b88f1e35", - "name": "model", - "type": "MODEL", - "linkIds": [ - 115, - 114 - ], - "localized_name": "model", - "pos": [ - 2960, - 4400 - ] - }, - { - "id": "dcb44156-9e87-4ef1-a7fd-6b8cb4de0beb", - "name": "audio_latent", - "type": "LATENT", - "linkIds": [ - 119 - ], - "localized_name": "audio_latent", - "pos": [ - 2960, - 4420 - ] - }, - { - "id": "0c7b8f30-c29e-4147-96e9-db7f6ca77e06", - "name": "vae", - "type": "VAE", - "linkIds": [ - 216 - ], - "localized_name": "vae", - "pos": [ - 2960, - 4440 - ] - }, - { - "id": "4286d415-0cbb-4310-94d2-0efdccfb429f", - "name": "latent", - "type": "LATENT", - "linkIds": [ - 118 - ], - "localized_name": "latent", - "label": "video_latent", - "pos": [ - 2960, - 4460 - ] - }, - { - "id": "24eebedb-6830-4afb-9337-bd06b1300d44", - "name": "guide_data", - "type": "GUIDE_DATA", - "linkIds": [ - 122 - ], - "localized_name": "guide_data", - "pos": [ - 2960, - 4480 - ] - }, - { - "id": "1dc63a0e-c430-406b-86a8-6eb45cbc7885", - "name": "positive", - "type": "CONDITIONING", - "linkIds": [ - 116, - 117 - ], - "localized_name": "positive", - "pos": [ - 2960, - 4500 - ] - }, - { - "id": "02c2f048-017d-4fec-8440-19aa412bf8c0", - "name": "frame_rate", - "type": "FLOAT", - "linkIds": [ - 120 - ], - "localized_name": "frame_rate", - "pos": [ - 2960, - 4520 - ] - }, - { - "id": "f8636f26-27cb-4c55-972e-e5d1f6f39fff", - "name": "noise_seed", - "type": "INT", - "linkIds": [ - 250 - ], - "pos": [ - 2960, - 4540 - ] - } - ], - "outputs": [ - { - "id": "84f8d65a-1c3c-4fdc-8cab-7abaf67c1875", - "name": "positive", - "type": "CONDITIONING", - "linkIds": [ - 54, - 54 - ], - "localized_name": "positive", - "pos": [ - 4470, - 4380 - ] - }, - { - "id": "6275837a-94b0-4318-bbce-15969c26e42b", - "name": "negative", - "type": "CONDITIONING", - "linkIds": [ - 55, - 55 - ], - "localized_name": "negative", - "pos": [ - 4470, - 4400 - ] - }, - { - "id": "4c1b1c7e-3c92-4c13-8495-539df653574e", - "name": "NOISE", - "type": "NOISE", - "linkIds": [ - 157 - ], - "localized_name": "NOISE", - "pos": [ - 4470, - 4420 - ] - }, - { - "id": "f0f1c347-9ebf-496e-aea8-ca0a0ff7601e", - "name": "video_latent", - "type": "LATENT", - "linkIds": [ - 142 - ], - "localized_name": "video_latent", - "pos": [ - 4470, - 4440 - ] - }, - { - "id": "bf7e617a-cf3a-4ef6-a485-d3ab3e84742a", - "name": "audio_latent", - "type": "LATENT", - "linkIds": [ - 143 - ], - "localized_name": "audio_latent", - "pos": [ - 4470, - 4460 - ] - } - ], - "widgets": [], - "nodes": [ - { - "id": 11, - "type": "BasicScheduler", - "pos": [ - 3740, - 4620 - ], - "size": [ - 210, - 110 - ], - "flags": {}, - "order": 7, - "mode": 0, - "inputs": [ - { - "localized_name": "model", - "name": "model", - "type": "MODEL", - "link": 115 - } - ], - "outputs": [ - { - "localized_name": "SIGMAS", - "name": "SIGMAS", - "type": "SIGMAS", - "links": [ - 52 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.16.4", - "Node name for S&R": "BasicScheduler" - }, - "widgets_values": [ - "linear_quadratic", - 8, - 1 - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 10, - "type": "SamplerCustomAdvanced", - "pos": [ - 3960, - 4390 - ], - "size": [ - 220, - 210 - ], - "flags": {}, - "order": 6, - "mode": 0, - "inputs": [ - { - "localized_name": "noise", - "name": "noise", - "type": "NOISE", - "link": 51 - }, - { - "localized_name": "guider", - "name": "guider", - "type": "GUIDER", - "link": 12 - }, - { - "localized_name": "sampler", - "name": "sampler", - "type": "SAMPLER", - "link": 53 - }, - { - "localized_name": "sigmas", - "name": "sigmas", - "type": "SIGMAS", - "link": 52 - }, - { - "localized_name": "latent_image", - "name": "latent_image", - "type": "LATENT", - "link": 14 - } - ], - "outputs": [ - { - "localized_name": "output", - "name": "output", - "type": "LATENT", - "links": [ - 156 - ] - }, - { - "localized_name": "denoised_output", - "name": "denoised_output", - "type": "LATENT", - "links": [] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.60", - "Node name for S&R": "SamplerCustomAdvanced", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 29, - "type": "KSamplerSelect", - "pos": [ - 3740, - 4520 - ], - "size": [ - 210, - 60 - ], - "flags": {}, - "order": 0, - "mode": 0, - "inputs": [], - "outputs": [ - { - "localized_name": "SAMPLER", - "name": "SAMPLER", - "type": "SAMPLER", - "links": [ - 53 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.56", - "Node name for S&R": "KSamplerSelect", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [ - "euler" - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 7, - "type": "LTXVConcatAVLatent", - "pos": [ - 3520, - 4530 - ], - "size": [ - 210, - 50 - ], - "flags": {}, - "order": 3, - "mode": 0, - "inputs": [ - { - "localized_name": "video_latent", - "name": "video_latent", - "type": "LATENT", - "link": 108 - }, - { - "localized_name": "audio_latent", - "name": "audio_latent", - "type": "LATENT", - "link": 119 - } - ], - "outputs": [ - { - "localized_name": "latent", - "name": "latent", - "type": "LATENT", - "links": [ - 14 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.16.0", - "Node name for S&R": "LTXVConcatAVLatent" - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 8, - "type": "LTXDirectorGuide", - "pos": [ - 3290, - 4400 - ], - "size": [ - 210, - 180 - ], - "flags": {}, - "order": 4, - "mode": 0, - "inputs": [ - { - "localized_name": "positive", - "name": "positive", - "type": "CONDITIONING", - "link": 6 - }, - { - "localized_name": "negative", - "name": "negative", - "type": "CONDITIONING", - "link": 7 - }, - { - "localized_name": "vae", - "name": "vae", - "type": "VAE", - "link": 216 - }, - { - "localized_name": "latent", - "name": "latent", - "type": "LATENT", - "link": 118 - }, - { - "localized_name": "guide_data", - "name": "guide_data", - "type": "GUIDE_DATA", - "link": 122 - } - ], - "outputs": [ - { - "localized_name": "positive", - "name": "positive", - "type": "CONDITIONING", - "links": [ - 10, - 54 - ] - }, - { - "localized_name": "negative", - "name": "negative", - "type": "CONDITIONING", - "links": [ - 11, - 55 - ] - }, - { - "localized_name": "latent", - "name": "latent", - "type": "LATENT", - "links": [ - 108 - ] - } - ], - "properties": { - "cnr_id": "whatdreamscost-comfyui", - "ver": "5cae3c00186ff2eb79771070f05a2c4e02f329bb", - "Node name for S&R": "LTXDirectorGuide", - "aux_id": "kijai/ComfyUI-PromptRelay" - }, - "widgets_values": [ - 0.5, - "bicubic" - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 9, - "type": "CFGGuider", - "pos": [ - 3520, - 4390 - ], - "size": [ - 210, - 100 - ], - "flags": {}, - "order": 5, - "mode": 0, - "inputs": [ - { - "localized_name": "model", - "name": "model", - "type": "MODEL", - "link": 114 - }, - { - "localized_name": "positive", - "name": "positive", - "type": "CONDITIONING", - "link": 10 - }, - { - "localized_name": "negative", - "name": "negative", - "type": "CONDITIONING", - "link": 11 - } - ], - "outputs": [ - { - "localized_name": "GUIDER", - "name": "GUIDER", - "type": "GUIDER", - "links": [ - 12 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.64", - "Node name for S&R": "CFGGuider", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [ - 1 - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 5, - "type": "LTXVConditioning", - "pos": [ - 3040, - 4400 - ], - "size": [ - 240, - 80 - ], - "flags": {}, - "order": 2, - "mode": 0, - "inputs": [ - { - "localized_name": "positive", - "name": "positive", - "type": "CONDITIONING", - "link": 116 - }, - { - "localized_name": "negative", - "name": "negative", - "type": "CONDITIONING", - "link": 50 - }, - { - "localized_name": "frame_rate", - "name": "frame_rate", - "type": "FLOAT", - "widget": { - "name": "frame_rate" - }, - "link": 120 - } - ], - "outputs": [ - { - "localized_name": "positive", - "name": "positive", - "type": "CONDITIONING", - "links": [ - 6 - ] - }, - { - "localized_name": "negative", - "name": "negative", - "type": "CONDITIONING", - "links": [ - 7 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.56", - "Node name for S&R": "LTXVConditioning", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [ - 24 - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 26, - "type": "ConditioningZeroOut", - "pos": [ - 3040, - 4530 - ], - "size": [ - 240, - 30 - ], - "flags": {}, - "order": 9, - "mode": 0, - "inputs": [ - { - "localized_name": "conditioning", - "name": "conditioning", - "type": "CONDITIONING", - "link": 117 - } - ], - "outputs": [ - { - "localized_name": "CONDITIONING", - "name": "CONDITIONING", - "type": "CONDITIONING", - "links": [ - 50 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.19.3", - "Node name for S&R": "ConditioningZeroOut" - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 13, - "type": "LTXVSeparateAVLatent", - "pos": [ - 4190, - 4390 - ], - "size": [ - 200, - 50 - ], - "flags": {}, - "order": 8, - "mode": 0, - "inputs": [ - { - "localized_name": "av_latent", - "name": "av_latent", - "type": "LATENT", - "link": 156 - } - ], - "outputs": [ - { - "localized_name": "video_latent", - "name": "video_latent", - "type": "LATENT", - "links": [ - 142 - ] - }, - { - "localized_name": "audio_latent", - "name": "audio_latent", - "type": "LATENT", - "links": [ - 143 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.16.0", - "Node name for S&R": "LTXVSeparateAVLatent" - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 28, - "type": "RandomNoise", - "pos": [ - 3740, - 4390 - ], - "size": [ - 210, - 90 - ], - "flags": {}, - "order": 1, - "mode": 0, - "inputs": [ - { - "label": "noise_seed", - "localized_name": "noise_seed", - "name": "noise_seed", - "type": "INT", - "widget": { - "name": "noise_seed" - }, - "link": 250 - } - ], - "outputs": [ - { - "localized_name": "NOISE", - "name": "NOISE", - "type": "NOISE", - "links": [ - 51, - 157 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.19.3", - "Node name for S&R": "RandomNoise" - }, - "widgets_values": [ - 12, - "fixed" - ], - "color": "#222", - "bgcolor": "#000" - } - ], - "groups": [], - "links": [ - { - "id": 51, - "origin_id": 28, - "origin_slot": 0, - "target_id": 10, - "target_slot": 0, - "type": "NOISE" - }, - { - "id": 12, - "origin_id": 9, - "origin_slot": 0, - "target_id": 10, - "target_slot": 1, - "type": "GUIDER" - }, - { - "id": 53, - "origin_id": 29, - "origin_slot": 0, - "target_id": 10, - "target_slot": 2, - "type": "SAMPLER" - }, - { - "id": 52, - "origin_id": 11, - "origin_slot": 0, - "target_id": 10, - "target_slot": 3, - "type": "SIGMAS" - }, - { - "id": 14, - "origin_id": 7, - "origin_slot": 0, - "target_id": 10, - "target_slot": 4, - "type": "LATENT" - }, - { - "id": 108, - "origin_id": 8, - "origin_slot": 2, - "target_id": 7, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 6, - "origin_id": 5, - "origin_slot": 0, - "target_id": 8, - "target_slot": 0, - "type": "CONDITIONING" - }, - { - "id": 7, - "origin_id": 5, - "origin_slot": 1, - "target_id": 8, - "target_slot": 1, - "type": "CONDITIONING" - }, - { - "id": 10, - "origin_id": 8, - "origin_slot": 0, - "target_id": 9, - "target_slot": 1, - "type": "CONDITIONING" - }, - { - "id": 11, - "origin_id": 8, - "origin_slot": 1, - "target_id": 9, - "target_slot": 2, - "type": "CONDITIONING" - }, - { - "id": 50, - "origin_id": 26, - "origin_slot": 0, - "target_id": 5, - "target_slot": 1, - "type": "CONDITIONING" - }, - { - "id": 156, - "origin_id": 10, - "origin_slot": 0, - "target_id": 13, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 115, - "origin_id": -10, - "origin_slot": 0, - "target_id": 11, - "target_slot": 0, - "type": "MODEL" - }, - { - "id": 114, - "origin_id": -10, - "origin_slot": 0, - "target_id": 9, - "target_slot": 0, - "type": "MODEL" - }, - { - "id": 119, - "origin_id": -10, - "origin_slot": 1, - "target_id": 7, - "target_slot": 1, - "type": "LATENT" - }, - { - "id": 216, - "origin_id": -10, - "origin_slot": 2, - "target_id": 8, - "target_slot": 2, - "type": "VAE" - }, - { - "id": 118, - "origin_id": -10, - "origin_slot": 3, - "target_id": 8, - "target_slot": 3, - "type": "LATENT" - }, - { - "id": 122, - "origin_id": -10, - "origin_slot": 4, - "target_id": 8, - "target_slot": 4, - "type": "GUIDE_DATA" - }, - { - "id": 116, - "origin_id": -10, - "origin_slot": 5, - "target_id": 5, - "target_slot": 0, - "type": "CONDITIONING" - }, - { - "id": 117, - "origin_id": -10, - "origin_slot": 5, - "target_id": 26, - "target_slot": 0, - "type": "CONDITIONING" - }, - { - "id": 120, - "origin_id": -10, - "origin_slot": 6, - "target_id": 5, - "target_slot": 2, - "type": "FLOAT" - }, - { - "id": 54, - "origin_id": 8, - "origin_slot": 0, - "target_id": -20, - "target_slot": 0, - "type": "CONDITIONING" - }, - { - "id": 55, - "origin_id": 8, - "origin_slot": 1, - "target_id": -20, - "target_slot": 1, - "type": "CONDITIONING" - }, - { - "id": 157, - "origin_id": 28, - "origin_slot": 0, - "target_id": -20, - "target_slot": 2, - "type": "NOISE" - }, - { - "id": 142, - "origin_id": 13, - "origin_slot": 0, - "target_id": -20, - "target_slot": 3, - "type": "LATENT" - }, - { - "id": 143, - "origin_id": 13, - "origin_slot": 1, - "target_id": -20, - "target_slot": 4, - "type": "LATENT" - }, - { - "id": 250, - "origin_id": -10, - "origin_slot": 7, - "target_id": 28, - "target_slot": 0, - "type": "INT" - } - ], - "extra": { - "workflowRendererVersion": "LG" - } - }, - { - "id": "7cf6430b-4784-4af1-8cde-9268e9465c62", - "version": 1, - "state": { - "lastGroupId": 6, - "lastNodeId": 96, - "lastLinkId": 250, - "lastRerouteId": 0 - }, - "revision": 0, - "config": {}, - "name": "Stage #2", - "inputNode": { - "id": -10, - "bounding": [ - 2870, - 3980, - 120, - 200 - ] - }, - "outputNode": { - "id": -20, - "bounding": [ - 4430, - 3900, - 120, - 80 - ] - }, - "inputs": [ - { - "id": "da8ac1d4-baaa-49af-a44f-51de815b8adc", - "name": "positive", - "type": "CONDITIONING", - "linkIds": [ - 228, - 227 - ], - "localized_name": "positive", - "pos": [ - 2970, - 4000 - ] - }, - { - "id": "2a2ae901-93e9-4551-97e5-bf33db9a7761", - "name": "negative", - "type": "CONDITIONING", - "linkIds": [ - 230, - 229 - ], - "localized_name": "negative", - "pos": [ - 2970, - 4020 - ] - }, - { - "id": "f6f33172-a93d-4505-a3c8-4e4e61488f94", - "name": "latent", - "type": "LATENT", - "linkIds": [ - 232 - ], - "localized_name": "latent", - "label": "video_latent", - "pos": [ - 2970, - 4040 - ] - }, - { - "id": "9d67923c-e4e8-4eb5-a91b-b37dbfb5882f", - "name": "vae", - "type": "VAE", - "linkIds": [ - 219, - 218 - ], - "localized_name": "vae", - "label": "video_vae", - "pos": [ - 2970, - 4060 - ] - }, - { - "id": "c5f8e7de-87ec-4509-892c-8cc4e1f14575", - "name": "guide_data", - "type": "GUIDE_DATA", - "linkIds": [ - 151 - ], - "localized_name": "guide_data", - "pos": [ - 2970, - 4080 - ] - }, - { - "id": "c9c467d0-0426-4288-a31e-b41be1891bdc", - "name": "model", - "type": "MODEL", - "linkIds": [ - 158, - 159 - ], - "localized_name": "model", - "pos": [ - 2970, - 4100 - ] - }, - { - "id": "1c4bd34a-4e48-455a-bd66-323524e5c2d8", - "name": "noise", - "type": "NOISE", - "linkIds": [ - 231 - ], - "localized_name": "noise", - "pos": [ - 2970, - 4120 - ] - }, - { - "id": "ef0cc4fb-4a9f-404b-a62c-92e04fd74171", - "name": "audio_latent", - "type": "LATENT", - "linkIds": [ - 233 - ], - "localized_name": "audio_latent", - "pos": [ - 2970, - 4140 - ] - } - ], - "outputs": [ - { - "id": "402aed7a-321d-48a8-a407-34f05a5ab1e2", - "name": "audio_latent", - "type": "LATENT", - "linkIds": [ - 202 - ], - "localized_name": "audio_latent", - "pos": [ - 4450, - 3920 - ] - }, - { - "id": "8fe53957-38d9-459e-a590-3ad4d96d3616", - "name": "latent", - "type": "LATENT", - "linkIds": [ - 208 - ], - "localized_name": "latent", - "pos": [ - 4450, - 3940 - ] - } - ], - "widgets": [], - "nodes": [ - { - "id": 55, - "type": "LTXVCropGuides", - "pos": [ - 3050, - 3930 - ], - "size": [ - 250, - 70 - ], - "flags": {}, - "order": 9, - "mode": 0, - "inputs": [ - { - "localized_name": "positive", - "name": "positive", - "type": "CONDITIONING", - "link": 228 - }, - { - "localized_name": "negative", - "name": "negative", - "type": "CONDITIONING", - "link": 230 - }, - { - "localized_name": "latent", - "name": "latent", - "type": "LATENT", - "link": 232 - } - ], - "outputs": [ - { - "localized_name": "positive", - "name": "positive", - "type": "CONDITIONING", - "links": [ - 146 - ] - }, - { - "localized_name": "negative", - "name": "negative", - "type": "CONDITIONING", - "links": [ - 147 - ] - }, - { - "localized_name": "latent", - "name": "latent", - "type": "LATENT", - "links": [ - 135 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.16.4", - "Node name for S&R": "LTXVCropGuides" - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 58, - "type": "LTXDirectorGuide", - "pos": [ - 3310, - 3930 - ], - "size": [ - 210, - 190 - ], - "flags": {}, - "order": 10, - "mode": 0, - "inputs": [ - { - "localized_name": "positive", - "name": "positive", - "type": "CONDITIONING", - "link": 146 - }, - { - "localized_name": "negative", - "name": "negative", - "type": "CONDITIONING", - "link": 147 - }, - { - "localized_name": "vae", - "name": "vae", - "type": "VAE", - "link": 219 - }, - { - "localized_name": "latent", - "name": "latent", - "type": "LATENT", - "link": 150 - }, - { - "localized_name": "guide_data", - "name": "guide_data", - "type": "GUIDE_DATA", - "link": 151 - } - ], - "outputs": [ - { - "localized_name": "positive", - "name": "positive", - "type": "CONDITIONING", - "links": [ - 152 - ] - }, - { - "localized_name": "negative", - "name": "negative", - "type": "CONDITIONING", - "links": [ - 153 - ] - }, - { - "localized_name": "latent", - "name": "latent", - "type": "LATENT", - "links": [ - 154 - ] - } - ], - "properties": { - "cnr_id": "whatdreamscost-comfyui", - "ver": "5cae3c00186ff2eb79771070f05a2c4e02f329bb", - "Node name for S&R": "LTXDirectorGuide", - "aux_id": "kijai/ComfyUI-PromptRelay" - }, - "widgets_values": [ - 1, - "bicubic" - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 49, - "type": "CFGGuider", - "pos": [ - 3540, - 3920 - ], - "size": [ - 210, - 100 - ], - "flags": {}, - "order": 5, - "mode": 0, - "inputs": [ - { - "localized_name": "model", - "name": "model", - "type": "MODEL", - "link": 158 - }, - { - "localized_name": "positive", - "name": "positive", - "type": "CONDITIONING", - "link": 152 - }, - { - "localized_name": "negative", - "name": "negative", - "type": "CONDITIONING", - "link": 153 - } - ], - "outputs": [ - { - "localized_name": "GUIDER", - "name": "GUIDER", - "type": "GUIDER", - "links": [ - 124 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.64", - "Node name for S&R": "CFGGuider", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [ - 1 - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 53, - "type": "KSamplerSelect", - "pos": [ - 3540, - 4060 - ], - "size": [ - 210, - 60 - ], - "flags": {}, - "order": 0, - "mode": 0, - "inputs": [], - "outputs": [ - { - "localized_name": "SAMPLER", - "name": "SAMPLER", - "type": "SAMPLER", - "links": [ - 125 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.56", - "Node name for S&R": "KSamplerSelect", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [ - "euler" - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 47, - "type": "SamplerCustomAdvanced", - "pos": [ - 3760, - 3920 - ], - "size": [ - 230, - 220 - ], - "flags": {}, - "order": 3, - "mode": 0, - "inputs": [ - { - "localized_name": "noise", - "name": "noise", - "type": "NOISE", - "link": 231 - }, - { - "localized_name": "guider", - "name": "guider", - "type": "GUIDER", - "link": 124 - }, - { - "localized_name": "sampler", - "name": "sampler", - "type": "SAMPLER", - "link": 125 - }, - { - "localized_name": "sigmas", - "name": "sigmas", - "type": "SIGMAS", - "link": 126 - }, - { - "localized_name": "latent_image", - "name": "latent_image", - "type": "LATENT", - "link": 127 - } - ], - "outputs": [ - { - "localized_name": "output", - "name": "output", - "type": "LATENT", - "links": [ - 180 - ] - }, - { - "localized_name": "denoised_output", - "name": "denoised_output", - "type": "LATENT", - "links": [] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.60", - "Node name for S&R": "SamplerCustomAdvanced", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 57, - "type": "LatentUpscaleModelLoader", - "pos": [ - 3050, - 4040 - ], - "size": [ - 250, - 70 - ], - "flags": {}, - "order": 1, - "mode": 0, - "inputs": [], - "outputs": [ - { - "localized_name": "LATENT_UPSCALE_MODEL", - "name": "LATENT_UPSCALE_MODEL", - "type": "LATENT_UPSCALE_MODEL", - "links": [ - 144 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.16.4", - "Node name for S&R": "LatentUpscaleModelLoader" - }, - "widgets_values": [ - "ltx-2.3-spatial-upscaler-x2-1.1.safetensors" - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 48, - "type": "LTXVSeparateAVLatent", - "pos": [ - 4000, - 3920 - ], - "size": [ - 200, - 50 - ], - "flags": {}, - "order": 4, - "mode": 0, - "inputs": [ - { - "localized_name": "av_latent", - "name": "av_latent", - "type": "LATENT", - "link": 180 - } - ], - "outputs": [ - { - "localized_name": "video_latent", - "name": "video_latent", - "type": "LATENT", - "links": [ - 136 - ] - }, - { - "localized_name": "audio_latent", - "name": "audio_latent", - "type": "LATENT", - "links": [ - 202 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.5.1", - "Node name for S&R": "LTXVSeparateAVLatent", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 52, - "type": "LTXVLatentUpsampler", - "pos": [ - 3050, - 4150 - ], - "size": [ - 250, - 70 - ], - "flags": {}, - "order": 7, - "mode": 0, - "inputs": [ - { - "localized_name": "samples", - "name": "samples", - "type": "LATENT", - "link": 135 - }, - { - "localized_name": "upscale_model", - "name": "upscale_model", - "type": "LATENT_UPSCALE_MODEL", - "link": 144 - }, - { - "localized_name": "vae", - "name": "vae", - "type": "VAE", - "link": 218 - } - ], - "outputs": [ - { - "localized_name": "LATENT", - "name": "LATENT", - "type": "LATENT", - "links": [ - 150 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.14.1", - "Node name for S&R": "LTXVLatentUpsampler", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 54, - "type": "BasicScheduler", - "pos": [ - 3540, - 4160 - ], - "size": [ - 210, - 110 - ], - "flags": {}, - "order": 8, - "mode": 0, - "inputs": [ - { - "localized_name": "model", - "name": "model", - "type": "MODEL", - "link": 159 - } - ], - "outputs": [ - { - "localized_name": "SIGMAS", - "name": "SIGMAS", - "type": "SIGMAS", - "links": [ - 126 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.16.4", - "Node name for S&R": "BasicScheduler" - }, - "widgets_values": [ - "linear_quadratic", - 4, - 0.42 - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 50, - "type": "LTXVConcatAVLatent", - "pos": [ - 3310, - 4160 - ], - "size": [ - 210, - 60 - ], - "flags": {}, - "order": 6, - "mode": 0, - "inputs": [ - { - "localized_name": "video_latent", - "name": "video_latent", - "type": "LATENT", - "link": 154 - }, - { - "localized_name": "audio_latent", - "name": "audio_latent", - "type": "LATENT", - "link": 233 - } - ], - "outputs": [ - { - "localized_name": "latent", - "name": "latent", - "type": "LATENT", - "links": [ - 127 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.7.0", - "Node name for S&R": "LTXVConcatAVLatent", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 14, - "type": "LTXVCropGuides", - "pos": [ - 4210, - 3920 - ], - "size": [ - 180, - 70 - ], - "flags": {}, - "order": 2, - "mode": 0, - "inputs": [ - { - "localized_name": "positive", - "name": "positive", - "type": "CONDITIONING", - "link": 227 - }, - { - "localized_name": "negative", - "name": "negative", - "type": "CONDITIONING", - "link": 229 - }, - { - "localized_name": "latent", - "name": "latent", - "type": "LATENT", - "link": 136 - } - ], - "outputs": [ - { - "localized_name": "positive", - "name": "positive", - "type": "CONDITIONING", - "links": null - }, - { - "localized_name": "negative", - "name": "negative", - "type": "CONDITIONING", - "links": null - }, - { - "localized_name": "latent", - "name": "latent", - "type": "LATENT", - "links": [ - 208 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.19.3", - "Node name for S&R": "LTXVCropGuides" - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - } - ], - "groups": [], - "links": [ - { - "id": 146, - "origin_id": 55, - "origin_slot": 0, - "target_id": 58, - "target_slot": 0, - "type": "CONDITIONING" - }, - { - "id": 147, - "origin_id": 55, - "origin_slot": 1, - "target_id": 58, - "target_slot": 1, - "type": "CONDITIONING" - }, - { - "id": 150, - "origin_id": 52, - "origin_slot": 0, - "target_id": 58, - "target_slot": 3, - "type": "LATENT" - }, - { - "id": 152, - "origin_id": 58, - "origin_slot": 0, - "target_id": 49, - "target_slot": 1, - "type": "CONDITIONING" - }, - { - "id": 153, - "origin_id": 58, - "origin_slot": 1, - "target_id": 49, - "target_slot": 2, - "type": "CONDITIONING" - }, - { - "id": 124, - "origin_id": 49, - "origin_slot": 0, - "target_id": 47, - "target_slot": 1, - "type": "GUIDER" - }, - { - "id": 125, - "origin_id": 53, - "origin_slot": 0, - "target_id": 47, - "target_slot": 2, - "type": "SAMPLER" - }, - { - "id": 126, - "origin_id": 54, - "origin_slot": 0, - "target_id": 47, - "target_slot": 3, - "type": "SIGMAS" - }, - { - "id": 127, - "origin_id": 50, - "origin_slot": 0, - "target_id": 47, - "target_slot": 4, - "type": "LATENT" - }, - { - "id": 180, - "origin_id": 47, - "origin_slot": 0, - "target_id": 48, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 135, - "origin_id": 55, - "origin_slot": 2, - "target_id": 52, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 144, - "origin_id": 57, - "origin_slot": 0, - "target_id": 52, - "target_slot": 1, - "type": "LATENT_UPSCALE_MODEL" - }, - { - "id": 154, - "origin_id": 58, - "origin_slot": 2, - "target_id": 50, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 136, - "origin_id": 48, - "origin_slot": 0, - "target_id": 14, - "target_slot": 2, - "type": "LATENT" - }, - { - "id": 228, - "origin_id": -10, - "origin_slot": 0, - "target_id": 55, - "target_slot": 0, - "type": "CONDITIONING" - }, - { - "id": 227, - "origin_id": -10, - "origin_slot": 0, - "target_id": 14, - "target_slot": 0, - "type": "CONDITIONING" - }, - { - "id": 230, - "origin_id": -10, - "origin_slot": 1, - "target_id": 55, - "target_slot": 1, - "type": "CONDITIONING" - }, - { - "id": 229, - "origin_id": -10, - "origin_slot": 1, - "target_id": 14, - "target_slot": 1, - "type": "CONDITIONING" - }, - { - "id": 232, - "origin_id": -10, - "origin_slot": 2, - "target_id": 55, - "target_slot": 2, - "type": "LATENT" - }, - { - "id": 219, - "origin_id": -10, - "origin_slot": 3, - "target_id": 58, - "target_slot": 2, - "type": "VAE" - }, - { - "id": 218, - "origin_id": -10, - "origin_slot": 3, - "target_id": 52, - "target_slot": 2, - "type": "VAE" - }, - { - "id": 151, - "origin_id": -10, - "origin_slot": 4, - "target_id": 58, - "target_slot": 4, - "type": "GUIDE_DATA" - }, - { - "id": 158, - "origin_id": -10, - "origin_slot": 5, - "target_id": 49, - "target_slot": 0, - "type": "MODEL" - }, - { - "id": 159, - "origin_id": -10, - "origin_slot": 5, - "target_id": 54, - "target_slot": 0, - "type": "MODEL" - }, - { - "id": 231, - "origin_id": -10, - "origin_slot": 6, - "target_id": 47, - "target_slot": 0, - "type": "NOISE" - }, - { - "id": 233, - "origin_id": -10, - "origin_slot": 7, - "target_id": 50, - "target_slot": 1, - "type": "LATENT" - }, - { - "id": 202, - "origin_id": 48, - "origin_slot": 1, - "target_id": -20, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 208, - "origin_id": 14, - "origin_slot": 2, - "target_id": -20, - "target_slot": 1, - "type": "LATENT" - } - ], - "extra": { - "workflowRendererVersion": "LG" - } - }, - { - "id": "739520a4-718f-490f-87d1-34ffbca98e3f", - "version": 1, - "state": { - "lastGroupId": 6, - "lastNodeId": 96, - "lastLinkId": 250, - "lastRerouteId": 0 - }, - "revision": 0, - "config": {}, - "name": "Decode", - "inputNode": { - "id": -10, - "bounding": [ - 3330, - 4105, - 120, - 140 - ] - }, - "outputNode": { - "id": -20, - "bounding": [ - 3780, - 4145, - 120, - 60 - ] - }, - "inputs": [ - { - "id": "35c40a03-3855-445e-a691-ae0ada359280", - "name": "samples", - "type": "LATENT", - "linkIds": [ - 242 - ], - "localized_name": "samples", - "label": "audio_latent", - "pos": [ - 3430, - 4125 - ] - }, - { - "id": "ddfb8ed1-1fd2-4086-ad94-d816dab3b6bb", - "name": "audio_vae", - "type": "VAE", - "linkIds": [ - 214 - ], - "localized_name": "audio_vae", - "label": "audio_vae", - "pos": [ - 3430, - 4145 - ] - }, - { - "id": "458157ba-efe8-4aa2-847e-be97d78a6873", - "name": "samples_1", - "type": "LATENT", - "linkIds": [ - 243 - ], - "localized_name": "samples_1", - "label": "video_latent", - "pos": [ - 3430, - 4165 - ] - }, - { - "id": "b02f1f71-c594-437f-8dda-4bb0f19c2e3e", - "name": "vae", - "type": "VAE", - "linkIds": [ - 217 - ], - "localized_name": "vae", - "label": "video_vae", - "pos": [ - 3430, - 4185 - ] - }, - { - "id": "83aa76da-c7d7-41b5-a3d6-4942595be90c", - "name": "fps", - "type": "FLOAT", - "linkIds": [ - 121 - ], - "localized_name": "fps", - "pos": [ - 3430, - 4205 - ] - } - ], - "outputs": [ - { - "id": "7d63e3c3-78d0-4613-9631-0fa830d1bf9a", - "name": "VIDEO", - "type": "VIDEO", - "linkIds": [ - 56 - ], - "localized_name": "VIDEO", - "pos": [ - 3800, - 4165 - ] - } - ], - "widgets": [], - "nodes": [ - { - "id": 16, - "type": "LTXVAudioVAEDecode", - "pos": [ - 3510, - 4060 - ], - "size": [ - 210, - 50 - ], - "flags": {}, - "order": 1, - "mode": 0, - "inputs": [ - { - "localized_name": "samples", - "name": "samples", - "type": "LATENT", - "link": 242 - }, - { - "label": "Audio VAE", - "localized_name": "audio_vae", - "name": "audio_vae", - "type": "VAE", - "link": 214 - } - ], - "outputs": [ - { - "localized_name": "Audio", - "name": "Audio", - "type": "AUDIO", - "links": [ - 25 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.16.0", - "Node name for S&R": "LTXVAudioVAEDecode" - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 15, - "type": "VAEDecode", - "pos": [ - 3510, - 4150 - ], - "size": [ - 210, - 50 - ], - "flags": {}, - "order": 0, - "mode": 0, - "inputs": [ - { - "localized_name": "samples", - "name": "samples", - "type": "LATENT", - "link": 243 - }, - { - "localized_name": "vae", - "name": "vae", - "type": "VAE", - "link": 217 - } - ], - "outputs": [ - { - "localized_name": "IMAGE", - "name": "IMAGE", - "type": "IMAGE", - "links": [ - 24 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.9.1", - "Node name for S&R": "VAEDecode" - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 17, - "type": "CreateVideo", - "pos": [ - 3510, - 4240 - ], - "size": [ - 210, - 80 - ], - "flags": {}, - "order": 2, - "mode": 0, - "inputs": [ - { - "localized_name": "images", - "name": "images", - "type": "IMAGE", - "link": 24 - }, - { - "localized_name": "audio", - "name": "audio", - "shape": 7, - "type": "AUDIO", - "link": 25 - }, - { - "localized_name": "fps", - "name": "fps", - "type": "FLOAT", - "widget": { - "name": "fps" - }, - "link": 121 - } - ], - "outputs": [ - { - "localized_name": "VIDEO", - "name": "VIDEO", - "type": "VIDEO", - "links": [ - 56 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.19.3", - "Node name for S&R": "CreateVideo" - }, - "widgets_values": [ - 24 - ], - "color": "#222", - "bgcolor": "#000" - } - ], - "groups": [], - "links": [ - { - "id": 24, - "origin_id": 15, - "origin_slot": 0, - "target_id": 17, - "target_slot": 0, - "type": "IMAGE" - }, - { - "id": 25, - "origin_id": 16, - "origin_slot": 0, - "target_id": 17, - "target_slot": 1, - "type": "AUDIO" - }, - { - "id": 242, - "origin_id": -10, - "origin_slot": 0, - "target_id": 16, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 214, - "origin_id": -10, - "origin_slot": 1, - "target_id": 16, - "target_slot": 1, - "type": "VAE" - }, - { - "id": 243, - "origin_id": -10, - "origin_slot": 2, - "target_id": 15, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 217, - "origin_id": -10, - "origin_slot": 3, - "target_id": 15, - "target_slot": 1, - "type": "VAE" - }, - { - "id": 121, - "origin_id": -10, - "origin_slot": 4, - "target_id": 17, - "target_slot": 2, - "type": "FLOAT" - }, - { - "id": 56, - "origin_id": 17, - "origin_slot": 0, - "target_id": -20, - "target_slot": 0, - "type": "VIDEO" - } - ], - "extra": { - "workflowRendererVersion": "LG" - } - } - ] - }, - "config": {}, - "extra": { - "frontendVersion": "1.40.0", - "VHS_latentpreview": false, - "VHS_latentpreviewrate": 0, - "VHS_MetadataImage": true, - "VHS_KeepIntermediate": true, - "ds": { - "scale": 0.8964143292718684, - "offset": [ - -1084.7781214353931, - -3754.5892800523366 - ] - }, - "workflowRendererVersion": "LG" - }, - "version": 0.4 +{ + "id": "c92620ea-60b4-4f41-a78f-621383a62c2a", + "revision": 0, + "last_node_id": 96, + "last_link_id": 250, + "nodes": [ + { + "id": 46, + "type": "LTXDirector", + "pos": [ + 1860, + 3970 + ], + "size": [ + 1120, + 801 + ], + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 212 + }, + { + "name": "clip", + "type": "CLIP", + "link": 213 + }, + { + "name": "audio_vae", + "shape": 7, + "type": "VAE", + "link": 215 + }, + { + "name": "optional_latent", + "shape": 7, + "type": "LATENT", + "link": null + } + ], + "outputs": [ + { + "name": "model", + "type": "MODEL", + "links": [ + 220, + 239 + ] + }, + { + "name": "positive", + "type": "CONDITIONING", + "links": [ + 225 + ] + }, + { + "name": "video_latent", + "type": "LATENT", + "links": [ + 223 + ] + }, + { + "name": "audio_latent", + "type": "LATENT", + "links": [ + 221 + ] + }, + { + "name": "guide_data", + "type": "GUIDE_DATA", + "links": [ + 224, + 238 + ] + }, + { + "name": "frame_rate", + "type": "FLOAT", + "links": [ + 226, + 248 + ] + }, + { + "name": "combined_audio", + "type": "AUDIO", + "links": [] + } + ], + "properties": { + "cnr_id": "whatdreamscost-comfyui", + "ver": "5cae3c00186ff2eb79771070f05a2c4e02f329bb", + "Node name for S&R": "LTXDirector", + "aux_id": "kijai/ComfyUI-PromptRelay" + }, + "widgets_values": [ + "", + 120, + 5, + "{\"segments\":[],\"audioSegments\":[]}", + "", + "", + 0.001, + "", + false, + 24, + "seconds", + 0, + 0, + "maintain aspect ratio", + 32, + 18, + "" + ] + }, + { + "id": 30, + "type": "SaveVideo", + "pos": [ + 3230, + 3970 + ], + "size": [ + 690, + 530 + ], + "flags": {}, + "order": 6, + "mode": 0, + "inputs": [ + { + "name": "video", + "type": "VIDEO", + "link": 249 + } + ], + "outputs": [], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.19.3", + "Node name for S&R": "SaveVideo" + }, + "widgets_values": [ + "video/LTX_Director", + "auto", + "auto" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 96, + "type": "739520a4-718f-490f-87d1-34ffbca98e3f", + "pos": [ + 3000, + 4620 + ], + "size": [ + 210, + 130 + ], + "flags": {}, + "order": 5, + "mode": 0, + "inputs": [ + { + "label": "audio_latent", + "name": "samples", + "type": "LATENT", + "link": 244 + }, + { + "label": "audio_vae", + "name": "audio_vae", + "type": "VAE", + "link": 245 + }, + { + "label": "video_latent", + "name": "samples_1", + "type": "LATENT", + "link": 246 + }, + { + "label": "video_vae", + "name": "vae", + "type": "VAE", + "link": 247 + }, + { + "name": "fps", + "type": "FLOAT", + "widget": { + "name": "fps" + }, + "link": 248 + } + ], + "outputs": [ + { + "name": "VIDEO", + "type": "VIDEO", + "links": [ + 249 + ] + } + ], + "properties": { + "proxyWidgets": [], + "cnr_id": "comfy-core", + "ver": "0.19.3" + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 93, + "type": "dc399e64-01cd-40b2-9337-972428cc66de", + "pos": [ + 1430, + 3970 + ], + "size": [ + 400, + 190 + ], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [], + "outputs": [ + { + "label": "model", + "name": "MODEL", + "type": "MODEL", + "links": [ + 212 + ] + }, + { + "label": "clip", + "name": "CLIP", + "type": "CLIP", + "links": [ + 213 + ] + }, + { + "label": "audio_vae", + "name": "VAE", + "type": "VAE", + "links": [ + 215, + 245 + ] + }, + { + "label": "video_vae", + "name": "VAE_1", + "type": "VAE", + "links": [ + 222, + 237, + 247 + ] + } + ], + "properties": { + "proxyWidgets": [ + [ + "77", + "ckpt_name" + ], + [ + "84", + "clip_name1" + ], + [ + "4", + "vae_name" + ], + [ + "3", + "vae_name" + ] + ], + "cnr_id": "comfy-core", + "ver": "0.19.3" + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 94, + "type": "4b017e47-bf16-441b-ad03-edce978b3be3", + "pos": [ + 3000, + 3970 + ], + "size": [ + 210, + 260 + ], + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 220 + }, + { + "name": "audio_latent", + "type": "LATENT", + "link": 221 + }, + { + "name": "vae", + "type": "VAE", + "link": 222 + }, + { + "label": "video_latent", + "name": "latent", + "type": "LATENT", + "link": 223 + }, + { + "name": "guide_data", + "type": "GUIDE_DATA", + "link": 224 + }, + { + "name": "positive", + "type": "CONDITIONING", + "link": 225 + }, + { + "name": "frame_rate", + "type": "FLOAT", + "widget": { + "name": "frame_rate" + }, + "link": 226 + } + ], + "outputs": [ + { + "name": "positive", + "type": "CONDITIONING", + "links": [ + 234 + ] + }, + { + "name": "negative", + "type": "CONDITIONING", + "links": [ + 235 + ] + }, + { + "name": "NOISE", + "type": "NOISE", + "links": [ + 240 + ] + }, + { + "name": "video_latent", + "type": "LATENT", + "links": [ + 236 + ] + }, + { + "name": "audio_latent", + "type": "LATENT", + "links": [ + 241 + ] + } + ], + "properties": { + "proxyWidgets": [ + [ + "-1", + "noise_seed" + ] + ], + "cnr_id": "comfy-core", + "ver": "0.19.3" + }, + "widgets_values": [ + 12 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 95, + "type": "7cf6430b-4784-4af1-8cde-9268e9465c62", + "pos": [ + 3000, + 4290 + ], + "size": [ + 210, + 270 + ], + "flags": {}, + "order": 4, + "mode": 0, + "inputs": [ + { + "name": "positive", + "type": "CONDITIONING", + "link": 234 + }, + { + "name": "negative", + "type": "CONDITIONING", + "link": 235 + }, + { + "label": "video_latent", + "name": "latent", + "type": "LATENT", + "link": 236 + }, + { + "label": "video_vae", + "name": "vae", + "type": "VAE", + "link": 237 + }, + { + "name": "guide_data", + "type": "GUIDE_DATA", + "link": 238 + }, + { + "name": "model", + "type": "MODEL", + "link": 239 + }, + { + "name": "noise", + "type": "NOISE", + "link": 240 + }, + { + "name": "audio_latent", + "type": "LATENT", + "link": 241 + } + ], + "outputs": [ + { + "name": "audio_latent", + "type": "LATENT", + "links": [ + 244 + ] + }, + { + "name": "latent", + "type": "LATENT", + "links": [ + 246 + ] + } + ], + "properties": { + "proxyWidgets": [], + "cnr_id": "comfy-core", + "ver": "0.19.3" + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 92, + "type": "MarkdownNote", + "pos": [ + 1430, + 4220 + ], + "size": [ + 400, + 530 + ], + "flags": {}, + "order": 1, + "mode": 0, + "inputs": [], + "outputs": [], + "title": "FAQ", + "properties": {}, + "widgets_values": [ + "# FAQ\n\n**How do I use this??**\n\nI'll be posting tutorials on my YouTube channel soon!\n\n\n**Where do I download these models??**\n\nThere is a good list of models and download links here: \n\nAlso the latest v1.1 spatial upscaler can be found here: \n\nThe tiny vae (taeltx2_3.safetensors) is found here:\n\n\n**I'm getting errors when running this workflow!**\n\nDouble check that you have all of the correct models loaded. This is using the newest version of 2x upscaler (v1.1). You can change it to the older one under the Model Loader.\n\n**How can I support you??**\n\nHelp me get a job. I'm unemployed and literally broke. (seriously, i'll even scrub toilets for minimum wage)\n\nOr follow me on my socials :)\n\n\n\n\n\n" + ], + "color": "#322", + "bgcolor": "#533" + } + ], + "links": [ + [ + 212, + 93, + 0, + 46, + 0, + "MODEL" + ], + [ + 213, + 93, + 1, + 46, + 1, + "CLIP" + ], + [ + 215, + 93, + 2, + 46, + 2, + "VAE" + ], + [ + 220, + 46, + 0, + 94, + 0, + "MODEL" + ], + [ + 221, + 46, + 3, + 94, + 1, + "LATENT" + ], + [ + 222, + 93, + 3, + 94, + 2, + "VAE" + ], + [ + 223, + 46, + 2, + 94, + 3, + "LATENT" + ], + [ + 224, + 46, + 4, + 94, + 4, + "GUIDE_DATA" + ], + [ + 225, + 46, + 1, + 94, + 5, + "CONDITIONING" + ], + [ + 226, + 46, + 5, + 94, + 6, + "FLOAT" + ], + [ + 234, + 94, + 0, + 95, + 0, + "CONDITIONING" + ], + [ + 235, + 94, + 1, + 95, + 1, + "CONDITIONING" + ], + [ + 236, + 94, + 3, + 95, + 2, + "LATENT" + ], + [ + 237, + 93, + 3, + 95, + 3, + "VAE" + ], + [ + 238, + 46, + 4, + 95, + 4, + "GUIDE_DATA" + ], + [ + 239, + 46, + 0, + 95, + 5, + "MODEL" + ], + [ + 240, + 94, + 2, + 95, + 6, + "NOISE" + ], + [ + 241, + 94, + 4, + 95, + 7, + "LATENT" + ], + [ + 244, + 95, + 0, + 96, + 0, + "LATENT" + ], + [ + 245, + 93, + 2, + 96, + 1, + "VAE" + ], + [ + 246, + 95, + 1, + 96, + 2, + "LATENT" + ], + [ + 247, + 93, + 3, + 96, + 3, + "VAE" + ], + [ + 248, + 46, + 5, + 96, + 4, + "FLOAT" + ], + [ + 249, + 96, + 0, + 30, + 0, + "VIDEO" + ] + ], + "groups": [], + "definitions": { + "subgraphs": [ + { + "id": "dc399e64-01cd-40b2-9337-972428cc66de", + "version": 1, + "state": { + "lastGroupId": 6, + "lastNodeId": 96, + "lastLinkId": 250, + "lastRerouteId": 0 + }, + "revision": 0, + "config": {}, + "name": "Model Loader", + "inputNode": { + "id": -10, + "bounding": [ + 1060, + 4265, + 120, + 40 + ] + }, + "outputNode": { + "id": -20, + "bounding": [ + 1870, + 4225, + 120, + 120 + ] + }, + "inputs": [], + "outputs": [ + { + "id": "68f91346-0127-4451-a92f-5cfb56fc7f02", + "name": "MODEL", + "type": "MODEL", + "linkIds": [ + 199 + ], + "localized_name": "MODEL", + "label": "model", + "pos": [ + 1890, + 4245 + ] + }, + { + "id": "d50bc485-29c2-44a9-9653-fbb738989bdc", + "name": "CLIP", + "type": "CLIP", + "linkIds": [ + 211 + ], + "localized_name": "CLIP", + "label": "clip", + "pos": [ + 1890, + 4265 + ] + }, + { + "id": "cfbbcf84-8477-40b1-a7ba-318c8d3b03a0", + "name": "VAE", + "type": "VAE", + "linkIds": [ + 58, + 58 + ], + "localized_name": "VAE", + "label": "audio_vae", + "pos": [ + 1890, + 4285 + ] + }, + { + "id": "815c9c85-34bc-49fc-8161-aca22b170346", + "name": "VAE_1", + "type": "VAE", + "linkIds": [ + 16, + 16, + 16, + 16 + ], + "localized_name": "VAE_1", + "label": "video_vae", + "pos": [ + 1890, + 4305 + ] + } + ], + "widgets": [], + "nodes": [ + { + "id": 77, + "type": "CheckpointLoaderSimple", + "pos": [ + 1240, + 4020 + ], + "size": [ + 270, + 100 + ], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [ + { + "label": "model", + "localized_name": "ckpt_name", + "name": "ckpt_name", + "type": "COMBO", + "widget": { + "name": "ckpt_name" + }, + "link": null + } + ], + "outputs": [ + { + "localized_name": "MODEL", + "name": "MODEL", + "type": "MODEL", + "links": [ + 210 + ] + }, + { + "localized_name": "CLIP", + "name": "CLIP", + "type": "CLIP", + "links": null + }, + { + "localized_name": "VAE", + "name": "VAE", + "type": "VAE", + "links": null + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.17.2", + "Node name for S&R": "CheckpointLoaderSimple" + }, + "widgets_values": [ + "ltx-2.3-22b-dev-fp8.safetensors" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 80, + "type": "LoraLoaderModelOnly", + "pos": [ + 1530, + 4160 + ], + "size": [ + 280, + 90 + ], + "flags": {}, + "order": 6, + "mode": 0, + "inputs": [ + { + "localized_name": "model", + "name": "model", + "type": "MODEL", + "link": 197 + } + ], + "outputs": [ + { + "localized_name": "MODEL", + "name": "MODEL", + "type": "MODEL", + "links": [ + 199 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.75", + "Node name for S&R": "LoraLoaderModelOnly", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65, + "models": [ + { + "name": "ltx-2.3-22b-distilled-lora-384.safetensors", + "url": "https://huggingface.co/Lightricks/LTX-2.3/resolve/main/ltx-2.3-22b-distilled-lora-384.safetensors", + "directory": "loras" + } + ] + }, + "widgets_values": [ + "ltx2\\ltx-2.3-22b-distilled-lora-dynamic_fro09_avg_rank_105_bf16.safetensors", + 0.5 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 79, + "type": "LTX2SamplingPreviewOverride", + "pos": [ + 1530, + 4020 + ], + "size": [ + 270, + 100 + ], + "flags": {}, + "order": 5, + "mode": 0, + "inputs": [ + { + "localized_name": "model", + "name": "model", + "type": "MODEL", + "link": 210 + }, + { + "localized_name": "latent_upscale_model", + "name": "latent_upscale_model", + "shape": 7, + "type": "LATENT_UPSCALE_MODEL", + "link": null + }, + { + "localized_name": "vae", + "name": "vae", + "shape": 7, + "type": "VAE", + "link": 196 + } + ], + "outputs": [ + { + "localized_name": "MODEL", + "name": "MODEL", + "type": "MODEL", + "links": [ + 197 + ] + } + ], + "properties": { + "cnr_id": "comfyui-kjnodes", + "ver": "1.3.4", + "Node name for S&R": "LTX2SamplingPreviewOverride" + }, + "widgets_values": [ + 24 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 78, + "type": "VAELoaderKJ", + "pos": [ + 1530, + 3870 + ], + "size": [ + 270, + 110 + ], + "flags": {}, + "order": 1, + "mode": 0, + "inputs": [], + "outputs": [ + { + "localized_name": "VAE", + "name": "VAE", + "type": "VAE", + "links": [ + 196 + ] + } + ], + "title": "Tiny VAELoader KJ", + "properties": { + "cnr_id": "comfyui-kjnodes", + "ver": "1.2.5", + "Node name for S&R": "VAELoaderKJ" + }, + "widgets_values": [ + "taeltx2_3.safetensors", + "main_device", + "bf16" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 4, + "type": "VAELoaderKJ", + "pos": [ + 1530, + 4470 + ], + "size": [ + 260, + 110 + ], + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [ + { + "label": "audio_vae", + "localized_name": "vae_name", + "name": "vae_name", + "type": "COMBO", + "widget": { + "name": "vae_name" + }, + "link": null + } + ], + "outputs": [ + { + "localized_name": "VAE", + "name": "VAE", + "type": "VAE", + "links": [ + 58 + ] + } + ], + "title": "Audio VAELoader", + "properties": { + "cnr_id": "comfyui-kjnodes", + "ver": "487c7d86a0230aae3d8c0a37d517159b73834f85", + "Node name for S&R": "VAELoaderKJ" + }, + "widgets_values": [ + "LTX23_audio_vae_bf16.safetensors", + "main_device", + "bf16" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 3, + "type": "VAELoaderKJ", + "pos": [ + 1530, + 4620 + ], + "size": [ + 260, + 110 + ], + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [ + { + "label": "video_vae", + "localized_name": "vae_name", + "name": "vae_name", + "type": "COMBO", + "widget": { + "name": "vae_name" + }, + "link": null + } + ], + "outputs": [ + { + "localized_name": "VAE", + "name": "VAE", + "type": "VAE", + "links": [ + 16 + ] + } + ], + "title": "Video VAELoader", + "properties": { + "cnr_id": "comfyui-kjnodes", + "ver": "1.2.5", + "Node name for S&R": "VAELoaderKJ" + }, + "widgets_values": [ + "LTX23_video_vae_bf16.safetensors", + "main_device", + "bf16" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 84, + "type": "DualCLIPLoader", + "pos": [ + 1530, + 4290 + ], + "size": [ + 270, + 130 + ], + "flags": {}, + "order": 4, + "mode": 0, + "inputs": [ + { + "label": "text_encoder", + "localized_name": "clip_name1", + "name": "clip_name1", + "type": "COMBO", + "widget": { + "name": "clip_name1" + }, + "link": null + } + ], + "outputs": [ + { + "localized_name": "CLIP", + "name": "CLIP", + "type": "CLIP", + "links": [ + 211 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.19.3", + "Node name for S&R": "DualCLIPLoader" + }, + "widgets_values": [ + "gemma_3_12B_it_fp4_mixed.safetensors", + "ltx-2.3_text_projection_bf16.safetensors", + "ltxv", + "default" + ], + "color": "#222", + "bgcolor": "#000" + } + ], + "groups": [], + "links": [ + { + "id": 197, + "origin_id": 79, + "origin_slot": 0, + "target_id": 80, + "target_slot": 0, + "type": "MODEL" + }, + { + "id": 210, + "origin_id": 77, + "origin_slot": 0, + "target_id": 79, + "target_slot": 0, + "type": "MODEL" + }, + { + "id": 196, + "origin_id": 78, + "origin_slot": 0, + "target_id": 79, + "target_slot": 2, + "type": "VAE" + }, + { + "id": 199, + "origin_id": 80, + "origin_slot": 0, + "target_id": -20, + "target_slot": 0, + "type": "MODEL" + }, + { + "id": 211, + "origin_id": 84, + "origin_slot": 0, + "target_id": -20, + "target_slot": 1, + "type": "CLIP" + }, + { + "id": 58, + "origin_id": 4, + "origin_slot": 0, + "target_id": -20, + "target_slot": 2, + "type": "VAE" + }, + { + "id": 16, + "origin_id": 3, + "origin_slot": 0, + "target_id": -20, + "target_slot": 3, + "type": "VAE" + } + ], + "extra": { + "workflowRendererVersion": "LG" + } + }, + { + "id": "4b017e47-bf16-441b-ad03-edce978b3be3", + "version": 1, + "state": { + "lastGroupId": 6, + "lastNodeId": 96, + "lastLinkId": 250, + "lastRerouteId": 0 + }, + "revision": 0, + "config": {}, + "name": "Stage #1", + "inputNode": { + "id": -10, + "bounding": [ + 2860, + 4380, + 120, + 200 + ] + }, + "outputNode": { + "id": -20, + "bounding": [ + 4450, + 4360, + 120, + 140 + ] + }, + "inputs": [ + { + "id": "3c716dfb-4465-4519-8705-d5e7b88f1e35", + "name": "model", + "type": "MODEL", + "linkIds": [ + 115, + 114 + ], + "localized_name": "model", + "pos": [ + 2960, + 4400 + ] + }, + { + "id": "dcb44156-9e87-4ef1-a7fd-6b8cb4de0beb", + "name": "audio_latent", + "type": "LATENT", + "linkIds": [ + 119 + ], + "localized_name": "audio_latent", + "pos": [ + 2960, + 4420 + ] + }, + { + "id": "0c7b8f30-c29e-4147-96e9-db7f6ca77e06", + "name": "vae", + "type": "VAE", + "linkIds": [ + 216 + ], + "localized_name": "vae", + "pos": [ + 2960, + 4440 + ] + }, + { + "id": "4286d415-0cbb-4310-94d2-0efdccfb429f", + "name": "latent", + "type": "LATENT", + "linkIds": [ + 118 + ], + "localized_name": "latent", + "label": "video_latent", + "pos": [ + 2960, + 4460 + ] + }, + { + "id": "24eebedb-6830-4afb-9337-bd06b1300d44", + "name": "guide_data", + "type": "GUIDE_DATA", + "linkIds": [ + 122 + ], + "localized_name": "guide_data", + "pos": [ + 2960, + 4480 + ] + }, + { + "id": "1dc63a0e-c430-406b-86a8-6eb45cbc7885", + "name": "positive", + "type": "CONDITIONING", + "linkIds": [ + 116, + 117 + ], + "localized_name": "positive", + "pos": [ + 2960, + 4500 + ] + }, + { + "id": "02c2f048-017d-4fec-8440-19aa412bf8c0", + "name": "frame_rate", + "type": "FLOAT", + "linkIds": [ + 120 + ], + "localized_name": "frame_rate", + "pos": [ + 2960, + 4520 + ] + }, + { + "id": "f8636f26-27cb-4c55-972e-e5d1f6f39fff", + "name": "noise_seed", + "type": "INT", + "linkIds": [ + 250 + ], + "pos": [ + 2960, + 4540 + ] + } + ], + "outputs": [ + { + "id": "84f8d65a-1c3c-4fdc-8cab-7abaf67c1875", + "name": "positive", + "type": "CONDITIONING", + "linkIds": [ + 54, + 54 + ], + "localized_name": "positive", + "pos": [ + 4470, + 4380 + ] + }, + { + "id": "6275837a-94b0-4318-bbce-15969c26e42b", + "name": "negative", + "type": "CONDITIONING", + "linkIds": [ + 55, + 55 + ], + "localized_name": "negative", + "pos": [ + 4470, + 4400 + ] + }, + { + "id": "4c1b1c7e-3c92-4c13-8495-539df653574e", + "name": "NOISE", + "type": "NOISE", + "linkIds": [ + 157 + ], + "localized_name": "NOISE", + "pos": [ + 4470, + 4420 + ] + }, + { + "id": "f0f1c347-9ebf-496e-aea8-ca0a0ff7601e", + "name": "video_latent", + "type": "LATENT", + "linkIds": [ + 142 + ], + "localized_name": "video_latent", + "pos": [ + 4470, + 4440 + ] + }, + { + "id": "bf7e617a-cf3a-4ef6-a485-d3ab3e84742a", + "name": "audio_latent", + "type": "LATENT", + "linkIds": [ + 143 + ], + "localized_name": "audio_latent", + "pos": [ + 4470, + 4460 + ] + } + ], + "widgets": [], + "nodes": [ + { + "id": 11, + "type": "BasicScheduler", + "pos": [ + 3740, + 4620 + ], + "size": [ + 210, + 110 + ], + "flags": {}, + "order": 7, + "mode": 0, + "inputs": [ + { + "localized_name": "model", + "name": "model", + "type": "MODEL", + "link": 115 + } + ], + "outputs": [ + { + "localized_name": "SIGMAS", + "name": "SIGMAS", + "type": "SIGMAS", + "links": [ + 52 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.16.4", + "Node name for S&R": "BasicScheduler" + }, + "widgets_values": [ + "linear_quadratic", + 8, + 1 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 10, + "type": "SamplerCustomAdvanced", + "pos": [ + 3960, + 4390 + ], + "size": [ + 220, + 210 + ], + "flags": {}, + "order": 6, + "mode": 0, + "inputs": [ + { + "localized_name": "noise", + "name": "noise", + "type": "NOISE", + "link": 51 + }, + { + "localized_name": "guider", + "name": "guider", + "type": "GUIDER", + "link": 12 + }, + { + "localized_name": "sampler", + "name": "sampler", + "type": "SAMPLER", + "link": 53 + }, + { + "localized_name": "sigmas", + "name": "sigmas", + "type": "SIGMAS", + "link": 52 + }, + { + "localized_name": "latent_image", + "name": "latent_image", + "type": "LATENT", + "link": 14 + } + ], + "outputs": [ + { + "localized_name": "output", + "name": "output", + "type": "LATENT", + "links": [ + 156 + ] + }, + { + "localized_name": "denoised_output", + "name": "denoised_output", + "type": "LATENT", + "links": [] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.60", + "Node name for S&R": "SamplerCustomAdvanced", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 29, + "type": "KSamplerSelect", + "pos": [ + 3740, + 4520 + ], + "size": [ + 210, + 60 + ], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [], + "outputs": [ + { + "localized_name": "SAMPLER", + "name": "SAMPLER", + "type": "SAMPLER", + "links": [ + 53 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.56", + "Node name for S&R": "KSamplerSelect", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [ + "euler" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 7, + "type": "LTXVConcatAVLatent", + "pos": [ + 3520, + 4530 + ], + "size": [ + 210, + 50 + ], + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [ + { + "localized_name": "video_latent", + "name": "video_latent", + "type": "LATENT", + "link": 108 + }, + { + "localized_name": "audio_latent", + "name": "audio_latent", + "type": "LATENT", + "link": 119 + } + ], + "outputs": [ + { + "localized_name": "latent", + "name": "latent", + "type": "LATENT", + "links": [ + 14 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.16.0", + "Node name for S&R": "LTXVConcatAVLatent" + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 8, + "type": "LTXDirectorGuide", + "pos": [ + 3290, + 4400 + ], + "size": [ + 210, + 180 + ], + "flags": {}, + "order": 4, + "mode": 0, + "inputs": [ + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "link": 6 + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "link": 7 + }, + { + "localized_name": "vae", + "name": "vae", + "type": "VAE", + "link": 216 + }, + { + "localized_name": "latent", + "name": "latent", + "type": "LATENT", + "link": 118 + }, + { + "localized_name": "guide_data", + "name": "guide_data", + "type": "GUIDE_DATA", + "link": 122 + } + ], + "outputs": [ + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "links": [ + 10, + 54 + ] + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "links": [ + 11, + 55 + ] + }, + { + "localized_name": "latent", + "name": "latent", + "type": "LATENT", + "links": [ + 108 + ] + } + ], + "properties": { + "cnr_id": "whatdreamscost-comfyui", + "ver": "5cae3c00186ff2eb79771070f05a2c4e02f329bb", + "Node name for S&R": "LTXDirectorGuide", + "aux_id": "kijai/ComfyUI-PromptRelay" + }, + "widgets_values": [ + 0.5, + "bicubic" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 9, + "type": "CFGGuider", + "pos": [ + 3520, + 4390 + ], + "size": [ + 210, + 100 + ], + "flags": {}, + "order": 5, + "mode": 0, + "inputs": [ + { + "localized_name": "model", + "name": "model", + "type": "MODEL", + "link": 114 + }, + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "link": 10 + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "link": 11 + } + ], + "outputs": [ + { + "localized_name": "GUIDER", + "name": "GUIDER", + "type": "GUIDER", + "links": [ + 12 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.64", + "Node name for S&R": "CFGGuider", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [ + 1 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 5, + "type": "LTXVConditioning", + "pos": [ + 3040, + 4400 + ], + "size": [ + 240, + 80 + ], + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [ + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "link": 116 + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "link": 50 + }, + { + "localized_name": "frame_rate", + "name": "frame_rate", + "type": "FLOAT", + "widget": { + "name": "frame_rate" + }, + "link": 120 + } + ], + "outputs": [ + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "links": [ + 6 + ] + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "links": [ + 7 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.56", + "Node name for S&R": "LTXVConditioning", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [ + 24 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 26, + "type": "ConditioningZeroOut", + "pos": [ + 3040, + 4530 + ], + "size": [ + 240, + 30 + ], + "flags": {}, + "order": 9, + "mode": 0, + "inputs": [ + { + "localized_name": "conditioning", + "name": "conditioning", + "type": "CONDITIONING", + "link": 117 + } + ], + "outputs": [ + { + "localized_name": "CONDITIONING", + "name": "CONDITIONING", + "type": "CONDITIONING", + "links": [ + 50 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.19.3", + "Node name for S&R": "ConditioningZeroOut" + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 13, + "type": "LTXVSeparateAVLatent", + "pos": [ + 4190, + 4390 + ], + "size": [ + 200, + 50 + ], + "flags": {}, + "order": 8, + "mode": 0, + "inputs": [ + { + "localized_name": "av_latent", + "name": "av_latent", + "type": "LATENT", + "link": 156 + } + ], + "outputs": [ + { + "localized_name": "video_latent", + "name": "video_latent", + "type": "LATENT", + "links": [ + 142 + ] + }, + { + "localized_name": "audio_latent", + "name": "audio_latent", + "type": "LATENT", + "links": [ + 143 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.16.0", + "Node name for S&R": "LTXVSeparateAVLatent" + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 28, + "type": "RandomNoise", + "pos": [ + 3740, + 4390 + ], + "size": [ + 210, + 90 + ], + "flags": {}, + "order": 1, + "mode": 0, + "inputs": [ + { + "label": "noise_seed", + "localized_name": "noise_seed", + "name": "noise_seed", + "type": "INT", + "widget": { + "name": "noise_seed" + }, + "link": 250 + } + ], + "outputs": [ + { + "localized_name": "NOISE", + "name": "NOISE", + "type": "NOISE", + "links": [ + 51, + 157 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.19.3", + "Node name for S&R": "RandomNoise" + }, + "widgets_values": [ + 12, + "fixed" + ], + "color": "#222", + "bgcolor": "#000" + } + ], + "groups": [], + "links": [ + { + "id": 51, + "origin_id": 28, + "origin_slot": 0, + "target_id": 10, + "target_slot": 0, + "type": "NOISE" + }, + { + "id": 12, + "origin_id": 9, + "origin_slot": 0, + "target_id": 10, + "target_slot": 1, + "type": "GUIDER" + }, + { + "id": 53, + "origin_id": 29, + "origin_slot": 0, + "target_id": 10, + "target_slot": 2, + "type": "SAMPLER" + }, + { + "id": 52, + "origin_id": 11, + "origin_slot": 0, + "target_id": 10, + "target_slot": 3, + "type": "SIGMAS" + }, + { + "id": 14, + "origin_id": 7, + "origin_slot": 0, + "target_id": 10, + "target_slot": 4, + "type": "LATENT" + }, + { + "id": 108, + "origin_id": 8, + "origin_slot": 2, + "target_id": 7, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 6, + "origin_id": 5, + "origin_slot": 0, + "target_id": 8, + "target_slot": 0, + "type": "CONDITIONING" + }, + { + "id": 7, + "origin_id": 5, + "origin_slot": 1, + "target_id": 8, + "target_slot": 1, + "type": "CONDITIONING" + }, + { + "id": 10, + "origin_id": 8, + "origin_slot": 0, + "target_id": 9, + "target_slot": 1, + "type": "CONDITIONING" + }, + { + "id": 11, + "origin_id": 8, + "origin_slot": 1, + "target_id": 9, + "target_slot": 2, + "type": "CONDITIONING" + }, + { + "id": 50, + "origin_id": 26, + "origin_slot": 0, + "target_id": 5, + "target_slot": 1, + "type": "CONDITIONING" + }, + { + "id": 156, + "origin_id": 10, + "origin_slot": 0, + "target_id": 13, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 115, + "origin_id": -10, + "origin_slot": 0, + "target_id": 11, + "target_slot": 0, + "type": "MODEL" + }, + { + "id": 114, + "origin_id": -10, + "origin_slot": 0, + "target_id": 9, + "target_slot": 0, + "type": "MODEL" + }, + { + "id": 119, + "origin_id": -10, + "origin_slot": 1, + "target_id": 7, + "target_slot": 1, + "type": "LATENT" + }, + { + "id": 216, + "origin_id": -10, + "origin_slot": 2, + "target_id": 8, + "target_slot": 2, + "type": "VAE" + }, + { + "id": 118, + "origin_id": -10, + "origin_slot": 3, + "target_id": 8, + "target_slot": 3, + "type": "LATENT" + }, + { + "id": 122, + "origin_id": -10, + "origin_slot": 4, + "target_id": 8, + "target_slot": 4, + "type": "GUIDE_DATA" + }, + { + "id": 116, + "origin_id": -10, + "origin_slot": 5, + "target_id": 5, + "target_slot": 0, + "type": "CONDITIONING" + }, + { + "id": 117, + "origin_id": -10, + "origin_slot": 5, + "target_id": 26, + "target_slot": 0, + "type": "CONDITIONING" + }, + { + "id": 120, + "origin_id": -10, + "origin_slot": 6, + "target_id": 5, + "target_slot": 2, + "type": "FLOAT" + }, + { + "id": 54, + "origin_id": 8, + "origin_slot": 0, + "target_id": -20, + "target_slot": 0, + "type": "CONDITIONING" + }, + { + "id": 55, + "origin_id": 8, + "origin_slot": 1, + "target_id": -20, + "target_slot": 1, + "type": "CONDITIONING" + }, + { + "id": 157, + "origin_id": 28, + "origin_slot": 0, + "target_id": -20, + "target_slot": 2, + "type": "NOISE" + }, + { + "id": 142, + "origin_id": 13, + "origin_slot": 0, + "target_id": -20, + "target_slot": 3, + "type": "LATENT" + }, + { + "id": 143, + "origin_id": 13, + "origin_slot": 1, + "target_id": -20, + "target_slot": 4, + "type": "LATENT" + }, + { + "id": 250, + "origin_id": -10, + "origin_slot": 7, + "target_id": 28, + "target_slot": 0, + "type": "INT" + } + ], + "extra": { + "workflowRendererVersion": "LG" + } + }, + { + "id": "7cf6430b-4784-4af1-8cde-9268e9465c62", + "version": 1, + "state": { + "lastGroupId": 6, + "lastNodeId": 96, + "lastLinkId": 250, + "lastRerouteId": 0 + }, + "revision": 0, + "config": {}, + "name": "Stage #2", + "inputNode": { + "id": -10, + "bounding": [ + 2870, + 3980, + 120, + 200 + ] + }, + "outputNode": { + "id": -20, + "bounding": [ + 4430, + 3900, + 120, + 80 + ] + }, + "inputs": [ + { + "id": "da8ac1d4-baaa-49af-a44f-51de815b8adc", + "name": "positive", + "type": "CONDITIONING", + "linkIds": [ + 228, + 227 + ], + "localized_name": "positive", + "pos": [ + 2970, + 4000 + ] + }, + { + "id": "2a2ae901-93e9-4551-97e5-bf33db9a7761", + "name": "negative", + "type": "CONDITIONING", + "linkIds": [ + 230, + 229 + ], + "localized_name": "negative", + "pos": [ + 2970, + 4020 + ] + }, + { + "id": "f6f33172-a93d-4505-a3c8-4e4e61488f94", + "name": "latent", + "type": "LATENT", + "linkIds": [ + 232 + ], + "localized_name": "latent", + "label": "video_latent", + "pos": [ + 2970, + 4040 + ] + }, + { + "id": "9d67923c-e4e8-4eb5-a91b-b37dbfb5882f", + "name": "vae", + "type": "VAE", + "linkIds": [ + 219, + 218 + ], + "localized_name": "vae", + "label": "video_vae", + "pos": [ + 2970, + 4060 + ] + }, + { + "id": "c5f8e7de-87ec-4509-892c-8cc4e1f14575", + "name": "guide_data", + "type": "GUIDE_DATA", + "linkIds": [ + 151 + ], + "localized_name": "guide_data", + "pos": [ + 2970, + 4080 + ] + }, + { + "id": "c9c467d0-0426-4288-a31e-b41be1891bdc", + "name": "model", + "type": "MODEL", + "linkIds": [ + 158, + 159 + ], + "localized_name": "model", + "pos": [ + 2970, + 4100 + ] + }, + { + "id": "1c4bd34a-4e48-455a-bd66-323524e5c2d8", + "name": "noise", + "type": "NOISE", + "linkIds": [ + 231 + ], + "localized_name": "noise", + "pos": [ + 2970, + 4120 + ] + }, + { + "id": "ef0cc4fb-4a9f-404b-a62c-92e04fd74171", + "name": "audio_latent", + "type": "LATENT", + "linkIds": [ + 233 + ], + "localized_name": "audio_latent", + "pos": [ + 2970, + 4140 + ] + } + ], + "outputs": [ + { + "id": "402aed7a-321d-48a8-a407-34f05a5ab1e2", + "name": "audio_latent", + "type": "LATENT", + "linkIds": [ + 202 + ], + "localized_name": "audio_latent", + "pos": [ + 4450, + 3920 + ] + }, + { + "id": "8fe53957-38d9-459e-a590-3ad4d96d3616", + "name": "latent", + "type": "LATENT", + "linkIds": [ + 208 + ], + "localized_name": "latent", + "pos": [ + 4450, + 3940 + ] + } + ], + "widgets": [], + "nodes": [ + { + "id": 55, + "type": "LTXVCropGuides", + "pos": [ + 3050, + 3930 + ], + "size": [ + 250, + 70 + ], + "flags": {}, + "order": 9, + "mode": 0, + "inputs": [ + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "link": 228 + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "link": 230 + }, + { + "localized_name": "latent", + "name": "latent", + "type": "LATENT", + "link": 232 + } + ], + "outputs": [ + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "links": [ + 146 + ] + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "links": [ + 147 + ] + }, + { + "localized_name": "latent", + "name": "latent", + "type": "LATENT", + "links": [ + 135 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.16.4", + "Node name for S&R": "LTXVCropGuides" + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 58, + "type": "LTXDirectorGuide", + "pos": [ + 3310, + 3930 + ], + "size": [ + 210, + 190 + ], + "flags": {}, + "order": 10, + "mode": 0, + "inputs": [ + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "link": 146 + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "link": 147 + }, + { + "localized_name": "vae", + "name": "vae", + "type": "VAE", + "link": 219 + }, + { + "localized_name": "latent", + "name": "latent", + "type": "LATENT", + "link": 150 + }, + { + "localized_name": "guide_data", + "name": "guide_data", + "type": "GUIDE_DATA", + "link": 151 + } + ], + "outputs": [ + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "links": [ + 152 + ] + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "links": [ + 153 + ] + }, + { + "localized_name": "latent", + "name": "latent", + "type": "LATENT", + "links": [ + 154 + ] + } + ], + "properties": { + "cnr_id": "whatdreamscost-comfyui", + "ver": "5cae3c00186ff2eb79771070f05a2c4e02f329bb", + "Node name for S&R": "LTXDirectorGuide", + "aux_id": "kijai/ComfyUI-PromptRelay" + }, + "widgets_values": [ + 1, + "bicubic" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 49, + "type": "CFGGuider", + "pos": [ + 3540, + 3920 + ], + "size": [ + 210, + 100 + ], + "flags": {}, + "order": 5, + "mode": 0, + "inputs": [ + { + "localized_name": "model", + "name": "model", + "type": "MODEL", + "link": 158 + }, + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "link": 152 + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "link": 153 + } + ], + "outputs": [ + { + "localized_name": "GUIDER", + "name": "GUIDER", + "type": "GUIDER", + "links": [ + 124 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.64", + "Node name for S&R": "CFGGuider", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [ + 1 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 53, + "type": "KSamplerSelect", + "pos": [ + 3540, + 4060 + ], + "size": [ + 210, + 60 + ], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [], + "outputs": [ + { + "localized_name": "SAMPLER", + "name": "SAMPLER", + "type": "SAMPLER", + "links": [ + 125 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.56", + "Node name for S&R": "KSamplerSelect", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [ + "euler" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 47, + "type": "SamplerCustomAdvanced", + "pos": [ + 3760, + 3920 + ], + "size": [ + 230, + 220 + ], + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [ + { + "localized_name": "noise", + "name": "noise", + "type": "NOISE", + "link": 231 + }, + { + "localized_name": "guider", + "name": "guider", + "type": "GUIDER", + "link": 124 + }, + { + "localized_name": "sampler", + "name": "sampler", + "type": "SAMPLER", + "link": 125 + }, + { + "localized_name": "sigmas", + "name": "sigmas", + "type": "SIGMAS", + "link": 126 + }, + { + "localized_name": "latent_image", + "name": "latent_image", + "type": "LATENT", + "link": 127 + } + ], + "outputs": [ + { + "localized_name": "output", + "name": "output", + "type": "LATENT", + "links": [ + 180 + ] + }, + { + "localized_name": "denoised_output", + "name": "denoised_output", + "type": "LATENT", + "links": [] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.60", + "Node name for S&R": "SamplerCustomAdvanced", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 57, + "type": "LatentUpscaleModelLoader", + "pos": [ + 3050, + 4040 + ], + "size": [ + 250, + 70 + ], + "flags": {}, + "order": 1, + "mode": 0, + "inputs": [], + "outputs": [ + { + "localized_name": "LATENT_UPSCALE_MODEL", + "name": "LATENT_UPSCALE_MODEL", + "type": "LATENT_UPSCALE_MODEL", + "links": [ + 144 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.16.4", + "Node name for S&R": "LatentUpscaleModelLoader" + }, + "widgets_values": [ + "ltx-2.3-spatial-upscaler-x2-1.1.safetensors" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 48, + "type": "LTXVSeparateAVLatent", + "pos": [ + 4000, + 3920 + ], + "size": [ + 200, + 50 + ], + "flags": {}, + "order": 4, + "mode": 0, + "inputs": [ + { + "localized_name": "av_latent", + "name": "av_latent", + "type": "LATENT", + "link": 180 + } + ], + "outputs": [ + { + "localized_name": "video_latent", + "name": "video_latent", + "type": "LATENT", + "links": [ + 136 + ] + }, + { + "localized_name": "audio_latent", + "name": "audio_latent", + "type": "LATENT", + "links": [ + 202 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.5.1", + "Node name for S&R": "LTXVSeparateAVLatent", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 52, + "type": "LTXVLatentUpsampler", + "pos": [ + 3050, + 4150 + ], + "size": [ + 250, + 70 + ], + "flags": {}, + "order": 7, + "mode": 0, + "inputs": [ + { + "localized_name": "samples", + "name": "samples", + "type": "LATENT", + "link": 135 + }, + { + "localized_name": "upscale_model", + "name": "upscale_model", + "type": "LATENT_UPSCALE_MODEL", + "link": 144 + }, + { + "localized_name": "vae", + "name": "vae", + "type": "VAE", + "link": 218 + } + ], + "outputs": [ + { + "localized_name": "LATENT", + "name": "LATENT", + "type": "LATENT", + "links": [ + 150 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.14.1", + "Node name for S&R": "LTXVLatentUpsampler", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 54, + "type": "BasicScheduler", + "pos": [ + 3540, + 4160 + ], + "size": [ + 210, + 110 + ], + "flags": {}, + "order": 8, + "mode": 0, + "inputs": [ + { + "localized_name": "model", + "name": "model", + "type": "MODEL", + "link": 159 + } + ], + "outputs": [ + { + "localized_name": "SIGMAS", + "name": "SIGMAS", + "type": "SIGMAS", + "links": [ + 126 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.16.4", + "Node name for S&R": "BasicScheduler" + }, + "widgets_values": [ + "linear_quadratic", + 4, + 0.42 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 50, + "type": "LTXVConcatAVLatent", + "pos": [ + 3310, + 4160 + ], + "size": [ + 210, + 60 + ], + "flags": {}, + "order": 6, + "mode": 0, + "inputs": [ + { + "localized_name": "video_latent", + "name": "video_latent", + "type": "LATENT", + "link": 154 + }, + { + "localized_name": "audio_latent", + "name": "audio_latent", + "type": "LATENT", + "link": 233 + } + ], + "outputs": [ + { + "localized_name": "latent", + "name": "latent", + "type": "LATENT", + "links": [ + 127 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.7.0", + "Node name for S&R": "LTXVConcatAVLatent", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 14, + "type": "LTXVCropGuides", + "pos": [ + 4210, + 3920 + ], + "size": [ + 180, + 70 + ], + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [ + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "link": 227 + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "link": 229 + }, + { + "localized_name": "latent", + "name": "latent", + "type": "LATENT", + "link": 136 + } + ], + "outputs": [ + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "links": null + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "links": null + }, + { + "localized_name": "latent", + "name": "latent", + "type": "LATENT", + "links": [ + 208 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.19.3", + "Node name for S&R": "LTXVCropGuides" + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + } + ], + "groups": [], + "links": [ + { + "id": 146, + "origin_id": 55, + "origin_slot": 0, + "target_id": 58, + "target_slot": 0, + "type": "CONDITIONING" + }, + { + "id": 147, + "origin_id": 55, + "origin_slot": 1, + "target_id": 58, + "target_slot": 1, + "type": "CONDITIONING" + }, + { + "id": 150, + "origin_id": 52, + "origin_slot": 0, + "target_id": 58, + "target_slot": 3, + "type": "LATENT" + }, + { + "id": 152, + "origin_id": 58, + "origin_slot": 0, + "target_id": 49, + "target_slot": 1, + "type": "CONDITIONING" + }, + { + "id": 153, + "origin_id": 58, + "origin_slot": 1, + "target_id": 49, + "target_slot": 2, + "type": "CONDITIONING" + }, + { + "id": 124, + "origin_id": 49, + "origin_slot": 0, + "target_id": 47, + "target_slot": 1, + "type": "GUIDER" + }, + { + "id": 125, + "origin_id": 53, + "origin_slot": 0, + "target_id": 47, + "target_slot": 2, + "type": "SAMPLER" + }, + { + "id": 126, + "origin_id": 54, + "origin_slot": 0, + "target_id": 47, + "target_slot": 3, + "type": "SIGMAS" + }, + { + "id": 127, + "origin_id": 50, + "origin_slot": 0, + "target_id": 47, + "target_slot": 4, + "type": "LATENT" + }, + { + "id": 180, + "origin_id": 47, + "origin_slot": 0, + "target_id": 48, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 135, + "origin_id": 55, + "origin_slot": 2, + "target_id": 52, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 144, + "origin_id": 57, + "origin_slot": 0, + "target_id": 52, + "target_slot": 1, + "type": "LATENT_UPSCALE_MODEL" + }, + { + "id": 154, + "origin_id": 58, + "origin_slot": 2, + "target_id": 50, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 136, + "origin_id": 48, + "origin_slot": 0, + "target_id": 14, + "target_slot": 2, + "type": "LATENT" + }, + { + "id": 228, + "origin_id": -10, + "origin_slot": 0, + "target_id": 55, + "target_slot": 0, + "type": "CONDITIONING" + }, + { + "id": 227, + "origin_id": -10, + "origin_slot": 0, + "target_id": 14, + "target_slot": 0, + "type": "CONDITIONING" + }, + { + "id": 230, + "origin_id": -10, + "origin_slot": 1, + "target_id": 55, + "target_slot": 1, + "type": "CONDITIONING" + }, + { + "id": 229, + "origin_id": -10, + "origin_slot": 1, + "target_id": 14, + "target_slot": 1, + "type": "CONDITIONING" + }, + { + "id": 232, + "origin_id": -10, + "origin_slot": 2, + "target_id": 55, + "target_slot": 2, + "type": "LATENT" + }, + { + "id": 219, + "origin_id": -10, + "origin_slot": 3, + "target_id": 58, + "target_slot": 2, + "type": "VAE" + }, + { + "id": 218, + "origin_id": -10, + "origin_slot": 3, + "target_id": 52, + "target_slot": 2, + "type": "VAE" + }, + { + "id": 151, + "origin_id": -10, + "origin_slot": 4, + "target_id": 58, + "target_slot": 4, + "type": "GUIDE_DATA" + }, + { + "id": 158, + "origin_id": -10, + "origin_slot": 5, + "target_id": 49, + "target_slot": 0, + "type": "MODEL" + }, + { + "id": 159, + "origin_id": -10, + "origin_slot": 5, + "target_id": 54, + "target_slot": 0, + "type": "MODEL" + }, + { + "id": 231, + "origin_id": -10, + "origin_slot": 6, + "target_id": 47, + "target_slot": 0, + "type": "NOISE" + }, + { + "id": 233, + "origin_id": -10, + "origin_slot": 7, + "target_id": 50, + "target_slot": 1, + "type": "LATENT" + }, + { + "id": 202, + "origin_id": 48, + "origin_slot": 1, + "target_id": -20, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 208, + "origin_id": 14, + "origin_slot": 2, + "target_id": -20, + "target_slot": 1, + "type": "LATENT" + } + ], + "extra": { + "workflowRendererVersion": "LG" + } + }, + { + "id": "739520a4-718f-490f-87d1-34ffbca98e3f", + "version": 1, + "state": { + "lastGroupId": 6, + "lastNodeId": 96, + "lastLinkId": 250, + "lastRerouteId": 0 + }, + "revision": 0, + "config": {}, + "name": "Decode", + "inputNode": { + "id": -10, + "bounding": [ + 3330, + 4105, + 120, + 140 + ] + }, + "outputNode": { + "id": -20, + "bounding": [ + 3780, + 4145, + 120, + 60 + ] + }, + "inputs": [ + { + "id": "35c40a03-3855-445e-a691-ae0ada359280", + "name": "samples", + "type": "LATENT", + "linkIds": [ + 242 + ], + "localized_name": "samples", + "label": "audio_latent", + "pos": [ + 3430, + 4125 + ] + }, + { + "id": "ddfb8ed1-1fd2-4086-ad94-d816dab3b6bb", + "name": "audio_vae", + "type": "VAE", + "linkIds": [ + 214 + ], + "localized_name": "audio_vae", + "label": "audio_vae", + "pos": [ + 3430, + 4145 + ] + }, + { + "id": "458157ba-efe8-4aa2-847e-be97d78a6873", + "name": "samples_1", + "type": "LATENT", + "linkIds": [ + 243 + ], + "localized_name": "samples_1", + "label": "video_latent", + "pos": [ + 3430, + 4165 + ] + }, + { + "id": "b02f1f71-c594-437f-8dda-4bb0f19c2e3e", + "name": "vae", + "type": "VAE", + "linkIds": [ + 217 + ], + "localized_name": "vae", + "label": "video_vae", + "pos": [ + 3430, + 4185 + ] + }, + { + "id": "83aa76da-c7d7-41b5-a3d6-4942595be90c", + "name": "fps", + "type": "FLOAT", + "linkIds": [ + 121 + ], + "localized_name": "fps", + "pos": [ + 3430, + 4205 + ] + } + ], + "outputs": [ + { + "id": "7d63e3c3-78d0-4613-9631-0fa830d1bf9a", + "name": "VIDEO", + "type": "VIDEO", + "linkIds": [ + 56 + ], + "localized_name": "VIDEO", + "pos": [ + 3800, + 4165 + ] + } + ], + "widgets": [], + "nodes": [ + { + "id": 16, + "type": "LTXVAudioVAEDecode", + "pos": [ + 3510, + 4060 + ], + "size": [ + 210, + 50 + ], + "flags": {}, + "order": 1, + "mode": 0, + "inputs": [ + { + "localized_name": "samples", + "name": "samples", + "type": "LATENT", + "link": 242 + }, + { + "label": "Audio VAE", + "localized_name": "audio_vae", + "name": "audio_vae", + "type": "VAE", + "link": 214 + } + ], + "outputs": [ + { + "localized_name": "Audio", + "name": "Audio", + "type": "AUDIO", + "links": [ + 25 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.16.0", + "Node name for S&R": "LTXVAudioVAEDecode" + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 15, + "type": "VAEDecode", + "pos": [ + 3510, + 4150 + ], + "size": [ + 210, + 50 + ], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [ + { + "localized_name": "samples", + "name": "samples", + "type": "LATENT", + "link": 243 + }, + { + "localized_name": "vae", + "name": "vae", + "type": "VAE", + "link": 217 + } + ], + "outputs": [ + { + "localized_name": "IMAGE", + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 24 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.9.1", + "Node name for S&R": "VAEDecode" + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 17, + "type": "CreateVideo", + "pos": [ + 3510, + 4240 + ], + "size": [ + 210, + 80 + ], + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [ + { + "localized_name": "images", + "name": "images", + "type": "IMAGE", + "link": 24 + }, + { + "localized_name": "audio", + "name": "audio", + "shape": 7, + "type": "AUDIO", + "link": 25 + }, + { + "localized_name": "fps", + "name": "fps", + "type": "FLOAT", + "widget": { + "name": "fps" + }, + "link": 121 + } + ], + "outputs": [ + { + "localized_name": "VIDEO", + "name": "VIDEO", + "type": "VIDEO", + "links": [ + 56 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.19.3", + "Node name for S&R": "CreateVideo" + }, + "widgets_values": [ + 24 + ], + "color": "#222", + "bgcolor": "#000" + } + ], + "groups": [], + "links": [ + { + "id": 24, + "origin_id": 15, + "origin_slot": 0, + "target_id": 17, + "target_slot": 0, + "type": "IMAGE" + }, + { + "id": 25, + "origin_id": 16, + "origin_slot": 0, + "target_id": 17, + "target_slot": 1, + "type": "AUDIO" + }, + { + "id": 242, + "origin_id": -10, + "origin_slot": 0, + "target_id": 16, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 214, + "origin_id": -10, + "origin_slot": 1, + "target_id": 16, + "target_slot": 1, + "type": "VAE" + }, + { + "id": 243, + "origin_id": -10, + "origin_slot": 2, + "target_id": 15, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 217, + "origin_id": -10, + "origin_slot": 3, + "target_id": 15, + "target_slot": 1, + "type": "VAE" + }, + { + "id": 121, + "origin_id": -10, + "origin_slot": 4, + "target_id": 17, + "target_slot": 2, + "type": "FLOAT" + }, + { + "id": 56, + "origin_id": 17, + "origin_slot": 0, + "target_id": -20, + "target_slot": 0, + "type": "VIDEO" + } + ], + "extra": { + "workflowRendererVersion": "LG" + } + } + ] + }, + "config": {}, + "extra": { + "frontendVersion": "1.40.0", + "VHS_latentpreview": false, + "VHS_latentpreviewrate": 0, + "VHS_MetadataImage": true, + "VHS_KeepIntermediate": true, + "ds": { + "scale": 0.8964143292718684, + "offset": [ + -1084.7781214353931, + -3754.5892800523366 + ] + }, + "workflowRendererVersion": "LG" + }, + "version": 0.4 } \ No newline at end of file diff --git a/example_workflows/LTX Director Workflow_cs.json b/example_workflows/LTX Director Workflow_cs.json new file mode 100644 index 0000000..5dc7144 --- /dev/null +++ b/example_workflows/LTX Director Workflow_cs.json @@ -0,0 +1 @@ +{"id":"5db2503d-8871-4398-bccf-8a89ed9d8841","revision":0,"last_node_id":150,"last_link_id":337,"nodes":[{"id":50,"type":"LTXVConcatAVLatent","pos":[3293.558756510417,4131.182287967565],"size":[210,60],"flags":{},"order":54,"mode":0,"inputs":[{"localized_name":"video_latent","name":"video_latent","type":"LATENT","link":154},{"localized_name":"audio_latent","name":"audio_latent","type":"LATENT","link":143}],"outputs":[{"localized_name":"latent","name":"latent","type":"LATENT","links":[127]}],"properties":{"cnr_id":"comfy-core","ver":"0.7.0","Node name for S&R":"LTXVConcatAVLatent","enableTabs":false,"tabWidth":65,"tabXOffset":10,"hasSecondTab":false,"secondTabText":"Send Back","secondTabOffset":80,"secondTabWidth":65},"widgets_values":[]},{"id":7,"type":"LTXVConcatAVLatent","pos":[3516.805994963843,4518.288648200755],"size":[210,50],"flags":{},"order":46,"mode":0,"inputs":[{"localized_name":"video_latent","name":"video_latent","type":"LATENT","link":108},{"localized_name":"audio_latent","name":"audio_latent","type":"LATENT","link":282}],"outputs":[{"localized_name":"latent","name":"latent","type":"LATENT","links":[14]}],"properties":{"cnr_id":"comfy-core","ver":"0.16.0","Node name for S&R":"LTXVConcatAVLatent"},"widgets_values":[]},{"id":55,"type":"LTXVCropGuides","pos":[3033.558756510417,3901.182287967567],"size":[250,70],"flags":{},"order":49,"mode":0,"inputs":[{"localized_name":"positive","name":"positive","type":"CONDITIONING","link":188},{"localized_name":"negative","name":"negative","type":"CONDITIONING","link":189},{"localized_name":"latent","name":"latent","type":"LATENT","link":142}],"outputs":[{"localized_name":"positive","name":"positive","type":"CONDITIONING","links":[146]},{"localized_name":"negative","name":"negative","type":"CONDITIONING","links":[147]},{"localized_name":"latent","name":"latent","type":"LATENT","links":[135]}],"properties":{"cnr_id":"comfy-core","ver":"0.16.4","Node name for S&R":"LTXVCropGuides"},"widgets_values":[]},{"id":92,"type":"MarkdownNote","pos":[992.7889713230645,4731.2150098993425],"size":[270,560],"flags":{"collapsed":true},"order":0,"mode":0,"inputs":[],"outputs":[],"title":"FAQ","properties":{},"widgets_values":["# FAQ\n\n**How do I use this??**\n\nI'll be posting tutorials on my YouTube channel soon!\n\n\n**Where do I download these models??**\n\nThere is a good list of models and download links here: \n\nAlso the latest v1.1 spatial upscaler can be found here: \n\nThe tiny vae (taeltx2_3.safetensors) is found here:\n\n\n**I'm getting errors when running this workflow!**\n\nDouble check that you have all of the correct models loaded. This is using the newest version of 2x upscaler (v1.1). You can change it to the older one under the Model Loader.\n\n**How can I support you?**\n\nHelp me get a job. I'm unemployed and literally broke. (seriously, i'll even scrub toilets for minimum wage)\n\nOr follow me on my socials :)\n\n\n\n\n\n\n"],"color":"#322","bgcolor":"#533"},{"id":78,"type":"VAELoaderKJ","pos":[990.6660127124089,3882.3244617066935],"size":[270,110],"flags":{"collapsed":true},"order":1,"mode":0,"inputs":[{"localized_name":"vae_name","name":"vae_name","type":"COMBO","widget":{"name":"vae_name"},"link":null},{"localized_name":"device","name":"device","type":"COMBO","widget":{"name":"device"},"link":null},{"localized_name":"weight_dtype","name":"weight_dtype","type":"COMBO","widget":{"name":"weight_dtype"},"link":null}],"outputs":[{"localized_name":"VAE","name":"VAE","type":"VAE","links":[196]}],"title":"Tiny VAELoader KJ","properties":{"cnr_id":"comfyui-kjnodes","ver":"1.2.5","Node name for S&R":"VAELoaderKJ"},"widgets_values":["taeltx2_3.safetensors","main_device","bf16"],"color":"#222","bgcolor":"#000"},{"id":94,"type":"UNETLoader","pos":[989.8512359027247,3883.195170992355],"size":[260.8147768096842,82],"flags":{},"order":2,"mode":0,"inputs":[{"localized_name":"unet_name","name":"unet_name","type":"COMBO","widget":{"name":"unet_name"},"link":null},{"localized_name":"weight_dtype","name":"weight_dtype","type":"COMBO","widget":{"name":"weight_dtype"},"link":null}],"outputs":[{"localized_name":"MODEL","name":"MODEL","type":"MODEL","links":[212]}],"properties":{"cnr_id":"comfy-core","ver":"0.21.1","Node name for S&R":"UNETLoader"},"widgets_values":["ltx\\ltx-2-3-22b-dev_transformer_only_fp8_input_scaled.safetensors","default"]},{"id":102,"type":"VHS_VideoCombine","pos":[4503.399045361829,3902.845212177243],"size":[846.91695475898,816.586274363422],"flags":{"collapsed":false},"order":60,"mode":0,"inputs":[{"localized_name":"images","name":"images","type":"IMAGE","link":222},{"localized_name":"audio","name":"audio","shape":7,"type":"AUDIO","link":223},{"localized_name":"meta_batch","name":"meta_batch","shape":7,"type":"VHS_BatchManager","link":null},{"localized_name":"vae","name":"vae","shape":7,"type":"VAE","link":null},{"localized_name":"frame_rate","name":"frame_rate","type":"FLOAT","widget":{"name":"frame_rate"},"link":279},{"localized_name":"loop_count","name":"loop_count","type":"INT","widget":{"name":"loop_count"},"link":null},{"localized_name":"filename_prefix","name":"filename_prefix","type":"STRING","widget":{"name":"filename_prefix"},"link":null},{"localized_name":"format","name":"format","type":"COMBO","widget":{"name":"format"},"link":null},{"localized_name":"pingpong","name":"pingpong","type":"BOOLEAN","widget":{"name":"pingpong"},"link":null},{"localized_name":"save_output","name":"save_output","type":"BOOLEAN","widget":{"name":"save_output"},"link":null},{"name":"pix_fmt","type":["yuv420p","p010le"],"widget":{"name":"pix_fmt"},"link":null},{"name":"bitrate","type":"INT","widget":{"name":"bitrate"},"link":null},{"name":"megabit","type":"BOOLEAN","widget":{"name":"megabit"},"link":null},{"name":"save_metadata","type":"BOOLEAN","widget":{"name":"save_metadata"},"link":null}],"outputs":[{"localized_name":"Filenames","name":"Filenames","type":"VHS_FILENAMES","links":null}],"properties":{"cnr_id":"comfyui-videohelpersuite","ver":"8923bd836bdab8b7bbdf4ed104b7d045e70c66e2","Node name for S&R":"VHS_VideoCombine"},"widgets_values":{"frame_rate":24,"loop_count":0,"filename_prefix":"LTX-2","format":"video/nvenc_h264-mp4","pix_fmt":"yuv420p","bitrate":10,"megabit":true,"save_metadata":true,"pingpong":false,"save_output":true,"videopreview":{"hidden":false,"paused":false,"params":{"filename":"LTX-2_00373-audio.mp4","subfolder":"","type":"output","format":"video/nvenc_h264-mp4","frame_rate":24,"workflow":"LTX-2_00373.png","fullpath":"S:\\ComfyUI\\ComfyUI\\output\\LTX-2_00373-audio.mp4"}}}},{"id":107,"type":"SetNode","pos":[1059.376057216068,4675.5170016463135],"size":[210,60],"flags":{"collapsed":true},"order":23,"mode":0,"inputs":[{"name":"VAE","type":"VAE","link":227}],"outputs":[{"name":"VAE","type":"VAE","links":null}],"title":"Set_audio_vae","properties":{"Node name for S&R":"SetNode","aux_id":"kijai/ComfyUI-KJNodes","previousName":"audio_vae"},"widgets_values":["audio_vae"],"color":"#322","bgcolor":"#533"},{"id":4,"type":"VAELoaderKJ","pos":[989.8512359027247,4619.775119357638],"size":[260,110],"flags":{"collapsed":false},"order":3,"mode":0,"inputs":[{"localized_name":"vae_name","name":"vae_name","type":"COMBO","widget":{"name":"vae_name"},"link":null},{"localized_name":"device","name":"device","type":"COMBO","widget":{"name":"device"},"link":null},{"localized_name":"weight_dtype","name":"weight_dtype","type":"COMBO","widget":{"name":"weight_dtype"},"link":null}],"outputs":[{"localized_name":"VAE","name":"VAE","type":"VAE","links":[227,276]}],"title":"Audio VAELoader","properties":{"cnr_id":"comfyui-kjnodes","ver":"487c7d86a0230aae3d8c0a37d517159b73834f85","Node name for S&R":"VAELoaderKJ"},"widgets_values":["vae_ltx\\LTX23_audio_vae_bf16.safetensors","main_device","bf16"]},{"id":108,"type":"GetNode","pos":[4211.339433902672,4599.840575634934],"size":[210,60],"flags":{"collapsed":true},"order":4,"mode":0,"inputs":[],"outputs":[{"name":"VAE","type":"VAE","links":[228]}],"title":"Get_audio_vae","properties":{"Node name for S&R":"GetNode","aux_id":"kijai/ComfyUI-KJNodes"},"widgets_values":["audio_vae"],"color":"#322","bgcolor":"#533"},{"id":109,"type":"SetNode","pos":[1055.4029246043383,4508.080338639629],"size":[210,60],"flags":{"collapsed":true},"order":24,"mode":0,"inputs":[{"name":"VAE","type":"VAE","link":229}],"outputs":[{"name":"VAE","type":"VAE","links":null}],"title":"Set_video_vae","properties":{"Node name for S&R":"SetNode","aux_id":"kijai/ComfyUI-KJNodes","previousName":"video_vae"},"widgets_values":["video_vae"],"color":"#322","bgcolor":"#533"},{"id":3,"type":"VAELoaderKJ","pos":[989.8512359027247,4474.070323805746],"size":[260,110],"flags":{"collapsed":false},"order":5,"mode":0,"inputs":[{"localized_name":"vae_name","name":"vae_name","type":"COMBO","widget":{"name":"vae_name"},"link":null},{"localized_name":"device","name":"device","type":"COMBO","widget":{"name":"device"},"link":null},{"localized_name":"weight_dtype","name":"weight_dtype","type":"COMBO","widget":{"name":"weight_dtype"},"link":null}],"outputs":[{"localized_name":"VAE","name":"VAE","type":"VAE","links":[229]}],"title":"Video VAELoader","properties":{"cnr_id":"comfyui-kjnodes","ver":"1.2.5","Node name for S&R":"VAELoaderKJ"},"widgets_values":["vae_ltx\\LTX23_video_vae_bf16.safetensors","main_device","bf16"]},{"id":111,"type":"GetNode","pos":[3070.256404012229,4149.83169322003],"size":[210,58],"flags":{"collapsed":true},"order":6,"mode":0,"inputs":[],"outputs":[{"name":"VAE","type":"VAE","links":[231]}],"title":"Get_video_vae","properties":{"Node name for S&R":"GetNode","aux_id":"kijai/ComfyUI-KJNodes"},"widgets_values":["video_vae"],"color":"#322","bgcolor":"#533"},{"id":112,"type":"GetNode","pos":[3329.042590603304,3984.671414407405],"size":[210,34],"flags":{"collapsed":true},"order":7,"mode":0,"inputs":[],"outputs":[{"name":"VAE","type":"VAE","links":[232]}],"title":"Get_video_vae","properties":{"Node name for S&R":"GetNode","aux_id":"kijai/ComfyUI-KJNodes"},"widgets_values":["video_vae"],"color":"#322","bgcolor":"#533"},{"id":58,"type":"LTXDirectorGuide","pos":[3293.558756510417,3901.182287967567],"size":[210,190],"flags":{"collapsed":false},"order":52,"mode":0,"inputs":[{"localized_name":"positive","name":"positive","type":"CONDITIONING","link":146},{"localized_name":"negative","name":"negative","type":"CONDITIONING","link":147},{"localized_name":"vae","name":"vae","type":"VAE","link":232},{"localized_name":"latent","name":"latent","type":"LATENT","link":150},{"localized_name":"guide_data","name":"guide_data","type":"GUIDE_DATA","link":280},{"localized_name":"scale_by","name":"scale_by","type":"FLOAT","widget":{"name":"scale_by"},"link":null},{"localized_name":"upscale_method","name":"upscale_method","type":"COMBO","widget":{"name":"upscale_method"},"link":null}],"outputs":[{"localized_name":"positive","name":"positive","type":"CONDITIONING","links":[152]},{"localized_name":"negative","name":"negative","type":"CONDITIONING","links":[153]},{"localized_name":"latent","name":"latent","type":"LATENT","links":[154]}],"properties":{"aux_id":"kijai/ComfyUI-PromptRelay","ver":"5cae3c00186ff2eb79771070f05a2c4e02f329bb","Node name for S&R":"LTXDirectorGuide","cnr_id":"whatdreamscost-comfyui"},"widgets_values":[1,"bicubic"]},{"id":52,"type":"LTXVLatentUpsampler","pos":[3033.558756510417,4121.182287967565],"size":[250,70],"flags":{},"order":51,"mode":0,"inputs":[{"localized_name":"samples","name":"samples","type":"LATENT","link":135},{"localized_name":"upscale_model","name":"upscale_model","type":"LATENT_UPSCALE_MODEL","link":144},{"localized_name":"vae","name":"vae","type":"VAE","link":231}],"outputs":[{"localized_name":"LATENT","name":"LATENT","type":"LATENT","links":[150]}],"properties":{"cnr_id":"comfy-core","ver":"0.14.1","Node name for S&R":"LTXVLatentUpsampler","enableTabs":false,"tabWidth":65,"tabXOffset":10,"hasSecondTab":false,"secondTabText":"Send Back","secondTabOffset":80,"secondTabWidth":65},"widgets_values":[]},{"id":110,"type":"GetNode","pos":[3317.249964086149,4468.510054837543],"size":[210,60],"flags":{"collapsed":true},"order":8,"mode":0,"inputs":[],"outputs":[{"name":"VAE","type":"VAE","links":[230]}],"title":"Get_video_vae","properties":{"Node name for S&R":"GetNode","aux_id":"kijai/ComfyUI-KJNodes"},"widgets_values":["video_vae"],"color":"#322","bgcolor":"#533"},{"id":113,"type":"GetNode","pos":[4202.1384023994015,4507.68757012965],"size":[210,34],"flags":{"collapsed":true},"order":9,"mode":0,"inputs":[],"outputs":[{"name":"VAE","type":"VAE","links":[233]}],"title":"Get_video_vae","properties":{"Node name for S&R":"GetNode","aux_id":"kijai/ComfyUI-KJNodes"},"widgets_values":["video_vae"],"color":"#322","bgcolor":"#533"},{"id":48,"type":"LTXVSeparateAVLatent","pos":[3983.558756510417,3891.182287967567],"size":[200,50],"flags":{},"order":56,"mode":0,"inputs":[{"localized_name":"av_latent","name":"av_latent","type":"LATENT","link":180}],"outputs":[{"localized_name":"video_latent","name":"video_latent","type":"LATENT","links":[136]},{"localized_name":"audio_latent","name":"audio_latent","type":"LATENT","links":[]}],"properties":{"cnr_id":"comfy-core","ver":"0.5.1","Node name for S&R":"LTXVSeparateAVLatent","enableTabs":false,"tabWidth":65,"tabXOffset":10,"hasSecondTab":false,"secondTabText":"Send Back","secondTabOffset":80,"secondTabWidth":65},"widgets_values":[]},{"id":16,"type":"LTXVAudioVAEDecode","pos":[4181.805994963843,4569.569898200755],"size":[210,50],"flags":{"collapsed":false},"order":50,"mode":0,"inputs":[{"localized_name":"samples","name":"samples","type":"LATENT","link":235},{"label":"Audio VAE","localized_name":"audio_vae","name":"audio_vae","type":"VAE","link":228}],"outputs":[{"localized_name":"Audio","name":"Audio","type":"AUDIO","links":[223]}],"properties":{"cnr_id":"comfy-core","ver":"0.16.0","Node name for S&R":"LTXVAudioVAEDecode"},"widgets_values":[]},{"id":13,"type":"LTXVSeparateAVLatent","pos":[4180.433024008876,4378.288648200755],"size":[200,50],"flags":{},"order":48,"mode":0,"inputs":[{"localized_name":"av_latent","name":"av_latent","type":"LATENT","link":267}],"outputs":[{"localized_name":"video_latent","name":"video_latent","type":"LATENT","links":[142]},{"localized_name":"audio_latent","name":"audio_latent","type":"LATENT","links":[143,235]}],"properties":{"cnr_id":"comfy-core","ver":"0.16.0","Node name for S&R":"LTXVSeparateAVLatent"},"widgets_values":[]},{"id":15,"type":"VAEDecode","pos":[4180.433024008876,4477.583852648863],"size":[210,50],"flags":{"collapsed":false},"order":59,"mode":0,"inputs":[{"localized_name":"samples","name":"samples","type":"LATENT","link":269},{"localized_name":"vae","name":"vae","type":"VAE","link":233}],"outputs":[{"localized_name":"IMAGE","name":"IMAGE","type":"IMAGE","links":[222]}],"properties":{"cnr_id":"comfy-core","ver":"0.9.1","Node name for S&R":"VAEDecode"},"widgets_values":[]},{"id":47,"type":"SamplerCustomAdvanced","pos":[3743.558756510417,3891.182287967567],"size":[230,246.9453125],"flags":{"collapsed":false},"order":55,"mode":0,"inputs":[{"localized_name":"noise","name":"noise","type":"NOISE","link":157},{"localized_name":"guider","name":"guider","type":"GUIDER","link":124},{"localized_name":"sampler","name":"sampler","type":"SAMPLER","link":125},{"localized_name":"sigmas","name":"sigmas","type":"SIGMAS","link":319},{"localized_name":"latent_image","name":"latent_image","type":"LATENT","link":127}],"outputs":[{"localized_name":"output","name":"output","type":"LATENT","links":[180]},{"localized_name":"denoised_output","name":"denoised_output","type":"LATENT","links":[]}],"properties":{"cnr_id":"comfy-core","ver":"0.3.60","Node name for S&R":"SamplerCustomAdvanced","enableTabs":false,"tabWidth":65,"tabXOffset":10,"hasSecondTab":false,"secondTabText":"Send Back","secondTabOffset":80,"secondTabWidth":65},"widgets_values":[]},{"id":10,"type":"SamplerCustomAdvanced","pos":[3950,4380],"size":[220,241.28125],"flags":{"collapsed":false},"order":47,"mode":0,"inputs":[{"localized_name":"noise","name":"noise","type":"NOISE","link":51},{"localized_name":"guider","name":"guider","type":"GUIDER","link":12},{"localized_name":"sampler","name":"sampler","type":"SAMPLER","link":53},{"localized_name":"sigmas","name":"sigmas","type":"SIGMAS","link":52},{"localized_name":"latent_image","name":"latent_image","type":"LATENT","link":14}],"outputs":[{"localized_name":"output","name":"output","type":"LATENT","links":[267]},{"localized_name":"denoised_output","name":"denoised_output","type":"LATENT","links":[]}],"properties":{"cnr_id":"comfy-core","ver":"0.3.60","Node name for S&R":"SamplerCustomAdvanced","enableTabs":false,"tabWidth":65,"tabXOffset":10,"hasSecondTab":false,"secondTabText":"Send Back","secondTabOffset":80,"secondTabWidth":65},"widgets_values":[]},{"id":97,"type":"PathchSageAttentionKJ","pos":[1342.1910389659208,4208.074618439494],"size":[270,82],"flags":{"collapsed":true},"order":30,"mode":0,"inputs":[{"localized_name":"model","name":"model","type":"MODEL","link":219},{"localized_name":"sage_attention","name":"sage_attention","type":"COMBO","widget":{"name":"sage_attention"},"link":null},{"localized_name":"allow_compile","name":"allow_compile","shape":7,"type":"BOOLEAN","widget":{"name":"allow_compile"},"link":null}],"outputs":[{"localized_name":"MODEL","name":"MODEL","type":"MODEL","links":[215]}],"properties":{"cnr_id":"comfyui-kjnodes","ver":"204f6d5aae73b10c0fe2fb26e61405fd6337bb77","Node name for S&R":"PathchSageAttentionKJ"},"widgets_values":["auto",false]},{"id":99,"type":"LTX2AttentionTunerPatch","pos":[1325.3851171503295,4249.494536389755],"size":[283.875,178],"flags":{"collapsed":true},"order":33,"mode":0,"inputs":[{"localized_name":"model","name":"model","type":"MODEL","link":216},{"localized_name":"blocks","name":"blocks","type":"STRING","widget":{"name":"blocks"},"link":null},{"localized_name":"video_scale","name":"video_scale","type":"FLOAT","widget":{"name":"video_scale"},"link":null},{"localized_name":"audio_scale","name":"audio_scale","type":"FLOAT","widget":{"name":"audio_scale"},"link":null},{"localized_name":"audio_to_video_scale","name":"audio_to_video_scale","type":"FLOAT","widget":{"name":"audio_to_video_scale"},"link":null},{"localized_name":"video_to_audio_scale","name":"video_to_audio_scale","type":"FLOAT","widget":{"name":"video_to_audio_scale"},"link":null},{"localized_name":"triton_kernels","name":"triton_kernels","type":"BOOLEAN","widget":{"name":"triton_kernels"},"link":null}],"outputs":[{"localized_name":"model","name":"model","type":"MODEL","links":[214,218]}],"properties":{"cnr_id":"comfyui-kjnodes","ver":"5b38397a6430fdb16c7bd14a6bd64c2b0e69a5f0","Node name for S&R":"LTX2AttentionTunerPatch"},"widgets_values":["",1,1,1,1,true]},{"id":100,"type":"LTXVChunkFeedForward","pos":[1519.180484814634,4211.128397940163],"size":[272.35703125,82],"flags":{"collapsed":true},"order":32,"mode":0,"inputs":[{"localized_name":"model","name":"model","type":"MODEL","link":217},{"localized_name":"chunks","name":"chunks","type":"INT","widget":{"name":"chunks"},"link":null},{"localized_name":"dim_threshold","name":"dim_threshold","type":"INT","widget":{"name":"dim_threshold"},"link":null}],"outputs":[{"localized_name":"model","name":"model","type":"MODEL","links":[216]}],"properties":{"cnr_id":"comfyui-kjnodes","ver":"0ea5ece1793263d9fb0ad97bb067f96b83a9dea3","Node name for S&R":"LTXVChunkFeedForward"},"widgets_values":[2,4096]},{"id":98,"type":"LTX2MemoryEfficientSageAttentionPatch","pos":[1505.1022257020957,4245.977222697065],"size":[275.179296875,58],"flags":{"collapsed":true},"order":31,"mode":0,"inputs":[{"localized_name":"model","name":"model","type":"MODEL","link":215},{"localized_name":"triton_kernels","name":"triton_kernels","type":"BOOLEAN","widget":{"name":"triton_kernels"},"link":null}],"outputs":[{"localized_name":"model","name":"model","type":"MODEL","links":[217]}],"properties":{"cnr_id":"comfyui-kjnodes","ver":"2acdef1766026ff3be00daf3c45f6a064db9100f","Node name for S&R":"LTX2MemoryEfficientSageAttentionPatch"},"widgets_values":[true]},{"id":95,"type":"SetNode","pos":[1463.834431808352,4006.5687430919115],"size":[210,34],"flags":{"collapsed":true},"order":36,"mode":0,"inputs":[{"name":"MODEL","type":"MODEL","link":213}],"outputs":[{"name":"MODEL","type":"MODEL","links":null}],"title":"Set_model_with_lora","properties":{"Node name for S&R":"SetNode","aux_id":"SetNode","previousName":"model_with_lora"},"widgets_values":["model_with_lora"],"color":"#223","bgcolor":"#335"},{"id":96,"type":"SetNode","pos":[1486.7781710342804,3946.0916043309503],"size":[210,34],"flags":{"collapsed":true},"order":34,"mode":0,"inputs":[{"name":"MODEL","type":"MODEL","link":214}],"outputs":[{"name":"MODEL","type":"MODEL","links":null}],"title":"Set_model_upscale","properties":{"Node name for S&R":"SetNode","aux_id":"kijai/ComfyUI-KJNodes","previousName":"model_upscale"},"widgets_values":["model_upscale"],"color":"#223","bgcolor":"#335"},{"id":117,"type":"MultiImageLoader","pos":[1351.4663543852748,4361.130451943635],"size":[400,485],"flags":{"collapsed":false},"order":10,"mode":0,"inputs":[{"localized_name":"image_paths","name":"image_paths","type":"STRING","widget":{"name":"image_paths"},"link":null},{"localized_name":"width","name":"width","type":"INT","widget":{"name":"width"},"link":null},{"localized_name":"height","name":"height","type":"INT","widget":{"name":"height"},"link":null},{"localized_name":"interpolation","name":"interpolation","type":"COMBO","widget":{"name":"interpolation"},"link":null},{"localized_name":"resize_method","name":"resize_method","type":"COMBO","widget":{"name":"resize_method"},"link":null},{"localized_name":"multiple_of","name":"multiple_of","type":"INT","widget":{"name":"multiple_of"},"link":null},{"localized_name":"img_compression","name":"img_compression","type":"INT","widget":{"name":"img_compression"},"link":null}],"outputs":[{"localized_name":"multi_output","name":"multi_output","type":"IMAGE","links":[277]},{"name":"image_1","type":"IMAGE","links":null},{"name":"image_2","type":"IMAGE","links":null}],"title":"Character Ref Sheet","properties":{"aux_id":"CGlide/WhatDreamsCost-ComfyUI","ver":"19c7d62fce1b2d86ee38602a65e6c39a81332a84","Node name for S&R":"MultiImageLoader","cnr_id":"WhatDreamsCost-ComfyUI"},"widgets_values":["Male_white_s.jpg\nsheep_scav_front-upscl.png",1920,1088,"lanczos","stretch",32,15,""],"color":"#222","bgcolor":"#000"},{"id":8,"type":"LTXDirectorGuide","pos":[3286.805994963843,4388.288648200755],"size":[210,180],"flags":{},"order":44,"mode":0,"inputs":[{"localized_name":"positive","name":"positive","type":"CONDITIONING","link":334},{"localized_name":"negative","name":"negative","type":"CONDITIONING","link":7},{"localized_name":"vae","name":"vae","type":"VAE","link":230},{"localized_name":"latent","name":"latent","type":"LATENT","link":283},{"localized_name":"guide_data","name":"guide_data","type":"GUIDE_DATA","link":281},{"localized_name":"scale_by","name":"scale_by","type":"FLOAT","widget":{"name":"scale_by"},"link":null},{"localized_name":"upscale_method","name":"upscale_method","type":"COMBO","widget":{"name":"upscale_method"},"link":null}],"outputs":[{"localized_name":"positive","name":"positive","type":"CONDITIONING","links":[10,54,188]},{"localized_name":"negative","name":"negative","type":"CONDITIONING","links":[11,55,189]},{"localized_name":"latent","name":"latent","type":"LATENT","links":[108]}],"properties":{"aux_id":"kijai/ComfyUI-PromptRelay","ver":"5cae3c00186ff2eb79771070f05a2c4e02f329bb","Node name for S&R":"LTXDirectorGuide","cnr_id":"whatdreamscost-comfyui"},"widgets_values":[0.5,"bicubic"]},{"id":57,"type":"LatentUpscaleModelLoader","pos":[3033.558756510417,4011.182287967567],"size":[250,70],"flags":{},"order":11,"mode":0,"inputs":[{"localized_name":"model_name","name":"model_name","type":"COMBO","widget":{"name":"model_name"},"link":null}],"outputs":[{"localized_name":"LATENT_UPSCALE_MODEL","name":"LATENT_UPSCALE_MODEL","type":"LATENT_UPSCALE_MODEL","links":[144]}],"properties":{"cnr_id":"comfy-core","ver":"0.16.4","Node name for S&R":"LatentUpscaleModelLoader"},"widgets_values":["ltx-2.3-spatial-upscaler-x2-1.1.safetensors"]},{"id":14,"type":"LTXVCropGuides","pos":[4200.433024008876,3895.992072827891],"size":[180,70],"flags":{},"order":57,"mode":0,"inputs":[{"localized_name":"positive","name":"positive","type":"CONDITIONING","link":54},{"localized_name":"negative","name":"negative","type":"CONDITIONING","link":55},{"localized_name":"latent","name":"latent","type":"LATENT","link":136}],"outputs":[{"localized_name":"positive","name":"positive","type":"CONDITIONING","links":null},{"localized_name":"negative","name":"negative","type":"CONDITIONING","links":null},{"localized_name":"latent","name":"latent","type":"LATENT","links":[268]}],"properties":{"cnr_id":"comfy-core","ver":"0.19.3","Node name for S&R":"LTXVCropGuides"},"widgets_values":[]},{"id":121,"type":"CleanLatentSlice","pos":[4170,4011.182287967567],"size":[210,82],"flags":{},"order":58,"mode":0,"inputs":[{"localized_name":"latent","name":"latent","type":"LATENT","link":268},{"localized_name":"start","name":"start","type":"INT","widget":{"name":"start"},"link":null},{"localized_name":"length","name":"length","type":"INT","widget":{"name":"length"},"link":278}],"outputs":[{"localized_name":"latent","name":"latent","type":"LATENT","links":[269]}],"properties":{"aux_id":"CGlide/WhatDreamsCost-ComfyUI","ver":"0604d7fb0eba5d0d1eb97dc606138996bf840ac1","Node name for S&R":"CleanLatentSlice"},"widgets_values":[1,1]},{"id":9,"type":"CFGGuider","pos":[3516.805994963843,4378.288648200755],"size":[210,100],"flags":{},"order":45,"mode":0,"inputs":[{"localized_name":"model","name":"model","type":"MODEL","link":292},{"localized_name":"positive","name":"positive","type":"CONDITIONING","link":10},{"localized_name":"negative","name":"negative","type":"CONDITIONING","link":11},{"localized_name":"cfg","name":"cfg","type":"FLOAT","widget":{"name":"cfg"},"link":null}],"outputs":[{"localized_name":"GUIDER","name":"GUIDER","type":"GUIDER","links":[12]}],"properties":{"cnr_id":"comfy-core","ver":"0.3.64","Node name for S&R":"CFGGuider","enableTabs":false,"tabWidth":65,"tabXOffset":10,"hasSecondTab":false,"secondTabText":"Send Back","secondTabOffset":80,"secondTabWidth":65},"widgets_values":[1]},{"id":129,"type":"SetNode","pos":[1056.1386753841218,4343.397012036485],"size":[210,60],"flags":{"collapsed":true},"order":25,"mode":0,"inputs":[{"name":"CLIP","type":"CLIP","link":294}],"outputs":[{"name":"CLIP","type":"CLIP","links":null}],"title":"Set_clip","properties":{"Node name for S&R":"SetNode","aux_id":"kijai/ComfyUI-KJNodes","previousName":"clip"},"widgets_values":["clip"],"color":"#432","bgcolor":"#653"},{"id":84,"type":"DualCLIPLoader","pos":[990.6660127124089,4296.4864711568825],"size":[260,135.83799054980682],"flags":{},"order":12,"mode":0,"inputs":[{"localized_name":"clip_name1","name":"clip_name1","type":"COMBO","widget":{"name":"clip_name1"},"link":null},{"localized_name":"clip_name2","name":"clip_name2","type":"COMBO","widget":{"name":"clip_name2"},"link":null},{"localized_name":"type","name":"type","type":"COMBO","widget":{"name":"type"},"link":null},{"localized_name":"device","name":"device","shape":7,"type":"COMBO","widget":{"name":"device"},"link":null}],"outputs":[{"localized_name":"CLIP","name":"CLIP","type":"CLIP","links":[275,294]}],"properties":{"cnr_id":"comfy-core","ver":"0.19.3","Node name for S&R":"DualCLIPLoader"},"widgets_values":["gemma_3_12B_it_fp8_scaled.safetensors","ltx-2.3_text_projection_bf16.safetensors","ltxv","default"]},{"id":79,"type":"LTX2SamplingPreviewOverride","pos":[990.6660127124089,4017.992072827891],"size":[260,100.00000000000045],"flags":{"collapsed":false},"order":22,"mode":0,"inputs":[{"localized_name":"model","name":"model","type":"MODEL","link":212},{"localized_name":"latent_upscale_model","name":"latent_upscale_model","shape":7,"type":"LATENT_UPSCALE_MODEL","link":null},{"localized_name":"vae","name":"vae","shape":7,"type":"VAE","link":196},{"localized_name":"preview_rate","name":"preview_rate","type":"INT","widget":{"name":"preview_rate"},"link":null}],"outputs":[{"localized_name":"MODEL","name":"MODEL","type":"MODEL","links":[197]}],"properties":{"cnr_id":"comfyui-kjnodes","ver":"1.3.4","Node name for S&R":"LTX2SamplingPreviewOverride"},"widgets_values":[24]},{"id":103,"type":"GetNode","pos":[3536.3808790638827,3945.689622751731],"size":[210,60],"flags":{"collapsed":true},"order":13,"mode":0,"inputs":[],"outputs":[{"name":"MODEL","type":"MODEL","links":[306,307]}],"title":"Get_model_upscale","properties":{"Node name for S&R":"GetNode","aux_id":"kijai/ComfyUI-KJNodes"},"widgets_values":["model_upscale"],"color":"#223","bgcolor":"#335"},{"id":49,"type":"CFGGuider","pos":[3523.558756510417,3895.992072827891],"size":[210,100],"flags":{"collapsed":false},"order":53,"mode":0,"inputs":[{"localized_name":"model","name":"model","type":"MODEL","link":306},{"localized_name":"positive","name":"positive","type":"CONDITIONING","link":152},{"localized_name":"negative","name":"negative","type":"CONDITIONING","link":153},{"localized_name":"cfg","name":"cfg","type":"FLOAT","widget":{"name":"cfg"},"link":null}],"outputs":[{"localized_name":"GUIDER","name":"GUIDER","type":"GUIDER","links":[124]}],"properties":{"cnr_id":"comfy-core","ver":"0.3.64","Node name for S&R":"CFGGuider","enableTabs":false,"tabWidth":65,"tabXOffset":10,"hasSecondTab":false,"secondTabText":"Send Back","secondTabOffset":80,"secondTabWidth":65},"widgets_values":[1]},{"id":53,"type":"KSamplerSelect","pos":[3523.558756510417,4031.182287967567],"size":[210,60],"flags":{},"order":14,"mode":0,"inputs":[{"localized_name":"sampler_name","name":"sampler_name","type":"COMBO","widget":{"name":"sampler_name"},"link":null}],"outputs":[{"localized_name":"SAMPLER","name":"SAMPLER","type":"SAMPLER","links":[125]}],"properties":{"cnr_id":"comfy-core","ver":"0.3.56","Node name for S&R":"KSamplerSelect","enableTabs":false,"tabWidth":65,"tabXOffset":10,"hasSecondTab":false,"secondTabText":"Send Back","secondTabOffset":80,"secondTabWidth":65},"widgets_values":["euler_cfg_pp"]},{"id":101,"type":"Power Lora Loader (rgthree)","pos":[1382.4083484980224,3886.975445291701],"size":[340.519921875,238],"flags":{"collapsed":false},"order":35,"mode":0,"inputs":[{"dir":3,"name":"model","type":"MODEL","link":218},{"dir":3,"name":"clip","type":"CLIP","link":null}],"outputs":[{"dir":4,"name":"MODEL","shape":3,"type":"MODEL","links":[213,274]},{"dir":4,"name":"CLIP","shape":3,"type":"CLIP","links":null}],"properties":{"cnr_id":"rgthree-comfy","ver":"1.0.2605082257","Show Strengths":"Single Strength","Match":""},"widgets_values":[{},{"type":"PowerLoraLoaderHeaderWidget"},{"on":false,"lora":"LTX\\ltx2.3-transition.safetensors","strength":0.2,"strengthTwo":null},{"on":false,"lora":"LTX\\LTX2.3_Crisp_Enhance.safetensors","strength":1,"strengthTwo":null},{"on":false,"lora":"LTX\\LTX2.3_Soft_Enhance.safetensors","strength":1,"strengthTwo":null},{"on":false,"lora":"LTX\\Ltx2.3-Licon-VBVR-I2V-390K-R32.safetensors","strength":1,"strengthTwo":null},{"on":false,"lora":"LTX\\LTX2.3-IC-LORA-Dual-Character.safetensors","strength":0.5231842041015627,"strengthTwo":null},{},""]},{"id":80,"type":"LoraLoaderModelOnly","pos":[990.6660127124089,4165.384924710864],"size":[260,85.21429623404856],"flags":{},"order":29,"mode":0,"inputs":[{"localized_name":"model","name":"model","type":"MODEL","link":197},{"localized_name":"lora_name","name":"lora_name","type":"COMBO","widget":{"name":"lora_name"},"link":null},{"localized_name":"strength_model","name":"strength_model","type":"FLOAT","widget":{"name":"strength_model"},"link":null}],"outputs":[{"localized_name":"MODEL","name":"MODEL","type":"MODEL","links":[219]}],"properties":{"cnr_id":"comfy-core","ver":"0.3.75","Node name for S&R":"LoraLoaderModelOnly","enableTabs":false,"tabWidth":65,"tabXOffset":10,"hasSecondTab":false,"secondTabText":"Send Back","secondTabOffset":80,"secondTabWidth":65,"models":[{"name":"ltx-2.3-22b-distilled-lora-384.safetensors","url":"https://huggingface.co/Lightricks/LTX-2.3/resolve/main/ltx-2.3-22b-distilled-lora-384.safetensors","directory":"loras"}]},"widgets_values":["LTX\\ltx-2.3-22b-distilled-lora-384-1.1.safetensors",0.6],"color":"#322","bgcolor":"#533"},{"id":54,"type":"BasicScheduler","pos":[3523.558756510417,4131.182287967565],"size":[210,110],"flags":{},"order":26,"mode":0,"inputs":[{"localized_name":"model","name":"model","type":"MODEL","link":307},{"localized_name":"scheduler","name":"scheduler","type":"COMBO","widget":{"name":"scheduler"},"link":null},{"localized_name":"steps","name":"steps","type":"INT","widget":{"name":"steps"},"link":null},{"localized_name":"denoise","name":"denoise","type":"FLOAT","widget":{"name":"denoise"},"link":null}],"outputs":[{"localized_name":"SIGMAS","name":"SIGMAS","type":"SIGMAS","links":[]}],"properties":{"cnr_id":"comfy-core","ver":"0.16.4","Node name for S&R":"BasicScheduler"},"widgets_values":["linear_quadratic",4,0.42]},{"id":29,"type":"KSamplerSelect","pos":[3736.805994963843,4508.288648200755],"size":[210,60],"flags":{},"order":15,"mode":0,"inputs":[{"localized_name":"sampler_name","name":"sampler_name","type":"COMBO","widget":{"name":"sampler_name"},"link":null}],"outputs":[{"localized_name":"SAMPLER","name":"SAMPLER","type":"SAMPLER","links":[53]}],"properties":{"cnr_id":"comfy-core","ver":"0.3.56","Node name for S&R":"KSamplerSelect","enableTabs":false,"tabWidth":65,"tabXOffset":10,"hasSecondTab":false,"secondTabText":"Send Back","secondTabOffset":80,"secondTabWidth":65},"widgets_values":["euler_ancestral_cfg_pp"]},{"id":142,"type":"LoadImage","pos":[4545.110987070562,4867.3663155382],"size":[492.2774894783015,440.65868099020827],"flags":{},"order":16,"mode":0,"inputs":[{"localized_name":"image","name":"image","type":"COMBO","widget":{"name":"image"},"link":null},{"localized_name":"choose file to upload","name":"upload","type":"IMAGEUPLOAD","widget":{"name":"upload"},"link":null}],"outputs":[{"localized_name":"IMAGE","name":"IMAGE","type":"IMAGE","links":null},{"localized_name":"MASK","name":"MASK","type":"MASK","links":null}],"properties":{"cnr_id":"comfy-core","ver":"0.22.0","Node name for S&R":"LoadImage"},"widgets_values":["Male_white_s.jpg","image"]},{"id":144,"type":"LoadImage","pos":[5104.177255381658,4867.3663155382],"size":[492.2774894783015,440.65868099020827],"flags":{},"order":17,"mode":0,"inputs":[{"localized_name":"image","name":"image","type":"COMBO","widget":{"name":"image"},"link":null},{"localized_name":"choose file to upload","name":"upload","type":"IMAGEUPLOAD","widget":{"name":"upload"},"link":null}],"outputs":[{"localized_name":"IMAGE","name":"IMAGE","type":"IMAGE","links":null},{"localized_name":"MASK","name":"MASK","type":"MASK","links":null}],"properties":{"cnr_id":"comfy-core","ver":"0.22.0","Node name for S&R":"LoadImage"},"widgets_values":["sheep_scav_front-upscl.png","image"]},{"id":104,"type":"ManualSigmas","pos":[3743.558756510417,4182.505601671006],"size":[240,58],"flags":{"collapsed":false},"order":27,"mode":0,"inputs":[{"localized_name":"sigmas","name":"sigmas","type":"STRING","widget":{"name":"sigmas"},"link":318}],"outputs":[{"localized_name":"SIGMAS","name":"SIGMAS","type":"SIGMAS","links":[319]}],"properties":{"cnr_id":"comfy-core","ver":"0.14.1","Node name for S&R":"ManualSigmas"},"widgets_values":["0.85, 0.7250, 0.4219, 0.0"]},{"id":146,"type":"PrimitiveStringMultiline","pos":[4033.9721650732627,4142.847508128066],"size":[241.02783492673734,97.65809354294015],"flags":{},"order":18,"mode":0,"inputs":[{"localized_name":"value","name":"value","type":"STRING","widget":{"name":"value"},"link":null}],"outputs":[{"localized_name":"STRING","name":"STRING","type":"STRING","links":[318]}],"properties":{"cnr_id":"comfy-core","ver":"0.22.0","Node name for S&R":"PrimitiveStringMultiline"},"widgets_values":["0.85, 0.8115, 0.6165, 0.3460, 0.0000"]},{"id":11,"type":"BasicScheduler","pos":[3736.805994963843,4649.569898200755],"size":[210,110],"flags":{},"order":42,"mode":0,"inputs":[{"localized_name":"model","name":"model","type":"MODEL","link":326},{"localized_name":"scheduler","name":"scheduler","type":"COMBO","widget":{"name":"scheduler"},"link":null},{"localized_name":"steps","name":"steps","type":"INT","widget":{"name":"steps"},"link":null},{"localized_name":"denoise","name":"denoise","type":"FLOAT","widget":{"name":"denoise"},"link":null}],"outputs":[{"localized_name":"SIGMAS","name":"SIGMAS","type":"SIGMAS","links":[52]}],"properties":{"cnr_id":"comfy-core","ver":"0.16.4","Node name for S&R":"BasicScheduler"},"widgets_values":["linear_quadratic",16,1]},{"id":135,"type":"ConditioningZeroOut","pos":[3036.805994963843,4530.288648200755],"size":[204.134765625,26],"flags":{},"order":39,"mode":0,"inputs":[{"localized_name":"conditioning","name":"conditioning","type":"CONDITIONING","link":330}],"outputs":[{"localized_name":"CONDITIONING","name":"CONDITIONING","type":"CONDITIONING","links":[335]}],"properties":{"cnr_id":"comfy-core","ver":"0.22.2","Node name for S&R":"ConditioningZeroOut"},"widgets_values":[]},{"id":5,"type":"LTXVConditioning","pos":[3036.805994963843,4388.288648200755],"size":[240,80],"flags":{},"order":43,"mode":0,"inputs":[{"localized_name":"positive","name":"positive","type":"CONDITIONING","link":285},{"localized_name":"negative","name":"negative","type":"CONDITIONING","link":336},{"localized_name":"frame_rate","name":"frame_rate","type":"FLOAT","widget":{"name":"frame_rate"},"link":null}],"outputs":[{"localized_name":"positive","name":"positive","type":"CONDITIONING","links":[334]},{"localized_name":"negative","name":"negative","type":"CONDITIONING","links":[7]}],"properties":{"cnr_id":"comfy-core","ver":"0.3.56","Node name for S&R":"LTXVConditioning","enableTabs":false,"tabWidth":65,"tabXOffset":10,"hasSecondTab":false,"secondTabText":"Send Back","secondTabOffset":80,"secondTabWidth":65},"widgets_values":[24]},{"id":130,"type":"GetNode","pos":[3157.3014359362382,4922.906048864631],"size":[210,60],"flags":{"collapsed":true},"order":19,"mode":0,"inputs":[],"outputs":[{"name":"CLIP","type":"CLIP","links":[295]}],"title":"Get_clip","properties":{"Node name for S&R":"GetNode","aux_id":"kijai/ComfyUI-KJNodes"},"widgets_values":["clip"],"color":"#432","bgcolor":"#653"},{"id":127,"type":"LTX2_NAG","pos":[3381.1532399783114,4644.298128464186],"size":[271.305509971064,170],"flags":{},"order":38,"mode":0,"inputs":[{"localized_name":"model","name":"model","type":"MODEL","link":324},{"localized_name":"nag_cond_video","name":"nag_cond_video","shape":7,"type":"CONDITIONING","link":289},{"localized_name":"nag_cond_audio","name":"nag_cond_audio","shape":7,"type":"CONDITIONING","link":296},{"localized_name":"nag_scale","name":"nag_scale","type":"FLOAT","widget":{"name":"nag_scale"},"link":null},{"localized_name":"nag_alpha","name":"nag_alpha","type":"FLOAT","widget":{"name":"nag_alpha"},"link":null},{"localized_name":"nag_tau","name":"nag_tau","type":"FLOAT","widget":{"name":"nag_tau"},"link":null},{"localized_name":"inplace","name":"inplace","shape":7,"type":"BOOLEAN","widget":{"name":"inplace"},"link":null}],"outputs":[{"localized_name":"model","name":"model","type":"MODEL","links":[292,325]}],"properties":{"cnr_id":"comfyui-kjnodes","ver":"ff79c7332d7ea4aa85097a88fb09359f28cb487e","Node name for S&R":"LTX2_NAG"},"widgets_values":[11,0.25,2.5,true]},{"id":128,"type":"CLIPTextEncode","pos":[3036.805994963843,4897.300827607634],"size":[344.34724501446817,133.4878205931209],"flags":{"collapsed":false},"order":28,"mode":0,"inputs":[{"localized_name":"clip","name":"clip","type":"CLIP","link":295},{"localized_name":"text","name":"text","type":"STRING","widget":{"name":"text"},"link":null}],"outputs":[{"localized_name":"CONDITIONING","name":"CONDITIONING","type":"CONDITIONING","links":[289,296,337]}],"title":"Negative Prompt","properties":{"cnr_id":"comfy-core","ver":"0.3.56","Node name for S&R":"CLIPTextEncode","enableTabs":false,"tabWidth":65,"tabXOffset":10,"hasSecondTab":false,"secondTabText":"Send Back","secondTabOffset":80,"secondTabWidth":65},"widgets_values":["low poly, low geometry, subtitles, multiple characters"],"color":"#222","bgcolor":"#000"},{"id":126,"type":"LTXDirector","pos":[1864.3545480609203,3925.7886482007552],"size":[1055.4329599763598,1105],"flags":{},"order":37,"mode":0,"inputs":[{"localized_name":"model","name":"model","type":"MODEL","link":274},{"localized_name":"clip","name":"clip","type":"CLIP","link":275},{"localized_name":"audio_vae","name":"audio_vae","shape":7,"type":"VAE","link":276},{"localized_name":"optional_latent","name":"optional_latent","shape":7,"type":"LATENT","link":null},{"localized_name":"reference_image","name":"reference_image","shape":7,"type":"IMAGE","link":277},{"localized_name":"reference_image_2","name":"reference_image_2","shape":7,"type":"IMAGE","link":null},{"localized_name":"reference_image_3","name":"reference_image_3","shape":7,"type":"IMAGE","link":null},{"localized_name":"global_prompt","name":"global_prompt","type":"STRING","widget":{"name":"global_prompt"},"link":null},{"localized_name":"duration_frames","name":"duration_frames","type":"INT","widget":{"name":"duration_frames"},"link":null},{"localized_name":"duration_seconds","name":"duration_seconds","type":"FLOAT","widget":{"name":"duration_seconds"},"link":null},{"localized_name":"timeline_data","name":"timeline_data","type":"STRING","widget":{"name":"timeline_data"},"link":null},{"localized_name":"use_custom_audio","name":"use_custom_audio","type":"BOOLEAN","widget":{"name":"use_custom_audio"},"link":null},{"localized_name":"local_prompts","name":"local_prompts","type":"STRING","widget":{"name":"local_prompts"},"link":null},{"localized_name":"segment_lengths","name":"segment_lengths","type":"STRING","widget":{"name":"segment_lengths"},"link":null},{"localized_name":"frame_rate","name":"frame_rate","type":"FLOAT","widget":{"name":"frame_rate"},"link":null},{"localized_name":"guide_strength","name":"guide_strength","type":"STRING","widget":{"name":"guide_strength"},"link":null},{"localized_name":"custom_width","name":"custom_width","type":"INT","widget":{"name":"custom_width"},"link":null},{"localized_name":"custom_height","name":"custom_height","type":"INT","widget":{"name":"custom_height"},"link":null},{"localized_name":"resize_method","name":"resize_method","type":"COMBO","widget":{"name":"resize_method"},"link":null},{"localized_name":"save_prompts_to_file","name":"save_prompts_to_file","shape":7,"type":"BOOLEAN","widget":{"name":"save_prompts_to_file"},"link":null},{"localized_name":"reference_strength","name":"reference_strength","shape":7,"type":"FLOAT","widget":{"name":"reference_strength"},"link":null},{"localized_name":"char1_description","name":"char1_description","shape":7,"type":"STRING","widget":{"name":"char1_description"},"link":null},{"localized_name":"char2_description","name":"char2_description","shape":7,"type":"STRING","widget":{"name":"char2_description"},"link":null},{"localized_name":"char3_description","name":"char3_description","shape":7,"type":"STRING","widget":{"name":"char3_description"},"link":null}],"outputs":[{"localized_name":"model","name":"model","type":"MODEL","links":[324]},{"localized_name":"positive","name":"positive","type":"CONDITIONING","links":[285,330]},{"localized_name":"video_latent","name":"video_latent","type":"LATENT","links":[283]},{"localized_name":"audio_latent","name":"audio_latent","type":"LATENT","links":[282]},{"localized_name":"guide_data","name":"guide_data","type":"GUIDE_DATA","links":[280,281]},{"localized_name":"frame_rate","name":"frame_rate","type":"FLOAT","links":[279]},{"localized_name":"combined_audio","name":"combined_audio","type":"AUDIO","links":null},{"localized_name":"clean_latent_frames","name":"clean_latent_frames","type":"INT","links":[278]},{"localized_name":"clean_pixel_frames","name":"clean_pixel_frames","type":"INT","links":null}],"properties":{"aux_id":"CGlide/WhatDreamsCost-ComfyUI","ver":"1.3.9","Node name for S&R":"LTXDirector","cnr_id":"whatdreamscost-comfyui"},"widgets_values":["",192,8,"{\"segments\":[{\"id\":\"1780172463764k6ucj\",\"start\":0,\"length\":107,\"prompt\":\"@char1 walks in a desert place, @char1 walks toward the camera looking around\",\"type\":\"image\",\"imageFile\":\"background (1).png\",\"imageB64\":\"/api/view?filename=background%20(1).png&type=input&subfolder=\"},{\"id\":\"1780173716516ng6at\",\"start\":107,\"length\":85,\"prompt\":\"@char1 turns back\",\"type\":\"text\"}],\"audioSegments\":[]}",false,"@char1 walks in a desert place, @char1 walks toward the camera looking around | @char1 turns back","107,85",0.001,24,"seconds","1.00",1920,1088,"stretch to fit",32,18,true,1,"An elite cyborg warrior encased in high-gloss white ceramic armor accented with intricate gold filigree.\nBeneath the segmented plating, a dark mechanical endoskeleton and complex cabling are visible, culminating in a sleek, angular helmet.","A sophisticated female android with long, platinum synthetic hair and a partially exposed face revealing human-like eyes and lips.\nHer body is encased in a sleek, form-fitting white chassis accented with gold mechanical plating, exposing intricate wiring and joint articulations along her torso and spine.","A rugged steampunk craftsman with a grey beard wears an intricate brass eyepiece featuring a glowing amber lens and detailed mechanical prosthetic forearms.\nHe is dressed in a weathered, tattered leather apron over a heavy olive-green jacket and a thick multicolored scarf with high-detail textures.",""]},{"id":149,"type":"ComfySwitchNode","pos":[3036.805994963843,4614.070323805746],"size":[270,78],"flags":{},"order":41,"mode":0,"inputs":[{"localized_name":"on_false","name":"on_false","type":"CONDITIONING","link":335},{"localized_name":"on_true","name":"on_true","type":"CONDITIONING","link":337},{"localized_name":"switch","name":"switch","type":"BOOLEAN","widget":{"name":"switch"},"link":null}],"outputs":[{"localized_name":"output","name":"output","type":"CONDITIONING","links":[336]}],"title":"NAG (1)","properties":{"cnr_id":"comfy-core","ver":"0.22.0","Node name for S&R":"ComfySwitchNode"},"widgets_values":[true],"color":"#322","bgcolor":"#533"},{"id":147,"type":"Fast Bypasser (rgthree)","pos":[3036.805994963843,4734.841667937323],"size":[273.123046875,79.45646052686334],"flags":{},"order":40,"mode":0,"inputs":[{"dir":3,"name":"LTX2 NAG","type":"*","link":325},{"dir":3,"name":"","type":"*","link":null}],"outputs":[{"dir":4,"name":"OPT_CONNECTION","type":"*","links":[326]}],"title":"NAG (2)","properties":{"toggleRestriction":"default"},"color":"#322","bgcolor":"#533"},{"id":28,"type":"RandomNoise","pos":[3733.558756510417,4373.288648200755],"size":[210,90],"flags":{},"order":20,"mode":0,"inputs":[{"localized_name":"noise_seed","name":"noise_seed","type":"INT","widget":{"name":"noise_seed"},"link":null}],"outputs":[{"localized_name":"NOISE","name":"NOISE","type":"NOISE","links":[51,157]}],"properties":{"cnr_id":"comfy-core","ver":"0.19.3","Node name for S&R":"RandomNoise"},"widgets_values":[18,"fixed"]},{"id":150,"type":"MarkdownNote","pos":[4060,4925.595991809775],"size":[288.8383013624807,147.09966422352863],"flags":{"collapsed":false},"order":21,"mode":0,"inputs":[],"outputs":[],"title":"About Models","properties":{},"widgets_values":["\neuler_ancestral_cgf_pp (more photorealistic)\nres 3s (slower)\n\n16 steps (best)\n8 steps (ok)\n\n0.6 lora (good ref sheet fidelity)\n\nNAG : enable both\n\n"],"color":"#222","bgcolor":"#000"}],"links":[[7,5,1,8,1,"CONDITIONING"],[10,8,0,9,1,"CONDITIONING"],[11,8,1,9,2,"CONDITIONING"],[12,9,0,10,1,"GUIDER"],[14,7,0,10,4,"LATENT"],[51,28,0,10,0,"NOISE"],[52,11,0,10,3,"SIGMAS"],[53,29,0,10,2,"SAMPLER"],[54,8,0,14,0,"CONDITIONING"],[55,8,1,14,1,"CONDITIONING"],[108,8,2,7,0,"LATENT"],[124,49,0,47,1,"GUIDER"],[125,53,0,47,2,"SAMPLER"],[127,50,0,47,4,"LATENT"],[135,55,2,52,0,"LATENT"],[136,48,0,14,2,"LATENT"],[142,13,0,55,2,"LATENT"],[143,13,1,50,1,"LATENT"],[144,57,0,52,1,"LATENT_UPSCALE_MODEL"],[146,55,0,58,0,"CONDITIONING"],[147,55,1,58,1,"CONDITIONING"],[150,52,0,58,3,"LATENT"],[152,58,0,49,1,"CONDITIONING"],[153,58,1,49,2,"CONDITIONING"],[154,58,2,50,0,"LATENT"],[157,28,0,47,0,"NOISE"],[180,47,0,48,0,"LATENT"],[188,8,0,55,0,"CONDITIONING"],[189,8,1,55,1,"CONDITIONING"],[196,78,0,79,2,"VAE"],[197,79,0,80,0,"MODEL"],[212,94,0,79,0,"MODEL"],[213,101,0,95,0,"MODEL"],[214,99,0,96,0,"MODEL"],[215,97,0,98,0,"MODEL"],[216,100,0,99,0,"MODEL"],[217,98,0,100,0,"MODEL"],[218,99,0,101,0,"MODEL"],[219,80,0,97,0,"MODEL"],[222,15,0,102,0,"IMAGE"],[223,16,0,102,1,"AUDIO"],[227,4,0,107,0,"VAE"],[228,108,0,16,1,"VAE"],[229,3,0,109,0,"VAE"],[230,110,0,8,2,"VAE"],[231,111,0,52,2,"VAE"],[232,112,0,58,2,"VAE"],[233,113,0,15,1,"VAE"],[235,13,1,16,0,"LATENT"],[267,10,0,13,0,"LATENT"],[268,14,2,121,0,"LATENT"],[269,121,0,15,0,"LATENT"],[274,101,0,126,0,"MODEL"],[275,84,0,126,1,"CLIP"],[276,4,0,126,2,"VAE"],[277,117,0,126,4,"IMAGE"],[278,126,7,121,2,"INT"],[279,126,5,102,4,"FLOAT"],[280,126,4,58,4,"GUIDE_DATA"],[281,126,4,8,4,"GUIDE_DATA"],[282,126,3,7,1,"LATENT"],[283,126,2,8,3,"LATENT"],[285,126,1,5,0,"CONDITIONING"],[289,128,0,127,1,"CONDITIONING"],[292,127,0,9,0,"MODEL"],[294,84,0,129,0,"CLIP"],[295,130,0,128,0,"CLIP"],[296,128,0,127,2,"CONDITIONING"],[306,103,0,49,0,"MODEL"],[307,103,0,54,0,"MODEL"],[318,146,0,104,0,"STRING"],[319,104,0,47,3,"SIGMAS"],[324,126,0,127,0,"MODEL"],[325,127,0,147,0,"MODEL"],[326,147,0,11,0,"MODEL"],[330,126,1,135,0,"CONDITIONING"],[334,5,0,8,0,"CONDITIONING"],[335,135,0,149,0,"CONDITIONING"],[336,149,0,5,1,"CONDITIONING"],[337,128,0,149,1,"CONDITIONING"]],"groups":[{"id":3,"title":"Models","bounding":[973.9901435955728,3814.084267255668,300.64000080159235,924.3658313936826],"flags":{}},{"id":4,"title":"Stage #1","bounding":[3016.805994963843,4308.288648200755,1384.2410041339422,813.2043430198282],"flags":{}},{"id":5,"title":"Stage #2 Upscale","bounding":[3013.558756510417,3821.182287967567,1389.017740885416,458.3415391710073],"flags":{}},{"id":6,"title":"Process Video","bounding":[4438.653539696067,3826.095783943629,961.6758977239515,922.5944956941676],"flags":{}},{"id":7,"title":"Timeline","bounding":[1822.946777343749,3817.5522867838554,1174.2045697969816,1307.1973981722067],"flags":{}},{"id":8,"title":"Peformace tweaks (help low vram)","bounding":[1295.3675050369993,4144.525617576839,500.5150133710133,109.75103239530472],"flags":{}},{"id":9,"title":"LTX-2 Loras","bounding":[1300.8568518853965,3816.979752345453,493.249044492934,316.97960132943217],"flags":{}},{"id":10,"title":"Group","bounding":[1292.6722197072684,4271.38921829519,507.5888320390143,592.6764849338833],"flags":{}}],"config":{},"extra":{"ds":{"scale":0.6578973430271261,"offset":[-2468.957891161718,-3551.27657807769]},"frontendVersion":"1.42.14","VHS_latentpreview":false,"VHS_latentpreviewrate":0,"VHS_MetadataImage":true,"VHS_KeepIntermediate":true},"version":0.4} \ No newline at end of file diff --git a/example_workflows/LTX I2V FFLF Custom Audio Workflow - SUPPORTS LATEST COMFYUI VERSION - V3.json b/example_workflows/LTX I2V FFLF Custom Audio Workflow - SUPPORTS LATEST COMFYUI VERSION - V3.json index 383947c..4ff2dea 100644 --- a/example_workflows/LTX I2V FFLF Custom Audio Workflow - SUPPORTS LATEST COMFYUI VERSION - V3.json +++ b/example_workflows/LTX I2V FFLF Custom Audio Workflow - SUPPORTS LATEST COMFYUI VERSION - V3.json @@ -1,7514 +1,7514 @@ -{ - "id": "c80f3abf-7abe-4c24-8663-009ac9c86e3d", - "revision": 0, - "last_node_id": 499, - "last_link_id": 1201, - "nodes": [ - { - "id": 4, - "type": "LTXVConcatAVLatent", - "pos": [ - 4160, - 6110 - ], - "size": [ - 210, - 50 - ], - "flags": {}, - "order": 10, - "mode": 0, - "inputs": [ - { - "name": "video_latent", - "type": "LATENT", - "link": 802 - }, - { - "name": "audio_latent", - "type": "LATENT", - "link": 1153 - } - ], - "outputs": [ - { - "name": "latent", - "type": "LATENT", - "links": [ - 804 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.7.0", - "Node name for S&R": "LTXVConcatAVLatent", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 5, - "type": "SaveVideo", - "pos": [ - 4890, - 5950 - ], - "size": [ - 500, - 370 - ], - "flags": {}, - "order": 13, - "mode": 0, - "inputs": [ - { - "name": "video", - "type": "VIDEO", - "link": 13 - } - ], - "outputs": [], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.5.1", - "Node name for S&R": "SaveVideo", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [ - "video/LTX", - "auto", - "auto" - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 376, - "type": "LTXSequencer", - "pos": [ - 3900, - 5950 - ], - "size": [ - 240, - 200 - ], - "flags": {}, - "order": 8, - "mode": 0, - "inputs": [ - { - "name": "positive", - "type": "CONDITIONING", - "link": 796 - }, - { - "name": "negative", - "type": "CONDITIONING", - "link": 797 - }, - { - "name": "vae", - "type": "VAE", - "link": 798 - }, - { - "name": "latent", - "type": "LATENT", - "link": 803 - }, - { - "name": "multi_input", - "type": "IMAGE", - "link": 1047 - }, - { - "name": "frame_rate", - "type": "INT", - "widget": { - "name": "frame_rate" - }, - "link": 1104 - } - ], - "outputs": [ - { - "name": "positive", - "type": "CONDITIONING", - "links": [ - 799 - ] - }, - { - "name": "negative", - "type": "CONDITIONING", - "links": [ - 800 - ] - }, - { - "name": "latent", - "type": "LATENT", - "links": [ - 802 - ] - } - ], - "properties": { - "cnr_id": "WhatDreamsCost-ComfyUI", - "ver": "ecd00e6b404f14b07c7b54b0b0033e8bccb32d24", - "Node name for S&R": "LTXSequencer", - "num_images": 0, - "insert_frame_1": 0, - "strength_1": 1, - "insert_frame_2": 0, - "strength_2": 1, - "insert_frame_3": 0, - "strength_3": 1, - "insert_frame_4": 0, - "strength_4": 1, - "insert_frame_5": 0, - "strength_5": 1, - "insert_frame_6": 0, - "strength_6": 1, - "insert_frame_7": 0, - "strength_7": 1, - "insert_frame_8": 0, - "strength_8": 1, - "insert_frame_9": 0, - "strength_9": 0, - "insert_frame_10": 1, - "strength_10": 1, - "insert_frame_11": 0, - "strength_11": 0, - "insert_frame_12": 1, - "strength_12": 1, - "insert_frame_13": 0, - "strength_13": 0, - "insert_frame_14": 1, - "strength_14": 1, - "insert_frame_15": 0, - "strength_15": 0, - "insert_frame_16": 1, - "strength_16": 1, - "insert_frame_17": 0, - "strength_17": 0, - "insert_frame_18": 1, - "strength_18": 1, - "insert_frame_19": 0, - "strength_19": 0, - "insert_frame_20": 1, - "strength_20": 1, - "insert_frame_21": 0, - "strength_21": 0, - "insert_frame_22": 1, - "strength_22": 1, - "insert_frame_23": 0, - "strength_23": 0, - "insert_frame_24": 1, - "strength_24": 1, - "insert_frame_25": 0, - "strength_25": 0, - "insert_frame_26": 1, - "strength_26": 1, - "insert_frame_27": 0, - "strength_27": 0, - "insert_frame_28": 1, - "strength_28": 1, - "insert_frame_29": 0, - "strength_29": 0, - "insert_frame_30": 1, - "strength_30": 1, - "insert_frame_31": 0, - "strength_31": 0, - "insert_frame_32": 1, - "strength_32": 1, - "insert_frame_33": 0, - "strength_33": 1, - "insert_frame_34": 0, - "strength_34": 1, - "insert_frame_35": 0, - "strength_35": 1, - "insert_frame_36": 0, - "strength_36": 1, - "insert_frame_37": 0, - "strength_37": 1, - "insert_frame_38": 0, - "strength_38": 1, - "insert_frame_39": 0, - "strength_39": 1, - "insert_frame_40": 0, - "strength_40": 1, - "insert_frame_41": 0, - "strength_41": 1, - "insert_frame_42": 0, - "strength_42": 1, - "insert_frame_43": 0, - "strength_43": 1, - "insert_frame_44": 0, - "strength_44": 1, - "insert_frame_45": 0, - "strength_45": 1, - "insert_frame_46": 0, - "strength_46": 1, - "insert_frame_47": 0, - "strength_47": 1, - "insert_frame_48": 0, - "strength_48": 1, - "insert_frame_49": 0, - "strength_49": 1, - "insert_frame_50": 0, - "strength_50": 1, - "insert_mode": "frames", - "frame_rate": 24, - "insert_second_1": 0, - "insert_second_2": 0, - "insert_second_3": 0, - "insert_second_4": 0, - "insert_second_5": 0, - "insert_second_6": 0, - "insert_second_7": 0, - "insert_second_8": 0, - "insert_second_9": 1, - "insert_second_10": 0, - "insert_second_11": 1, - "insert_second_12": 0, - "insert_second_13": 1, - "insert_second_14": 0, - "insert_second_15": 1, - "insert_second_16": 0, - "insert_second_17": 1, - "insert_second_18": 0, - "insert_second_19": 1, - "insert_second_20": 0, - "insert_second_21": 1, - "insert_second_22": 0, - "insert_second_23": 1, - "insert_second_24": 0, - "insert_second_25": 1, - "insert_second_26": 0, - "insert_second_27": 1, - "insert_second_28": 0, - "insert_second_29": 1, - "insert_second_30": 0, - "insert_second_31": 1, - "insert_second_32": 0, - "insert_second_33": 1, - "insert_second_34": 0, - "insert_second_35": 0, - "insert_second_36": 0, - "insert_second_37": 0, - "insert_second_38": 0, - "insert_second_39": 0, - "insert_second_40": 0, - "insert_second_41": 0, - "insert_second_42": 0, - "insert_second_43": 0, - "insert_second_44": 0, - "insert_second_45": 0, - "insert_second_46": 0, - "insert_second_47": 0, - "insert_second_48": 0, - "insert_second_49": 0, - "insert_second_50": 0, - "insert_images_using": "frames", - "video_length": 1, - "insert_percentage_1": 0, - "insert_percentage_2": 1, - "insert_percentage_3": 0, - "insert_percentage_4": 0, - "insert_percentage_5": 0, - "insert_percentage_6": 0, - "insert_percentage_7": 0, - "insert_percentage_8": 0, - "insert_percentage_9": 0, - "insert_percentage_10": 0, - "insert_percentage_11": 0, - "insert_percentage_12": 0, - "insert_percentage_13": 0, - "insert_percentage_14": 0, - "insert_percentage_15": 0, - "insert_percentage_16": 0, - "insert_percentage_17": 0, - "insert_percentage_18": 0, - "insert_percentage_19": 0, - "insert_percentage_20": 0, - "insert_percentage_21": 0, - "insert_percentage_22": 0, - "insert_percentage_23": 0, - "insert_percentage_24": 0, - "insert_percentage_25": 0, - "insert_percentage_26": 0, - "insert_percentage_27": 0, - "insert_percentage_28": 0, - "insert_percentage_29": 0, - "insert_percentage_30": 0, - "insert_percentage_31": 0, - "insert_percentage_32": 0, - "insert_percentage_33": 0, - "insert_percentage_34": 0, - "insert_percentage_35": 0, - "insert_percentage_36": 0, - "insert_percentage_37": 0, - "insert_percentage_38": 0, - "insert_percentage_39": 0, - "insert_percentage_40": 0, - "insert_percentage_41": 0, - "insert_percentage_42": 0, - "insert_percentage_43": 0, - "insert_percentage_44": 0, - "insert_percentage_45": 0, - "insert_percentage_46": 0, - "insert_percentage_47": 0, - "insert_percentage_48": 0, - "insert_percentage_49": 0, - "insert_percentage_50": 0, - "insert_percent_1": 0, - "insert_percent_2": 50, - "insert_percent_3": 100, - "insert_percent_4": 0, - "insert_percent_5": 0, - "insert_percent_6": 0, - "insert_percent_7": 0, - "insert_percent_8": 0, - "insert_percent_9": 0, - "insert_percent_10": 0, - "insert_percent_11": 0, - "insert_percent_12": 0, - "insert_percent_13": 0, - "insert_percent_14": 0, - "insert_percent_15": 0, - "insert_percent_16": 0, - "insert_percent_17": 0, - "insert_percent_18": 0, - "insert_percent_19": 0, - "insert_percent_20": 0, - "insert_percent_21": 0, - "insert_percent_22": 0, - "insert_percent_23": 0, - "insert_percent_24": 0, - "insert_percent_25": 0, - "insert_percent_26": 0, - "insert_percent_27": 0, - "insert_percent_28": 0, - "insert_percent_29": 0, - "insert_percent_30": 0, - "insert_percent_31": 0, - "insert_percent_32": 0, - "insert_percent_33": 0, - "insert_percent_34": 0, - "insert_percent_35": 0, - "insert_percent_36": 0, - "insert_percent_37": 0, - "insert_percent_38": 0, - "insert_percent_39": 0, - "insert_percent_40": 0, - "insert_percent_41": 0, - "insert_percent_42": 0, - "insert_percent_43": 0, - "insert_percent_44": 0, - "insert_percent_45": 0, - "insert_percent_46": 0, - "insert_percent_47": 0, - "insert_percent_48": 0, - "insert_percent_49": 0, - "insert_percent_50": 0 - }, - "widgets_values": [ - 0, - "frames", - 24, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1 - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 481, - "type": "e8ae487a-31b9-46cf-be65-76e634e0bee1", - "pos": [ - 4160, - 6200 - ], - "size": [ - 220, - 120 - ], - "flags": {}, - "order": 9, - "mode": 0, - "inputs": [ - { - "name": "audio", - "type": "AUDIO", - "link": 1200 - }, - { - "name": "audio_vae", - "type": "VAE", - "link": 1158 - }, - { - "name": "image", - "type": "IMAGE", - "link": 1157 - }, - { - "label": "Use Custom Audio", - "name": "value", - "type": "BOOLEAN", - "widget": { - "name": "value" - }, - "link": 1164 - }, - { - "label": "audio_latent", - "name": "on_false", - "type": "*", - "link": 1166 - } - ], - "outputs": [ - { - "name": "*", - "type": "*", - "links": [ - 1153 - ] - } - ], - "properties": { - "proxyWidgets": [ - [ - "482", - "value" - ] - ], - "cnr_id": "comfy-core", - "ver": "0.19.3" - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 1, - "type": "34d63160-e149-4935-ab1f-892ffe617fec", - "pos": [ - 4660, - 5950 - ], - "size": [ - 210, - 170 - ], - "flags": {}, - "order": 12, - "mode": 0, - "inputs": [ - { - "name": "av_latent", - "type": "LATENT", - "link": 47 - }, - { - "name": "audio_vae", - "type": "VAE", - "link": 2 - }, - { - "label": "video_vae", - "name": "vae", - "type": "VAE", - "link": 31 - }, - { - "label": "frame_rate", - "name": "a", - "type": "INT", - "widget": { - "name": "a" - }, - "link": 1132 - }, - { - "label": "custom audio", - "name": "on_true", - "type": "*", - "link": 1201 - }, - { - "label": "use custom audio", - "name": "switch", - "type": "BOOLEAN", - "widget": { - "name": "switch" - }, - "link": 1176 - }, - { - "label": "use tiled vae", - "name": "value", - "type": "BOOLEAN", - "widget": { - "name": "value" - }, - "link": null - } - ], - "outputs": [ - { - "name": "VIDEO", - "type": "VIDEO", - "links": [ - 13 - ] - } - ], - "properties": { - "proxyWidgets": [ - [ - "468", - "a" - ], - [ - "487", - "switch" - ], - [ - "341", - "value" - ] - ], - "cnr_id": "comfy-core", - "ver": "0.17.2" - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 11, - "type": "360d64f7-96cd-42a6-9b13-6eb0387e17d9", - "pos": [ - 4400, - 5950 - ], - "size": [ - 250, - 370 - ], - "flags": {}, - "order": 11, - "mode": 0, - "inputs": [ - { - "name": "model", - "type": "MODEL", - "link": 40 - }, - { - "name": "positive", - "type": "CONDITIONING", - "link": 799 - }, - { - "name": "negative", - "type": "CONDITIONING", - "link": 800 - }, - { - "name": "latent_image", - "type": "LATENT", - "link": 804 - }, - { - "name": "noise", - "type": "NOISE", - "link": 44 - }, - { - "label": "video_vae", - "name": "vae", - "type": "VAE", - "link": 45 - }, - { - "name": "image", - "type": "IMAGE", - "link": null - }, - { - "name": "multi_input", - "type": "IMAGE", - "link": 1046 - }, - { - "name": "frame_rate", - "type": "INT", - "widget": { - "name": "frame_rate" - }, - "link": 1199 - }, - { - "label": "2x_spatial_upscale_model", - "name": "upscale_model", - "type": "LATENT_UPSCALE_MODEL", - "link": 1116 - } - ], - "outputs": [ - { - "label": "av_output", - "name": "output", - "type": "LATENT", - "links": [ - 47 - ] - } - ], - "properties": { - "proxyWidgets": [ - [ - "6", - "frame_rate" - ] - ], - "cnr_id": "comfy-core", - "ver": "0.17.2" - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 7, - "type": "2979f3eb-1c4c-45ff-8387-f1f4addb72f4", - "pos": [ - 4160, - 5950 - ], - "size": [ - 220, - 110 - ], - "flags": {}, - "order": 7, - "mode": 0, - "inputs": [ - { - "name": "image", - "type": "IMAGE", - "link": 1048 - }, - { - "label": "length_seconds", - "name": "length", - "type": "INT", - "widget": { - "name": "length" - }, - "link": 21 - }, - { - "name": "audio_vae", - "type": "VAE", - "link": 22 - }, - { - "name": "frame_rate", - "type": "INT", - "widget": { - "name": "frame_rate" - }, - "link": 1109 - } - ], - "outputs": [ - { - "label": "video_latent", - "name": "LATENT", - "type": "LATENT", - "links": [ - 803 - ] - }, - { - "label": "audio_latent", - "name": "Latent", - "type": "LATENT", - "links": [ - 1166 - ] - } - ], - "properties": { - "proxyWidgets": [ - [ - "352", - "length" - ], - [ - "353", - "frame_rate" - ] - ], - "cnr_id": "comfy-core", - "ver": "0.17.2" - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 381, - "type": "MultiImageLoader", - "pos": [ - 3540, - 5950 - ], - "size": [ - 340, - 445 - ], - "flags": {}, - "order": 0, - "mode": 0, - "inputs": [], - "outputs": [ - { - "name": "multi_output", - "type": "IMAGE", - "links": [ - 1046, - 1047, - 1048, - 1157 - ] - } - ], - "properties": { - "cnr_id": "WhatDreamsCost-ComfyUI", - "ver": "ecd00e6b404f14b07c7b54b0b0033e8bccb32d24", - "Node name for S&R": "MultiImageLoader" - }, - "widgets_values": [ - "", - 0, - 0, - "lanczos", - "keep proportion", - 32, - 18, - "" - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 10, - "type": "958b2c7a-1f75-4a30-84a8-892bddb9e426", - "pos": [ - 2720, - 5960 - ], - "size": [ - 390, - 480 - ], - "flags": {}, - "order": 1, - "mode": 0, - "inputs": [ - { - "label": "use custom audio", - "name": "value", - "type": "STRING", - "widget": { - "name": "value" - }, - "link": null - }, - { - "label": "frame_rate", - "name": "value_1", - "type": "INT", - "widget": { - "name": "value_1" - }, - "link": null - }, - { - "label": "length (seconds)", - "name": "value_2", - "type": "INT", - "widget": { - "name": "value_2" - }, - "link": null - } - ], - "outputs": [ - { - "name": "optimized_model", - "type": "MODEL", - "links": [ - 40 - ] - }, - { - "label": "video_vae", - "name": "VAE", - "type": "VAE", - "links": [ - 31, - 45, - 798 - ] - }, - { - "label": "audio_vae", - "name": "VAE_1", - "type": "VAE", - "links": [ - 2, - 22, - 1158 - ] - }, - { - "name": "positive", - "type": "CONDITIONING", - "links": [ - 796 - ] - }, - { - "name": "negative", - "type": "CONDITIONING", - "links": [ - 797 - ] - }, - { - "label": "length_seconds", - "name": "INT", - "type": "INT", - "links": [ - 21 - ] - }, - { - "label": "noise", - "name": "NOISE", - "type": "NOISE", - "links": [ - 44 - ] - }, - { - "label": "2x_spatial_upscale_model", - "name": "LATENT_UPSCALE_MODEL", - "type": "LATENT_UPSCALE_MODEL", - "links": [ - 1116 - ] - }, - { - "label": "frame_rate", - "name": "INT_1", - "type": "INT", - "links": [ - 1104, - 1109, - 1132, - 1199 - ] - }, - { - "label": "use custom audio", - "name": "BOOLEAN", - "type": "BOOLEAN", - "links": [ - 1164, - 1176 - ] - } - ], - "properties": { - "proxyWidgets": [ - [ - "464", - "value" - ], - [ - "467", - "value" - ], - [ - "365", - "value" - ], - [ - "364", - "noise_seed" - ], - [ - "364", - "control_after_generate" - ], - [ - "484", - "value" - ] - ], - "cnr_id": "comfy-core", - "ver": "0.17.2" - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 418, - "type": "Note", - "pos": [ - 2730, - 5820 - ], - "size": [ - 270, - 90 - ], - "flags": {}, - "order": 2, - "mode": 0, - "inputs": [], - "outputs": [], - "title": "Model Loader Note", - "properties": {}, - "widgets_values": [ - "↓ Select your own models in this subgraph ↓" - ], - "color": "#223", - "bgcolor": "#335" - }, - { - "id": 499, - "type": "LoadAudioUI", - "pos": [ - 3130, - 5960 - ], - "size": [ - 390, - 378 - ], - "flags": {}, - "order": 3, - "mode": 0, - "inputs": [], - "outputs": [ - { - "name": "audio", - "type": "AUDIO", - "links": [ - 1200, - 1201 - ] - }, - { - "name": "duration", - "type": "FLOAT", - "links": null - } - ], - "properties": { - "cnr_id": "WhatDreamsCost-ComfyUI", - "ver": "919e3c15e53502a7f9165c4355a635b4fa2a07ae", - "Node name for S&R": "LoadAudioUI" - }, - "widgets_values": [ - "audio.wav", - 0, - 25, - 25, - null, - null, - "" - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 15, - "type": "Note", - "pos": [ - 3900, - 5740 - ], - "size": [ - 230, - 150 - ], - "flags": {}, - "order": 4, - "mode": 0, - "inputs": [], - "outputs": [], - "title": "LTX Sequencer Note", - "properties": {}, - "widgets_values": [ - "Use one image at full strength for regular image to video.\n\nIf frame_insert is -1, it will be inserted at the last frame.\n\nThe insert_mode allows to switch between inserting images by setting the exact frame or by setting the exact second." - ], - "color": "#223", - "bgcolor": "#335" - }, - { - "id": 439, - "type": "MarkdownNote", - "pos": [ - 2340, - 6060 - ], - "size": [ - 350, - 380 - ], - "flags": {}, - "order": 5, - "mode": 0, - "inputs": [], - "outputs": [], - "title": "FAQ", - "properties": {}, - "widgets_values": [ - "# FAQ\n\n**How do I set the length of the video?**\n\nYou can set the length of the video in the Model Loader Subgraph →\n\n**I keep getting errors when running this workflow!**\n\nDouble check that all of the correct models loaded. This is using the newest version of 2x upscaler (v1.1). You can change it to the older one under the Model Loader.\n\n**Where do I download these models??**\n\nThere is a good list of models and download links here: \n\nAlso the latest v1.1 spatial upscaler can be found here: \n\nThe tiny vae (taeltx2_3.safetensors) is found here:\n\n\n**I want to use my own models!**\n\nYou can change the models in the Model Loader Subgraph →\n\n\n\n\n" - ], - "color": "#323", - "bgcolor": "#535" - }, - { - "id": 495, - "type": "Note", - "pos": [ - 3130, - 6380 - ], - "size": [ - 270, - 90 - ], - "flags": {}, - "order": 6, - "mode": 0, - "inputs": [], - "outputs": [], - "title": "Custom Audio Note", - "properties": {}, - "widgets_values": [ - "↑ Load Custom Audio Here ↑\n\n(Make sure to set use custom audio to true to enable it)" - ], - "color": "#223", - "bgcolor": "#335" - } - ], - "links": [ - [ - 2, - 10, - 2, - 1, - 1, - "VAE" - ], - [ - 13, - 1, - 0, - 5, - 0, - "VIDEO" - ], - [ - 21, - 10, - 5, - 7, - 1, - "INT" - ], - [ - 22, - 10, - 2, - 7, - 2, - "VAE" - ], - [ - 31, - 10, - 1, - 1, - 2, - "VAE" - ], - [ - 40, - 10, - 0, - 11, - 0, - "MODEL" - ], - [ - 44, - 10, - 6, - 11, - 4, - "NOISE" - ], - [ - 45, - 10, - 1, - 11, - 5, - "VAE" - ], - [ - 47, - 11, - 0, - 1, - 0, - "LATENT" - ], - [ - 796, - 10, - 3, - 376, - 0, - "CONDITIONING" - ], - [ - 797, - 10, - 4, - 376, - 1, - "CONDITIONING" - ], - [ - 798, - 10, - 1, - 376, - 2, - "VAE" - ], - [ - 799, - 376, - 0, - 11, - 1, - "CONDITIONING" - ], - [ - 800, - 376, - 1, - 11, - 2, - "CONDITIONING" - ], - [ - 802, - 376, - 2, - 4, - 0, - "LATENT" - ], - [ - 803, - 7, - 0, - 376, - 3, - "LATENT" - ], - [ - 804, - 4, - 0, - 11, - 3, - "LATENT" - ], - [ - 1046, - 381, - 0, - 11, - 7, - "IMAGE" - ], - [ - 1047, - 381, - 0, - 376, - 4, - "IMAGE" - ], - [ - 1048, - 381, - 0, - 7, - 0, - "IMAGE" - ], - [ - 1104, - 10, - 8, - 376, - 5, - "INT" - ], - [ - 1109, - 10, - 8, - 7, - 3, - "INT" - ], - [ - 1116, - 10, - 7, - 11, - 9, - "LATENT_UPSCALE_MODEL" - ], - [ - 1132, - 10, - 8, - 1, - 3, - "INT" - ], - [ - 1153, - 481, - 0, - 4, - 1, - "LATENT" - ], - [ - 1157, - 381, - 0, - 481, - 2, - "IMAGE" - ], - [ - 1158, - 10, - 2, - 481, - 1, - "VAE" - ], - [ - 1164, - 10, - 9, - 481, - 3, - "BOOLEAN" - ], - [ - 1166, - 7, - 1, - 481, - 4, - "LATENT" - ], - [ - 1176, - 10, - 9, - 1, - 5, - "BOOLEAN" - ], - [ - 1199, - 10, - 8, - 11, - 8, - "INT" - ], - [ - 1200, - 499, - 0, - 481, - 0, - "AUDIO" - ], - [ - 1201, - 499, - 0, - 1, - 4, - "AUDIO" - ] - ], - "groups": [], - "definitions": { - "subgraphs": [ - { - "id": "34d63160-e149-4935-ab1f-892ffe617fec", - "version": 1, - "state": { - "lastGroupId": 33, - "lastNodeId": 499, - "lastLinkId": 1201, - "lastRerouteId": 0 - }, - "revision": 0, - "config": {}, - "name": "Decode", - "inputNode": { - "id": -10, - "bounding": [ - 1610, - 7190, - 140.259765625, - 180 - ] - }, - "outputNode": { - "id": -20, - "bounding": [ - 2970, - 7320, - 120, - 60 - ] - }, - "inputs": [ - { - "id": "1d567d10-9078-49fc-a4a1-67e6d4b5744f", - "name": "av_latent", - "type": "LATENT", - "linkIds": [ - 692 - ], - "pos": [ - 1730.259765625, - 7210 - ] - }, - { - "id": "bef9341f-4429-46af-9bca-11e025bec4d0", - "name": "audio_vae", - "type": "VAE", - "linkIds": [ - 693 - ], - "pos": [ - 1730.259765625, - 7230 - ] - }, - { - "id": "4588ee3d-96a4-46a8-b829-a78181f6c305", - "name": "vae", - "type": "VAE", - "linkIds": [ - 703, - 1060 - ], - "label": "video_vae", - "pos": [ - 1730.259765625, - 7250 - ] - }, - { - "id": "19bcc2d8-c8cd-4e77-bb85-535e398e6b4a", - "name": "a", - "type": "INT", - "linkIds": [ - 1130 - ], - "label": "frame_rate", - "pos": [ - 1730.259765625, - 7270 - ] - }, - { - "id": "da4ad9a2-95b3-4799-a4fb-f0c100fd4970", - "name": "on_true", - "type": "*", - "linkIds": [ - 1179 - ], - "label": "custom audio", - "pos": [ - 1730.259765625, - 7290 - ] - }, - { - "id": "b68ee794-f7af-41a5-b690-fc54a2a5bda4", - "name": "switch", - "type": "BOOLEAN", - "linkIds": [ - 1178 - ], - "label": "use custom audio", - "pos": [ - 1730.259765625, - 7310 - ] - }, - { - "id": "0e84a82b-9dd4-4a34-a1b7-0ffd7affe1f7", - "name": "value", - "type": "BOOLEAN", - "linkIds": [ - 1177 - ], - "label": "use tiled vae", - "pos": [ - 1730.259765625, - 7330 - ] - } - ], - "outputs": [ - { - "id": "7e889c1c-e185-47d7-a0f7-c5f1d971c6fe", - "name": "VIDEO", - "type": "VIDEO", - "linkIds": [ - 704 - ], - "pos": [ - 2990, - 7340 - ] - } - ], - "widgets": [], - "nodes": [ - { - "id": 334, - "type": "LTXVSeparateAVLatent", - "pos": [ - 1820, - 7190 - ], - "size": [ - 240, - 50 - ], - "flags": {}, - "order": 1, - "mode": 0, - "inputs": [ - { - "localized_name": "av_latent", - "name": "av_latent", - "type": "LATENT", - "link": 692 - } - ], - "outputs": [ - { - "localized_name": "video_latent", - "name": "video_latent", - "type": "LATENT", - "links": [ - 702, - 1059 - ] - }, - { - "localized_name": "audio_latent", - "name": "audio_latent", - "type": "LATENT", - "links": [ - 687 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.5.1", - "Node name for S&R": "LTXVSeparateAVLatent", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 340, - "type": "VAEDecode", - "pos": [ - 2110, - 7190 - ], - "size": [ - 150, - 50 - ], - "flags": {}, - "order": 4, - "mode": 0, - "inputs": [ - { - "localized_name": "samples", - "name": "samples", - "type": "LATENT", - "link": 702 - }, - { - "localized_name": "vae", - "name": "vae", - "type": "VAE", - "link": 703 - } - ], - "outputs": [ - { - "localized_name": "IMAGE", - "name": "IMAGE", - "type": "IMAGE", - "links": [ - 701 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.17.2", - "Node name for S&R": "VAEDecode" - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 336, - "type": "CreateVideo", - "pos": [ - 2740, - 7340 - ], - "size": [ - 210, - 80 - ], - "flags": {}, - "order": 2, - "mode": 0, - "inputs": [ - { - "localized_name": "images", - "name": "images", - "type": "IMAGE", - "link": 698 - }, - { - "localized_name": "audio", - "name": "audio", - "shape": 7, - "type": "AUDIO", - "link": 1174 - }, - { - "localized_name": "fps", - "name": "fps", - "type": "FLOAT", - "widget": { - "name": "fps" - }, - "link": 1128 - } - ], - "outputs": [ - { - "localized_name": "VIDEO", - "name": "VIDEO", - "type": "VIDEO", - "links": [ - 704 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.5.1", - "Node name for S&R": "CreateVideo", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [ - 24 - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 468, - "type": "CM_IntToFloat", - "pos": [ - 1820, - 7380 - ], - "size": [ - 240, - 60 - ], - "flags": {}, - "order": 7, - "mode": 0, - "inputs": [ - { - "localized_name": "a", - "name": "a", - "type": "INT", - "widget": { - "name": "a" - }, - "link": 1130 - } - ], - "outputs": [ - { - "localized_name": "FLOAT", - "name": "FLOAT", - "type": "FLOAT", - "links": [ - 1128 - ] - } - ], - "title": "frame_rate_covert", - "properties": { - "cnr_id": "ComfyMath", - "ver": "c01177221c31b8e5fbc062778fc8254aeb541638", - "Node name for S&R": "CM_IntToFloat" - }, - "widgets_values": [ - 0 - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 432, - "type": "VAEDecodeTiled", - "pos": [ - 2110, - 7320 - ], - "size": [ - 270, - 150 - ], - "flags": {}, - "order": 6, - "mode": 0, - "inputs": [ - { - "localized_name": "samples", - "name": "samples", - "type": "LATENT", - "link": 1059 - }, - { - "localized_name": "vae", - "name": "vae", - "type": "VAE", - "link": 1060 - } - ], - "outputs": [ - { - "localized_name": "IMAGE", - "name": "IMAGE", - "type": "IMAGE", - "links": [ - 1058 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.17.2", - "Node name for S&R": "VAEDecodeTiled" - }, - "widgets_values": [ - 512, - 128, - 128, - 8 - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 337, - "type": "LazySwitchKJ", - "pos": [ - 2450, - 7340 - ], - "size": [ - 270, - 80 - ], - "flags": {}, - "order": 3, - "mode": 0, - "inputs": [ - { - "localized_name": "on_false", - "name": "on_false", - "type": "*", - "link": 701 - }, - { - "localized_name": "on_true", - "name": "on_true", - "type": "*", - "link": 1058 - }, - { - "localized_name": "switch", - "name": "switch", - "type": "BOOLEAN", - "widget": { - "name": "switch" - }, - "link": 707 - } - ], - "outputs": [ - { - "localized_name": "*", - "name": "*", - "type": "*", - "links": [ - 698 - ] - } - ], - "properties": { - "cnr_id": "comfyui-kjnodes", - "ver": "1.3.4", - "Node name for S&R": "LazySwitchKJ" - }, - "widgets_values": [ - false - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 487, - "type": "LazySwitchKJ", - "pos": [ - 2110, - 7520 - ], - "size": [ - 270, - 80 - ], - "flags": {}, - "order": 8, - "mode": 0, - "inputs": [ - { - "localized_name": "on_false", - "name": "on_false", - "type": "*", - "link": 1180 - }, - { - "localized_name": "on_true", - "name": "on_true", - "type": "*", - "link": 1179 - }, - { - "localized_name": "switch", - "name": "switch", - "type": "BOOLEAN", - "widget": { - "name": "switch" - }, - "link": 1178 - } - ], - "outputs": [ - { - "localized_name": "*", - "name": "*", - "type": "*", - "links": [ - 1174 - ] - } - ], - "properties": { - "cnr_id": "comfyui-kjnodes", - "ver": "53987d76fbf61830a724628d7681ae5724c98e2b", - "Node name for S&R": "LazySwitchKJ" - }, - "widgets_values": [ - false - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 341, - "type": "PrimitiveBoolean", - "pos": [ - 2450, - 7230 - ], - "size": [ - 230, - 60 - ], - "flags": {}, - "order": 5, - "mode": 0, - "inputs": [ - { - "label": "use tiled vae", - "localized_name": "value", - "name": "value", - "type": "BOOLEAN", - "widget": { - "name": "value" - }, - "link": 1177 - } - ], - "outputs": [ - { - "localized_name": "BOOLEAN", - "name": "BOOLEAN", - "type": "BOOLEAN", - "links": [ - 707 - ] - } - ], - "title": "Use Optimized Decoding", - "properties": { - "cnr_id": "comfy-core", - "ver": "0.17.2", - "Node name for S&R": "PrimitiveBoolean" - }, - "widgets_values": [ - false - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 330, - "type": "LTXVAudioVAEDecode", - "pos": [ - 1820, - 7280 - ], - "size": [ - 240, - 50 - ], - "flags": {}, - "order": 0, - "mode": 0, - "inputs": [ - { - "localized_name": "samples", - "name": "samples", - "type": "LATENT", - "link": 687 - }, - { - "label": "Audio VAE", - "localized_name": "audio_vae", - "name": "audio_vae", - "type": "VAE", - "link": 693 - } - ], - "outputs": [ - { - "localized_name": "Audio", - "name": "Audio", - "type": "AUDIO", - "links": [ - 1180 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.7.0", - "Node name for S&R": "LTXVAudioVAEDecode", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - } - ], - "groups": [], - "links": [ - { - "id": 687, - "origin_id": 334, - "origin_slot": 1, - "target_id": 330, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 692, - "origin_id": -10, - "origin_slot": 0, - "target_id": 334, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 693, - "origin_id": -10, - "origin_slot": 1, - "target_id": 330, - "target_slot": 1, - "type": "VAE" - }, - { - "id": 698, - "origin_id": 337, - "origin_slot": 0, - "target_id": 336, - "target_slot": 0, - "type": "IMAGE" - }, - { - "id": 701, - "origin_id": 340, - "origin_slot": 0, - "target_id": 337, - "target_slot": 0, - "type": "IMAGE" - }, - { - "id": 702, - "origin_id": 334, - "origin_slot": 0, - "target_id": 340, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 703, - "origin_id": -10, - "origin_slot": 2, - "target_id": 340, - "target_slot": 1, - "type": "VAE" - }, - { - "id": 704, - "origin_id": 336, - "origin_slot": 0, - "target_id": -20, - "target_slot": 0, - "type": "VIDEO" - }, - { - "id": 707, - "origin_id": 341, - "origin_slot": 0, - "target_id": 337, - "target_slot": 2, - "type": "BOOLEAN" - }, - { - "id": 1058, - "origin_id": 432, - "origin_slot": 0, - "target_id": 337, - "target_slot": 1, - "type": "IMAGE" - }, - { - "id": 1059, - "origin_id": 334, - "origin_slot": 0, - "target_id": 432, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 1060, - "origin_id": -10, - "origin_slot": 2, - "target_id": 432, - "target_slot": 1, - "type": "VAE" - }, - { - "id": 1128, - "origin_id": 468, - "origin_slot": 0, - "target_id": 336, - "target_slot": 2, - "type": "FLOAT" - }, - { - "id": 1130, - "origin_id": -10, - "origin_slot": 3, - "target_id": 468, - "target_slot": 0, - "type": "INT" - }, - { - "id": 1174, - "origin_id": 487, - "origin_slot": 0, - "target_id": 336, - "target_slot": 1, - "type": "AUDIO" - }, - { - "id": 1177, - "origin_id": -10, - "origin_slot": 6, - "target_id": 341, - "target_slot": 0, - "type": "BOOLEAN" - }, - { - "id": 1178, - "origin_id": -10, - "origin_slot": 5, - "target_id": 487, - "target_slot": 2, - "type": "BOOLEAN" - }, - { - "id": 1179, - "origin_id": -10, - "origin_slot": 4, - "target_id": 487, - "target_slot": 1, - "type": "*" - }, - { - "id": 1180, - "origin_id": 330, - "origin_slot": 0, - "target_id": 487, - "target_slot": 0, - "type": "AUDIO" - } - ], - "extra": { - "workflowRendererVersion": "LG" - } - }, - { - "id": "afa29944-eb58-40a5-8e76-484f2503fa0c", - "version": 1, - "state": { - "lastGroupId": 33, - "lastNodeId": 499, - "lastLinkId": 1201, - "lastRerouteId": 0 - }, - "revision": 0, - "config": {}, - "name": "Stage #1", - "inputNode": { - "id": -10, - "bounding": [ - 280, - 7113, - 120, - 140 - ] - }, - "outputNode": { - "id": -20, - "bounding": [ - 1150, - 6970, - 120, - 80 - ] - }, - "inputs": [ - { - "id": "e760d60a-9b49-4b40-8f39-3c81f29c08d8", - "name": "model", - "type": "MODEL", - "linkIds": [ - 683, - 686 - ], - "pos": [ - 380, - 7133 - ] - }, - { - "id": "6ab6641e-8318-4330-8b01-43ce43648dcf", - "name": "positive", - "type": "CONDITIONING", - "linkIds": [ - 786 - ], - "pos": [ - 380, - 7153 - ] - }, - { - "id": "e1e7e2f6-49fb-4754-92ab-b71442b03da7", - "name": "negative", - "type": "CONDITIONING", - "linkIds": [ - 787 - ], - "pos": [ - 380, - 7173 - ] - }, - { - "id": "5c9d4c86-ec46-4099-b7e1-41a9288a00d0", - "name": "latent_image", - "type": "LATENT", - "linkIds": [ - 687 - ], - "pos": [ - 380, - 7193 - ] - }, - { - "id": "0a89214b-81f0-4fa9-86fa-5f77079ddb7b", - "name": "noise", - "type": "NOISE", - "linkIds": [ - 688 - ], - "pos": [ - 380, - 7213 - ] - } - ], - "outputs": [ - { - "id": "b6f80403-e98c-41ad-a692-270973783381", - "name": "video_latent", - "type": "LATENT", - "linkIds": [ - 689 - ], - "pos": [ - 1170, - 6990 - ] - }, - { - "id": "f54a00fd-f0db-4e4e-9205-9c9d3e955032", - "name": "audio_latent", - "type": "LATENT", - "linkIds": [ - 690 - ], - "pos": [ - 1170, - 7010 - ] - } - ], - "widgets": [], - "nodes": [ - { - "id": 325, - "type": "KSamplerSelect", - "pos": [ - 460, - 7120 - ], - "size": [ - 210, - 60 - ], - "flags": {}, - "order": 0, - "mode": 0, - "inputs": [], - "outputs": [ - { - "localized_name": "SAMPLER", - "name": "SAMPLER", - "type": "SAMPLER", - "links": [ - 680 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.56", - "Node name for S&R": "KSamplerSelect", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [ - "euler" - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 326, - "type": "BasicScheduler", - "pos": [ - 460, - 7220 - ], - "size": [ - 210, - 110 - ], - "flags": {}, - "order": 4, - "mode": 0, - "inputs": [ - { - "localized_name": "model", - "name": "model", - "type": "MODEL", - "link": 686 - } - ], - "outputs": [ - { - "localized_name": "SIGMAS", - "name": "SIGMAS", - "type": "SIGMAS", - "links": [ - 681 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.16.4", - "Node name for S&R": "BasicScheduler" - }, - "widgets_values": [ - "linear_quadratic", - 8, - 1 - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 322, - "type": "SamplerCustomAdvanced", - "pos": [ - 690, - 6980 - ], - "size": [ - 220, - 420 - ], - "flags": {}, - "order": 1, - "mode": 0, - "inputs": [ - { - "localized_name": "noise", - "name": "noise", - "type": "NOISE", - "link": 688 - }, - { - "localized_name": "guider", - "name": "guider", - "type": "GUIDER", - "link": 679 - }, - { - "localized_name": "sampler", - "name": "sampler", - "type": "SAMPLER", - "link": 680 - }, - { - "localized_name": "sigmas", - "name": "sigmas", - "type": "SIGMAS", - "link": 681 - }, - { - "localized_name": "latent_image", - "name": "latent_image", - "type": "LATENT", - "link": 687 - } - ], - "outputs": [ - { - "localized_name": "output", - "name": "output", - "type": "LATENT", - "links": [ - 682 - ] - }, - { - "localized_name": "denoised_output", - "name": "denoised_output", - "type": "LATENT", - "links": [] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.60", - "Node name for S&R": "SamplerCustomAdvanced", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 323, - "type": "LTXVSeparateAVLatent", - "pos": [ - 920, - 6980 - ], - "size": [ - 200, - 50 - ], - "flags": {}, - "order": 2, - "mode": 0, - "inputs": [ - { - "localized_name": "av_latent", - "name": "av_latent", - "type": "LATENT", - "link": 682 - } - ], - "outputs": [ - { - "localized_name": "video_latent", - "name": "video_latent", - "type": "LATENT", - "links": [ - 689 - ] - }, - { - "localized_name": "audio_latent", - "name": "audio_latent", - "type": "LATENT", - "links": [ - 690 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.5.1", - "Node name for S&R": "LTXVSeparateAVLatent", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 324, - "type": "CFGGuider", - "pos": [ - 460, - 6990 - ], - "size": [ - 210, - 100 - ], - "flags": {}, - "order": 3, - "mode": 0, - "inputs": [ - { - "localized_name": "model", - "name": "model", - "type": "MODEL", - "link": 683 - }, - { - "localized_name": "positive", - "name": "positive", - "type": "CONDITIONING", - "link": 786 - }, - { - "localized_name": "negative", - "name": "negative", - "type": "CONDITIONING", - "link": 787 - } - ], - "outputs": [ - { - "localized_name": "GUIDER", - "name": "GUIDER", - "type": "GUIDER", - "links": [ - 679 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.64", - "Node name for S&R": "CFGGuider", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [ - 1 - ], - "color": "#222", - "bgcolor": "#000" - } - ], - "groups": [], - "links": [ - { - "id": 679, - "origin_id": 324, - "origin_slot": 0, - "target_id": 322, - "target_slot": 1, - "type": "GUIDER" - }, - { - "id": 680, - "origin_id": 325, - "origin_slot": 0, - "target_id": 322, - "target_slot": 2, - "type": "SAMPLER" - }, - { - "id": 681, - "origin_id": 326, - "origin_slot": 0, - "target_id": 322, - "target_slot": 3, - "type": "SIGMAS" - }, - { - "id": 682, - "origin_id": 322, - "origin_slot": 0, - "target_id": 323, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 683, - "origin_id": -10, - "origin_slot": 0, - "target_id": 324, - "target_slot": 0, - "type": "MODEL" - }, - { - "id": 686, - "origin_id": -10, - "origin_slot": 0, - "target_id": 326, - "target_slot": 0, - "type": "MODEL" - }, - { - "id": 687, - "origin_id": -10, - "origin_slot": 3, - "target_id": 322, - "target_slot": 4, - "type": "LATENT" - }, - { - "id": 688, - "origin_id": -10, - "origin_slot": 4, - "target_id": 322, - "target_slot": 0, - "type": "NOISE" - }, - { - "id": 689, - "origin_id": 323, - "origin_slot": 0, - "target_id": -20, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 690, - "origin_id": 323, - "origin_slot": 1, - "target_id": -20, - "target_slot": 1, - "type": "LATENT" - }, - { - "id": 786, - "origin_id": -10, - "origin_slot": 1, - "target_id": 324, - "target_slot": 1, - "type": "CONDITIONING" - }, - { - "id": 787, - "origin_id": -10, - "origin_slot": 2, - "target_id": 324, - "target_slot": 2, - "type": "CONDITIONING" - } - ], - "extra": { - "workflowRendererVersion": "LG" - } - }, - { - "id": "ce8f7842-cb03-41b4-9b1e-8d032b2a4e72", - "version": 1, - "state": { - "lastGroupId": 33, - "lastNodeId": 499, - "lastLinkId": 1201, - "lastRerouteId": 0 - }, - "revision": 0, - "config": {}, - "name": "Stage #2", - "inputNode": { - "id": -10, - "bounding": [ - 280, - 7113, - 125.224609375, - 260 - ] - }, - "outputNode": { - "id": -20, - "bounding": [ - 1820, - 7130, - 120, - 120 - ] - }, - "inputs": [ - { - "id": "e760d60a-9b49-4b40-8f39-3c81f29c08d8", - "name": "model", - "type": "MODEL", - "linkIds": [ - 683, - 686 - ], - "pos": [ - 385.224609375, - 7133 - ] - }, - { - "id": "6ab6641e-8318-4330-8b01-43ce43648dcf", - "name": "positive", - "type": "CONDITIONING", - "linkIds": [ - 703 - ], - "pos": [ - 385.224609375, - 7153 - ] - }, - { - "id": "e1e7e2f6-49fb-4754-92ab-b71442b03da7", - "name": "negative", - "type": "CONDITIONING", - "linkIds": [ - 704 - ], - "pos": [ - 385.224609375, - 7173 - ] - }, - { - "id": "0a89214b-81f0-4fa9-86fa-5f77079ddb7b", - "name": "noise", - "type": "NOISE", - "linkIds": [ - 688 - ], - "pos": [ - 385.224609375, - 7193 - ] - }, - { - "id": "d8824343-6b21-42aa-9eaf-65d1b267662d", - "name": "vae", - "type": "VAE", - "linkIds": [ - 706, - 990 - ], - "label": "video_vae", - "pos": [ - 385.224609375, - 7213 - ] - }, - { - "id": "6ea10719-25f1-449a-9d2d-bb395d89609d", - "name": "image", - "type": "IMAGE", - "linkIds": [], - "pos": [ - 385.224609375, - 7233 - ] - }, - { - "id": "6c54f923-7ccc-4832-86ca-38d39cfcc656", - "name": "samples", - "type": "LATENT", - "linkIds": [ - 705 - ], - "label": "video_latent", - "pos": [ - 385.224609375, - 7253 - ] - }, - { - "id": "5ce3a92e-d38a-4078-bc1f-7426a38fa1ff", - "name": "audio_latent", - "type": "LATENT", - "linkIds": [ - 700 - ], - "pos": [ - 385.224609375, - 7273 - ] - }, - { - "id": "6a8ebcb1-8f4b-4558-893c-1b6be5c462ec", - "name": "multi_input", - "type": "IMAGE", - "linkIds": [ - 1004 - ], - "pos": [ - 385.224609375, - 7293 - ] - }, - { - "id": "7443af4e-2549-4707-813a-a7e7efa13046", - "name": "upscale_model", - "type": "LATENT_UPSCALE_MODEL", - "linkIds": [ - 1035 - ], - "pos": [ - 385.224609375, - 7313 - ] - }, - { - "id": "26a11f82-8eda-4ba1-be18-c0d429489b0e", - "name": "frame_rate", - "type": "INT", - "linkIds": [ - 1125 - ], - "pos": [ - 385.224609375, - 7333 - ] - } - ], - "outputs": [ - { - "id": "b6f80403-e98c-41ad-a692-270973783381", - "name": "video_latent", - "type": "LATENT", - "linkIds": [ - 843 - ], - "pos": [ - 1840, - 7150 - ] - }, - { - "id": "f54a00fd-f0db-4e4e-9205-9c9d3e955032", - "name": "audio_latent", - "type": "LATENT", - "linkIds": [ - 690 - ], - "pos": [ - 1840, - 7170 - ] - }, - { - "id": "3ecaa920-05d1-4c39-8ea0-b7b21e2df49b", - "name": "positive", - "type": "CONDITIONING", - "linkIds": [ - 985 - ], - "pos": [ - 1840, - 7190 - ] - }, - { - "id": "d1c64c87-487c-4677-9f80-d4a7ef6cdcc6", - "name": "negative", - "type": "CONDITIONING", - "linkIds": [ - 986 - ], - "pos": [ - 1840, - 7210 - ] - } - ], - "widgets": [], - "nodes": [ - { - "id": 445, - "type": "KSamplerSelect", - "pos": [ - 480, - 7110 - ], - "size": [ - 210, - 60 - ], - "flags": {}, - "order": 0, - "mode": 0, - "inputs": [], - "outputs": [ - { - "localized_name": "SAMPLER", - "name": "SAMPLER", - "type": "SAMPLER", - "links": [ - 680 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.56", - "Node name for S&R": "KSamplerSelect", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [ - "euler" - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 331, - "type": "LTXVCropGuides", - "pos": [ - 490, - 7000 - ], - "size": [ - 160, - 70 - ], - "flags": {}, - "order": 3, - "mode": 0, - "inputs": [ - { - "localized_name": "positive", - "name": "positive", - "type": "CONDITIONING", - "link": 703 - }, - { - "localized_name": "negative", - "name": "negative", - "type": "CONDITIONING", - "link": 704 - }, - { - "localized_name": "latent", - "name": "latent", - "type": "LATENT", - "link": 705 - } - ], - "outputs": [ - { - "localized_name": "positive", - "name": "positive", - "type": "CONDITIONING", - "links": [ - 828, - 846, - 988 - ] - }, - { - "localized_name": "negative", - "name": "negative", - "type": "CONDITIONING", - "links": [ - 829, - 847, - 989 - ] - }, - { - "localized_name": "latent", - "name": "latent", - "type": "LATENT", - "links": [ - 850 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.16.4", - "Node name for S&R": "LTXVCropGuides" - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 328, - "type": "LTXVLatentUpsampler", - "pos": [ - 480, - 7370 - ], - "size": [ - 320, - 70 - ], - "flags": {}, - "order": 1, - "mode": 0, - "inputs": [ - { - "localized_name": "samples", - "name": "samples", - "type": "LATENT", - "link": 850 - }, - { - "localized_name": "upscale_model", - "name": "upscale_model", - "type": "LATENT_UPSCALE_MODEL", - "link": 1035 - }, - { - "localized_name": "vae", - "name": "vae", - "type": "VAE", - "link": 706 - } - ], - "outputs": [ - { - "localized_name": "LATENT", - "name": "LATENT", - "type": "LATENT", - "links": [ - 991 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.14.1", - "Node name for S&R": "LTXVLatentUpsampler", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 409, - "type": "LTXSequencer", - "pos": [ - 850, - 7140 - ], - "size": [ - 210, - 200 - ], - "flags": {}, - "order": 4, - "mode": 0, - "inputs": [ - { - "localized_name": "positive", - "name": "positive", - "type": "CONDITIONING", - "link": 988 - }, - { - "localized_name": "negative", - "name": "negative", - "type": "CONDITIONING", - "link": 989 - }, - { - "localized_name": "vae", - "name": "vae", - "type": "VAE", - "link": 990 - }, - { - "localized_name": "latent", - "name": "latent", - "type": "LATENT", - "link": 991 - }, - { - "localized_name": "multi_input", - "name": "multi_input", - "type": "IMAGE", - "link": 1004 - }, - { - "localized_name": "frame_rate", - "name": "frame_rate", - "type": "INT", - "widget": { - "name": "frame_rate" - }, - "link": 1125 - } - ], - "outputs": [ - { - "localized_name": "positive", - "name": "positive", - "type": "CONDITIONING", - "links": [ - 983, - 985 - ] - }, - { - "localized_name": "negative", - "name": "negative", - "type": "CONDITIONING", - "links": [ - 984, - 986 - ] - }, - { - "localized_name": "latent", - "name": "latent", - "type": "LATENT", - "links": [ - 987 - ] - } - ], - "properties": { - "cnr_id": "WhatDreamsCost-ComfyUI", - "ver": "ecd00e6b404f14b07c7b54b0b0033e8bccb32d24", - "Node name for S&R": "LTXSequencer", - "num_images": 0, - "insert_frame_1": 0, - "strength_1": 1, - "insert_frame_2": 0, - "strength_2": 1, - "insert_frame_3": 0, - "strength_3": 1, - "insert_frame_4": 0, - "strength_4": 1, - "insert_frame_5": 0, - "strength_5": 1, - "insert_frame_6": 0, - "strength_6": 1, - "insert_frame_7": 0, - "strength_7": 1, - "insert_frame_8": 0, - "strength_8": 1, - "insert_frame_9": 0, - "strength_9": 0, - "insert_frame_10": 1, - "strength_10": 1, - "insert_frame_11": 0, - "strength_11": 0, - "insert_frame_12": 1, - "strength_12": 1, - "insert_frame_13": 0, - "strength_13": 0, - "insert_frame_14": 1, - "strength_14": 1, - "insert_frame_15": 0, - "strength_15": 0, - "insert_frame_16": 1, - "strength_16": 1, - "insert_frame_17": 0, - "strength_17": 0, - "insert_frame_18": 1, - "strength_18": 1, - "insert_frame_19": 0, - "strength_19": 0, - "insert_frame_20": 1, - "strength_20": 1, - "insert_frame_21": 0, - "strength_21": 0, - "insert_frame_22": 1, - "strength_22": 1, - "insert_frame_23": 0, - "strength_23": 0, - "insert_frame_24": 1, - "strength_24": 1, - "insert_frame_25": 0, - "strength_25": 0, - "insert_frame_26": 1, - "strength_26": 1, - "insert_frame_27": 0, - "strength_27": 0, - "insert_frame_28": 1, - "strength_28": 1, - "insert_frame_29": 0, - "strength_29": 0, - "insert_frame_30": 1, - "strength_30": 1, - "insert_frame_31": 0, - "strength_31": 0, - "insert_frame_32": 1, - "strength_32": 1, - "insert_frame_33": 0, - "strength_33": 1, - "insert_frame_34": 0, - "strength_34": 1, - "insert_frame_35": 0, - "strength_35": 1, - "insert_frame_36": 0, - "strength_36": 1, - "insert_frame_37": 0, - "strength_37": 1, - "insert_frame_38": 0, - "strength_38": 1, - "insert_frame_39": 0, - "strength_39": 1, - "insert_frame_40": 0, - "strength_40": 1, - "insert_frame_41": 0, - "strength_41": 1, - "insert_frame_42": 0, - "strength_42": 1, - "insert_frame_43": 0, - "strength_43": 1, - "insert_frame_44": 0, - "strength_44": 1, - "insert_frame_45": 0, - "strength_45": 1, - "insert_frame_46": 0, - "strength_46": 1, - "insert_frame_47": 0, - "strength_47": 1, - "insert_frame_48": 0, - "strength_48": 1, - "insert_frame_49": 0, - "strength_49": 1, - "insert_frame_50": 0, - "strength_50": 1, - "insert_mode": "frames", - "frame_rate": 24, - "insert_second_1": 0, - "insert_second_2": 0, - "insert_second_3": 0, - "insert_second_4": 0, - "insert_second_5": 0, - "insert_second_6": 0, - "insert_second_7": 0, - "insert_second_8": 0, - "insert_second_9": 1, - "insert_second_10": 0, - "insert_second_11": 1, - "insert_second_12": 0, - "insert_second_13": 1, - "insert_second_14": 0, - "insert_second_15": 1, - "insert_second_16": 0, - "insert_second_17": 1, - "insert_second_18": 0, - "insert_second_19": 1, - "insert_second_20": 0, - "insert_second_21": 1, - "insert_second_22": 0, - "insert_second_23": 1, - "insert_second_24": 0, - "insert_second_25": 1, - "insert_second_26": 0, - "insert_second_27": 1, - "insert_second_28": 0, - "insert_second_29": 1, - "insert_second_30": 0, - "insert_second_31": 1, - "insert_second_32": 0, - "insert_second_33": 1, - "insert_second_34": 0, - "insert_second_35": 0, - "insert_second_36": 0, - "insert_second_37": 0, - "insert_second_38": 0, - "insert_second_39": 0, - "insert_second_40": 0, - "insert_second_41": 0, - "insert_second_42": 0, - "insert_second_43": 0, - "insert_second_44": 0, - "insert_second_45": 0, - "insert_second_46": 0, - "insert_second_47": 0, - "insert_second_48": 0, - "insert_second_49": 0, - "insert_second_50": 0, - "insert_images_using": "frames", - "video_length": 1, - "insert_percentage_1": 0, - "insert_percentage_2": 1, - "insert_percentage_3": 0, - "insert_percentage_4": 0, - "insert_percentage_5": 0, - "insert_percentage_6": 0, - "insert_percentage_7": 0, - "insert_percentage_8": 0, - "insert_percentage_9": 0, - "insert_percentage_10": 0, - "insert_percentage_11": 0, - "insert_percentage_12": 0, - "insert_percentage_13": 0, - "insert_percentage_14": 0, - "insert_percentage_15": 0, - "insert_percentage_16": 0, - "insert_percentage_17": 0, - "insert_percentage_18": 0, - "insert_percentage_19": 0, - "insert_percentage_20": 0, - "insert_percentage_21": 0, - "insert_percentage_22": 0, - "insert_percentage_23": 0, - "insert_percentage_24": 0, - "insert_percentage_25": 0, - "insert_percentage_26": 0, - "insert_percentage_27": 0, - "insert_percentage_28": 0, - "insert_percentage_29": 0, - "insert_percentage_30": 0, - "insert_percentage_31": 0, - "insert_percentage_32": 0, - "insert_percentage_33": 0, - "insert_percentage_34": 0, - "insert_percentage_35": 0, - "insert_percentage_36": 0, - "insert_percentage_37": 0, - "insert_percentage_38": 0, - "insert_percentage_39": 0, - "insert_percentage_40": 0, - "insert_percentage_41": 0, - "insert_percentage_42": 0, - "insert_percentage_43": 0, - "insert_percentage_44": 0, - "insert_percentage_45": 0, - "insert_percentage_46": 0, - "insert_percentage_47": 0, - "insert_percentage_48": 0, - "insert_percentage_49": 0, - "insert_percentage_50": 0, - "insert_percent_1": 0, - "insert_percent_2": 50, - "insert_percent_3": 100, - "insert_percent_4": 0, - "insert_percent_5": 0, - "insert_percent_6": 0, - "insert_percent_7": 0, - "insert_percent_8": 0, - "insert_percent_9": 0, - "insert_percent_10": 0, - "insert_percent_11": 0, - "insert_percent_12": 0, - "insert_percent_13": 0, - "insert_percent_14": 0, - "insert_percent_15": 0, - "insert_percent_16": 0, - "insert_percent_17": 0, - "insert_percent_18": 0, - "insert_percent_19": 0, - "insert_percent_20": 0, - "insert_percent_21": 0, - "insert_percent_22": 0, - "insert_percent_23": 0, - "insert_percent_24": 0, - "insert_percent_25": 0, - "insert_percent_26": 0, - "insert_percent_27": 0, - "insert_percent_28": 0, - "insert_percent_29": 0, - "insert_percent_30": 0, - "insert_percent_31": 0, - "insert_percent_32": 0, - "insert_percent_33": 0, - "insert_percent_34": 0, - "insert_percent_35": 0, - "insert_percent_36": 0, - "insert_percent_37": 0, - "insert_percent_38": 0, - "insert_percent_39": 0, - "insert_percent_40": 0, - "insert_percent_41": 0, - "insert_percent_42": 0, - "insert_percent_43": 0, - "insert_percent_44": 0, - "insert_percent_45": 0, - "insert_percent_46": 0, - "insert_percent_47": 0, - "insert_percent_48": 0, - "insert_percent_49": 0, - "insert_percent_50": 0 - }, - "widgets_values": [ - 0, - "frames", - 24, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1 - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 444, - "type": "BasicScheduler", - "pos": [ - 480, - 7210 - ], - "size": [ - 210, - 110 - ], - "flags": {}, - "order": 5, - "mode": 0, - "inputs": [ - { - "localized_name": "model", - "name": "model", - "type": "MODEL", - "link": 686 - } - ], - "outputs": [ - { - "localized_name": "SIGMAS", - "name": "SIGMAS", - "type": "SIGMAS", - "links": [ - 681 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.16.4", - "Node name for S&R": "BasicScheduler" - }, - "widgets_values": [ - "linear_quadratic", - 4, - 0.42 - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 448, - "type": "CFGGuider", - "pos": [ - 1090, - 7130 - ], - "size": [ - 210, - 100 - ], - "flags": {}, - "order": 8, - "mode": 0, - "inputs": [ - { - "localized_name": "model", - "name": "model", - "type": "MODEL", - "link": 683 - }, - { - "localized_name": "positive", - "name": "positive", - "type": "CONDITIONING", - "link": 983 - }, - { - "localized_name": "negative", - "name": "negative", - "type": "CONDITIONING", - "link": 984 - } - ], - "outputs": [ - { - "localized_name": "GUIDER", - "name": "GUIDER", - "type": "GUIDER", - "links": [ - 679 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.64", - "Node name for S&R": "CFGGuider", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [ - 1 - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 329, - "type": "LTXVConcatAVLatent", - "pos": [ - 1090, - 7270 - ], - "size": [ - 190, - 60 - ], - "flags": {}, - "order": 2, - "mode": 0, - "inputs": [ - { - "localized_name": "video_latent", - "name": "video_latent", - "type": "LATENT", - "link": 987 - }, - { - "localized_name": "audio_latent", - "name": "audio_latent", - "type": "LATENT", - "link": 700 - } - ], - "outputs": [ - { - "localized_name": "latent", - "name": "latent", - "type": "LATENT", - "links": [ - 697 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.7.0", - "Node name for S&R": "LTXVConcatAVLatent", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 446, - "type": "SamplerCustomAdvanced", - "pos": [ - 1330, - 7130 - ], - "size": [ - 220, - 420 - ], - "flags": {}, - "order": 6, - "mode": 0, - "inputs": [ - { - "localized_name": "noise", - "name": "noise", - "type": "NOISE", - "link": 688 - }, - { - "localized_name": "guider", - "name": "guider", - "type": "GUIDER", - "link": 679 - }, - { - "localized_name": "sampler", - "name": "sampler", - "type": "SAMPLER", - "link": 680 - }, - { - "localized_name": "sigmas", - "name": "sigmas", - "type": "SIGMAS", - "link": 681 - }, - { - "localized_name": "latent_image", - "name": "latent_image", - "type": "LATENT", - "link": 697 - } - ], - "outputs": [ - { - "localized_name": "output", - "name": "output", - "type": "LATENT", - "links": [ - 682 - ] - }, - { - "localized_name": "denoised_output", - "name": "denoised_output", - "type": "LATENT", - "links": [] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.60", - "Node name for S&R": "SamplerCustomAdvanced", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 447, - "type": "LTXVSeparateAVLatent", - "pos": [ - 1570, - 7130 - ], - "size": [ - 200, - 50 - ], - "flags": {}, - "order": 7, - "mode": 0, - "inputs": [ - { - "localized_name": "av_latent", - "name": "av_latent", - "type": "LATENT", - "link": 682 - } - ], - "outputs": [ - { - "localized_name": "video_latent", - "name": "video_latent", - "type": "LATENT", - "links": [ - 843 - ] - }, - { - "localized_name": "audio_latent", - "name": "audio_latent", - "type": "LATENT", - "links": [ - 690 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.5.1", - "Node name for S&R": "LTXVSeparateAVLatent", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - } - ], - "groups": [], - "links": [ - { - "id": 679, - "origin_id": 448, - "origin_slot": 0, - "target_id": 446, - "target_slot": 1, - "type": "GUIDER" - }, - { - "id": 680, - "origin_id": 445, - "origin_slot": 0, - "target_id": 446, - "target_slot": 2, - "type": "SAMPLER" - }, - { - "id": 681, - "origin_id": 444, - "origin_slot": 0, - "target_id": 446, - "target_slot": 3, - "type": "SIGMAS" - }, - { - "id": 682, - "origin_id": 446, - "origin_slot": 0, - "target_id": 447, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 683, - "origin_id": -10, - "origin_slot": 0, - "target_id": 448, - "target_slot": 0, - "type": "MODEL" - }, - { - "id": 686, - "origin_id": -10, - "origin_slot": 0, - "target_id": 444, - "target_slot": 0, - "type": "MODEL" - }, - { - "id": 688, - "origin_id": -10, - "origin_slot": 3, - "target_id": 446, - "target_slot": 0, - "type": "NOISE" - }, - { - "id": 690, - "origin_id": 447, - "origin_slot": 1, - "target_id": -20, - "target_slot": 1, - "type": "LATENT" - }, - { - "id": 697, - "origin_id": 329, - "origin_slot": 0, - "target_id": 446, - "target_slot": 4, - "type": "LATENT" - }, - { - "id": 700, - "origin_id": -10, - "origin_slot": 7, - "target_id": 329, - "target_slot": 1, - "type": "LATENT" - }, - { - "id": 703, - "origin_id": -10, - "origin_slot": 1, - "target_id": 331, - "target_slot": 0, - "type": "CONDITIONING" - }, - { - "id": 704, - "origin_id": -10, - "origin_slot": 2, - "target_id": 331, - "target_slot": 1, - "type": "CONDITIONING" - }, - { - "id": 705, - "origin_id": -10, - "origin_slot": 6, - "target_id": 331, - "target_slot": 2, - "type": "LATENT" - }, - { - "id": 706, - "origin_id": -10, - "origin_slot": 4, - "target_id": 328, - "target_slot": 2, - "type": "VAE" - }, - { - "id": 843, - "origin_id": 447, - "origin_slot": 0, - "target_id": -20, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 850, - "origin_id": 331, - "origin_slot": 2, - "target_id": 328, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 983, - "origin_id": 409, - "origin_slot": 0, - "target_id": 448, - "target_slot": 1, - "type": "CONDITIONING" - }, - { - "id": 984, - "origin_id": 409, - "origin_slot": 1, - "target_id": 448, - "target_slot": 2, - "type": "CONDITIONING" - }, - { - "id": 985, - "origin_id": 409, - "origin_slot": 0, - "target_id": -20, - "target_slot": 2, - "type": "CONDITIONING" - }, - { - "id": 986, - "origin_id": 409, - "origin_slot": 1, - "target_id": -20, - "target_slot": 3, - "type": "CONDITIONING" - }, - { - "id": 987, - "origin_id": 409, - "origin_slot": 2, - "target_id": 329, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 988, - "origin_id": 331, - "origin_slot": 0, - "target_id": 409, - "target_slot": 0, - "type": "CONDITIONING" - }, - { - "id": 989, - "origin_id": 331, - "origin_slot": 1, - "target_id": 409, - "target_slot": 1, - "type": "CONDITIONING" - }, - { - "id": 990, - "origin_id": -10, - "origin_slot": 4, - "target_id": 409, - "target_slot": 2, - "type": "VAE" - }, - { - "id": 991, - "origin_id": 328, - "origin_slot": 0, - "target_id": 409, - "target_slot": 3, - "type": "LATENT" - }, - { - "id": 1004, - "origin_id": -10, - "origin_slot": 8, - "target_id": 409, - "target_slot": 4, - "type": "IMAGE" - }, - { - "id": 1035, - "origin_id": -10, - "origin_slot": 9, - "target_id": 328, - "target_slot": 1, - "type": "LATENT_UPSCALE_MODEL" - }, - { - "id": 1125, - "origin_id": -10, - "origin_slot": 10, - "target_id": 409, - "target_slot": 5, - "type": "INT" - } - ], - "extra": { - "workflowRendererVersion": "LG" - } - }, - { - "id": "2979f3eb-1c4c-45ff-8387-f1f4addb72f4", - "version": 1, - "state": { - "lastGroupId": 33, - "lastNodeId": 499, - "lastLinkId": 1201, - "lastRerouteId": 0 - }, - "revision": 0, - "config": {}, - "name": "Process Latents", - "inputNode": { - "id": -10, - "bounding": [ - 2630, - 6303, - 128.998046875, - 120 - ] - }, - "outputNode": { - "id": -20, - "bounding": [ - 3500, - 6303, - 120, - 80 - ] - }, - "inputs": [ - { - "id": "f9d3f39d-4628-4ef2-a464-ffb98c1fb4bc", - "name": "image", - "type": "IMAGE", - "linkIds": [ - 713 - ], - "localized_name": "image", - "pos": [ - 2738.998046875, - 6323 - ] - }, - { - "id": "6c61b79c-c6a2-4cd9-96a7-72073d3603c1", - "name": "length", - "type": "INT", - "linkIds": [ - 708, - 709 - ], - "localized_name": "length", - "label": "length_seconds", - "pos": [ - 2738.998046875, - 6343 - ] - }, - { - "id": "610c02c9-497c-45ac-b603-ea5bd91aec4c", - "name": "audio_vae", - "type": "VAE", - "linkIds": [ - 714 - ], - "pos": [ - 2738.998046875, - 6363 - ] - }, - { - "id": "01e20d2c-0498-4d41-8a62-b867efe06044", - "name": "frame_rate", - "type": "INT", - "linkIds": [ - 1135 - ], - "pos": [ - 2738.998046875, - 6383 - ] - } - ], - "outputs": [ - { - "id": "2cf8b0ee-4e7a-4045-a1ee-bbd4b878068e", - "name": "LATENT", - "type": "LATENT", - "linkIds": [ - 710 - ], - "localized_name": "LATENT", - "label": "video_latent", - "pos": [ - 3520, - 6323 - ] - }, - { - "id": "44e92917-679f-4cc3-aa57-51b4a6ef24a8", - "name": "Latent", - "type": "LATENT", - "linkIds": [ - 705 - ], - "localized_name": "Latent", - "label": "audio_latent", - "pos": [ - 3520, - 6343 - ] - } - ], - "widgets": [], - "nodes": [ - { - "id": 356, - "type": "GetImageSize", - "pos": [ - 3030, - 6210 - ], - "size": [ - 170, - 70 - ], - "flags": {}, - "order": 2, - "mode": 0, - "inputs": [ - { - "localized_name": "image", - "name": "image", - "type": "IMAGE", - "link": 711 - } - ], - "outputs": [ - { - "localized_name": "width", - "name": "width", - "type": "INT", - "links": [ - 706 - ] - }, - { - "localized_name": "height", - "name": "height", - "type": "INT", - "links": [ - 707 - ] - }, - { - "localized_name": "batch_size", - "name": "batch_size", - "type": "INT", - "links": null - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.7.0", - "Node name for S&R": "GetImageSize", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 352, - "type": "EmptyLTXVLatentVideo", - "pos": [ - 3210, - 6210 - ], - "size": [ - 210, - 130 - ], - "flags": {}, - "order": 0, - "mode": 0, - "inputs": [ - { - "localized_name": "width", - "name": "width", - "type": "INT", - "widget": { - "name": "width" - }, - "link": 706 - }, - { - "localized_name": "height", - "name": "height", - "type": "INT", - "widget": { - "name": "height" - }, - "link": 707 - }, - { - "localized_name": "length", - "name": "length", - "type": "INT", - "widget": { - "name": "length" - }, - "link": 708 - } - ], - "outputs": [ - { - "localized_name": "LATENT", - "name": "LATENT", - "type": "LATENT", - "links": [ - 710 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.60", - "Node name for S&R": "EmptyLTXVLatentVideo", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [ - 768, - 512, - 1, - 1 - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 357, - "type": "ImageScaleBy", - "pos": [ - 2810, - 6210 - ], - "size": [ - 210, - 90 - ], - "flags": {}, - "order": 3, - "mode": 0, - "inputs": [ - { - "localized_name": "image", - "name": "image", - "type": "IMAGE", - "link": 713 - } - ], - "outputs": [ - { - "localized_name": "IMAGE", - "name": "IMAGE", - "type": "IMAGE", - "links": [ - 711 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.5.1", - "Node name for S&R": "ImageScaleBy", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65, - "ue_properties": { - "version": "7.0.1", - "widget_ue_connectable": {} - } - }, - "widgets_values": [ - "bilinear", - 0.5 - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 353, - "type": "LTXVEmptyLatentAudio", - "pos": [ - 3220, - 6380 - ], - "size": [ - 210, - 110 - ], - "flags": {}, - "order": 1, - "mode": 0, - "inputs": [ - { - "localized_name": "audio_vae", - "name": "audio_vae", - "type": "VAE", - "link": 714 - }, - { - "localized_name": "frames_number", - "name": "frames_number", - "type": "INT", - "widget": { - "name": "frames_number" - }, - "link": 709 - }, - { - "label": "frame_rate", - "localized_name": "frame_rate", - "name": "frame_rate", - "type": "INT", - "widget": { - "name": "frame_rate" - }, - "link": 1135 - } - ], - "outputs": [ - { - "localized_name": "Latent", - "name": "Latent", - "type": "LATENT", - "links": [ - 705 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.68", - "Node name for S&R": "LTXVEmptyLatentAudio", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [ - 1, - 24, - 1 - ], - "color": "#222", - "bgcolor": "#000" - } - ], - "groups": [], - "links": [ - { - "id": 711, - "origin_id": 357, - "origin_slot": 0, - "target_id": 356, - "target_slot": 0, - "type": "IMAGE" - }, - { - "id": 706, - "origin_id": 356, - "origin_slot": 0, - "target_id": 352, - "target_slot": 0, - "type": "INT" - }, - { - "id": 707, - "origin_id": 356, - "origin_slot": 1, - "target_id": 352, - "target_slot": 1, - "type": "INT" - }, - { - "id": 713, - "origin_id": -10, - "origin_slot": 0, - "target_id": 357, - "target_slot": 0, - "type": "IMAGE" - }, - { - "id": 708, - "origin_id": -10, - "origin_slot": 1, - "target_id": 352, - "target_slot": 2, - "type": "INT" - }, - { - "id": 709, - "origin_id": -10, - "origin_slot": 1, - "target_id": 353, - "target_slot": 1, - "type": "INT" - }, - { - "id": 710, - "origin_id": 352, - "origin_slot": 0, - "target_id": -20, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 705, - "origin_id": 353, - "origin_slot": 0, - "target_id": -20, - "target_slot": 1, - "type": "LATENT" - }, - { - "id": 714, - "origin_id": -10, - "origin_slot": 2, - "target_id": 353, - "target_slot": 0, - "type": "VAE" - }, - { - "id": 1135, - "origin_id": -10, - "origin_slot": 3, - "target_id": 353, - "target_slot": 2, - "type": "INT" - } - ], - "extra": { - "workflowRendererVersion": "LG" - } - }, - { - "id": "958b2c7a-1f75-4a30-84a8-892bddb9e426", - "version": 1, - "state": { - "lastGroupId": 33, - "lastNodeId": 499, - "lastLinkId": 1201, - "lastRerouteId": 0 - }, - "revision": 0, - "config": {}, - "name": "Model Loader", - "inputNode": { - "id": -10, - "bounding": [ - 2340, - 6060, - 140.259765625, - 100 - ] - }, - "outputNode": { - "id": -20, - "bounding": [ - 3570, - 5660, - 187.052734375, - 240 - ] - }, - "inputs": [ - { - "id": "f0ec1006-3b28-4d50-b32e-931ea4500394", - "name": "value", - "type": "STRING", - "linkIds": [ - 1091 - ], - "label": "use custom audio", - "pos": [ - 2460.259765625, - 6080 - ] - }, - { - "id": "1f2a8c23-9530-441b-9848-9efe951534ab", - "name": "value_1", - "type": "INT", - "linkIds": [ - 1167 - ], - "label": "frame_rate", - "pos": [ - 2460.259765625, - 6100 - ] - }, - { - "id": "650e141a-0cdc-4eac-be88-30f5865b298d", - "name": "value_2", - "type": "INT", - "linkIds": [ - 1168 - ], - "label": "length (seconds)", - "pos": [ - 2460.259765625, - 6120 - ] - } - ], - "outputs": [ - { - "id": "a72640aa-8055-45bb-8cbb-f68ec7413515", - "name": "optimized_model", - "type": "MODEL", - "linkIds": [ - 1067 - ], - "pos": [ - 3590, - 5680 - ] - }, - { - "id": "cfb48567-9b75-46f5-8b71-385f3388173c", - "name": "VAE", - "type": "VAE", - "linkIds": [ - 721 - ], - "label": "video_vae", - "pos": [ - 3590, - 5700 - ] - }, - { - "id": "4e095394-8a92-47e4-8993-49fb4ce73bd8", - "name": "VAE_1", - "type": "VAE", - "linkIds": [ - 722 - ], - "label": "audio_vae", - "pos": [ - 3590, - 5720 - ] - }, - { - "id": "8bbe34b1-d094-40a3-9c04-5a19cd7b2af2", - "name": "positive", - "type": "CONDITIONING", - "linkIds": [ - 726 - ], - "pos": [ - 3590, - 5740 - ] - }, - { - "id": "dac0ad69-373b-4e47-b0cd-eb8fd18e1c07", - "name": "negative", - "type": "CONDITIONING", - "linkIds": [ - 727 - ], - "pos": [ - 3590, - 5760 - ] - }, - { - "id": "b4d0868d-86e8-42ba-83fc-44c5324ac35d", - "name": "INT", - "type": "INT", - "linkIds": [ - 729 - ], - "label": "length_seconds", - "pos": [ - 3590, - 5780 - ] - }, - { - "id": "12a111ff-cd34-4b59-bca3-4c5f24626a62", - "name": "NOISE", - "type": "NOISE", - "linkIds": [ - 730 - ], - "label": "noise", - "pos": [ - 3590, - 5800 - ] - }, - { - "id": "9f03af49-218d-4431-a2e9-891d16a8eb05", - "name": "LATENT_UPSCALE_MODEL", - "type": "LATENT_UPSCALE_MODEL", - "linkIds": [ - 1034 - ], - "label": "2x_spatial_upscale_model", - "pos": [ - 3590, - 5820 - ] - }, - { - "id": "6ec59b1a-d1dc-4a6d-8844-18050ea86a5a", - "name": "INT_1", - "type": "INT", - "linkIds": [ - 1103 - ], - "label": "frame_rate", - "pos": [ - 3590, - 5840 - ] - }, - { - "id": "eaf7211d-70e5-4f7a-9465-958ea3e2e594", - "name": "BOOLEAN", - "type": "BOOLEAN", - "linkIds": [ - 1162 - ], - "label": "use custom audio", - "pos": [ - 3590, - 5860 - ] - } - ], - "widgets": [], - "nodes": [ - { - "id": 338, - "type": "VAELoaderKJ", - "pos": [ - 2560, - 5780 - ], - "size": [ - 270, - 110 - ], - "flags": {}, - "order": 0, - "mode": 0, - "inputs": [], - "outputs": [ - { - "localized_name": "VAE", - "name": "VAE", - "type": "VAE", - "links": [ - 722 - ] - } - ], - "title": "Audio VAELoader KJ", - "properties": { - "cnr_id": "comfyui-kjnodes", - "ver": "487c7d86a0230aae3d8c0a37d517159b73834f85", - "Node name for S&R": "VAELoaderKJ" - }, - "widgets_values": [ - "LTX23_audio_vae_bf16.safetensors", - "main_device", - "bf16" - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 457, - "type": "VAELoaderKJ", - "pos": [ - 2560, - 5630 - ], - "size": [ - 270, - 110 - ], - "flags": {}, - "order": 1, - "mode": 0, - "inputs": [], - "outputs": [ - { - "localized_name": "VAE", - "name": "VAE", - "type": "VAE", - "links": [ - 721 - ] - } - ], - "title": "Video VAELoader KJ", - "properties": { - "cnr_id": "comfyui-kjnodes", - "ver": "1.2.5", - "Node name for S&R": "VAELoaderKJ" - }, - "widgets_values": [ - "LTX23_video_vae_bf16.safetensors", - "main_device", - "bf16" - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 443, - "type": "VAELoaderKJ", - "pos": [ - 2560, - 5470 - ], - "size": [ - 270, - 110 - ], - "flags": {}, - "order": 2, - "mode": 0, - "inputs": [], - "outputs": [ - { - "localized_name": "VAE", - "name": "VAE", - "type": "VAE", - "links": [ - 1072 - ] - } - ], - "title": "Tiny VAELoader KJ", - "properties": { - "cnr_id": "comfyui-kjnodes", - "ver": "1.2.5", - "Node name for S&R": "VAELoaderKJ" - }, - "widgets_values": [ - "taeltx2_3.safetensors", - "main_device", - "bf16" - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 442, - "type": "LTX2SamplingPreviewOverride", - "pos": [ - 2850, - 5470 - ], - "size": [ - 270, - 100 - ], - "flags": {}, - "order": 9, - "mode": 0, - "inputs": [ - { - "localized_name": "model", - "name": "model", - "type": "MODEL", - "link": 1185 - }, - { - "localized_name": "latent_upscale_model", - "name": "latent_upscale_model", - "shape": 7, - "type": "LATENT_UPSCALE_MODEL", - "link": null - }, - { - "localized_name": "vae", - "name": "vae", - "shape": 7, - "type": "VAE", - "link": 1072 - } - ], - "outputs": [ - { - "localized_name": "MODEL", - "name": "MODEL", - "type": "MODEL", - "links": [ - 1074 - ] - } - ], - "properties": { - "cnr_id": "comfyui-kjnodes", - "ver": "1.3.4", - "Node name for S&R": "LTX2SamplingPreviewOverride" - }, - "widgets_values": [ - 24 - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 426, - "type": "CheckpointLoaderSimple", - "pos": [ - 2560, - 5330 - ], - "size": [ - 270, - 100 - ], - "flags": {}, - "order": 3, - "mode": 0, - "inputs": [], - "outputs": [ - { - "localized_name": "MODEL", - "name": "MODEL", - "type": "MODEL", - "links": [ - 1185 - ] - }, - { - "localized_name": "CLIP", - "name": "CLIP", - "type": "CLIP", - "links": null - }, - { - "localized_name": "VAE", - "name": "VAE", - "type": "VAE", - "links": null - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.17.2", - "Node name for S&R": "CheckpointLoaderSimple" - }, - "widgets_values": [ - "ltx-2.3-22b-dev-fp8.safetensors" - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 484, - "type": "PrimitiveBoolean", - "pos": [ - 2560, - 6230 - ], - "size": [ - 230, - 60 - ], - "flags": {}, - "order": 4, - "mode": 0, - "inputs": [ - { - "label": "use custom audio", - "localized_name": "value", - "name": "value", - "type": "BOOLEAN", - "widget": { - "name": "value" - }, - "link": null - } - ], - "outputs": [ - { - "localized_name": "BOOLEAN", - "name": "BOOLEAN", - "type": "BOOLEAN", - "links": [ - 1162 - ] - } - ], - "title": "Use Custom Audio", - "properties": { - "cnr_id": "comfy-core", - "ver": "0.17.2", - "Node name for S&R": "PrimitiveBoolean" - }, - "widgets_values": [ - false - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 364, - "type": "RandomNoise", - "pos": [ - 2850, - 5790 - ], - "size": [ - 210, - 90 - ], - "flags": {}, - "order": 5, - "mode": 0, - "inputs": [ - { - "label": "noise_seed", - "localized_name": "noise_seed", - "name": "noise_seed", - "type": "INT", - "widget": { - "name": "noise_seed" - }, - "link": null - } - ], - "outputs": [ - { - "localized_name": "NOISE", - "name": "NOISE", - "type": "NOISE", - "links": [ - 730 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.56", - "Node name for S&R": "RandomNoise", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [ - 0, - "fixed" - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 470, - "type": "CM_IntToFloat", - "pos": [ - 3080, - 5820 - ], - "size": [ - 210, - 60 - ], - "flags": {}, - "order": 17, - "mode": 0, - "inputs": [ - { - "localized_name": "a", - "name": "a", - "type": "INT", - "widget": { - "name": "a" - }, - "link": 1133 - } - ], - "outputs": [ - { - "localized_name": "FLOAT", - "name": "FLOAT", - "type": "FLOAT", - "links": [ - 1134 - ] - } - ], - "title": "frame_rate_covert", - "properties": { - "cnr_id": "ComfyMath", - "ver": "c01177221c31b8e5fbc062778fc8254aeb541638", - "Node name for S&R": "CM_IntToFloat" - }, - "widgets_values": [ - 0 - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 467, - "type": "PrimitiveInt", - "pos": [ - 2850, - 5640 - ], - "size": [ - 210, - 90 - ], - "flags": {}, - "order": 16, - "mode": 0, - "inputs": [ - { - "localized_name": "value", - "name": "value", - "type": "INT", - "widget": { - "name": "value" - }, - "link": 1167 - } - ], - "outputs": [ - { - "localized_name": "INT", - "name": "INT", - "type": "INT", - "links": [ - 1102, - 1103, - 1133 - ] - } - ], - "title": "frame_rate", - "properties": { - "cnr_id": "comfy-core", - "ver": "0.19.3", - "Node name for S&R": "PrimitiveInt" - }, - "widgets_values": [ - 24, - "fixed" - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 365, - "type": "07609e03-45f4-49c7-a233-a933ebf95094", - "pos": [ - 3070, - 5640 - ], - "size": [ - 210, - 90 - ], - "flags": {}, - "order": 13, - "mode": 0, - "inputs": [ - { - "label": "seconds", - "name": "value", - "type": "INT", - "widget": { - "name": "value" - }, - "link": 1168 - }, - { - "label": "frame_rate", - "name": "b", - "type": "INT", - "widget": { - "name": "b" - }, - "link": 1102 - } - ], - "outputs": [ - { - "localized_name": "INT", - "name": "INT", - "type": "INT", - "links": [ - 729 - ] - } - ], - "properties": { - "proxyWidgets": [ - [ - "139", - "value" - ], - [ - "137", - "b" - ] - ], - "cnr_id": "comfy-core", - "ver": "0.16.4" - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 413, - "type": "LTXAVTextEncoderLoader", - "pos": [ - 2840, - 5940 - ], - "size": [ - 320, - 130 - ], - "flags": {}, - "order": 6, - "mode": 0, - "inputs": [], - "outputs": [ - { - "localized_name": "CLIP", - "name": "CLIP", - "type": "CLIP", - "links": [ - 1187 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.14.1", - "Node name for S&R": "LTXAVTextEncoderLoader" - }, - "widgets_values": [ - "gemma_3_12B_it_fp4_mixed.safetensors", - "ltx-2.3-22b-dev-fp8.safetensors", - "default" - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 464, - "type": "PrimitiveStringMultiline", - "pos": [ - 2560, - 5940 - ], - "size": [ - 270, - 130 - ], - "flags": {}, - "order": 15, - "mode": 0, - "inputs": [ - { - "localized_name": "value", - "name": "value", - "type": "STRING", - "widget": { - "name": "value" - }, - "link": 1091 - } - ], - "outputs": [ - { - "localized_name": "STRING", - "name": "STRING", - "type": "STRING", - "links": [ - 1090 - ] - } - ], - "title": "Prompt", - "properties": { - "cnr_id": "comfy-core", - "ver": "0.19.3", - "Node name for S&R": "PrimitiveStringMultiline" - }, - "widgets_values": [ - "" - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 419, - "type": "LatentUpscaleModelLoader", - "pos": [ - 2560, - 6120 - ], - "size": [ - 270, - 60 - ], - "flags": {}, - "order": 7, - "mode": 0, - "inputs": [], - "outputs": [ - { - "localized_name": "LATENT_UPSCALE_MODEL", - "name": "LATENT_UPSCALE_MODEL", - "type": "LATENT_UPSCALE_MODEL", - "links": [ - 1034 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.16.4", - "Node name for S&R": "LatentUpscaleModelLoader" - }, - "widgets_values": [ - "ltx-2.3-spatial-upscaler-x2-1.1.safetensors" - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 430, - "type": "CLIPTextEncode", - "pos": [ - 2850, - 6110 - ], - "size": [ - 280, - 90 - ], - "flags": {}, - "order": 14, - "mode": 0, - "inputs": [ - { - "localized_name": "clip", - "name": "clip", - "type": "CLIP", - "link": 1187 - }, - { - "localized_name": "text", - "name": "text", - "type": "STRING", - "widget": { - "name": "text" - }, - "link": 1090 - } - ], - "outputs": [ - { - "localized_name": "CONDITIONING", - "name": "CONDITIONING", - "type": "CONDITIONING", - "slot_index": 0, - "links": [ - 1186, - 1188 - ] - } - ], - "title": "CLIP Text Encode (Positive Prompt)", - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.28", - "Node name for S&R": "CLIPTextEncode" - }, - "widgets_values": [ - "" - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 361, - "type": "ConditioningZeroOut", - "pos": [ - 2850, - 6240 - ], - "size": [ - 210, - 30 - ], - "flags": {}, - "order": 11, - "mode": 0, - "inputs": [ - { - "localized_name": "conditioning", - "name": "conditioning", - "type": "CONDITIONING", - "link": 1186 - } - ], - "outputs": [ - { - "localized_name": "CONDITIONING", - "name": "CONDITIONING", - "type": "CONDITIONING", - "links": [ - 725 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.7.0", - "Node name for S&R": "ConditioningZeroOut", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 363, - "type": "LTXVConditioning", - "pos": [ - 3170, - 5940 - ], - "size": [ - 210, - 80 - ], - "flags": {}, - "order": 12, - "mode": 0, - "inputs": [ - { - "localized_name": "positive", - "name": "positive", - "type": "CONDITIONING", - "link": 1188 - }, - { - "localized_name": "negative", - "name": "negative", - "type": "CONDITIONING", - "link": 725 - }, - { - "localized_name": "frame_rate", - "name": "frame_rate", - "type": "FLOAT", - "widget": { - "name": "frame_rate" - }, - "link": 1134 - } - ], - "outputs": [ - { - "localized_name": "positive", - "name": "positive", - "type": "CONDITIONING", - "links": [ - 726 - ] - }, - { - "localized_name": "negative", - "name": "negative", - "type": "CONDITIONING", - "links": [ - 727 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.56", - "Node name for S&R": "LTXVConditioning", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [ - 24 - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 429, - "type": "LoraLoaderModelOnly", - "pos": [ - 3140, - 5480 - ], - "size": [ - 250, - 90 - ], - "flags": {}, - "order": 10, - "mode": 0, - "inputs": [ - { - "localized_name": "model", - "name": "model", - "type": "MODEL", - "link": 1074 - } - ], - "outputs": [ - { - "localized_name": "MODEL", - "name": "MODEL", - "type": "MODEL", - "links": [ - 1067 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.75", - "Node name for S&R": "LoraLoaderModelOnly", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65, - "models": [ - { - "name": "ltx-2.3-22b-distilled-lora-384.safetensors", - "url": "https://huggingface.co/Lightricks/LTX-2.3/resolve/main/ltx-2.3-22b-distilled-lora-384.safetensors", - "directory": "loras" - } - ] - }, - "widgets_values": [ - "ltx2\\ltx-2.3-22b-distilled-lora-dynamic_fro09_avg_rank_105_bf16.safetensors", - 0.5 - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 496, - "type": "MarkdownNote", - "pos": [ - 2180, - 5510 - ], - "size": [ - 350, - 380 - ], - "flags": {}, - "order": 8, - "mode": 0, - "inputs": [], - "outputs": [], - "title": "FAQ", - "properties": {}, - "widgets_values": [ - "# FAQ\n\n**How do I set the length of the video?**\n\nYou can set the length of the video in the Model Loader Subgraph →\n\n**I keep getting errors when running this workflow!**\n\nDouble check that all of the correct models loaded. This is using the newest version of 2x upscaler (v1.1). You can change it to the older one under the Model Loader.\n\n**Where do I download these models??**\n\nThere is a good list of models and download links here: \n\nAlso the latest v1.1 spatial upscaler can be found here: \n\nThe tiny vae (taeltx2_3.safetensors) is found here:\n\n\n**I want to use my own models!**\n\nYou can change the models in the Model Loader Subgraph →\n\n\n\n\n" - ], - "color": "#323", - "bgcolor": "#535" - } - ], - "groups": [], - "links": [ - { - "id": 721, - "origin_id": 457, - "origin_slot": 0, - "target_id": -20, - "target_slot": 1, - "type": "VAE" - }, - { - "id": 722, - "origin_id": 338, - "origin_slot": 0, - "target_id": -20, - "target_slot": 2, - "type": "VAE" - }, - { - "id": 725, - "origin_id": 361, - "origin_slot": 0, - "target_id": 363, - "target_slot": 1, - "type": "CONDITIONING" - }, - { - "id": 726, - "origin_id": 363, - "origin_slot": 0, - "target_id": -20, - "target_slot": 3, - "type": "CONDITIONING" - }, - { - "id": 727, - "origin_id": 363, - "origin_slot": 1, - "target_id": -20, - "target_slot": 4, - "type": "CONDITIONING" - }, - { - "id": 729, - "origin_id": 365, - "origin_slot": 0, - "target_id": -20, - "target_slot": 5, - "type": "INT" - }, - { - "id": 730, - "origin_id": 364, - "origin_slot": 0, - "target_id": -20, - "target_slot": 6, - "type": "NOISE" - }, - { - "id": 1034, - "origin_id": 419, - "origin_slot": 0, - "target_id": -20, - "target_slot": 7, - "type": "LATENT_UPSCALE_MODEL" - }, - { - "id": 1067, - "origin_id": 429, - "origin_slot": 0, - "target_id": -20, - "target_slot": 0, - "type": "MODEL" - }, - { - "id": 1072, - "origin_id": 443, - "origin_slot": 0, - "target_id": 442, - "target_slot": 2, - "type": "VAE" - }, - { - "id": 1074, - "origin_id": 442, - "origin_slot": 0, - "target_id": 429, - "target_slot": 0, - "type": "MODEL" - }, - { - "id": 1090, - "origin_id": 464, - "origin_slot": 0, - "target_id": 430, - "target_slot": 1, - "type": "STRING" - }, - { - "id": 1091, - "origin_id": -10, - "origin_slot": 0, - "target_id": 464, - "target_slot": 0, - "type": "STRING" - }, - { - "id": 1102, - "origin_id": 467, - "origin_slot": 0, - "target_id": 365, - "target_slot": 1, - "type": "INT" - }, - { - "id": 1103, - "origin_id": 467, - "origin_slot": 0, - "target_id": -20, - "target_slot": 8, - "type": "INT" - }, - { - "id": 1133, - "origin_id": 467, - "origin_slot": 0, - "target_id": 470, - "target_slot": 0, - "type": "INT" - }, - { - "id": 1134, - "origin_id": 470, - "origin_slot": 0, - "target_id": 363, - "target_slot": 2, - "type": "FLOAT" - }, - { - "id": 1162, - "origin_id": 484, - "origin_slot": 0, - "target_id": -20, - "target_slot": 9, - "type": "BOOLEAN" - }, - { - "id": 1167, - "origin_id": -10, - "origin_slot": 1, - "target_id": 467, - "target_slot": 0, - "type": "INT" - }, - { - "id": 1168, - "origin_id": -10, - "origin_slot": 2, - "target_id": 365, - "target_slot": 0, - "type": "INT" - }, - { - "id": 1185, - "origin_id": 426, - "origin_slot": 0, - "target_id": 442, - "target_slot": 0, - "type": "MODEL" - }, - { - "id": 1186, - "origin_id": 430, - "origin_slot": 0, - "target_id": 361, - "target_slot": 0, - "type": "CONDITIONING" - }, - { - "id": 1187, - "origin_id": 413, - "origin_slot": 0, - "target_id": 430, - "target_slot": 0, - "type": "CLIP" - }, - { - "id": 1188, - "origin_id": 430, - "origin_slot": 0, - "target_id": 363, - "target_slot": 0, - "type": "CONDITIONING" - } - ], - "extra": { - "workflowRendererVersion": "LG" - } - }, - { - "id": "07609e03-45f4-49c7-a233-a933ebf95094", - "version": 1, - "state": { - "lastGroupId": 33, - "lastNodeId": 499, - "lastLinkId": 1201, - "lastRerouteId": 0 - }, - "revision": 0, - "config": {}, - "name": "length (seconds)", - "inputNode": { - "id": -10, - "bounding": [ - -662.1270448096031, - 4310.82566166648, - 120, - 80 - ] - }, - "outputNode": { - "id": -20, - "bounding": [ - 255.79328565979654, - 4300.82566166648, - 120, - 60 - ] - }, - "inputs": [ - { - "id": "df88c383-3299-420f-8c75-fd0d6a39a9af", - "name": "value", - "type": "INT", - "linkIds": [ - 1071 - ], - "label": "seconds", - "pos": [ - -562.1270448096031, - 4330.82566166648 - ] - }, - { - "id": "633c6862-8fcb-44af-aaa4-45a1e233c4d3", - "name": "b", - "type": "INT", - "linkIds": [ - 1095 - ], - "label": "frame_rate", - "pos": [ - -562.1270448096031, - 4350.82566166648 - ] - } - ], - "outputs": [ - { - "id": "9b5a4dc0-bb87-45a3-bd3d-551edc18c371", - "name": "INT", - "type": "INT", - "linkIds": [ - 359 - ], - "localized_name": "INT", - "pos": [ - 275.79328565979654, - 4320.82566166648 - ] - } - ], - "widgets": [], - "nodes": [ - { - "id": 137, - "type": "easy mathInt", - "pos": [ - -240, - 4300 - ], - "size": [ - 210, - 110 - ], - "flags": {}, - "order": 0, - "mode": 0, - "inputs": [ - { - "localized_name": "a", - "name": "a", - "type": "INT", - "widget": { - "name": "a" - }, - "link": 360 - }, - { - "localized_name": "b", - "name": "b", - "type": "INT", - "widget": { - "name": "b" - }, - "link": 1095 - } - ], - "outputs": [ - { - "localized_name": "INT", - "name": "INT", - "type": "INT", - "links": [ - 358 - ] - } - ], - "properties": { - "cnr_id": "comfyui-easy-use", - "ver": "1.3.4", - "Node name for S&R": "easy mathInt" - }, - "widgets_values": [ - 0, - 24, - "multiply" - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 139, - "type": "PrimitiveInt", - "pos": [ - -480, - 4310 - ], - "size": [ - 210, - 90 - ], - "flags": {}, - "order": 2, - "mode": 0, - "inputs": [ - { - "localized_name": "value", - "name": "value", - "type": "INT", - "widget": { - "name": "value" - }, - "link": 1071 - } - ], - "outputs": [ - { - "localized_name": "INT", - "name": "INT", - "type": "INT", - "links": [ - 360 - ] - } - ], - "title": "seconds", - "properties": { - "cnr_id": "comfy-core", - "ver": "0.16.4", - "Node name for S&R": "PrimitiveInt" - }, - "widgets_values": [ - 7, - "fixed" - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 138, - "type": "easy mathInt", - "pos": [ - -10, - 4300 - ], - "size": [ - 210, - 110 - ], - "flags": {}, - "order": 1, - "mode": 0, - "inputs": [ - { - "localized_name": "a", - "name": "a", - "type": "INT", - "widget": { - "name": "a" - }, - "link": 358 - } - ], - "outputs": [ - { - "localized_name": "INT", - "name": "INT", - "type": "INT", - "links": [ - 359 - ] - } - ], - "properties": { - "cnr_id": "comfyui-easy-use", - "ver": "1.3.4", - "Node name for S&R": "easy mathInt" - }, - "widgets_values": [ - 0, - 1, - "add" - ], - "color": "#222", - "bgcolor": "#000" - } - ], - "groups": [], - "links": [ - { - "id": 360, - "origin_id": 139, - "origin_slot": 0, - "target_id": 137, - "target_slot": 0, - "type": "INT" - }, - { - "id": 358, - "origin_id": 137, - "origin_slot": 0, - "target_id": 138, - "target_slot": 0, - "type": "INT" - }, - { - "id": 359, - "origin_id": 138, - "origin_slot": 0, - "target_id": -20, - "target_slot": 0, - "type": "INT" - }, - { - "id": 1071, - "origin_id": -10, - "origin_slot": 0, - "target_id": 139, - "target_slot": 0, - "type": "INT" - }, - { - "id": 1095, - "origin_id": -10, - "origin_slot": 1, - "target_id": 137, - "target_slot": 1, - "type": "INT" - } - ], - "extra": { - "workflowRendererVersion": "LG" - } - }, - { - "id": "360d64f7-96cd-42a6-9b13-6eb0387e17d9", - "version": 1, - "state": { - "lastGroupId": 33, - "lastNodeId": 499, - "lastLinkId": 1201, - "lastRerouteId": 0 - }, - "revision": 0, - "config": {}, - "name": "Generate Video", - "inputNode": { - "id": -10, - "bounding": [ - 4300, - 5930, - 187.052734375, - 240 - ] - }, - "outputNode": { - "id": -20, - "bounding": [ - 5470, - 5960, - 120, - 60 - ] - }, - "inputs": [ - { - "id": "f3d52cbc-fafe-46a6-978c-2eaff2c185c3", - "name": "model", - "type": "MODEL", - "linkIds": [ - 3, - 14 - ], - "pos": [ - 4467.052734375, - 5950 - ] - }, - { - "id": "d0e8f1b7-b19b-49cd-8a2b-aa4a639857b3", - "name": "positive", - "type": "CONDITIONING", - "linkIds": [ - 4, - 15, - 871 - ], - "pos": [ - 4467.052734375, - 5970 - ] - }, - { - "id": "36e3ff32-f9b2-41d9-94de-9d8d9a91f1a6", - "name": "negative", - "type": "CONDITIONING", - "linkIds": [ - 5, - 16, - 872 - ], - "pos": [ - 4467.052734375, - 5990 - ] - }, - { - "id": "3f2a7956-c7c2-4478-99ad-2487644cbbc2", - "name": "latent_image", - "type": "LATENT", - "linkIds": [ - 6 - ], - "pos": [ - 4467.052734375, - 6010 - ] - }, - { - "id": "cc2edb71-c53a-4056-8a85-5d8199e79c72", - "name": "noise", - "type": "NOISE", - "linkIds": [ - 7, - 17 - ], - "pos": [ - 4467.052734375, - 6030 - ] - }, - { - "id": "d162c9e7-24cc-4421-a6fb-d253bfae8883", - "name": "vae", - "type": "VAE", - "linkIds": [ - 32 - ], - "label": "video_vae", - "pos": [ - 4467.052734375, - 6050 - ] - }, - { - "id": "e9cd552e-a79c-4b6e-8508-4ad3a9f4218b", - "name": "image", - "type": "IMAGE", - "linkIds": [ - 29 - ], - "pos": [ - 4467.052734375, - 6070 - ] - }, - { - "id": "45629d5b-0676-4430-aa3d-8b0cf516de0a", - "name": "multi_input", - "type": "IMAGE", - "linkIds": [ - 1006 - ], - "pos": [ - 4467.052734375, - 6090 - ] - }, - { - "id": "899832ca-633e-4c2b-ac1b-303e21cab106", - "name": "frame_rate", - "type": "INT", - "linkIds": [ - 1198 - ], - "pos": [ - 4467.052734375, - 6110 - ] - }, - { - "id": "8cdaef23-307e-4e12-8b8f-366ef4b8e2b7", - "name": "upscale_model", - "type": "LATENT_UPSCALE_MODEL", - "linkIds": [ - 1115 - ], - "label": "2x_spatial_upscale_model", - "pos": [ - 4467.052734375, - 6130 - ] - } - ], - "outputs": [ - { - "id": "2fae6e6d-6667-450e-933b-b16e88c7968f", - "name": "output", - "type": "LATENT", - "linkIds": [ - 765 - ], - "label": "av_output", - "pos": [ - 5490, - 5980 - ] - } - ], - "widgets": [], - "nodes": [ - { - "id": 371, - "type": "LTXVConcatAVLatent", - "pos": [ - 5250, - 5970 - ], - "size": [ - 190, - 50 - ], - "flags": {}, - "order": 2, - "mode": 0, - "inputs": [ - { - "localized_name": "video_latent", - "name": "video_latent", - "type": "LATENT", - "link": 852 - }, - { - "localized_name": "audio_latent", - "name": "audio_latent", - "type": "LATENT", - "link": 1194 - } - ], - "outputs": [ - { - "localized_name": "latent", - "name": "latent", - "type": "LATENT", - "links": [ - 765 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.17.2", - "Node name for S&R": "LTXVConcatAVLatent" - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 2, - "type": "afa29944-eb58-40a5-8e76-484f2503fa0c", - "pos": [ - 4530, - 5950 - ], - "size": [ - 190, - 280 - ], - "flags": {}, - "order": 0, - "mode": 0, - "inputs": [ - { - "name": "model", - "type": "MODEL", - "link": 3 - }, - { - "name": "positive", - "type": "CONDITIONING", - "link": 4 - }, - { - "name": "negative", - "type": "CONDITIONING", - "link": 5 - }, - { - "name": "latent_image", - "type": "LATENT", - "link": 6 - }, - { - "name": "noise", - "type": "NOISE", - "link": 7 - } - ], - "outputs": [ - { - "name": "video_latent", - "type": "LATENT", - "links": [ - 837 - ] - }, - { - "name": "audio_latent", - "type": "LATENT", - "links": [ - 794 - ] - } - ], - "properties": { - "proxyWidgets": [], - "cnr_id": "comfy-core", - "ver": "0.17.2" - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 383, - "type": "LTXVCropGuides", - "pos": [ - 4970, - 5950 - ], - "size": [ - 260, - 70 - ], - "flags": {}, - "order": 3, - "mode": 0, - "inputs": [ - { - "localized_name": "positive", - "name": "positive", - "type": "CONDITIONING", - "link": 871 - }, - { - "localized_name": "negative", - "name": "negative", - "type": "CONDITIONING", - "link": 872 - }, - { - "localized_name": "latent", - "name": "latent", - "type": "LATENT", - "link": 1193 - } - ], - "outputs": [ - { - "localized_name": "positive", - "name": "positive", - "type": "CONDITIONING", - "links": null - }, - { - "localized_name": "negative", - "name": "negative", - "type": "CONDITIONING", - "links": null - }, - { - "localized_name": "latent", - "name": "latent", - "type": "LATENT", - "links": [ - 852 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.17.2", - "Node name for S&R": "LTXVCropGuides" - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 6, - "type": "ce8f7842-cb03-41b4-9b1e-8d032b2a4e72", - "pos": [ - 4740, - 5950 - ], - "size": [ - 210, - 280 - ], - "flags": {}, - "order": 1, - "mode": 0, - "inputs": [ - { - "name": "model", - "type": "MODEL", - "link": 14 - }, - { - "name": "positive", - "type": "CONDITIONING", - "link": 15 - }, - { - "name": "negative", - "type": "CONDITIONING", - "link": 16 - }, - { - "name": "noise", - "type": "NOISE", - "link": 17 - }, - { - "label": "video_vae", - "name": "vae", - "type": "VAE", - "link": 32 - }, - { - "name": "image", - "type": "IMAGE", - "link": 29 - }, - { - "label": "video_latent", - "name": "samples", - "type": "LATENT", - "link": 837 - }, - { - "name": "audio_latent", - "type": "LATENT", - "link": 794 - }, - { - "name": "multi_input", - "type": "IMAGE", - "link": 1006 - }, - { - "name": "upscale_model", - "type": "LATENT_UPSCALE_MODEL", - "link": 1115 - }, - { - "name": "frame_rate", - "type": "INT", - "widget": { - "name": "frame_rate" - }, - "link": 1198 - } - ], - "outputs": [ - { - "name": "video_latent", - "type": "LATENT", - "links": [ - 1193 - ] - }, - { - "name": "audio_latent", - "type": "LATENT", - "links": [ - 1194 - ] - }, - { - "name": "positive", - "type": "CONDITIONING", - "links": [ - 855 - ] - }, - { - "name": "negative", - "type": "CONDITIONING", - "links": [ - 856 - ] - } - ], - "properties": { - "proxyWidgets": [ - [ - "409", - "frame_rate" - ] - ], - "cnr_id": "comfy-core", - "ver": "0.17.2" - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - } - ], - "groups": [], - "links": [ - { - "id": 3, - "origin_id": -10, - "origin_slot": 0, - "target_id": 2, - "target_slot": 0, - "type": "MODEL" - }, - { - "id": 14, - "origin_id": -10, - "origin_slot": 0, - "target_id": 6, - "target_slot": 0, - "type": "MODEL" - }, - { - "id": 4, - "origin_id": -10, - "origin_slot": 1, - "target_id": 2, - "target_slot": 1, - "type": "CONDITIONING" - }, - { - "id": 15, - "origin_id": -10, - "origin_slot": 1, - "target_id": 6, - "target_slot": 1, - "type": "CONDITIONING" - }, - { - "id": 5, - "origin_id": -10, - "origin_slot": 2, - "target_id": 2, - "target_slot": 2, - "type": "CONDITIONING" - }, - { - "id": 16, - "origin_id": -10, - "origin_slot": 2, - "target_id": 6, - "target_slot": 2, - "type": "CONDITIONING" - }, - { - "id": 6, - "origin_id": -10, - "origin_slot": 3, - "target_id": 2, - "target_slot": 3, - "type": "LATENT" - }, - { - "id": 7, - "origin_id": -10, - "origin_slot": 4, - "target_id": 2, - "target_slot": 4, - "type": "NOISE" - }, - { - "id": 17, - "origin_id": -10, - "origin_slot": 4, - "target_id": 6, - "target_slot": 3, - "type": "NOISE" - }, - { - "id": 32, - "origin_id": -10, - "origin_slot": 5, - "target_id": 6, - "target_slot": 4, - "type": "VAE" - }, - { - "id": 29, - "origin_id": -10, - "origin_slot": 6, - "target_id": 6, - "target_slot": 5, - "type": "IMAGE" - }, - { - "id": 765, - "origin_id": 371, - "origin_slot": 0, - "target_id": -20, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 794, - "origin_id": 2, - "origin_slot": 1, - "target_id": 6, - "target_slot": 7, - "type": "LATENT" - }, - { - "id": 837, - "origin_id": 2, - "origin_slot": 0, - "target_id": 6, - "target_slot": 6, - "type": "LATENT" - }, - { - "id": 852, - "origin_id": 383, - "origin_slot": 2, - "target_id": 371, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 871, - "origin_id": -10, - "origin_slot": 1, - "target_id": 383, - "target_slot": 0, - "type": "CONDITIONING" - }, - { - "id": 872, - "origin_id": -10, - "origin_slot": 2, - "target_id": 383, - "target_slot": 1, - "type": "CONDITIONING" - }, - { - "id": 1006, - "origin_id": -10, - "origin_slot": 7, - "target_id": 6, - "target_slot": 8, - "type": "IMAGE" - }, - { - "id": 1115, - "origin_id": -10, - "origin_slot": 9, - "target_id": 6, - "target_slot": 9, - "type": "LATENT_UPSCALE_MODEL" - }, - { - "id": 1193, - "origin_id": 6, - "origin_slot": 0, - "target_id": 383, - "target_slot": 2, - "type": "LATENT" - }, - { - "id": 1194, - "origin_id": 6, - "origin_slot": 1, - "target_id": 371, - "target_slot": 1, - "type": "LATENT" - }, - { - "id": 1198, - "origin_id": -10, - "origin_slot": 8, - "target_id": 6, - "target_slot": 10, - "type": "INT" - } - ], - "extra": { - "workflowRendererVersion": "LG" - } - }, - { - "id": "e8ae487a-31b9-46cf-be65-76e634e0bee1", - "version": 1, - "state": { - "lastGroupId": 33, - "lastNodeId": 499, - "lastLinkId": 1201, - "lastRerouteId": 0 - }, - "revision": 0, - "config": {}, - "name": "Custom Audio", - "inputNode": { - "id": -10, - "bounding": [ - 3590, - 6700, - 145.544921875, - 140 - ] - }, - "outputNode": { - "id": -20, - "bounding": [ - 4760, - 6685, - 120, - 60 - ] - }, - "inputs": [ - { - "id": "2fcd3481-9803-435d-9cb3-aba0a6a4deb0", - "name": "audio", - "type": "AUDIO", - "linkIds": [ - 1154 - ], - "pos": [ - 3715.544921875, - 6720 - ] - }, - { - "id": "cb8ce3ad-67c5-406f-bf48-a85faa47efb3", - "name": "audio_vae", - "type": "VAE", - "linkIds": [ - 1155 - ], - "pos": [ - 3715.544921875, - 6740 - ] - }, - { - "id": "49e68ab3-a9ad-4320-930b-ef8e4644fca5", - "name": "image", - "type": "IMAGE", - "linkIds": [ - 1156 - ], - "pos": [ - 3715.544921875, - 6760 - ] - }, - { - "id": "dc47eb1c-66f8-4208-a52f-1fc1dc88e38c", - "name": "value", - "type": "BOOLEAN", - "linkIds": [ - 1161 - ], - "label": "Use Custom Audio", - "pos": [ - 3715.544921875, - 6780 - ] - }, - { - "id": "39bd2433-03a9-45d0-bd12-1cbfdd25563d", - "name": "on_false", - "type": "*", - "linkIds": [ - 1165 - ], - "label": "audio_latent", - "pos": [ - 3715.544921875, - 6800 - ] - } - ], - "outputs": [ - { - "id": "7d1d8ff4-39f0-4c96-b7c1-48449c842c86", - "name": "*", - "type": "*", - "linkIds": [ - 1147 - ], - "localized_name": "*", - "pos": [ - 4780, - 6705 - ] - } - ], - "widgets": [], - "nodes": [ - { - "id": 471, - "type": "SetLatentNoiseMask", - "pos": [ - 4240, - 6690 - ], - "size": [ - 210, - 60 - ], - "flags": {}, - "order": 0, - "mode": 0, - "inputs": [ - { - "localized_name": "samples", - "name": "samples", - "type": "LATENT", - "link": 1136 - }, - { - "localized_name": "mask", - "name": "mask", - "type": "MASK", - "link": 1137 - } - ], - "outputs": [ - { - "localized_name": "LATENT", - "name": "LATENT", - "type": "LATENT", - "links": [ - 1144 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.7.0", - "Node name for S&R": "SetLatentNoiseMask", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65, - "ue_properties": { - "widget_ue_connectable": {}, - "version": "7.5.2", - "input_ue_unconnectable": {} - } - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 472, - "type": "SolidMask", - "pos": [ - 3970, - 6760 - ], - "size": [ - 240, - 110 - ], - "flags": { - "collapsed": false - }, - "order": 1, - "mode": 0, - "inputs": [ - { - "localized_name": "width", - "name": "width", - "type": "INT", - "widget": { - "name": "width" - }, - "link": 1138 - }, - { - "localized_name": "height", - "name": "height", - "type": "INT", - "widget": { - "name": "height" - }, - "link": 1139 - } - ], - "outputs": [ - { - "localized_name": "MASK", - "name": "MASK", - "type": "MASK", - "links": [ - 1137 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.7.0", - "Node name for S&R": "SolidMask", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65, - "ue_properties": { - "widget_ue_connectable": { - "width": true, - "height": true - }, - "version": "7.5.2", - "input_ue_unconnectable": {} - } - }, - "widgets_values": [ - 0, - 1024, - 1024 - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 473, - "type": "LTXVAudioVAEEncode", - "pos": [ - 3960, - 6660 - ], - "size": [ - 250, - 50 - ], - "flags": {}, - "order": 2, - "mode": 0, - "inputs": [ - { - "localized_name": "audio", - "name": "audio", - "type": "AUDIO", - "link": 1154 - }, - { - "label": "Audio VAE", - "localized_name": "audio_vae", - "name": "audio_vae", - "type": "VAE", - "link": 1155 - } - ], - "outputs": [ - { - "localized_name": "Audio Latent", - "name": "Audio Latent", - "type": "LATENT", - "links": [ - 1136 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.16.3", - "Node name for S&R": "LTXVAudioVAEEncode", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 474, - "type": "GetImageSize", - "pos": [ - 3770, - 6770 - ], - "size": [ - 180, - 70 - ], - "flags": {}, - "order": 3, - "mode": 0, - "inputs": [ - { - "localized_name": "image", - "name": "image", - "type": "IMAGE", - "link": 1156 - } - ], - "outputs": [ - { - "localized_name": "width", - "name": "width", - "type": "INT", - "links": [ - 1138 - ] - }, - { - "localized_name": "height", - "name": "height", - "type": "INT", - "links": [ - 1139 - ] - }, - { - "localized_name": "batch_size", - "name": "batch_size", - "type": "INT", - "links": null - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.17.2", - "Node name for S&R": "GetImageSize" - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 482, - "type": "PrimitiveBoolean", - "pos": [ - 4470, - 6580 - ], - "size": [ - 230, - 60 - ], - "flags": {}, - "order": 5, - "mode": 0, - "inputs": [ - { - "localized_name": "value", - "name": "value", - "type": "BOOLEAN", - "widget": { - "name": "value" - }, - "link": 1161 - } - ], - "outputs": [ - { - "localized_name": "BOOLEAN", - "name": "BOOLEAN", - "type": "BOOLEAN", - "links": [ - 1160 - ] - } - ], - "title": "Use Custom Audio", - "properties": { - "cnr_id": "comfy-core", - "ver": "0.17.2", - "Node name for S&R": "PrimitiveBoolean" - }, - "widgets_values": [ - false - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 478, - "type": "LazySwitchKJ", - "pos": [ - 4470, - 6690 - ], - "size": [ - 270, - 80 - ], - "flags": {}, - "order": 4, - "mode": 0, - "inputs": [ - { - "localized_name": "on_false", - "name": "on_false", - "type": "*", - "link": 1165 - }, - { - "localized_name": "on_true", - "name": "on_true", - "type": "*", - "link": 1144 - }, - { - "localized_name": "switch", - "name": "switch", - "type": "BOOLEAN", - "widget": { - "name": "switch" - }, - "link": 1160 - } - ], - "outputs": [ - { - "localized_name": "*", - "name": "*", - "type": "*", - "links": [ - 1147 - ] - } - ], - "properties": { - "cnr_id": "comfyui-kjnodes", - "ver": "1.3.4", - "Node name for S&R": "LazySwitchKJ" - }, - "widgets_values": [ - true - ], - "color": "#222", - "bgcolor": "#000" - } - ], - "groups": [], - "links": [ - { - "id": 1136, - "origin_id": 473, - "origin_slot": 0, - "target_id": 471, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 1137, - "origin_id": 472, - "origin_slot": 0, - "target_id": 471, - "target_slot": 1, - "type": "MASK" - }, - { - "id": 1138, - "origin_id": 474, - "origin_slot": 0, - "target_id": 472, - "target_slot": 0, - "type": "INT" - }, - { - "id": 1139, - "origin_id": 474, - "origin_slot": 1, - "target_id": 472, - "target_slot": 1, - "type": "INT" - }, - { - "id": 1144, - "origin_id": 471, - "origin_slot": 0, - "target_id": 478, - "target_slot": 1, - "type": "LATENT" - }, - { - "id": 1147, - "origin_id": 478, - "origin_slot": 0, - "target_id": -20, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 1154, - "origin_id": -10, - "origin_slot": 0, - "target_id": 473, - "target_slot": 0, - "type": "AUDIO" - }, - { - "id": 1155, - "origin_id": -10, - "origin_slot": 1, - "target_id": 473, - "target_slot": 1, - "type": "VAE" - }, - { - "id": 1156, - "origin_id": -10, - "origin_slot": 2, - "target_id": 474, - "target_slot": 0, - "type": "IMAGE" - }, - { - "id": 1160, - "origin_id": 482, - "origin_slot": 0, - "target_id": 478, - "target_slot": 2, - "type": "BOOLEAN" - }, - { - "id": 1161, - "origin_id": -10, - "origin_slot": 3, - "target_id": 482, - "target_slot": 0, - "type": "BOOLEAN" - }, - { - "id": 1165, - "origin_id": -10, - "origin_slot": 4, - "target_id": 478, - "target_slot": 0, - "type": "*" - } - ], - "extra": {} - } - ] - }, - "config": {}, - "extra": { - "frontendVersion": "1.42.14", - "workflowRendererVersion": "LG", - "VHS_latentpreview": false, - "VHS_latentpreviewrate": 0, - "VHS_MetadataImage": true, - "VHS_KeepIntermediate": true, - "ds": { - "scale": 0.6830134553650705, - "offset": [ - -2314.684986327, - -5200.910351747899 - ] - } - }, - "version": 0.4 +{ + "id": "c80f3abf-7abe-4c24-8663-009ac9c86e3d", + "revision": 0, + "last_node_id": 499, + "last_link_id": 1201, + "nodes": [ + { + "id": 4, + "type": "LTXVConcatAVLatent", + "pos": [ + 4160, + 6110 + ], + "size": [ + 210, + 50 + ], + "flags": {}, + "order": 10, + "mode": 0, + "inputs": [ + { + "name": "video_latent", + "type": "LATENT", + "link": 802 + }, + { + "name": "audio_latent", + "type": "LATENT", + "link": 1153 + } + ], + "outputs": [ + { + "name": "latent", + "type": "LATENT", + "links": [ + 804 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.7.0", + "Node name for S&R": "LTXVConcatAVLatent", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 5, + "type": "SaveVideo", + "pos": [ + 4890, + 5950 + ], + "size": [ + 500, + 370 + ], + "flags": {}, + "order": 13, + "mode": 0, + "inputs": [ + { + "name": "video", + "type": "VIDEO", + "link": 13 + } + ], + "outputs": [], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.5.1", + "Node name for S&R": "SaveVideo", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [ + "video/LTX", + "auto", + "auto" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 376, + "type": "LTXSequencer", + "pos": [ + 3900, + 5950 + ], + "size": [ + 240, + 200 + ], + "flags": {}, + "order": 8, + "mode": 0, + "inputs": [ + { + "name": "positive", + "type": "CONDITIONING", + "link": 796 + }, + { + "name": "negative", + "type": "CONDITIONING", + "link": 797 + }, + { + "name": "vae", + "type": "VAE", + "link": 798 + }, + { + "name": "latent", + "type": "LATENT", + "link": 803 + }, + { + "name": "multi_input", + "type": "IMAGE", + "link": 1047 + }, + { + "name": "frame_rate", + "type": "INT", + "widget": { + "name": "frame_rate" + }, + "link": 1104 + } + ], + "outputs": [ + { + "name": "positive", + "type": "CONDITIONING", + "links": [ + 799 + ] + }, + { + "name": "negative", + "type": "CONDITIONING", + "links": [ + 800 + ] + }, + { + "name": "latent", + "type": "LATENT", + "links": [ + 802 + ] + } + ], + "properties": { + "cnr_id": "WhatDreamsCost-ComfyUI", + "ver": "ecd00e6b404f14b07c7b54b0b0033e8bccb32d24", + "Node name for S&R": "LTXSequencer", + "num_images": 0, + "insert_frame_1": 0, + "strength_1": 1, + "insert_frame_2": 0, + "strength_2": 1, + "insert_frame_3": 0, + "strength_3": 1, + "insert_frame_4": 0, + "strength_4": 1, + "insert_frame_5": 0, + "strength_5": 1, + "insert_frame_6": 0, + "strength_6": 1, + "insert_frame_7": 0, + "strength_7": 1, + "insert_frame_8": 0, + "strength_8": 1, + "insert_frame_9": 0, + "strength_9": 0, + "insert_frame_10": 1, + "strength_10": 1, + "insert_frame_11": 0, + "strength_11": 0, + "insert_frame_12": 1, + "strength_12": 1, + "insert_frame_13": 0, + "strength_13": 0, + "insert_frame_14": 1, + "strength_14": 1, + "insert_frame_15": 0, + "strength_15": 0, + "insert_frame_16": 1, + "strength_16": 1, + "insert_frame_17": 0, + "strength_17": 0, + "insert_frame_18": 1, + "strength_18": 1, + "insert_frame_19": 0, + "strength_19": 0, + "insert_frame_20": 1, + "strength_20": 1, + "insert_frame_21": 0, + "strength_21": 0, + "insert_frame_22": 1, + "strength_22": 1, + "insert_frame_23": 0, + "strength_23": 0, + "insert_frame_24": 1, + "strength_24": 1, + "insert_frame_25": 0, + "strength_25": 0, + "insert_frame_26": 1, + "strength_26": 1, + "insert_frame_27": 0, + "strength_27": 0, + "insert_frame_28": 1, + "strength_28": 1, + "insert_frame_29": 0, + "strength_29": 0, + "insert_frame_30": 1, + "strength_30": 1, + "insert_frame_31": 0, + "strength_31": 0, + "insert_frame_32": 1, + "strength_32": 1, + "insert_frame_33": 0, + "strength_33": 1, + "insert_frame_34": 0, + "strength_34": 1, + "insert_frame_35": 0, + "strength_35": 1, + "insert_frame_36": 0, + "strength_36": 1, + "insert_frame_37": 0, + "strength_37": 1, + "insert_frame_38": 0, + "strength_38": 1, + "insert_frame_39": 0, + "strength_39": 1, + "insert_frame_40": 0, + "strength_40": 1, + "insert_frame_41": 0, + "strength_41": 1, + "insert_frame_42": 0, + "strength_42": 1, + "insert_frame_43": 0, + "strength_43": 1, + "insert_frame_44": 0, + "strength_44": 1, + "insert_frame_45": 0, + "strength_45": 1, + "insert_frame_46": 0, + "strength_46": 1, + "insert_frame_47": 0, + "strength_47": 1, + "insert_frame_48": 0, + "strength_48": 1, + "insert_frame_49": 0, + "strength_49": 1, + "insert_frame_50": 0, + "strength_50": 1, + "insert_mode": "frames", + "frame_rate": 24, + "insert_second_1": 0, + "insert_second_2": 0, + "insert_second_3": 0, + "insert_second_4": 0, + "insert_second_5": 0, + "insert_second_6": 0, + "insert_second_7": 0, + "insert_second_8": 0, + "insert_second_9": 1, + "insert_second_10": 0, + "insert_second_11": 1, + "insert_second_12": 0, + "insert_second_13": 1, + "insert_second_14": 0, + "insert_second_15": 1, + "insert_second_16": 0, + "insert_second_17": 1, + "insert_second_18": 0, + "insert_second_19": 1, + "insert_second_20": 0, + "insert_second_21": 1, + "insert_second_22": 0, + "insert_second_23": 1, + "insert_second_24": 0, + "insert_second_25": 1, + "insert_second_26": 0, + "insert_second_27": 1, + "insert_second_28": 0, + "insert_second_29": 1, + "insert_second_30": 0, + "insert_second_31": 1, + "insert_second_32": 0, + "insert_second_33": 1, + "insert_second_34": 0, + "insert_second_35": 0, + "insert_second_36": 0, + "insert_second_37": 0, + "insert_second_38": 0, + "insert_second_39": 0, + "insert_second_40": 0, + "insert_second_41": 0, + "insert_second_42": 0, + "insert_second_43": 0, + "insert_second_44": 0, + "insert_second_45": 0, + "insert_second_46": 0, + "insert_second_47": 0, + "insert_second_48": 0, + "insert_second_49": 0, + "insert_second_50": 0, + "insert_images_using": "frames", + "video_length": 1, + "insert_percentage_1": 0, + "insert_percentage_2": 1, + "insert_percentage_3": 0, + "insert_percentage_4": 0, + "insert_percentage_5": 0, + "insert_percentage_6": 0, + "insert_percentage_7": 0, + "insert_percentage_8": 0, + "insert_percentage_9": 0, + "insert_percentage_10": 0, + "insert_percentage_11": 0, + "insert_percentage_12": 0, + "insert_percentage_13": 0, + "insert_percentage_14": 0, + "insert_percentage_15": 0, + "insert_percentage_16": 0, + "insert_percentage_17": 0, + "insert_percentage_18": 0, + "insert_percentage_19": 0, + "insert_percentage_20": 0, + "insert_percentage_21": 0, + "insert_percentage_22": 0, + "insert_percentage_23": 0, + "insert_percentage_24": 0, + "insert_percentage_25": 0, + "insert_percentage_26": 0, + "insert_percentage_27": 0, + "insert_percentage_28": 0, + "insert_percentage_29": 0, + "insert_percentage_30": 0, + "insert_percentage_31": 0, + "insert_percentage_32": 0, + "insert_percentage_33": 0, + "insert_percentage_34": 0, + "insert_percentage_35": 0, + "insert_percentage_36": 0, + "insert_percentage_37": 0, + "insert_percentage_38": 0, + "insert_percentage_39": 0, + "insert_percentage_40": 0, + "insert_percentage_41": 0, + "insert_percentage_42": 0, + "insert_percentage_43": 0, + "insert_percentage_44": 0, + "insert_percentage_45": 0, + "insert_percentage_46": 0, + "insert_percentage_47": 0, + "insert_percentage_48": 0, + "insert_percentage_49": 0, + "insert_percentage_50": 0, + "insert_percent_1": 0, + "insert_percent_2": 50, + "insert_percent_3": 100, + "insert_percent_4": 0, + "insert_percent_5": 0, + "insert_percent_6": 0, + "insert_percent_7": 0, + "insert_percent_8": 0, + "insert_percent_9": 0, + "insert_percent_10": 0, + "insert_percent_11": 0, + "insert_percent_12": 0, + "insert_percent_13": 0, + "insert_percent_14": 0, + "insert_percent_15": 0, + "insert_percent_16": 0, + "insert_percent_17": 0, + "insert_percent_18": 0, + "insert_percent_19": 0, + "insert_percent_20": 0, + "insert_percent_21": 0, + "insert_percent_22": 0, + "insert_percent_23": 0, + "insert_percent_24": 0, + "insert_percent_25": 0, + "insert_percent_26": 0, + "insert_percent_27": 0, + "insert_percent_28": 0, + "insert_percent_29": 0, + "insert_percent_30": 0, + "insert_percent_31": 0, + "insert_percent_32": 0, + "insert_percent_33": 0, + "insert_percent_34": 0, + "insert_percent_35": 0, + "insert_percent_36": 0, + "insert_percent_37": 0, + "insert_percent_38": 0, + "insert_percent_39": 0, + "insert_percent_40": 0, + "insert_percent_41": 0, + "insert_percent_42": 0, + "insert_percent_43": 0, + "insert_percent_44": 0, + "insert_percent_45": 0, + "insert_percent_46": 0, + "insert_percent_47": 0, + "insert_percent_48": 0, + "insert_percent_49": 0, + "insert_percent_50": 0 + }, + "widgets_values": [ + 0, + "frames", + 24, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 481, + "type": "e8ae487a-31b9-46cf-be65-76e634e0bee1", + "pos": [ + 4160, + 6200 + ], + "size": [ + 220, + 120 + ], + "flags": {}, + "order": 9, + "mode": 0, + "inputs": [ + { + "name": "audio", + "type": "AUDIO", + "link": 1200 + }, + { + "name": "audio_vae", + "type": "VAE", + "link": 1158 + }, + { + "name": "image", + "type": "IMAGE", + "link": 1157 + }, + { + "label": "Use Custom Audio", + "name": "value", + "type": "BOOLEAN", + "widget": { + "name": "value" + }, + "link": 1164 + }, + { + "label": "audio_latent", + "name": "on_false", + "type": "*", + "link": 1166 + } + ], + "outputs": [ + { + "name": "*", + "type": "*", + "links": [ + 1153 + ] + } + ], + "properties": { + "proxyWidgets": [ + [ + "482", + "value" + ] + ], + "cnr_id": "comfy-core", + "ver": "0.19.3" + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 1, + "type": "34d63160-e149-4935-ab1f-892ffe617fec", + "pos": [ + 4660, + 5950 + ], + "size": [ + 210, + 170 + ], + "flags": {}, + "order": 12, + "mode": 0, + "inputs": [ + { + "name": "av_latent", + "type": "LATENT", + "link": 47 + }, + { + "name": "audio_vae", + "type": "VAE", + "link": 2 + }, + { + "label": "video_vae", + "name": "vae", + "type": "VAE", + "link": 31 + }, + { + "label": "frame_rate", + "name": "a", + "type": "INT", + "widget": { + "name": "a" + }, + "link": 1132 + }, + { + "label": "custom audio", + "name": "on_true", + "type": "*", + "link": 1201 + }, + { + "label": "use custom audio", + "name": "switch", + "type": "BOOLEAN", + "widget": { + "name": "switch" + }, + "link": 1176 + }, + { + "label": "use tiled vae", + "name": "value", + "type": "BOOLEAN", + "widget": { + "name": "value" + }, + "link": null + } + ], + "outputs": [ + { + "name": "VIDEO", + "type": "VIDEO", + "links": [ + 13 + ] + } + ], + "properties": { + "proxyWidgets": [ + [ + "468", + "a" + ], + [ + "487", + "switch" + ], + [ + "341", + "value" + ] + ], + "cnr_id": "comfy-core", + "ver": "0.17.2" + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 11, + "type": "360d64f7-96cd-42a6-9b13-6eb0387e17d9", + "pos": [ + 4400, + 5950 + ], + "size": [ + 250, + 370 + ], + "flags": {}, + "order": 11, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 40 + }, + { + "name": "positive", + "type": "CONDITIONING", + "link": 799 + }, + { + "name": "negative", + "type": "CONDITIONING", + "link": 800 + }, + { + "name": "latent_image", + "type": "LATENT", + "link": 804 + }, + { + "name": "noise", + "type": "NOISE", + "link": 44 + }, + { + "label": "video_vae", + "name": "vae", + "type": "VAE", + "link": 45 + }, + { + "name": "image", + "type": "IMAGE", + "link": null + }, + { + "name": "multi_input", + "type": "IMAGE", + "link": 1046 + }, + { + "name": "frame_rate", + "type": "INT", + "widget": { + "name": "frame_rate" + }, + "link": 1199 + }, + { + "label": "2x_spatial_upscale_model", + "name": "upscale_model", + "type": "LATENT_UPSCALE_MODEL", + "link": 1116 + } + ], + "outputs": [ + { + "label": "av_output", + "name": "output", + "type": "LATENT", + "links": [ + 47 + ] + } + ], + "properties": { + "proxyWidgets": [ + [ + "6", + "frame_rate" + ] + ], + "cnr_id": "comfy-core", + "ver": "0.17.2" + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 7, + "type": "2979f3eb-1c4c-45ff-8387-f1f4addb72f4", + "pos": [ + 4160, + 5950 + ], + "size": [ + 220, + 110 + ], + "flags": {}, + "order": 7, + "mode": 0, + "inputs": [ + { + "name": "image", + "type": "IMAGE", + "link": 1048 + }, + { + "label": "length_seconds", + "name": "length", + "type": "INT", + "widget": { + "name": "length" + }, + "link": 21 + }, + { + "name": "audio_vae", + "type": "VAE", + "link": 22 + }, + { + "name": "frame_rate", + "type": "INT", + "widget": { + "name": "frame_rate" + }, + "link": 1109 + } + ], + "outputs": [ + { + "label": "video_latent", + "name": "LATENT", + "type": "LATENT", + "links": [ + 803 + ] + }, + { + "label": "audio_latent", + "name": "Latent", + "type": "LATENT", + "links": [ + 1166 + ] + } + ], + "properties": { + "proxyWidgets": [ + [ + "352", + "length" + ], + [ + "353", + "frame_rate" + ] + ], + "cnr_id": "comfy-core", + "ver": "0.17.2" + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 381, + "type": "MultiImageLoader", + "pos": [ + 3540, + 5950 + ], + "size": [ + 340, + 445 + ], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "multi_output", + "type": "IMAGE", + "links": [ + 1046, + 1047, + 1048, + 1157 + ] + } + ], + "properties": { + "cnr_id": "WhatDreamsCost-ComfyUI", + "ver": "ecd00e6b404f14b07c7b54b0b0033e8bccb32d24", + "Node name for S&R": "MultiImageLoader" + }, + "widgets_values": [ + "", + 0, + 0, + "lanczos", + "keep proportion", + 32, + 18, + "" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 10, + "type": "958b2c7a-1f75-4a30-84a8-892bddb9e426", + "pos": [ + 2720, + 5960 + ], + "size": [ + 390, + 480 + ], + "flags": {}, + "order": 1, + "mode": 0, + "inputs": [ + { + "label": "use custom audio", + "name": "value", + "type": "STRING", + "widget": { + "name": "value" + }, + "link": null + }, + { + "label": "frame_rate", + "name": "value_1", + "type": "INT", + "widget": { + "name": "value_1" + }, + "link": null + }, + { + "label": "length (seconds)", + "name": "value_2", + "type": "INT", + "widget": { + "name": "value_2" + }, + "link": null + } + ], + "outputs": [ + { + "name": "optimized_model", + "type": "MODEL", + "links": [ + 40 + ] + }, + { + "label": "video_vae", + "name": "VAE", + "type": "VAE", + "links": [ + 31, + 45, + 798 + ] + }, + { + "label": "audio_vae", + "name": "VAE_1", + "type": "VAE", + "links": [ + 2, + 22, + 1158 + ] + }, + { + "name": "positive", + "type": "CONDITIONING", + "links": [ + 796 + ] + }, + { + "name": "negative", + "type": "CONDITIONING", + "links": [ + 797 + ] + }, + { + "label": "length_seconds", + "name": "INT", + "type": "INT", + "links": [ + 21 + ] + }, + { + "label": "noise", + "name": "NOISE", + "type": "NOISE", + "links": [ + 44 + ] + }, + { + "label": "2x_spatial_upscale_model", + "name": "LATENT_UPSCALE_MODEL", + "type": "LATENT_UPSCALE_MODEL", + "links": [ + 1116 + ] + }, + { + "label": "frame_rate", + "name": "INT_1", + "type": "INT", + "links": [ + 1104, + 1109, + 1132, + 1199 + ] + }, + { + "label": "use custom audio", + "name": "BOOLEAN", + "type": "BOOLEAN", + "links": [ + 1164, + 1176 + ] + } + ], + "properties": { + "proxyWidgets": [ + [ + "464", + "value" + ], + [ + "467", + "value" + ], + [ + "365", + "value" + ], + [ + "364", + "noise_seed" + ], + [ + "364", + "control_after_generate" + ], + [ + "484", + "value" + ] + ], + "cnr_id": "comfy-core", + "ver": "0.17.2" + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 418, + "type": "Note", + "pos": [ + 2730, + 5820 + ], + "size": [ + 270, + 90 + ], + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [], + "outputs": [], + "title": "Model Loader Note", + "properties": {}, + "widgets_values": [ + "↓ Select your own models in this subgraph ↓" + ], + "color": "#223", + "bgcolor": "#335" + }, + { + "id": 499, + "type": "LoadAudioUI", + "pos": [ + 3130, + 5960 + ], + "size": [ + 390, + 378 + ], + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "audio", + "type": "AUDIO", + "links": [ + 1200, + 1201 + ] + }, + { + "name": "duration", + "type": "FLOAT", + "links": null + } + ], + "properties": { + "cnr_id": "WhatDreamsCost-ComfyUI", + "ver": "919e3c15e53502a7f9165c4355a635b4fa2a07ae", + "Node name for S&R": "LoadAudioUI" + }, + "widgets_values": [ + "audio.wav", + 0, + 25, + 25, + null, + null, + "" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 15, + "type": "Note", + "pos": [ + 3900, + 5740 + ], + "size": [ + 230, + 150 + ], + "flags": {}, + "order": 4, + "mode": 0, + "inputs": [], + "outputs": [], + "title": "LTX Sequencer Note", + "properties": {}, + "widgets_values": [ + "Use one image at full strength for regular image to video.\n\nIf frame_insert is -1, it will be inserted at the last frame.\n\nThe insert_mode allows to switch between inserting images by setting the exact frame or by setting the exact second." + ], + "color": "#223", + "bgcolor": "#335" + }, + { + "id": 439, + "type": "MarkdownNote", + "pos": [ + 2340, + 6060 + ], + "size": [ + 350, + 380 + ], + "flags": {}, + "order": 5, + "mode": 0, + "inputs": [], + "outputs": [], + "title": "FAQ", + "properties": {}, + "widgets_values": [ + "# FAQ\n\n**How do I set the length of the video?**\n\nYou can set the length of the video in the Model Loader Subgraph →\n\n**I keep getting errors when running this workflow!**\n\nDouble check that all of the correct models loaded. This is using the newest version of 2x upscaler (v1.1). You can change it to the older one under the Model Loader.\n\n**Where do I download these models??**\n\nThere is a good list of models and download links here: \n\nAlso the latest v1.1 spatial upscaler can be found here: \n\nThe tiny vae (taeltx2_3.safetensors) is found here:\n\n\n**I want to use my own models!**\n\nYou can change the models in the Model Loader Subgraph →\n\n\n\n\n" + ], + "color": "#323", + "bgcolor": "#535" + }, + { + "id": 495, + "type": "Note", + "pos": [ + 3130, + 6380 + ], + "size": [ + 270, + 90 + ], + "flags": {}, + "order": 6, + "mode": 0, + "inputs": [], + "outputs": [], + "title": "Custom Audio Note", + "properties": {}, + "widgets_values": [ + "↑ Load Custom Audio Here ↑\n\n(Make sure to set use custom audio to true to enable it)" + ], + "color": "#223", + "bgcolor": "#335" + } + ], + "links": [ + [ + 2, + 10, + 2, + 1, + 1, + "VAE" + ], + [ + 13, + 1, + 0, + 5, + 0, + "VIDEO" + ], + [ + 21, + 10, + 5, + 7, + 1, + "INT" + ], + [ + 22, + 10, + 2, + 7, + 2, + "VAE" + ], + [ + 31, + 10, + 1, + 1, + 2, + "VAE" + ], + [ + 40, + 10, + 0, + 11, + 0, + "MODEL" + ], + [ + 44, + 10, + 6, + 11, + 4, + "NOISE" + ], + [ + 45, + 10, + 1, + 11, + 5, + "VAE" + ], + [ + 47, + 11, + 0, + 1, + 0, + "LATENT" + ], + [ + 796, + 10, + 3, + 376, + 0, + "CONDITIONING" + ], + [ + 797, + 10, + 4, + 376, + 1, + "CONDITIONING" + ], + [ + 798, + 10, + 1, + 376, + 2, + "VAE" + ], + [ + 799, + 376, + 0, + 11, + 1, + "CONDITIONING" + ], + [ + 800, + 376, + 1, + 11, + 2, + "CONDITIONING" + ], + [ + 802, + 376, + 2, + 4, + 0, + "LATENT" + ], + [ + 803, + 7, + 0, + 376, + 3, + "LATENT" + ], + [ + 804, + 4, + 0, + 11, + 3, + "LATENT" + ], + [ + 1046, + 381, + 0, + 11, + 7, + "IMAGE" + ], + [ + 1047, + 381, + 0, + 376, + 4, + "IMAGE" + ], + [ + 1048, + 381, + 0, + 7, + 0, + "IMAGE" + ], + [ + 1104, + 10, + 8, + 376, + 5, + "INT" + ], + [ + 1109, + 10, + 8, + 7, + 3, + "INT" + ], + [ + 1116, + 10, + 7, + 11, + 9, + "LATENT_UPSCALE_MODEL" + ], + [ + 1132, + 10, + 8, + 1, + 3, + "INT" + ], + [ + 1153, + 481, + 0, + 4, + 1, + "LATENT" + ], + [ + 1157, + 381, + 0, + 481, + 2, + "IMAGE" + ], + [ + 1158, + 10, + 2, + 481, + 1, + "VAE" + ], + [ + 1164, + 10, + 9, + 481, + 3, + "BOOLEAN" + ], + [ + 1166, + 7, + 1, + 481, + 4, + "LATENT" + ], + [ + 1176, + 10, + 9, + 1, + 5, + "BOOLEAN" + ], + [ + 1199, + 10, + 8, + 11, + 8, + "INT" + ], + [ + 1200, + 499, + 0, + 481, + 0, + "AUDIO" + ], + [ + 1201, + 499, + 0, + 1, + 4, + "AUDIO" + ] + ], + "groups": [], + "definitions": { + "subgraphs": [ + { + "id": "34d63160-e149-4935-ab1f-892ffe617fec", + "version": 1, + "state": { + "lastGroupId": 33, + "lastNodeId": 499, + "lastLinkId": 1201, + "lastRerouteId": 0 + }, + "revision": 0, + "config": {}, + "name": "Decode", + "inputNode": { + "id": -10, + "bounding": [ + 1610, + 7190, + 140.259765625, + 180 + ] + }, + "outputNode": { + "id": -20, + "bounding": [ + 2970, + 7320, + 120, + 60 + ] + }, + "inputs": [ + { + "id": "1d567d10-9078-49fc-a4a1-67e6d4b5744f", + "name": "av_latent", + "type": "LATENT", + "linkIds": [ + 692 + ], + "pos": [ + 1730.259765625, + 7210 + ] + }, + { + "id": "bef9341f-4429-46af-9bca-11e025bec4d0", + "name": "audio_vae", + "type": "VAE", + "linkIds": [ + 693 + ], + "pos": [ + 1730.259765625, + 7230 + ] + }, + { + "id": "4588ee3d-96a4-46a8-b829-a78181f6c305", + "name": "vae", + "type": "VAE", + "linkIds": [ + 703, + 1060 + ], + "label": "video_vae", + "pos": [ + 1730.259765625, + 7250 + ] + }, + { + "id": "19bcc2d8-c8cd-4e77-bb85-535e398e6b4a", + "name": "a", + "type": "INT", + "linkIds": [ + 1130 + ], + "label": "frame_rate", + "pos": [ + 1730.259765625, + 7270 + ] + }, + { + "id": "da4ad9a2-95b3-4799-a4fb-f0c100fd4970", + "name": "on_true", + "type": "*", + "linkIds": [ + 1179 + ], + "label": "custom audio", + "pos": [ + 1730.259765625, + 7290 + ] + }, + { + "id": "b68ee794-f7af-41a5-b690-fc54a2a5bda4", + "name": "switch", + "type": "BOOLEAN", + "linkIds": [ + 1178 + ], + "label": "use custom audio", + "pos": [ + 1730.259765625, + 7310 + ] + }, + { + "id": "0e84a82b-9dd4-4a34-a1b7-0ffd7affe1f7", + "name": "value", + "type": "BOOLEAN", + "linkIds": [ + 1177 + ], + "label": "use tiled vae", + "pos": [ + 1730.259765625, + 7330 + ] + } + ], + "outputs": [ + { + "id": "7e889c1c-e185-47d7-a0f7-c5f1d971c6fe", + "name": "VIDEO", + "type": "VIDEO", + "linkIds": [ + 704 + ], + "pos": [ + 2990, + 7340 + ] + } + ], + "widgets": [], + "nodes": [ + { + "id": 334, + "type": "LTXVSeparateAVLatent", + "pos": [ + 1820, + 7190 + ], + "size": [ + 240, + 50 + ], + "flags": {}, + "order": 1, + "mode": 0, + "inputs": [ + { + "localized_name": "av_latent", + "name": "av_latent", + "type": "LATENT", + "link": 692 + } + ], + "outputs": [ + { + "localized_name": "video_latent", + "name": "video_latent", + "type": "LATENT", + "links": [ + 702, + 1059 + ] + }, + { + "localized_name": "audio_latent", + "name": "audio_latent", + "type": "LATENT", + "links": [ + 687 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.5.1", + "Node name for S&R": "LTXVSeparateAVLatent", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 340, + "type": "VAEDecode", + "pos": [ + 2110, + 7190 + ], + "size": [ + 150, + 50 + ], + "flags": {}, + "order": 4, + "mode": 0, + "inputs": [ + { + "localized_name": "samples", + "name": "samples", + "type": "LATENT", + "link": 702 + }, + { + "localized_name": "vae", + "name": "vae", + "type": "VAE", + "link": 703 + } + ], + "outputs": [ + { + "localized_name": "IMAGE", + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 701 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.17.2", + "Node name for S&R": "VAEDecode" + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 336, + "type": "CreateVideo", + "pos": [ + 2740, + 7340 + ], + "size": [ + 210, + 80 + ], + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [ + { + "localized_name": "images", + "name": "images", + "type": "IMAGE", + "link": 698 + }, + { + "localized_name": "audio", + "name": "audio", + "shape": 7, + "type": "AUDIO", + "link": 1174 + }, + { + "localized_name": "fps", + "name": "fps", + "type": "FLOAT", + "widget": { + "name": "fps" + }, + "link": 1128 + } + ], + "outputs": [ + { + "localized_name": "VIDEO", + "name": "VIDEO", + "type": "VIDEO", + "links": [ + 704 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.5.1", + "Node name for S&R": "CreateVideo", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [ + 24 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 468, + "type": "CM_IntToFloat", + "pos": [ + 1820, + 7380 + ], + "size": [ + 240, + 60 + ], + "flags": {}, + "order": 7, + "mode": 0, + "inputs": [ + { + "localized_name": "a", + "name": "a", + "type": "INT", + "widget": { + "name": "a" + }, + "link": 1130 + } + ], + "outputs": [ + { + "localized_name": "FLOAT", + "name": "FLOAT", + "type": "FLOAT", + "links": [ + 1128 + ] + } + ], + "title": "frame_rate_covert", + "properties": { + "cnr_id": "ComfyMath", + "ver": "c01177221c31b8e5fbc062778fc8254aeb541638", + "Node name for S&R": "CM_IntToFloat" + }, + "widgets_values": [ + 0 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 432, + "type": "VAEDecodeTiled", + "pos": [ + 2110, + 7320 + ], + "size": [ + 270, + 150 + ], + "flags": {}, + "order": 6, + "mode": 0, + "inputs": [ + { + "localized_name": "samples", + "name": "samples", + "type": "LATENT", + "link": 1059 + }, + { + "localized_name": "vae", + "name": "vae", + "type": "VAE", + "link": 1060 + } + ], + "outputs": [ + { + "localized_name": "IMAGE", + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 1058 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.17.2", + "Node name for S&R": "VAEDecodeTiled" + }, + "widgets_values": [ + 512, + 128, + 128, + 8 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 337, + "type": "LazySwitchKJ", + "pos": [ + 2450, + 7340 + ], + "size": [ + 270, + 80 + ], + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [ + { + "localized_name": "on_false", + "name": "on_false", + "type": "*", + "link": 701 + }, + { + "localized_name": "on_true", + "name": "on_true", + "type": "*", + "link": 1058 + }, + { + "localized_name": "switch", + "name": "switch", + "type": "BOOLEAN", + "widget": { + "name": "switch" + }, + "link": 707 + } + ], + "outputs": [ + { + "localized_name": "*", + "name": "*", + "type": "*", + "links": [ + 698 + ] + } + ], + "properties": { + "cnr_id": "comfyui-kjnodes", + "ver": "1.3.4", + "Node name for S&R": "LazySwitchKJ" + }, + "widgets_values": [ + false + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 487, + "type": "LazySwitchKJ", + "pos": [ + 2110, + 7520 + ], + "size": [ + 270, + 80 + ], + "flags": {}, + "order": 8, + "mode": 0, + "inputs": [ + { + "localized_name": "on_false", + "name": "on_false", + "type": "*", + "link": 1180 + }, + { + "localized_name": "on_true", + "name": "on_true", + "type": "*", + "link": 1179 + }, + { + "localized_name": "switch", + "name": "switch", + "type": "BOOLEAN", + "widget": { + "name": "switch" + }, + "link": 1178 + } + ], + "outputs": [ + { + "localized_name": "*", + "name": "*", + "type": "*", + "links": [ + 1174 + ] + } + ], + "properties": { + "cnr_id": "comfyui-kjnodes", + "ver": "53987d76fbf61830a724628d7681ae5724c98e2b", + "Node name for S&R": "LazySwitchKJ" + }, + "widgets_values": [ + false + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 341, + "type": "PrimitiveBoolean", + "pos": [ + 2450, + 7230 + ], + "size": [ + 230, + 60 + ], + "flags": {}, + "order": 5, + "mode": 0, + "inputs": [ + { + "label": "use tiled vae", + "localized_name": "value", + "name": "value", + "type": "BOOLEAN", + "widget": { + "name": "value" + }, + "link": 1177 + } + ], + "outputs": [ + { + "localized_name": "BOOLEAN", + "name": "BOOLEAN", + "type": "BOOLEAN", + "links": [ + 707 + ] + } + ], + "title": "Use Optimized Decoding", + "properties": { + "cnr_id": "comfy-core", + "ver": "0.17.2", + "Node name for S&R": "PrimitiveBoolean" + }, + "widgets_values": [ + false + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 330, + "type": "LTXVAudioVAEDecode", + "pos": [ + 1820, + 7280 + ], + "size": [ + 240, + 50 + ], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [ + { + "localized_name": "samples", + "name": "samples", + "type": "LATENT", + "link": 687 + }, + { + "label": "Audio VAE", + "localized_name": "audio_vae", + "name": "audio_vae", + "type": "VAE", + "link": 693 + } + ], + "outputs": [ + { + "localized_name": "Audio", + "name": "Audio", + "type": "AUDIO", + "links": [ + 1180 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.7.0", + "Node name for S&R": "LTXVAudioVAEDecode", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + } + ], + "groups": [], + "links": [ + { + "id": 687, + "origin_id": 334, + "origin_slot": 1, + "target_id": 330, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 692, + "origin_id": -10, + "origin_slot": 0, + "target_id": 334, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 693, + "origin_id": -10, + "origin_slot": 1, + "target_id": 330, + "target_slot": 1, + "type": "VAE" + }, + { + "id": 698, + "origin_id": 337, + "origin_slot": 0, + "target_id": 336, + "target_slot": 0, + "type": "IMAGE" + }, + { + "id": 701, + "origin_id": 340, + "origin_slot": 0, + "target_id": 337, + "target_slot": 0, + "type": "IMAGE" + }, + { + "id": 702, + "origin_id": 334, + "origin_slot": 0, + "target_id": 340, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 703, + "origin_id": -10, + "origin_slot": 2, + "target_id": 340, + "target_slot": 1, + "type": "VAE" + }, + { + "id": 704, + "origin_id": 336, + "origin_slot": 0, + "target_id": -20, + "target_slot": 0, + "type": "VIDEO" + }, + { + "id": 707, + "origin_id": 341, + "origin_slot": 0, + "target_id": 337, + "target_slot": 2, + "type": "BOOLEAN" + }, + { + "id": 1058, + "origin_id": 432, + "origin_slot": 0, + "target_id": 337, + "target_slot": 1, + "type": "IMAGE" + }, + { + "id": 1059, + "origin_id": 334, + "origin_slot": 0, + "target_id": 432, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 1060, + "origin_id": -10, + "origin_slot": 2, + "target_id": 432, + "target_slot": 1, + "type": "VAE" + }, + { + "id": 1128, + "origin_id": 468, + "origin_slot": 0, + "target_id": 336, + "target_slot": 2, + "type": "FLOAT" + }, + { + "id": 1130, + "origin_id": -10, + "origin_slot": 3, + "target_id": 468, + "target_slot": 0, + "type": "INT" + }, + { + "id": 1174, + "origin_id": 487, + "origin_slot": 0, + "target_id": 336, + "target_slot": 1, + "type": "AUDIO" + }, + { + "id": 1177, + "origin_id": -10, + "origin_slot": 6, + "target_id": 341, + "target_slot": 0, + "type": "BOOLEAN" + }, + { + "id": 1178, + "origin_id": -10, + "origin_slot": 5, + "target_id": 487, + "target_slot": 2, + "type": "BOOLEAN" + }, + { + "id": 1179, + "origin_id": -10, + "origin_slot": 4, + "target_id": 487, + "target_slot": 1, + "type": "*" + }, + { + "id": 1180, + "origin_id": 330, + "origin_slot": 0, + "target_id": 487, + "target_slot": 0, + "type": "AUDIO" + } + ], + "extra": { + "workflowRendererVersion": "LG" + } + }, + { + "id": "afa29944-eb58-40a5-8e76-484f2503fa0c", + "version": 1, + "state": { + "lastGroupId": 33, + "lastNodeId": 499, + "lastLinkId": 1201, + "lastRerouteId": 0 + }, + "revision": 0, + "config": {}, + "name": "Stage #1", + "inputNode": { + "id": -10, + "bounding": [ + 280, + 7113, + 120, + 140 + ] + }, + "outputNode": { + "id": -20, + "bounding": [ + 1150, + 6970, + 120, + 80 + ] + }, + "inputs": [ + { + "id": "e760d60a-9b49-4b40-8f39-3c81f29c08d8", + "name": "model", + "type": "MODEL", + "linkIds": [ + 683, + 686 + ], + "pos": [ + 380, + 7133 + ] + }, + { + "id": "6ab6641e-8318-4330-8b01-43ce43648dcf", + "name": "positive", + "type": "CONDITIONING", + "linkIds": [ + 786 + ], + "pos": [ + 380, + 7153 + ] + }, + { + "id": "e1e7e2f6-49fb-4754-92ab-b71442b03da7", + "name": "negative", + "type": "CONDITIONING", + "linkIds": [ + 787 + ], + "pos": [ + 380, + 7173 + ] + }, + { + "id": "5c9d4c86-ec46-4099-b7e1-41a9288a00d0", + "name": "latent_image", + "type": "LATENT", + "linkIds": [ + 687 + ], + "pos": [ + 380, + 7193 + ] + }, + { + "id": "0a89214b-81f0-4fa9-86fa-5f77079ddb7b", + "name": "noise", + "type": "NOISE", + "linkIds": [ + 688 + ], + "pos": [ + 380, + 7213 + ] + } + ], + "outputs": [ + { + "id": "b6f80403-e98c-41ad-a692-270973783381", + "name": "video_latent", + "type": "LATENT", + "linkIds": [ + 689 + ], + "pos": [ + 1170, + 6990 + ] + }, + { + "id": "f54a00fd-f0db-4e4e-9205-9c9d3e955032", + "name": "audio_latent", + "type": "LATENT", + "linkIds": [ + 690 + ], + "pos": [ + 1170, + 7010 + ] + } + ], + "widgets": [], + "nodes": [ + { + "id": 325, + "type": "KSamplerSelect", + "pos": [ + 460, + 7120 + ], + "size": [ + 210, + 60 + ], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [], + "outputs": [ + { + "localized_name": "SAMPLER", + "name": "SAMPLER", + "type": "SAMPLER", + "links": [ + 680 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.56", + "Node name for S&R": "KSamplerSelect", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [ + "euler" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 326, + "type": "BasicScheduler", + "pos": [ + 460, + 7220 + ], + "size": [ + 210, + 110 + ], + "flags": {}, + "order": 4, + "mode": 0, + "inputs": [ + { + "localized_name": "model", + "name": "model", + "type": "MODEL", + "link": 686 + } + ], + "outputs": [ + { + "localized_name": "SIGMAS", + "name": "SIGMAS", + "type": "SIGMAS", + "links": [ + 681 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.16.4", + "Node name for S&R": "BasicScheduler" + }, + "widgets_values": [ + "linear_quadratic", + 8, + 1 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 322, + "type": "SamplerCustomAdvanced", + "pos": [ + 690, + 6980 + ], + "size": [ + 220, + 420 + ], + "flags": {}, + "order": 1, + "mode": 0, + "inputs": [ + { + "localized_name": "noise", + "name": "noise", + "type": "NOISE", + "link": 688 + }, + { + "localized_name": "guider", + "name": "guider", + "type": "GUIDER", + "link": 679 + }, + { + "localized_name": "sampler", + "name": "sampler", + "type": "SAMPLER", + "link": 680 + }, + { + "localized_name": "sigmas", + "name": "sigmas", + "type": "SIGMAS", + "link": 681 + }, + { + "localized_name": "latent_image", + "name": "latent_image", + "type": "LATENT", + "link": 687 + } + ], + "outputs": [ + { + "localized_name": "output", + "name": "output", + "type": "LATENT", + "links": [ + 682 + ] + }, + { + "localized_name": "denoised_output", + "name": "denoised_output", + "type": "LATENT", + "links": [] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.60", + "Node name for S&R": "SamplerCustomAdvanced", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 323, + "type": "LTXVSeparateAVLatent", + "pos": [ + 920, + 6980 + ], + "size": [ + 200, + 50 + ], + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [ + { + "localized_name": "av_latent", + "name": "av_latent", + "type": "LATENT", + "link": 682 + } + ], + "outputs": [ + { + "localized_name": "video_latent", + "name": "video_latent", + "type": "LATENT", + "links": [ + 689 + ] + }, + { + "localized_name": "audio_latent", + "name": "audio_latent", + "type": "LATENT", + "links": [ + 690 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.5.1", + "Node name for S&R": "LTXVSeparateAVLatent", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 324, + "type": "CFGGuider", + "pos": [ + 460, + 6990 + ], + "size": [ + 210, + 100 + ], + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [ + { + "localized_name": "model", + "name": "model", + "type": "MODEL", + "link": 683 + }, + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "link": 786 + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "link": 787 + } + ], + "outputs": [ + { + "localized_name": "GUIDER", + "name": "GUIDER", + "type": "GUIDER", + "links": [ + 679 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.64", + "Node name for S&R": "CFGGuider", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [ + 1 + ], + "color": "#222", + "bgcolor": "#000" + } + ], + "groups": [], + "links": [ + { + "id": 679, + "origin_id": 324, + "origin_slot": 0, + "target_id": 322, + "target_slot": 1, + "type": "GUIDER" + }, + { + "id": 680, + "origin_id": 325, + "origin_slot": 0, + "target_id": 322, + "target_slot": 2, + "type": "SAMPLER" + }, + { + "id": 681, + "origin_id": 326, + "origin_slot": 0, + "target_id": 322, + "target_slot": 3, + "type": "SIGMAS" + }, + { + "id": 682, + "origin_id": 322, + "origin_slot": 0, + "target_id": 323, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 683, + "origin_id": -10, + "origin_slot": 0, + "target_id": 324, + "target_slot": 0, + "type": "MODEL" + }, + { + "id": 686, + "origin_id": -10, + "origin_slot": 0, + "target_id": 326, + "target_slot": 0, + "type": "MODEL" + }, + { + "id": 687, + "origin_id": -10, + "origin_slot": 3, + "target_id": 322, + "target_slot": 4, + "type": "LATENT" + }, + { + "id": 688, + "origin_id": -10, + "origin_slot": 4, + "target_id": 322, + "target_slot": 0, + "type": "NOISE" + }, + { + "id": 689, + "origin_id": 323, + "origin_slot": 0, + "target_id": -20, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 690, + "origin_id": 323, + "origin_slot": 1, + "target_id": -20, + "target_slot": 1, + "type": "LATENT" + }, + { + "id": 786, + "origin_id": -10, + "origin_slot": 1, + "target_id": 324, + "target_slot": 1, + "type": "CONDITIONING" + }, + { + "id": 787, + "origin_id": -10, + "origin_slot": 2, + "target_id": 324, + "target_slot": 2, + "type": "CONDITIONING" + } + ], + "extra": { + "workflowRendererVersion": "LG" + } + }, + { + "id": "ce8f7842-cb03-41b4-9b1e-8d032b2a4e72", + "version": 1, + "state": { + "lastGroupId": 33, + "lastNodeId": 499, + "lastLinkId": 1201, + "lastRerouteId": 0 + }, + "revision": 0, + "config": {}, + "name": "Stage #2", + "inputNode": { + "id": -10, + "bounding": [ + 280, + 7113, + 125.224609375, + 260 + ] + }, + "outputNode": { + "id": -20, + "bounding": [ + 1820, + 7130, + 120, + 120 + ] + }, + "inputs": [ + { + "id": "e760d60a-9b49-4b40-8f39-3c81f29c08d8", + "name": "model", + "type": "MODEL", + "linkIds": [ + 683, + 686 + ], + "pos": [ + 385.224609375, + 7133 + ] + }, + { + "id": "6ab6641e-8318-4330-8b01-43ce43648dcf", + "name": "positive", + "type": "CONDITIONING", + "linkIds": [ + 703 + ], + "pos": [ + 385.224609375, + 7153 + ] + }, + { + "id": "e1e7e2f6-49fb-4754-92ab-b71442b03da7", + "name": "negative", + "type": "CONDITIONING", + "linkIds": [ + 704 + ], + "pos": [ + 385.224609375, + 7173 + ] + }, + { + "id": "0a89214b-81f0-4fa9-86fa-5f77079ddb7b", + "name": "noise", + "type": "NOISE", + "linkIds": [ + 688 + ], + "pos": [ + 385.224609375, + 7193 + ] + }, + { + "id": "d8824343-6b21-42aa-9eaf-65d1b267662d", + "name": "vae", + "type": "VAE", + "linkIds": [ + 706, + 990 + ], + "label": "video_vae", + "pos": [ + 385.224609375, + 7213 + ] + }, + { + "id": "6ea10719-25f1-449a-9d2d-bb395d89609d", + "name": "image", + "type": "IMAGE", + "linkIds": [], + "pos": [ + 385.224609375, + 7233 + ] + }, + { + "id": "6c54f923-7ccc-4832-86ca-38d39cfcc656", + "name": "samples", + "type": "LATENT", + "linkIds": [ + 705 + ], + "label": "video_latent", + "pos": [ + 385.224609375, + 7253 + ] + }, + { + "id": "5ce3a92e-d38a-4078-bc1f-7426a38fa1ff", + "name": "audio_latent", + "type": "LATENT", + "linkIds": [ + 700 + ], + "pos": [ + 385.224609375, + 7273 + ] + }, + { + "id": "6a8ebcb1-8f4b-4558-893c-1b6be5c462ec", + "name": "multi_input", + "type": "IMAGE", + "linkIds": [ + 1004 + ], + "pos": [ + 385.224609375, + 7293 + ] + }, + { + "id": "7443af4e-2549-4707-813a-a7e7efa13046", + "name": "upscale_model", + "type": "LATENT_UPSCALE_MODEL", + "linkIds": [ + 1035 + ], + "pos": [ + 385.224609375, + 7313 + ] + }, + { + "id": "26a11f82-8eda-4ba1-be18-c0d429489b0e", + "name": "frame_rate", + "type": "INT", + "linkIds": [ + 1125 + ], + "pos": [ + 385.224609375, + 7333 + ] + } + ], + "outputs": [ + { + "id": "b6f80403-e98c-41ad-a692-270973783381", + "name": "video_latent", + "type": "LATENT", + "linkIds": [ + 843 + ], + "pos": [ + 1840, + 7150 + ] + }, + { + "id": "f54a00fd-f0db-4e4e-9205-9c9d3e955032", + "name": "audio_latent", + "type": "LATENT", + "linkIds": [ + 690 + ], + "pos": [ + 1840, + 7170 + ] + }, + { + "id": "3ecaa920-05d1-4c39-8ea0-b7b21e2df49b", + "name": "positive", + "type": "CONDITIONING", + "linkIds": [ + 985 + ], + "pos": [ + 1840, + 7190 + ] + }, + { + "id": "d1c64c87-487c-4677-9f80-d4a7ef6cdcc6", + "name": "negative", + "type": "CONDITIONING", + "linkIds": [ + 986 + ], + "pos": [ + 1840, + 7210 + ] + } + ], + "widgets": [], + "nodes": [ + { + "id": 445, + "type": "KSamplerSelect", + "pos": [ + 480, + 7110 + ], + "size": [ + 210, + 60 + ], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [], + "outputs": [ + { + "localized_name": "SAMPLER", + "name": "SAMPLER", + "type": "SAMPLER", + "links": [ + 680 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.56", + "Node name for S&R": "KSamplerSelect", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [ + "euler" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 331, + "type": "LTXVCropGuides", + "pos": [ + 490, + 7000 + ], + "size": [ + 160, + 70 + ], + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [ + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "link": 703 + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "link": 704 + }, + { + "localized_name": "latent", + "name": "latent", + "type": "LATENT", + "link": 705 + } + ], + "outputs": [ + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "links": [ + 828, + 846, + 988 + ] + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "links": [ + 829, + 847, + 989 + ] + }, + { + "localized_name": "latent", + "name": "latent", + "type": "LATENT", + "links": [ + 850 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.16.4", + "Node name for S&R": "LTXVCropGuides" + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 328, + "type": "LTXVLatentUpsampler", + "pos": [ + 480, + 7370 + ], + "size": [ + 320, + 70 + ], + "flags": {}, + "order": 1, + "mode": 0, + "inputs": [ + { + "localized_name": "samples", + "name": "samples", + "type": "LATENT", + "link": 850 + }, + { + "localized_name": "upscale_model", + "name": "upscale_model", + "type": "LATENT_UPSCALE_MODEL", + "link": 1035 + }, + { + "localized_name": "vae", + "name": "vae", + "type": "VAE", + "link": 706 + } + ], + "outputs": [ + { + "localized_name": "LATENT", + "name": "LATENT", + "type": "LATENT", + "links": [ + 991 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.14.1", + "Node name for S&R": "LTXVLatentUpsampler", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 409, + "type": "LTXSequencer", + "pos": [ + 850, + 7140 + ], + "size": [ + 210, + 200 + ], + "flags": {}, + "order": 4, + "mode": 0, + "inputs": [ + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "link": 988 + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "link": 989 + }, + { + "localized_name": "vae", + "name": "vae", + "type": "VAE", + "link": 990 + }, + { + "localized_name": "latent", + "name": "latent", + "type": "LATENT", + "link": 991 + }, + { + "localized_name": "multi_input", + "name": "multi_input", + "type": "IMAGE", + "link": 1004 + }, + { + "localized_name": "frame_rate", + "name": "frame_rate", + "type": "INT", + "widget": { + "name": "frame_rate" + }, + "link": 1125 + } + ], + "outputs": [ + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "links": [ + 983, + 985 + ] + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "links": [ + 984, + 986 + ] + }, + { + "localized_name": "latent", + "name": "latent", + "type": "LATENT", + "links": [ + 987 + ] + } + ], + "properties": { + "cnr_id": "WhatDreamsCost-ComfyUI", + "ver": "ecd00e6b404f14b07c7b54b0b0033e8bccb32d24", + "Node name for S&R": "LTXSequencer", + "num_images": 0, + "insert_frame_1": 0, + "strength_1": 1, + "insert_frame_2": 0, + "strength_2": 1, + "insert_frame_3": 0, + "strength_3": 1, + "insert_frame_4": 0, + "strength_4": 1, + "insert_frame_5": 0, + "strength_5": 1, + "insert_frame_6": 0, + "strength_6": 1, + "insert_frame_7": 0, + "strength_7": 1, + "insert_frame_8": 0, + "strength_8": 1, + "insert_frame_9": 0, + "strength_9": 0, + "insert_frame_10": 1, + "strength_10": 1, + "insert_frame_11": 0, + "strength_11": 0, + "insert_frame_12": 1, + "strength_12": 1, + "insert_frame_13": 0, + "strength_13": 0, + "insert_frame_14": 1, + "strength_14": 1, + "insert_frame_15": 0, + "strength_15": 0, + "insert_frame_16": 1, + "strength_16": 1, + "insert_frame_17": 0, + "strength_17": 0, + "insert_frame_18": 1, + "strength_18": 1, + "insert_frame_19": 0, + "strength_19": 0, + "insert_frame_20": 1, + "strength_20": 1, + "insert_frame_21": 0, + "strength_21": 0, + "insert_frame_22": 1, + "strength_22": 1, + "insert_frame_23": 0, + "strength_23": 0, + "insert_frame_24": 1, + "strength_24": 1, + "insert_frame_25": 0, + "strength_25": 0, + "insert_frame_26": 1, + "strength_26": 1, + "insert_frame_27": 0, + "strength_27": 0, + "insert_frame_28": 1, + "strength_28": 1, + "insert_frame_29": 0, + "strength_29": 0, + "insert_frame_30": 1, + "strength_30": 1, + "insert_frame_31": 0, + "strength_31": 0, + "insert_frame_32": 1, + "strength_32": 1, + "insert_frame_33": 0, + "strength_33": 1, + "insert_frame_34": 0, + "strength_34": 1, + "insert_frame_35": 0, + "strength_35": 1, + "insert_frame_36": 0, + "strength_36": 1, + "insert_frame_37": 0, + "strength_37": 1, + "insert_frame_38": 0, + "strength_38": 1, + "insert_frame_39": 0, + "strength_39": 1, + "insert_frame_40": 0, + "strength_40": 1, + "insert_frame_41": 0, + "strength_41": 1, + "insert_frame_42": 0, + "strength_42": 1, + "insert_frame_43": 0, + "strength_43": 1, + "insert_frame_44": 0, + "strength_44": 1, + "insert_frame_45": 0, + "strength_45": 1, + "insert_frame_46": 0, + "strength_46": 1, + "insert_frame_47": 0, + "strength_47": 1, + "insert_frame_48": 0, + "strength_48": 1, + "insert_frame_49": 0, + "strength_49": 1, + "insert_frame_50": 0, + "strength_50": 1, + "insert_mode": "frames", + "frame_rate": 24, + "insert_second_1": 0, + "insert_second_2": 0, + "insert_second_3": 0, + "insert_second_4": 0, + "insert_second_5": 0, + "insert_second_6": 0, + "insert_second_7": 0, + "insert_second_8": 0, + "insert_second_9": 1, + "insert_second_10": 0, + "insert_second_11": 1, + "insert_second_12": 0, + "insert_second_13": 1, + "insert_second_14": 0, + "insert_second_15": 1, + "insert_second_16": 0, + "insert_second_17": 1, + "insert_second_18": 0, + "insert_second_19": 1, + "insert_second_20": 0, + "insert_second_21": 1, + "insert_second_22": 0, + "insert_second_23": 1, + "insert_second_24": 0, + "insert_second_25": 1, + "insert_second_26": 0, + "insert_second_27": 1, + "insert_second_28": 0, + "insert_second_29": 1, + "insert_second_30": 0, + "insert_second_31": 1, + "insert_second_32": 0, + "insert_second_33": 1, + "insert_second_34": 0, + "insert_second_35": 0, + "insert_second_36": 0, + "insert_second_37": 0, + "insert_second_38": 0, + "insert_second_39": 0, + "insert_second_40": 0, + "insert_second_41": 0, + "insert_second_42": 0, + "insert_second_43": 0, + "insert_second_44": 0, + "insert_second_45": 0, + "insert_second_46": 0, + "insert_second_47": 0, + "insert_second_48": 0, + "insert_second_49": 0, + "insert_second_50": 0, + "insert_images_using": "frames", + "video_length": 1, + "insert_percentage_1": 0, + "insert_percentage_2": 1, + "insert_percentage_3": 0, + "insert_percentage_4": 0, + "insert_percentage_5": 0, + "insert_percentage_6": 0, + "insert_percentage_7": 0, + "insert_percentage_8": 0, + "insert_percentage_9": 0, + "insert_percentage_10": 0, + "insert_percentage_11": 0, + "insert_percentage_12": 0, + "insert_percentage_13": 0, + "insert_percentage_14": 0, + "insert_percentage_15": 0, + "insert_percentage_16": 0, + "insert_percentage_17": 0, + "insert_percentage_18": 0, + "insert_percentage_19": 0, + "insert_percentage_20": 0, + "insert_percentage_21": 0, + "insert_percentage_22": 0, + "insert_percentage_23": 0, + "insert_percentage_24": 0, + "insert_percentage_25": 0, + "insert_percentage_26": 0, + "insert_percentage_27": 0, + "insert_percentage_28": 0, + "insert_percentage_29": 0, + "insert_percentage_30": 0, + "insert_percentage_31": 0, + "insert_percentage_32": 0, + "insert_percentage_33": 0, + "insert_percentage_34": 0, + "insert_percentage_35": 0, + "insert_percentage_36": 0, + "insert_percentage_37": 0, + "insert_percentage_38": 0, + "insert_percentage_39": 0, + "insert_percentage_40": 0, + "insert_percentage_41": 0, + "insert_percentage_42": 0, + "insert_percentage_43": 0, + "insert_percentage_44": 0, + "insert_percentage_45": 0, + "insert_percentage_46": 0, + "insert_percentage_47": 0, + "insert_percentage_48": 0, + "insert_percentage_49": 0, + "insert_percentage_50": 0, + "insert_percent_1": 0, + "insert_percent_2": 50, + "insert_percent_3": 100, + "insert_percent_4": 0, + "insert_percent_5": 0, + "insert_percent_6": 0, + "insert_percent_7": 0, + "insert_percent_8": 0, + "insert_percent_9": 0, + "insert_percent_10": 0, + "insert_percent_11": 0, + "insert_percent_12": 0, + "insert_percent_13": 0, + "insert_percent_14": 0, + "insert_percent_15": 0, + "insert_percent_16": 0, + "insert_percent_17": 0, + "insert_percent_18": 0, + "insert_percent_19": 0, + "insert_percent_20": 0, + "insert_percent_21": 0, + "insert_percent_22": 0, + "insert_percent_23": 0, + "insert_percent_24": 0, + "insert_percent_25": 0, + "insert_percent_26": 0, + "insert_percent_27": 0, + "insert_percent_28": 0, + "insert_percent_29": 0, + "insert_percent_30": 0, + "insert_percent_31": 0, + "insert_percent_32": 0, + "insert_percent_33": 0, + "insert_percent_34": 0, + "insert_percent_35": 0, + "insert_percent_36": 0, + "insert_percent_37": 0, + "insert_percent_38": 0, + "insert_percent_39": 0, + "insert_percent_40": 0, + "insert_percent_41": 0, + "insert_percent_42": 0, + "insert_percent_43": 0, + "insert_percent_44": 0, + "insert_percent_45": 0, + "insert_percent_46": 0, + "insert_percent_47": 0, + "insert_percent_48": 0, + "insert_percent_49": 0, + "insert_percent_50": 0 + }, + "widgets_values": [ + 0, + "frames", + 24, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 444, + "type": "BasicScheduler", + "pos": [ + 480, + 7210 + ], + "size": [ + 210, + 110 + ], + "flags": {}, + "order": 5, + "mode": 0, + "inputs": [ + { + "localized_name": "model", + "name": "model", + "type": "MODEL", + "link": 686 + } + ], + "outputs": [ + { + "localized_name": "SIGMAS", + "name": "SIGMAS", + "type": "SIGMAS", + "links": [ + 681 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.16.4", + "Node name for S&R": "BasicScheduler" + }, + "widgets_values": [ + "linear_quadratic", + 4, + 0.42 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 448, + "type": "CFGGuider", + "pos": [ + 1090, + 7130 + ], + "size": [ + 210, + 100 + ], + "flags": {}, + "order": 8, + "mode": 0, + "inputs": [ + { + "localized_name": "model", + "name": "model", + "type": "MODEL", + "link": 683 + }, + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "link": 983 + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "link": 984 + } + ], + "outputs": [ + { + "localized_name": "GUIDER", + "name": "GUIDER", + "type": "GUIDER", + "links": [ + 679 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.64", + "Node name for S&R": "CFGGuider", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [ + 1 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 329, + "type": "LTXVConcatAVLatent", + "pos": [ + 1090, + 7270 + ], + "size": [ + 190, + 60 + ], + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [ + { + "localized_name": "video_latent", + "name": "video_latent", + "type": "LATENT", + "link": 987 + }, + { + "localized_name": "audio_latent", + "name": "audio_latent", + "type": "LATENT", + "link": 700 + } + ], + "outputs": [ + { + "localized_name": "latent", + "name": "latent", + "type": "LATENT", + "links": [ + 697 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.7.0", + "Node name for S&R": "LTXVConcatAVLatent", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 446, + "type": "SamplerCustomAdvanced", + "pos": [ + 1330, + 7130 + ], + "size": [ + 220, + 420 + ], + "flags": {}, + "order": 6, + "mode": 0, + "inputs": [ + { + "localized_name": "noise", + "name": "noise", + "type": "NOISE", + "link": 688 + }, + { + "localized_name": "guider", + "name": "guider", + "type": "GUIDER", + "link": 679 + }, + { + "localized_name": "sampler", + "name": "sampler", + "type": "SAMPLER", + "link": 680 + }, + { + "localized_name": "sigmas", + "name": "sigmas", + "type": "SIGMAS", + "link": 681 + }, + { + "localized_name": "latent_image", + "name": "latent_image", + "type": "LATENT", + "link": 697 + } + ], + "outputs": [ + { + "localized_name": "output", + "name": "output", + "type": "LATENT", + "links": [ + 682 + ] + }, + { + "localized_name": "denoised_output", + "name": "denoised_output", + "type": "LATENT", + "links": [] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.60", + "Node name for S&R": "SamplerCustomAdvanced", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 447, + "type": "LTXVSeparateAVLatent", + "pos": [ + 1570, + 7130 + ], + "size": [ + 200, + 50 + ], + "flags": {}, + "order": 7, + "mode": 0, + "inputs": [ + { + "localized_name": "av_latent", + "name": "av_latent", + "type": "LATENT", + "link": 682 + } + ], + "outputs": [ + { + "localized_name": "video_latent", + "name": "video_latent", + "type": "LATENT", + "links": [ + 843 + ] + }, + { + "localized_name": "audio_latent", + "name": "audio_latent", + "type": "LATENT", + "links": [ + 690 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.5.1", + "Node name for S&R": "LTXVSeparateAVLatent", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + } + ], + "groups": [], + "links": [ + { + "id": 679, + "origin_id": 448, + "origin_slot": 0, + "target_id": 446, + "target_slot": 1, + "type": "GUIDER" + }, + { + "id": 680, + "origin_id": 445, + "origin_slot": 0, + "target_id": 446, + "target_slot": 2, + "type": "SAMPLER" + }, + { + "id": 681, + "origin_id": 444, + "origin_slot": 0, + "target_id": 446, + "target_slot": 3, + "type": "SIGMAS" + }, + { + "id": 682, + "origin_id": 446, + "origin_slot": 0, + "target_id": 447, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 683, + "origin_id": -10, + "origin_slot": 0, + "target_id": 448, + "target_slot": 0, + "type": "MODEL" + }, + { + "id": 686, + "origin_id": -10, + "origin_slot": 0, + "target_id": 444, + "target_slot": 0, + "type": "MODEL" + }, + { + "id": 688, + "origin_id": -10, + "origin_slot": 3, + "target_id": 446, + "target_slot": 0, + "type": "NOISE" + }, + { + "id": 690, + "origin_id": 447, + "origin_slot": 1, + "target_id": -20, + "target_slot": 1, + "type": "LATENT" + }, + { + "id": 697, + "origin_id": 329, + "origin_slot": 0, + "target_id": 446, + "target_slot": 4, + "type": "LATENT" + }, + { + "id": 700, + "origin_id": -10, + "origin_slot": 7, + "target_id": 329, + "target_slot": 1, + "type": "LATENT" + }, + { + "id": 703, + "origin_id": -10, + "origin_slot": 1, + "target_id": 331, + "target_slot": 0, + "type": "CONDITIONING" + }, + { + "id": 704, + "origin_id": -10, + "origin_slot": 2, + "target_id": 331, + "target_slot": 1, + "type": "CONDITIONING" + }, + { + "id": 705, + "origin_id": -10, + "origin_slot": 6, + "target_id": 331, + "target_slot": 2, + "type": "LATENT" + }, + { + "id": 706, + "origin_id": -10, + "origin_slot": 4, + "target_id": 328, + "target_slot": 2, + "type": "VAE" + }, + { + "id": 843, + "origin_id": 447, + "origin_slot": 0, + "target_id": -20, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 850, + "origin_id": 331, + "origin_slot": 2, + "target_id": 328, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 983, + "origin_id": 409, + "origin_slot": 0, + "target_id": 448, + "target_slot": 1, + "type": "CONDITIONING" + }, + { + "id": 984, + "origin_id": 409, + "origin_slot": 1, + "target_id": 448, + "target_slot": 2, + "type": "CONDITIONING" + }, + { + "id": 985, + "origin_id": 409, + "origin_slot": 0, + "target_id": -20, + "target_slot": 2, + "type": "CONDITIONING" + }, + { + "id": 986, + "origin_id": 409, + "origin_slot": 1, + "target_id": -20, + "target_slot": 3, + "type": "CONDITIONING" + }, + { + "id": 987, + "origin_id": 409, + "origin_slot": 2, + "target_id": 329, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 988, + "origin_id": 331, + "origin_slot": 0, + "target_id": 409, + "target_slot": 0, + "type": "CONDITIONING" + }, + { + "id": 989, + "origin_id": 331, + "origin_slot": 1, + "target_id": 409, + "target_slot": 1, + "type": "CONDITIONING" + }, + { + "id": 990, + "origin_id": -10, + "origin_slot": 4, + "target_id": 409, + "target_slot": 2, + "type": "VAE" + }, + { + "id": 991, + "origin_id": 328, + "origin_slot": 0, + "target_id": 409, + "target_slot": 3, + "type": "LATENT" + }, + { + "id": 1004, + "origin_id": -10, + "origin_slot": 8, + "target_id": 409, + "target_slot": 4, + "type": "IMAGE" + }, + { + "id": 1035, + "origin_id": -10, + "origin_slot": 9, + "target_id": 328, + "target_slot": 1, + "type": "LATENT_UPSCALE_MODEL" + }, + { + "id": 1125, + "origin_id": -10, + "origin_slot": 10, + "target_id": 409, + "target_slot": 5, + "type": "INT" + } + ], + "extra": { + "workflowRendererVersion": "LG" + } + }, + { + "id": "2979f3eb-1c4c-45ff-8387-f1f4addb72f4", + "version": 1, + "state": { + "lastGroupId": 33, + "lastNodeId": 499, + "lastLinkId": 1201, + "lastRerouteId": 0 + }, + "revision": 0, + "config": {}, + "name": "Process Latents", + "inputNode": { + "id": -10, + "bounding": [ + 2630, + 6303, + 128.998046875, + 120 + ] + }, + "outputNode": { + "id": -20, + "bounding": [ + 3500, + 6303, + 120, + 80 + ] + }, + "inputs": [ + { + "id": "f9d3f39d-4628-4ef2-a464-ffb98c1fb4bc", + "name": "image", + "type": "IMAGE", + "linkIds": [ + 713 + ], + "localized_name": "image", + "pos": [ + 2738.998046875, + 6323 + ] + }, + { + "id": "6c61b79c-c6a2-4cd9-96a7-72073d3603c1", + "name": "length", + "type": "INT", + "linkIds": [ + 708, + 709 + ], + "localized_name": "length", + "label": "length_seconds", + "pos": [ + 2738.998046875, + 6343 + ] + }, + { + "id": "610c02c9-497c-45ac-b603-ea5bd91aec4c", + "name": "audio_vae", + "type": "VAE", + "linkIds": [ + 714 + ], + "pos": [ + 2738.998046875, + 6363 + ] + }, + { + "id": "01e20d2c-0498-4d41-8a62-b867efe06044", + "name": "frame_rate", + "type": "INT", + "linkIds": [ + 1135 + ], + "pos": [ + 2738.998046875, + 6383 + ] + } + ], + "outputs": [ + { + "id": "2cf8b0ee-4e7a-4045-a1ee-bbd4b878068e", + "name": "LATENT", + "type": "LATENT", + "linkIds": [ + 710 + ], + "localized_name": "LATENT", + "label": "video_latent", + "pos": [ + 3520, + 6323 + ] + }, + { + "id": "44e92917-679f-4cc3-aa57-51b4a6ef24a8", + "name": "Latent", + "type": "LATENT", + "linkIds": [ + 705 + ], + "localized_name": "Latent", + "label": "audio_latent", + "pos": [ + 3520, + 6343 + ] + } + ], + "widgets": [], + "nodes": [ + { + "id": 356, + "type": "GetImageSize", + "pos": [ + 3030, + 6210 + ], + "size": [ + 170, + 70 + ], + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [ + { + "localized_name": "image", + "name": "image", + "type": "IMAGE", + "link": 711 + } + ], + "outputs": [ + { + "localized_name": "width", + "name": "width", + "type": "INT", + "links": [ + 706 + ] + }, + { + "localized_name": "height", + "name": "height", + "type": "INT", + "links": [ + 707 + ] + }, + { + "localized_name": "batch_size", + "name": "batch_size", + "type": "INT", + "links": null + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.7.0", + "Node name for S&R": "GetImageSize", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 352, + "type": "EmptyLTXVLatentVideo", + "pos": [ + 3210, + 6210 + ], + "size": [ + 210, + 130 + ], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [ + { + "localized_name": "width", + "name": "width", + "type": "INT", + "widget": { + "name": "width" + }, + "link": 706 + }, + { + "localized_name": "height", + "name": "height", + "type": "INT", + "widget": { + "name": "height" + }, + "link": 707 + }, + { + "localized_name": "length", + "name": "length", + "type": "INT", + "widget": { + "name": "length" + }, + "link": 708 + } + ], + "outputs": [ + { + "localized_name": "LATENT", + "name": "LATENT", + "type": "LATENT", + "links": [ + 710 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.60", + "Node name for S&R": "EmptyLTXVLatentVideo", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [ + 768, + 512, + 1, + 1 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 357, + "type": "ImageScaleBy", + "pos": [ + 2810, + 6210 + ], + "size": [ + 210, + 90 + ], + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [ + { + "localized_name": "image", + "name": "image", + "type": "IMAGE", + "link": 713 + } + ], + "outputs": [ + { + "localized_name": "IMAGE", + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 711 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.5.1", + "Node name for S&R": "ImageScaleBy", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65, + "ue_properties": { + "version": "7.0.1", + "widget_ue_connectable": {} + } + }, + "widgets_values": [ + "bilinear", + 0.5 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 353, + "type": "LTXVEmptyLatentAudio", + "pos": [ + 3220, + 6380 + ], + "size": [ + 210, + 110 + ], + "flags": {}, + "order": 1, + "mode": 0, + "inputs": [ + { + "localized_name": "audio_vae", + "name": "audio_vae", + "type": "VAE", + "link": 714 + }, + { + "localized_name": "frames_number", + "name": "frames_number", + "type": "INT", + "widget": { + "name": "frames_number" + }, + "link": 709 + }, + { + "label": "frame_rate", + "localized_name": "frame_rate", + "name": "frame_rate", + "type": "INT", + "widget": { + "name": "frame_rate" + }, + "link": 1135 + } + ], + "outputs": [ + { + "localized_name": "Latent", + "name": "Latent", + "type": "LATENT", + "links": [ + 705 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.68", + "Node name for S&R": "LTXVEmptyLatentAudio", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [ + 1, + 24, + 1 + ], + "color": "#222", + "bgcolor": "#000" + } + ], + "groups": [], + "links": [ + { + "id": 711, + "origin_id": 357, + "origin_slot": 0, + "target_id": 356, + "target_slot": 0, + "type": "IMAGE" + }, + { + "id": 706, + "origin_id": 356, + "origin_slot": 0, + "target_id": 352, + "target_slot": 0, + "type": "INT" + }, + { + "id": 707, + "origin_id": 356, + "origin_slot": 1, + "target_id": 352, + "target_slot": 1, + "type": "INT" + }, + { + "id": 713, + "origin_id": -10, + "origin_slot": 0, + "target_id": 357, + "target_slot": 0, + "type": "IMAGE" + }, + { + "id": 708, + "origin_id": -10, + "origin_slot": 1, + "target_id": 352, + "target_slot": 2, + "type": "INT" + }, + { + "id": 709, + "origin_id": -10, + "origin_slot": 1, + "target_id": 353, + "target_slot": 1, + "type": "INT" + }, + { + "id": 710, + "origin_id": 352, + "origin_slot": 0, + "target_id": -20, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 705, + "origin_id": 353, + "origin_slot": 0, + "target_id": -20, + "target_slot": 1, + "type": "LATENT" + }, + { + "id": 714, + "origin_id": -10, + "origin_slot": 2, + "target_id": 353, + "target_slot": 0, + "type": "VAE" + }, + { + "id": 1135, + "origin_id": -10, + "origin_slot": 3, + "target_id": 353, + "target_slot": 2, + "type": "INT" + } + ], + "extra": { + "workflowRendererVersion": "LG" + } + }, + { + "id": "958b2c7a-1f75-4a30-84a8-892bddb9e426", + "version": 1, + "state": { + "lastGroupId": 33, + "lastNodeId": 499, + "lastLinkId": 1201, + "lastRerouteId": 0 + }, + "revision": 0, + "config": {}, + "name": "Model Loader", + "inputNode": { + "id": -10, + "bounding": [ + 2340, + 6060, + 140.259765625, + 100 + ] + }, + "outputNode": { + "id": -20, + "bounding": [ + 3570, + 5660, + 187.052734375, + 240 + ] + }, + "inputs": [ + { + "id": "f0ec1006-3b28-4d50-b32e-931ea4500394", + "name": "value", + "type": "STRING", + "linkIds": [ + 1091 + ], + "label": "use custom audio", + "pos": [ + 2460.259765625, + 6080 + ] + }, + { + "id": "1f2a8c23-9530-441b-9848-9efe951534ab", + "name": "value_1", + "type": "INT", + "linkIds": [ + 1167 + ], + "label": "frame_rate", + "pos": [ + 2460.259765625, + 6100 + ] + }, + { + "id": "650e141a-0cdc-4eac-be88-30f5865b298d", + "name": "value_2", + "type": "INT", + "linkIds": [ + 1168 + ], + "label": "length (seconds)", + "pos": [ + 2460.259765625, + 6120 + ] + } + ], + "outputs": [ + { + "id": "a72640aa-8055-45bb-8cbb-f68ec7413515", + "name": "optimized_model", + "type": "MODEL", + "linkIds": [ + 1067 + ], + "pos": [ + 3590, + 5680 + ] + }, + { + "id": "cfb48567-9b75-46f5-8b71-385f3388173c", + "name": "VAE", + "type": "VAE", + "linkIds": [ + 721 + ], + "label": "video_vae", + "pos": [ + 3590, + 5700 + ] + }, + { + "id": "4e095394-8a92-47e4-8993-49fb4ce73bd8", + "name": "VAE_1", + "type": "VAE", + "linkIds": [ + 722 + ], + "label": "audio_vae", + "pos": [ + 3590, + 5720 + ] + }, + { + "id": "8bbe34b1-d094-40a3-9c04-5a19cd7b2af2", + "name": "positive", + "type": "CONDITIONING", + "linkIds": [ + 726 + ], + "pos": [ + 3590, + 5740 + ] + }, + { + "id": "dac0ad69-373b-4e47-b0cd-eb8fd18e1c07", + "name": "negative", + "type": "CONDITIONING", + "linkIds": [ + 727 + ], + "pos": [ + 3590, + 5760 + ] + }, + { + "id": "b4d0868d-86e8-42ba-83fc-44c5324ac35d", + "name": "INT", + "type": "INT", + "linkIds": [ + 729 + ], + "label": "length_seconds", + "pos": [ + 3590, + 5780 + ] + }, + { + "id": "12a111ff-cd34-4b59-bca3-4c5f24626a62", + "name": "NOISE", + "type": "NOISE", + "linkIds": [ + 730 + ], + "label": "noise", + "pos": [ + 3590, + 5800 + ] + }, + { + "id": "9f03af49-218d-4431-a2e9-891d16a8eb05", + "name": "LATENT_UPSCALE_MODEL", + "type": "LATENT_UPSCALE_MODEL", + "linkIds": [ + 1034 + ], + "label": "2x_spatial_upscale_model", + "pos": [ + 3590, + 5820 + ] + }, + { + "id": "6ec59b1a-d1dc-4a6d-8844-18050ea86a5a", + "name": "INT_1", + "type": "INT", + "linkIds": [ + 1103 + ], + "label": "frame_rate", + "pos": [ + 3590, + 5840 + ] + }, + { + "id": "eaf7211d-70e5-4f7a-9465-958ea3e2e594", + "name": "BOOLEAN", + "type": "BOOLEAN", + "linkIds": [ + 1162 + ], + "label": "use custom audio", + "pos": [ + 3590, + 5860 + ] + } + ], + "widgets": [], + "nodes": [ + { + "id": 338, + "type": "VAELoaderKJ", + "pos": [ + 2560, + 5780 + ], + "size": [ + 270, + 110 + ], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [], + "outputs": [ + { + "localized_name": "VAE", + "name": "VAE", + "type": "VAE", + "links": [ + 722 + ] + } + ], + "title": "Audio VAELoader KJ", + "properties": { + "cnr_id": "comfyui-kjnodes", + "ver": "487c7d86a0230aae3d8c0a37d517159b73834f85", + "Node name for S&R": "VAELoaderKJ" + }, + "widgets_values": [ + "LTX23_audio_vae_bf16.safetensors", + "main_device", + "bf16" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 457, + "type": "VAELoaderKJ", + "pos": [ + 2560, + 5630 + ], + "size": [ + 270, + 110 + ], + "flags": {}, + "order": 1, + "mode": 0, + "inputs": [], + "outputs": [ + { + "localized_name": "VAE", + "name": "VAE", + "type": "VAE", + "links": [ + 721 + ] + } + ], + "title": "Video VAELoader KJ", + "properties": { + "cnr_id": "comfyui-kjnodes", + "ver": "1.2.5", + "Node name for S&R": "VAELoaderKJ" + }, + "widgets_values": [ + "LTX23_video_vae_bf16.safetensors", + "main_device", + "bf16" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 443, + "type": "VAELoaderKJ", + "pos": [ + 2560, + 5470 + ], + "size": [ + 270, + 110 + ], + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [], + "outputs": [ + { + "localized_name": "VAE", + "name": "VAE", + "type": "VAE", + "links": [ + 1072 + ] + } + ], + "title": "Tiny VAELoader KJ", + "properties": { + "cnr_id": "comfyui-kjnodes", + "ver": "1.2.5", + "Node name for S&R": "VAELoaderKJ" + }, + "widgets_values": [ + "taeltx2_3.safetensors", + "main_device", + "bf16" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 442, + "type": "LTX2SamplingPreviewOverride", + "pos": [ + 2850, + 5470 + ], + "size": [ + 270, + 100 + ], + "flags": {}, + "order": 9, + "mode": 0, + "inputs": [ + { + "localized_name": "model", + "name": "model", + "type": "MODEL", + "link": 1185 + }, + { + "localized_name": "latent_upscale_model", + "name": "latent_upscale_model", + "shape": 7, + "type": "LATENT_UPSCALE_MODEL", + "link": null + }, + { + "localized_name": "vae", + "name": "vae", + "shape": 7, + "type": "VAE", + "link": 1072 + } + ], + "outputs": [ + { + "localized_name": "MODEL", + "name": "MODEL", + "type": "MODEL", + "links": [ + 1074 + ] + } + ], + "properties": { + "cnr_id": "comfyui-kjnodes", + "ver": "1.3.4", + "Node name for S&R": "LTX2SamplingPreviewOverride" + }, + "widgets_values": [ + 24 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 426, + "type": "CheckpointLoaderSimple", + "pos": [ + 2560, + 5330 + ], + "size": [ + 270, + 100 + ], + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [], + "outputs": [ + { + "localized_name": "MODEL", + "name": "MODEL", + "type": "MODEL", + "links": [ + 1185 + ] + }, + { + "localized_name": "CLIP", + "name": "CLIP", + "type": "CLIP", + "links": null + }, + { + "localized_name": "VAE", + "name": "VAE", + "type": "VAE", + "links": null + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.17.2", + "Node name for S&R": "CheckpointLoaderSimple" + }, + "widgets_values": [ + "ltx-2.3-22b-dev-fp8.safetensors" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 484, + "type": "PrimitiveBoolean", + "pos": [ + 2560, + 6230 + ], + "size": [ + 230, + 60 + ], + "flags": {}, + "order": 4, + "mode": 0, + "inputs": [ + { + "label": "use custom audio", + "localized_name": "value", + "name": "value", + "type": "BOOLEAN", + "widget": { + "name": "value" + }, + "link": null + } + ], + "outputs": [ + { + "localized_name": "BOOLEAN", + "name": "BOOLEAN", + "type": "BOOLEAN", + "links": [ + 1162 + ] + } + ], + "title": "Use Custom Audio", + "properties": { + "cnr_id": "comfy-core", + "ver": "0.17.2", + "Node name for S&R": "PrimitiveBoolean" + }, + "widgets_values": [ + false + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 364, + "type": "RandomNoise", + "pos": [ + 2850, + 5790 + ], + "size": [ + 210, + 90 + ], + "flags": {}, + "order": 5, + "mode": 0, + "inputs": [ + { + "label": "noise_seed", + "localized_name": "noise_seed", + "name": "noise_seed", + "type": "INT", + "widget": { + "name": "noise_seed" + }, + "link": null + } + ], + "outputs": [ + { + "localized_name": "NOISE", + "name": "NOISE", + "type": "NOISE", + "links": [ + 730 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.56", + "Node name for S&R": "RandomNoise", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [ + 0, + "fixed" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 470, + "type": "CM_IntToFloat", + "pos": [ + 3080, + 5820 + ], + "size": [ + 210, + 60 + ], + "flags": {}, + "order": 17, + "mode": 0, + "inputs": [ + { + "localized_name": "a", + "name": "a", + "type": "INT", + "widget": { + "name": "a" + }, + "link": 1133 + } + ], + "outputs": [ + { + "localized_name": "FLOAT", + "name": "FLOAT", + "type": "FLOAT", + "links": [ + 1134 + ] + } + ], + "title": "frame_rate_covert", + "properties": { + "cnr_id": "ComfyMath", + "ver": "c01177221c31b8e5fbc062778fc8254aeb541638", + "Node name for S&R": "CM_IntToFloat" + }, + "widgets_values": [ + 0 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 467, + "type": "PrimitiveInt", + "pos": [ + 2850, + 5640 + ], + "size": [ + 210, + 90 + ], + "flags": {}, + "order": 16, + "mode": 0, + "inputs": [ + { + "localized_name": "value", + "name": "value", + "type": "INT", + "widget": { + "name": "value" + }, + "link": 1167 + } + ], + "outputs": [ + { + "localized_name": "INT", + "name": "INT", + "type": "INT", + "links": [ + 1102, + 1103, + 1133 + ] + } + ], + "title": "frame_rate", + "properties": { + "cnr_id": "comfy-core", + "ver": "0.19.3", + "Node name for S&R": "PrimitiveInt" + }, + "widgets_values": [ + 24, + "fixed" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 365, + "type": "07609e03-45f4-49c7-a233-a933ebf95094", + "pos": [ + 3070, + 5640 + ], + "size": [ + 210, + 90 + ], + "flags": {}, + "order": 13, + "mode": 0, + "inputs": [ + { + "label": "seconds", + "name": "value", + "type": "INT", + "widget": { + "name": "value" + }, + "link": 1168 + }, + { + "label": "frame_rate", + "name": "b", + "type": "INT", + "widget": { + "name": "b" + }, + "link": 1102 + } + ], + "outputs": [ + { + "localized_name": "INT", + "name": "INT", + "type": "INT", + "links": [ + 729 + ] + } + ], + "properties": { + "proxyWidgets": [ + [ + "139", + "value" + ], + [ + "137", + "b" + ] + ], + "cnr_id": "comfy-core", + "ver": "0.16.4" + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 413, + "type": "LTXAVTextEncoderLoader", + "pos": [ + 2840, + 5940 + ], + "size": [ + 320, + 130 + ], + "flags": {}, + "order": 6, + "mode": 0, + "inputs": [], + "outputs": [ + { + "localized_name": "CLIP", + "name": "CLIP", + "type": "CLIP", + "links": [ + 1187 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.14.1", + "Node name for S&R": "LTXAVTextEncoderLoader" + }, + "widgets_values": [ + "gemma_3_12B_it_fp4_mixed.safetensors", + "ltx-2.3-22b-dev-fp8.safetensors", + "default" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 464, + "type": "PrimitiveStringMultiline", + "pos": [ + 2560, + 5940 + ], + "size": [ + 270, + 130 + ], + "flags": {}, + "order": 15, + "mode": 0, + "inputs": [ + { + "localized_name": "value", + "name": "value", + "type": "STRING", + "widget": { + "name": "value" + }, + "link": 1091 + } + ], + "outputs": [ + { + "localized_name": "STRING", + "name": "STRING", + "type": "STRING", + "links": [ + 1090 + ] + } + ], + "title": "Prompt", + "properties": { + "cnr_id": "comfy-core", + "ver": "0.19.3", + "Node name for S&R": "PrimitiveStringMultiline" + }, + "widgets_values": [ + "" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 419, + "type": "LatentUpscaleModelLoader", + "pos": [ + 2560, + 6120 + ], + "size": [ + 270, + 60 + ], + "flags": {}, + "order": 7, + "mode": 0, + "inputs": [], + "outputs": [ + { + "localized_name": "LATENT_UPSCALE_MODEL", + "name": "LATENT_UPSCALE_MODEL", + "type": "LATENT_UPSCALE_MODEL", + "links": [ + 1034 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.16.4", + "Node name for S&R": "LatentUpscaleModelLoader" + }, + "widgets_values": [ + "ltx-2.3-spatial-upscaler-x2-1.1.safetensors" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 430, + "type": "CLIPTextEncode", + "pos": [ + 2850, + 6110 + ], + "size": [ + 280, + 90 + ], + "flags": {}, + "order": 14, + "mode": 0, + "inputs": [ + { + "localized_name": "clip", + "name": "clip", + "type": "CLIP", + "link": 1187 + }, + { + "localized_name": "text", + "name": "text", + "type": "STRING", + "widget": { + "name": "text" + }, + "link": 1090 + } + ], + "outputs": [ + { + "localized_name": "CONDITIONING", + "name": "CONDITIONING", + "type": "CONDITIONING", + "slot_index": 0, + "links": [ + 1186, + 1188 + ] + } + ], + "title": "CLIP Text Encode (Positive Prompt)", + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.28", + "Node name for S&R": "CLIPTextEncode" + }, + "widgets_values": [ + "" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 361, + "type": "ConditioningZeroOut", + "pos": [ + 2850, + 6240 + ], + "size": [ + 210, + 30 + ], + "flags": {}, + "order": 11, + "mode": 0, + "inputs": [ + { + "localized_name": "conditioning", + "name": "conditioning", + "type": "CONDITIONING", + "link": 1186 + } + ], + "outputs": [ + { + "localized_name": "CONDITIONING", + "name": "CONDITIONING", + "type": "CONDITIONING", + "links": [ + 725 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.7.0", + "Node name for S&R": "ConditioningZeroOut", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 363, + "type": "LTXVConditioning", + "pos": [ + 3170, + 5940 + ], + "size": [ + 210, + 80 + ], + "flags": {}, + "order": 12, + "mode": 0, + "inputs": [ + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "link": 1188 + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "link": 725 + }, + { + "localized_name": "frame_rate", + "name": "frame_rate", + "type": "FLOAT", + "widget": { + "name": "frame_rate" + }, + "link": 1134 + } + ], + "outputs": [ + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "links": [ + 726 + ] + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "links": [ + 727 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.56", + "Node name for S&R": "LTXVConditioning", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [ + 24 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 429, + "type": "LoraLoaderModelOnly", + "pos": [ + 3140, + 5480 + ], + "size": [ + 250, + 90 + ], + "flags": {}, + "order": 10, + "mode": 0, + "inputs": [ + { + "localized_name": "model", + "name": "model", + "type": "MODEL", + "link": 1074 + } + ], + "outputs": [ + { + "localized_name": "MODEL", + "name": "MODEL", + "type": "MODEL", + "links": [ + 1067 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.75", + "Node name for S&R": "LoraLoaderModelOnly", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65, + "models": [ + { + "name": "ltx-2.3-22b-distilled-lora-384.safetensors", + "url": "https://huggingface.co/Lightricks/LTX-2.3/resolve/main/ltx-2.3-22b-distilled-lora-384.safetensors", + "directory": "loras" + } + ] + }, + "widgets_values": [ + "ltx2\\ltx-2.3-22b-distilled-lora-dynamic_fro09_avg_rank_105_bf16.safetensors", + 0.5 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 496, + "type": "MarkdownNote", + "pos": [ + 2180, + 5510 + ], + "size": [ + 350, + 380 + ], + "flags": {}, + "order": 8, + "mode": 0, + "inputs": [], + "outputs": [], + "title": "FAQ", + "properties": {}, + "widgets_values": [ + "# FAQ\n\n**How do I set the length of the video?**\n\nYou can set the length of the video in the Model Loader Subgraph →\n\n**I keep getting errors when running this workflow!**\n\nDouble check that all of the correct models loaded. This is using the newest version of 2x upscaler (v1.1). You can change it to the older one under the Model Loader.\n\n**Where do I download these models??**\n\nThere is a good list of models and download links here: \n\nAlso the latest v1.1 spatial upscaler can be found here: \n\nThe tiny vae (taeltx2_3.safetensors) is found here:\n\n\n**I want to use my own models!**\n\nYou can change the models in the Model Loader Subgraph →\n\n\n\n\n" + ], + "color": "#323", + "bgcolor": "#535" + } + ], + "groups": [], + "links": [ + { + "id": 721, + "origin_id": 457, + "origin_slot": 0, + "target_id": -20, + "target_slot": 1, + "type": "VAE" + }, + { + "id": 722, + "origin_id": 338, + "origin_slot": 0, + "target_id": -20, + "target_slot": 2, + "type": "VAE" + }, + { + "id": 725, + "origin_id": 361, + "origin_slot": 0, + "target_id": 363, + "target_slot": 1, + "type": "CONDITIONING" + }, + { + "id": 726, + "origin_id": 363, + "origin_slot": 0, + "target_id": -20, + "target_slot": 3, + "type": "CONDITIONING" + }, + { + "id": 727, + "origin_id": 363, + "origin_slot": 1, + "target_id": -20, + "target_slot": 4, + "type": "CONDITIONING" + }, + { + "id": 729, + "origin_id": 365, + "origin_slot": 0, + "target_id": -20, + "target_slot": 5, + "type": "INT" + }, + { + "id": 730, + "origin_id": 364, + "origin_slot": 0, + "target_id": -20, + "target_slot": 6, + "type": "NOISE" + }, + { + "id": 1034, + "origin_id": 419, + "origin_slot": 0, + "target_id": -20, + "target_slot": 7, + "type": "LATENT_UPSCALE_MODEL" + }, + { + "id": 1067, + "origin_id": 429, + "origin_slot": 0, + "target_id": -20, + "target_slot": 0, + "type": "MODEL" + }, + { + "id": 1072, + "origin_id": 443, + "origin_slot": 0, + "target_id": 442, + "target_slot": 2, + "type": "VAE" + }, + { + "id": 1074, + "origin_id": 442, + "origin_slot": 0, + "target_id": 429, + "target_slot": 0, + "type": "MODEL" + }, + { + "id": 1090, + "origin_id": 464, + "origin_slot": 0, + "target_id": 430, + "target_slot": 1, + "type": "STRING" + }, + { + "id": 1091, + "origin_id": -10, + "origin_slot": 0, + "target_id": 464, + "target_slot": 0, + "type": "STRING" + }, + { + "id": 1102, + "origin_id": 467, + "origin_slot": 0, + "target_id": 365, + "target_slot": 1, + "type": "INT" + }, + { + "id": 1103, + "origin_id": 467, + "origin_slot": 0, + "target_id": -20, + "target_slot": 8, + "type": "INT" + }, + { + "id": 1133, + "origin_id": 467, + "origin_slot": 0, + "target_id": 470, + "target_slot": 0, + "type": "INT" + }, + { + "id": 1134, + "origin_id": 470, + "origin_slot": 0, + "target_id": 363, + "target_slot": 2, + "type": "FLOAT" + }, + { + "id": 1162, + "origin_id": 484, + "origin_slot": 0, + "target_id": -20, + "target_slot": 9, + "type": "BOOLEAN" + }, + { + "id": 1167, + "origin_id": -10, + "origin_slot": 1, + "target_id": 467, + "target_slot": 0, + "type": "INT" + }, + { + "id": 1168, + "origin_id": -10, + "origin_slot": 2, + "target_id": 365, + "target_slot": 0, + "type": "INT" + }, + { + "id": 1185, + "origin_id": 426, + "origin_slot": 0, + "target_id": 442, + "target_slot": 0, + "type": "MODEL" + }, + { + "id": 1186, + "origin_id": 430, + "origin_slot": 0, + "target_id": 361, + "target_slot": 0, + "type": "CONDITIONING" + }, + { + "id": 1187, + "origin_id": 413, + "origin_slot": 0, + "target_id": 430, + "target_slot": 0, + "type": "CLIP" + }, + { + "id": 1188, + "origin_id": 430, + "origin_slot": 0, + "target_id": 363, + "target_slot": 0, + "type": "CONDITIONING" + } + ], + "extra": { + "workflowRendererVersion": "LG" + } + }, + { + "id": "07609e03-45f4-49c7-a233-a933ebf95094", + "version": 1, + "state": { + "lastGroupId": 33, + "lastNodeId": 499, + "lastLinkId": 1201, + "lastRerouteId": 0 + }, + "revision": 0, + "config": {}, + "name": "length (seconds)", + "inputNode": { + "id": -10, + "bounding": [ + -662.1270448096031, + 4310.82566166648, + 120, + 80 + ] + }, + "outputNode": { + "id": -20, + "bounding": [ + 255.79328565979654, + 4300.82566166648, + 120, + 60 + ] + }, + "inputs": [ + { + "id": "df88c383-3299-420f-8c75-fd0d6a39a9af", + "name": "value", + "type": "INT", + "linkIds": [ + 1071 + ], + "label": "seconds", + "pos": [ + -562.1270448096031, + 4330.82566166648 + ] + }, + { + "id": "633c6862-8fcb-44af-aaa4-45a1e233c4d3", + "name": "b", + "type": "INT", + "linkIds": [ + 1095 + ], + "label": "frame_rate", + "pos": [ + -562.1270448096031, + 4350.82566166648 + ] + } + ], + "outputs": [ + { + "id": "9b5a4dc0-bb87-45a3-bd3d-551edc18c371", + "name": "INT", + "type": "INT", + "linkIds": [ + 359 + ], + "localized_name": "INT", + "pos": [ + 275.79328565979654, + 4320.82566166648 + ] + } + ], + "widgets": [], + "nodes": [ + { + "id": 137, + "type": "easy mathInt", + "pos": [ + -240, + 4300 + ], + "size": [ + 210, + 110 + ], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [ + { + "localized_name": "a", + "name": "a", + "type": "INT", + "widget": { + "name": "a" + }, + "link": 360 + }, + { + "localized_name": "b", + "name": "b", + "type": "INT", + "widget": { + "name": "b" + }, + "link": 1095 + } + ], + "outputs": [ + { + "localized_name": "INT", + "name": "INT", + "type": "INT", + "links": [ + 358 + ] + } + ], + "properties": { + "cnr_id": "comfyui-easy-use", + "ver": "1.3.4", + "Node name for S&R": "easy mathInt" + }, + "widgets_values": [ + 0, + 24, + "multiply" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 139, + "type": "PrimitiveInt", + "pos": [ + -480, + 4310 + ], + "size": [ + 210, + 90 + ], + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [ + { + "localized_name": "value", + "name": "value", + "type": "INT", + "widget": { + "name": "value" + }, + "link": 1071 + } + ], + "outputs": [ + { + "localized_name": "INT", + "name": "INT", + "type": "INT", + "links": [ + 360 + ] + } + ], + "title": "seconds", + "properties": { + "cnr_id": "comfy-core", + "ver": "0.16.4", + "Node name for S&R": "PrimitiveInt" + }, + "widgets_values": [ + 7, + "fixed" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 138, + "type": "easy mathInt", + "pos": [ + -10, + 4300 + ], + "size": [ + 210, + 110 + ], + "flags": {}, + "order": 1, + "mode": 0, + "inputs": [ + { + "localized_name": "a", + "name": "a", + "type": "INT", + "widget": { + "name": "a" + }, + "link": 358 + } + ], + "outputs": [ + { + "localized_name": "INT", + "name": "INT", + "type": "INT", + "links": [ + 359 + ] + } + ], + "properties": { + "cnr_id": "comfyui-easy-use", + "ver": "1.3.4", + "Node name for S&R": "easy mathInt" + }, + "widgets_values": [ + 0, + 1, + "add" + ], + "color": "#222", + "bgcolor": "#000" + } + ], + "groups": [], + "links": [ + { + "id": 360, + "origin_id": 139, + "origin_slot": 0, + "target_id": 137, + "target_slot": 0, + "type": "INT" + }, + { + "id": 358, + "origin_id": 137, + "origin_slot": 0, + "target_id": 138, + "target_slot": 0, + "type": "INT" + }, + { + "id": 359, + "origin_id": 138, + "origin_slot": 0, + "target_id": -20, + "target_slot": 0, + "type": "INT" + }, + { + "id": 1071, + "origin_id": -10, + "origin_slot": 0, + "target_id": 139, + "target_slot": 0, + "type": "INT" + }, + { + "id": 1095, + "origin_id": -10, + "origin_slot": 1, + "target_id": 137, + "target_slot": 1, + "type": "INT" + } + ], + "extra": { + "workflowRendererVersion": "LG" + } + }, + { + "id": "360d64f7-96cd-42a6-9b13-6eb0387e17d9", + "version": 1, + "state": { + "lastGroupId": 33, + "lastNodeId": 499, + "lastLinkId": 1201, + "lastRerouteId": 0 + }, + "revision": 0, + "config": {}, + "name": "Generate Video", + "inputNode": { + "id": -10, + "bounding": [ + 4300, + 5930, + 187.052734375, + 240 + ] + }, + "outputNode": { + "id": -20, + "bounding": [ + 5470, + 5960, + 120, + 60 + ] + }, + "inputs": [ + { + "id": "f3d52cbc-fafe-46a6-978c-2eaff2c185c3", + "name": "model", + "type": "MODEL", + "linkIds": [ + 3, + 14 + ], + "pos": [ + 4467.052734375, + 5950 + ] + }, + { + "id": "d0e8f1b7-b19b-49cd-8a2b-aa4a639857b3", + "name": "positive", + "type": "CONDITIONING", + "linkIds": [ + 4, + 15, + 871 + ], + "pos": [ + 4467.052734375, + 5970 + ] + }, + { + "id": "36e3ff32-f9b2-41d9-94de-9d8d9a91f1a6", + "name": "negative", + "type": "CONDITIONING", + "linkIds": [ + 5, + 16, + 872 + ], + "pos": [ + 4467.052734375, + 5990 + ] + }, + { + "id": "3f2a7956-c7c2-4478-99ad-2487644cbbc2", + "name": "latent_image", + "type": "LATENT", + "linkIds": [ + 6 + ], + "pos": [ + 4467.052734375, + 6010 + ] + }, + { + "id": "cc2edb71-c53a-4056-8a85-5d8199e79c72", + "name": "noise", + "type": "NOISE", + "linkIds": [ + 7, + 17 + ], + "pos": [ + 4467.052734375, + 6030 + ] + }, + { + "id": "d162c9e7-24cc-4421-a6fb-d253bfae8883", + "name": "vae", + "type": "VAE", + "linkIds": [ + 32 + ], + "label": "video_vae", + "pos": [ + 4467.052734375, + 6050 + ] + }, + { + "id": "e9cd552e-a79c-4b6e-8508-4ad3a9f4218b", + "name": "image", + "type": "IMAGE", + "linkIds": [ + 29 + ], + "pos": [ + 4467.052734375, + 6070 + ] + }, + { + "id": "45629d5b-0676-4430-aa3d-8b0cf516de0a", + "name": "multi_input", + "type": "IMAGE", + "linkIds": [ + 1006 + ], + "pos": [ + 4467.052734375, + 6090 + ] + }, + { + "id": "899832ca-633e-4c2b-ac1b-303e21cab106", + "name": "frame_rate", + "type": "INT", + "linkIds": [ + 1198 + ], + "pos": [ + 4467.052734375, + 6110 + ] + }, + { + "id": "8cdaef23-307e-4e12-8b8f-366ef4b8e2b7", + "name": "upscale_model", + "type": "LATENT_UPSCALE_MODEL", + "linkIds": [ + 1115 + ], + "label": "2x_spatial_upscale_model", + "pos": [ + 4467.052734375, + 6130 + ] + } + ], + "outputs": [ + { + "id": "2fae6e6d-6667-450e-933b-b16e88c7968f", + "name": "output", + "type": "LATENT", + "linkIds": [ + 765 + ], + "label": "av_output", + "pos": [ + 5490, + 5980 + ] + } + ], + "widgets": [], + "nodes": [ + { + "id": 371, + "type": "LTXVConcatAVLatent", + "pos": [ + 5250, + 5970 + ], + "size": [ + 190, + 50 + ], + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [ + { + "localized_name": "video_latent", + "name": "video_latent", + "type": "LATENT", + "link": 852 + }, + { + "localized_name": "audio_latent", + "name": "audio_latent", + "type": "LATENT", + "link": 1194 + } + ], + "outputs": [ + { + "localized_name": "latent", + "name": "latent", + "type": "LATENT", + "links": [ + 765 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.17.2", + "Node name for S&R": "LTXVConcatAVLatent" + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 2, + "type": "afa29944-eb58-40a5-8e76-484f2503fa0c", + "pos": [ + 4530, + 5950 + ], + "size": [ + 190, + 280 + ], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 3 + }, + { + "name": "positive", + "type": "CONDITIONING", + "link": 4 + }, + { + "name": "negative", + "type": "CONDITIONING", + "link": 5 + }, + { + "name": "latent_image", + "type": "LATENT", + "link": 6 + }, + { + "name": "noise", + "type": "NOISE", + "link": 7 + } + ], + "outputs": [ + { + "name": "video_latent", + "type": "LATENT", + "links": [ + 837 + ] + }, + { + "name": "audio_latent", + "type": "LATENT", + "links": [ + 794 + ] + } + ], + "properties": { + "proxyWidgets": [], + "cnr_id": "comfy-core", + "ver": "0.17.2" + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 383, + "type": "LTXVCropGuides", + "pos": [ + 4970, + 5950 + ], + "size": [ + 260, + 70 + ], + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [ + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "link": 871 + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "link": 872 + }, + { + "localized_name": "latent", + "name": "latent", + "type": "LATENT", + "link": 1193 + } + ], + "outputs": [ + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "links": null + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "links": null + }, + { + "localized_name": "latent", + "name": "latent", + "type": "LATENT", + "links": [ + 852 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.17.2", + "Node name for S&R": "LTXVCropGuides" + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 6, + "type": "ce8f7842-cb03-41b4-9b1e-8d032b2a4e72", + "pos": [ + 4740, + 5950 + ], + "size": [ + 210, + 280 + ], + "flags": {}, + "order": 1, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 14 + }, + { + "name": "positive", + "type": "CONDITIONING", + "link": 15 + }, + { + "name": "negative", + "type": "CONDITIONING", + "link": 16 + }, + { + "name": "noise", + "type": "NOISE", + "link": 17 + }, + { + "label": "video_vae", + "name": "vae", + "type": "VAE", + "link": 32 + }, + { + "name": "image", + "type": "IMAGE", + "link": 29 + }, + { + "label": "video_latent", + "name": "samples", + "type": "LATENT", + "link": 837 + }, + { + "name": "audio_latent", + "type": "LATENT", + "link": 794 + }, + { + "name": "multi_input", + "type": "IMAGE", + "link": 1006 + }, + { + "name": "upscale_model", + "type": "LATENT_UPSCALE_MODEL", + "link": 1115 + }, + { + "name": "frame_rate", + "type": "INT", + "widget": { + "name": "frame_rate" + }, + "link": 1198 + } + ], + "outputs": [ + { + "name": "video_latent", + "type": "LATENT", + "links": [ + 1193 + ] + }, + { + "name": "audio_latent", + "type": "LATENT", + "links": [ + 1194 + ] + }, + { + "name": "positive", + "type": "CONDITIONING", + "links": [ + 855 + ] + }, + { + "name": "negative", + "type": "CONDITIONING", + "links": [ + 856 + ] + } + ], + "properties": { + "proxyWidgets": [ + [ + "409", + "frame_rate" + ] + ], + "cnr_id": "comfy-core", + "ver": "0.17.2" + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + } + ], + "groups": [], + "links": [ + { + "id": 3, + "origin_id": -10, + "origin_slot": 0, + "target_id": 2, + "target_slot": 0, + "type": "MODEL" + }, + { + "id": 14, + "origin_id": -10, + "origin_slot": 0, + "target_id": 6, + "target_slot": 0, + "type": "MODEL" + }, + { + "id": 4, + "origin_id": -10, + "origin_slot": 1, + "target_id": 2, + "target_slot": 1, + "type": "CONDITIONING" + }, + { + "id": 15, + "origin_id": -10, + "origin_slot": 1, + "target_id": 6, + "target_slot": 1, + "type": "CONDITIONING" + }, + { + "id": 5, + "origin_id": -10, + "origin_slot": 2, + "target_id": 2, + "target_slot": 2, + "type": "CONDITIONING" + }, + { + "id": 16, + "origin_id": -10, + "origin_slot": 2, + "target_id": 6, + "target_slot": 2, + "type": "CONDITIONING" + }, + { + "id": 6, + "origin_id": -10, + "origin_slot": 3, + "target_id": 2, + "target_slot": 3, + "type": "LATENT" + }, + { + "id": 7, + "origin_id": -10, + "origin_slot": 4, + "target_id": 2, + "target_slot": 4, + "type": "NOISE" + }, + { + "id": 17, + "origin_id": -10, + "origin_slot": 4, + "target_id": 6, + "target_slot": 3, + "type": "NOISE" + }, + { + "id": 32, + "origin_id": -10, + "origin_slot": 5, + "target_id": 6, + "target_slot": 4, + "type": "VAE" + }, + { + "id": 29, + "origin_id": -10, + "origin_slot": 6, + "target_id": 6, + "target_slot": 5, + "type": "IMAGE" + }, + { + "id": 765, + "origin_id": 371, + "origin_slot": 0, + "target_id": -20, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 794, + "origin_id": 2, + "origin_slot": 1, + "target_id": 6, + "target_slot": 7, + "type": "LATENT" + }, + { + "id": 837, + "origin_id": 2, + "origin_slot": 0, + "target_id": 6, + "target_slot": 6, + "type": "LATENT" + }, + { + "id": 852, + "origin_id": 383, + "origin_slot": 2, + "target_id": 371, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 871, + "origin_id": -10, + "origin_slot": 1, + "target_id": 383, + "target_slot": 0, + "type": "CONDITIONING" + }, + { + "id": 872, + "origin_id": -10, + "origin_slot": 2, + "target_id": 383, + "target_slot": 1, + "type": "CONDITIONING" + }, + { + "id": 1006, + "origin_id": -10, + "origin_slot": 7, + "target_id": 6, + "target_slot": 8, + "type": "IMAGE" + }, + { + "id": 1115, + "origin_id": -10, + "origin_slot": 9, + "target_id": 6, + "target_slot": 9, + "type": "LATENT_UPSCALE_MODEL" + }, + { + "id": 1193, + "origin_id": 6, + "origin_slot": 0, + "target_id": 383, + "target_slot": 2, + "type": "LATENT" + }, + { + "id": 1194, + "origin_id": 6, + "origin_slot": 1, + "target_id": 371, + "target_slot": 1, + "type": "LATENT" + }, + { + "id": 1198, + "origin_id": -10, + "origin_slot": 8, + "target_id": 6, + "target_slot": 10, + "type": "INT" + } + ], + "extra": { + "workflowRendererVersion": "LG" + } + }, + { + "id": "e8ae487a-31b9-46cf-be65-76e634e0bee1", + "version": 1, + "state": { + "lastGroupId": 33, + "lastNodeId": 499, + "lastLinkId": 1201, + "lastRerouteId": 0 + }, + "revision": 0, + "config": {}, + "name": "Custom Audio", + "inputNode": { + "id": -10, + "bounding": [ + 3590, + 6700, + 145.544921875, + 140 + ] + }, + "outputNode": { + "id": -20, + "bounding": [ + 4760, + 6685, + 120, + 60 + ] + }, + "inputs": [ + { + "id": "2fcd3481-9803-435d-9cb3-aba0a6a4deb0", + "name": "audio", + "type": "AUDIO", + "linkIds": [ + 1154 + ], + "pos": [ + 3715.544921875, + 6720 + ] + }, + { + "id": "cb8ce3ad-67c5-406f-bf48-a85faa47efb3", + "name": "audio_vae", + "type": "VAE", + "linkIds": [ + 1155 + ], + "pos": [ + 3715.544921875, + 6740 + ] + }, + { + "id": "49e68ab3-a9ad-4320-930b-ef8e4644fca5", + "name": "image", + "type": "IMAGE", + "linkIds": [ + 1156 + ], + "pos": [ + 3715.544921875, + 6760 + ] + }, + { + "id": "dc47eb1c-66f8-4208-a52f-1fc1dc88e38c", + "name": "value", + "type": "BOOLEAN", + "linkIds": [ + 1161 + ], + "label": "Use Custom Audio", + "pos": [ + 3715.544921875, + 6780 + ] + }, + { + "id": "39bd2433-03a9-45d0-bd12-1cbfdd25563d", + "name": "on_false", + "type": "*", + "linkIds": [ + 1165 + ], + "label": "audio_latent", + "pos": [ + 3715.544921875, + 6800 + ] + } + ], + "outputs": [ + { + "id": "7d1d8ff4-39f0-4c96-b7c1-48449c842c86", + "name": "*", + "type": "*", + "linkIds": [ + 1147 + ], + "localized_name": "*", + "pos": [ + 4780, + 6705 + ] + } + ], + "widgets": [], + "nodes": [ + { + "id": 471, + "type": "SetLatentNoiseMask", + "pos": [ + 4240, + 6690 + ], + "size": [ + 210, + 60 + ], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [ + { + "localized_name": "samples", + "name": "samples", + "type": "LATENT", + "link": 1136 + }, + { + "localized_name": "mask", + "name": "mask", + "type": "MASK", + "link": 1137 + } + ], + "outputs": [ + { + "localized_name": "LATENT", + "name": "LATENT", + "type": "LATENT", + "links": [ + 1144 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.7.0", + "Node name for S&R": "SetLatentNoiseMask", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65, + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 472, + "type": "SolidMask", + "pos": [ + 3970, + 6760 + ], + "size": [ + 240, + 110 + ], + "flags": { + "collapsed": false + }, + "order": 1, + "mode": 0, + "inputs": [ + { + "localized_name": "width", + "name": "width", + "type": "INT", + "widget": { + "name": "width" + }, + "link": 1138 + }, + { + "localized_name": "height", + "name": "height", + "type": "INT", + "widget": { + "name": "height" + }, + "link": 1139 + } + ], + "outputs": [ + { + "localized_name": "MASK", + "name": "MASK", + "type": "MASK", + "links": [ + 1137 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.7.0", + "Node name for S&R": "SolidMask", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65, + "ue_properties": { + "widget_ue_connectable": { + "width": true, + "height": true + }, + "version": "7.5.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + 0, + 1024, + 1024 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 473, + "type": "LTXVAudioVAEEncode", + "pos": [ + 3960, + 6660 + ], + "size": [ + 250, + 50 + ], + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [ + { + "localized_name": "audio", + "name": "audio", + "type": "AUDIO", + "link": 1154 + }, + { + "label": "Audio VAE", + "localized_name": "audio_vae", + "name": "audio_vae", + "type": "VAE", + "link": 1155 + } + ], + "outputs": [ + { + "localized_name": "Audio Latent", + "name": "Audio Latent", + "type": "LATENT", + "links": [ + 1136 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.16.3", + "Node name for S&R": "LTXVAudioVAEEncode", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 474, + "type": "GetImageSize", + "pos": [ + 3770, + 6770 + ], + "size": [ + 180, + 70 + ], + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [ + { + "localized_name": "image", + "name": "image", + "type": "IMAGE", + "link": 1156 + } + ], + "outputs": [ + { + "localized_name": "width", + "name": "width", + "type": "INT", + "links": [ + 1138 + ] + }, + { + "localized_name": "height", + "name": "height", + "type": "INT", + "links": [ + 1139 + ] + }, + { + "localized_name": "batch_size", + "name": "batch_size", + "type": "INT", + "links": null + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.17.2", + "Node name for S&R": "GetImageSize" + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 482, + "type": "PrimitiveBoolean", + "pos": [ + 4470, + 6580 + ], + "size": [ + 230, + 60 + ], + "flags": {}, + "order": 5, + "mode": 0, + "inputs": [ + { + "localized_name": "value", + "name": "value", + "type": "BOOLEAN", + "widget": { + "name": "value" + }, + "link": 1161 + } + ], + "outputs": [ + { + "localized_name": "BOOLEAN", + "name": "BOOLEAN", + "type": "BOOLEAN", + "links": [ + 1160 + ] + } + ], + "title": "Use Custom Audio", + "properties": { + "cnr_id": "comfy-core", + "ver": "0.17.2", + "Node name for S&R": "PrimitiveBoolean" + }, + "widgets_values": [ + false + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 478, + "type": "LazySwitchKJ", + "pos": [ + 4470, + 6690 + ], + "size": [ + 270, + 80 + ], + "flags": {}, + "order": 4, + "mode": 0, + "inputs": [ + { + "localized_name": "on_false", + "name": "on_false", + "type": "*", + "link": 1165 + }, + { + "localized_name": "on_true", + "name": "on_true", + "type": "*", + "link": 1144 + }, + { + "localized_name": "switch", + "name": "switch", + "type": "BOOLEAN", + "widget": { + "name": "switch" + }, + "link": 1160 + } + ], + "outputs": [ + { + "localized_name": "*", + "name": "*", + "type": "*", + "links": [ + 1147 + ] + } + ], + "properties": { + "cnr_id": "comfyui-kjnodes", + "ver": "1.3.4", + "Node name for S&R": "LazySwitchKJ" + }, + "widgets_values": [ + true + ], + "color": "#222", + "bgcolor": "#000" + } + ], + "groups": [], + "links": [ + { + "id": 1136, + "origin_id": 473, + "origin_slot": 0, + "target_id": 471, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 1137, + "origin_id": 472, + "origin_slot": 0, + "target_id": 471, + "target_slot": 1, + "type": "MASK" + }, + { + "id": 1138, + "origin_id": 474, + "origin_slot": 0, + "target_id": 472, + "target_slot": 0, + "type": "INT" + }, + { + "id": 1139, + "origin_id": 474, + "origin_slot": 1, + "target_id": 472, + "target_slot": 1, + "type": "INT" + }, + { + "id": 1144, + "origin_id": 471, + "origin_slot": 0, + "target_id": 478, + "target_slot": 1, + "type": "LATENT" + }, + { + "id": 1147, + "origin_id": 478, + "origin_slot": 0, + "target_id": -20, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 1154, + "origin_id": -10, + "origin_slot": 0, + "target_id": 473, + "target_slot": 0, + "type": "AUDIO" + }, + { + "id": 1155, + "origin_id": -10, + "origin_slot": 1, + "target_id": 473, + "target_slot": 1, + "type": "VAE" + }, + { + "id": 1156, + "origin_id": -10, + "origin_slot": 2, + "target_id": 474, + "target_slot": 0, + "type": "IMAGE" + }, + { + "id": 1160, + "origin_id": 482, + "origin_slot": 0, + "target_id": 478, + "target_slot": 2, + "type": "BOOLEAN" + }, + { + "id": 1161, + "origin_id": -10, + "origin_slot": 3, + "target_id": 482, + "target_slot": 0, + "type": "BOOLEAN" + }, + { + "id": 1165, + "origin_id": -10, + "origin_slot": 4, + "target_id": 478, + "target_slot": 0, + "type": "*" + } + ], + "extra": {} + } + ] + }, + "config": {}, + "extra": { + "frontendVersion": "1.42.14", + "workflowRendererVersion": "LG", + "VHS_latentpreview": false, + "VHS_latentpreviewrate": 0, + "VHS_MetadataImage": true, + "VHS_KeepIntermediate": true, + "ds": { + "scale": 0.6830134553650705, + "offset": [ + -2314.684986327, + -5200.910351747899 + ] + } + }, + "version": 0.4 } \ No newline at end of file diff --git a/example_workflows/LTX I2V First Last Frame 2 Stage Workflow v6.json b/example_workflows/LTX I2V First Last Frame 2 Stage Workflow v6.json index 4aa02ef..0cd1596 100644 --- a/example_workflows/LTX I2V First Last Frame 2 Stage Workflow v6.json +++ b/example_workflows/LTX I2V First Last Frame 2 Stage Workflow v6.json @@ -1,7915 +1,7915 @@ -{ - "id": "a41dc75e-fc80-4307-897e-f3118e4fa21b", - "revision": 0, - "last_node_id": 443, - "last_link_id": 1080, - "nodes": [ - { - "id": 4, - "type": "LTXVConcatAVLatent", - "pos": [ - 4180, - 6090 - ], - "size": [ - 210, - 60 - ], - "flags": {}, - "order": 9, - "mode": 0, - "inputs": [ - { - "name": "video_latent", - "type": "LATENT", - "link": 802 - }, - { - "name": "audio_latent", - "type": "LATENT", - "link": 12 - } - ], - "outputs": [ - { - "name": "latent", - "type": "LATENT", - "links": [ - 804 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.7.0", - "Node name for S&R": "LTXVConcatAVLatent", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 5, - "type": "SaveVideo", - "pos": [ - 4940, - 5950 - ], - "size": [ - 660, - 440 - ], - "flags": {}, - "order": 12, - "mode": 0, - "inputs": [ - { - "name": "video", - "type": "VIDEO", - "link": 13 - } - ], - "outputs": [], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.5.1", - "Node name for S&R": "SaveVideo", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [ - "video/LTX", - "auto", - "auto" - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 14, - "type": "Note", - "pos": [ - 4410, - 5780 - ], - "size": [ - 210, - 110 - ], - "flags": {}, - "order": 0, - "mode": 0, - "inputs": [], - "outputs": [], - "title": "3 Stage Upscale Note", - "properties": {}, - "widgets_values": [ - "Stage 3 will default to 1.5x on the 3rd stage if false.\n\nIf you want to disable Stage 3, simply bypass the 3rd stage in the subraph below." - ], - "color": "#223", - "bgcolor": "#335" - }, - { - "id": 418, - "type": "Note", - "pos": [ - 3080, - 5820 - ], - "size": [ - 280, - 88 - ], - "flags": {}, - "order": 1, - "mode": 0, - "inputs": [], - "outputs": [], - "title": "Model Loader Note", - "properties": {}, - "widgets_values": [ - "↓ Select your own models in this subgraph ↓" - ], - "color": "#223", - "bgcolor": "#335" - }, - { - "id": 16, - "type": "Note", - "pos": [ - 4670, - 6110 - ], - "size": [ - 210, - 88 - ], - "flags": {}, - "order": 2, - "mode": 0, - "inputs": [], - "outputs": [], - "title": "Decode Note", - "properties": {}, - "widgets_values": [ - "Enable optimized decoding to use tiled vae decoder." - ], - "color": "#223", - "bgcolor": "#335" - }, - { - "id": 439, - "type": "MarkdownNote", - "pos": [ - 2680, - 6180 - ], - "size": [ - 370, - 320 - ], - "flags": {}, - "order": 3, - "mode": 0, - "inputs": [], - "outputs": [], - "title": "FAQ", - "properties": {}, - "widgets_values": [ - "# FAQ\n\n**How do I set the length of the video?**\n\nYou can set the length of the video in the Model Loader Subgraph →\n\n**I keep getting errors when running this workflow!**\n\nDouble check that all of the correct models loaded. This is using the newest version of 2x upscaler (v1.1). You can change it to the older one under the Model Loader.\n\n**Where do I download these models??**\n\nThere is a good list of models and download links here: \n\nAlso the latest v1.1 spatial upscaler can be found here: \n\nThe tiny vae (taeltx2_3.safetensors) is found here:\n\n\n\n\n\n\n\n\n\n" - ], - "color": "#432", - "bgcolor": "#653" - }, - { - "id": 1, - "type": "34d63160-e149-4935-ab1f-892ffe617fec", - "pos": [ - 4670, - 5950 - ], - "size": [ - 244.98828125, - 114 - ], - "flags": {}, - "order": 11, - "mode": 0, - "inputs": [ - { - "name": "av_latent", - "type": "LATENT", - "link": 47 - }, - { - "name": "audio_vae", - "type": "VAE", - "link": 2 - }, - { - "label": "video_vae", - "name": "vae", - "type": "VAE", - "link": 31 - }, - { - "label": "Use Optimized Decoding", - "name": "value", - "type": "BOOLEAN", - "widget": { - "name": "value" - }, - "link": null - } - ], - "outputs": [ - { - "name": "VIDEO", - "type": "VIDEO", - "links": [ - 13 - ] - } - ], - "properties": { - "proxyWidgets": [ - [ - "-1", - "value" - ] - ], - "cnr_id": "comfy-core", - "ver": "0.17.2" - }, - "widgets_values": [ - false - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 10, - "type": "958b2c7a-1f75-4a30-84a8-892bddb9e426", - "pos": [ - 3080, - 5950 - ], - "size": [ - 440, - 550 - ], - "flags": {}, - "order": 4, - "mode": 0, - "inputs": [ - { - "label": "Prompt", - "name": "value", - "type": "STRING", - "widget": { - "name": "value" - }, - "link": null - }, - { - "label": "length (seconds)", - "name": "value_1", - "type": "INT", - "widget": { - "name": "value_1" - }, - "link": null - } - ], - "outputs": [ - { - "name": "optimized_model", - "type": "MODEL", - "links": [ - 40 - ] - }, - { - "label": "video_vae", - "name": "VAE", - "type": "VAE", - "links": [ - 31, - 45, - 798 - ] - }, - { - "label": "audio_vae", - "name": "VAE_1", - "type": "VAE", - "links": [ - 2, - 22 - ] - }, - { - "name": "positive", - "type": "CONDITIONING", - "links": [ - 796 - ] - }, - { - "name": "negative", - "type": "CONDITIONING", - "links": [ - 797 - ] - }, - { - "label": "length_seconds", - "name": "INT", - "type": "INT", - "links": [ - 21 - ] - }, - { - "label": "noise", - "name": "NOISE", - "type": "NOISE", - "links": [ - 44 - ] - }, - { - "label": "2x_spatial_upscale_model", - "name": "LATENT_UPSCALE_MODEL", - "type": "LATENT_UPSCALE_MODEL", - "links": [ - 1037 - ] - } - ], - "properties": { - "proxyWidgets": [ - [ - "-1", - "value" - ], - [ - "-1", - "value_1" - ], - [ - "364", - "noise_seed" - ], - [ - "364", - "control_after_generate" - ] - ], - "cnr_id": "comfy-core", - "ver": "0.17.2" - }, - "widgets_values": [ - "", - 5 - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 381, - "type": "MultiImageLoader", - "pos": [ - 3550, - 5950 - ], - "size": [ - 350, - 297 - ], - "flags": {}, - "order": 5, - "mode": 0, - "inputs": [], - "outputs": [ - { - "name": "multi_output", - "type": "IMAGE", - "links": [ - 1046, - 1047, - 1048 - ] - } - ], - "properties": { - "cnr_id": "WhatDreamsCost-ComfyUI", - "ver": "ecd00e6b404f14b07c7b54b0b0033e8bccb32d24", - "Node name for S&R": "MultiImageLoader" - }, - "widgets_values": [ - "", - 0, - 0, - "lanczos", - "keep proportion", - 0, - 18, - "" - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 7, - "type": "2979f3eb-1c4c-45ff-8387-f1f4addb72f4", - "pos": [ - 4180, - 5950 - ], - "size": [ - 210, - 100 - ], - "flags": {}, - "order": 7, - "mode": 0, - "inputs": [ - { - "name": "image", - "type": "IMAGE", - "link": 1048 - }, - { - "label": "length_seconds", - "name": "length", - "type": "INT", - "widget": { - "name": "length" - }, - "link": 21 - }, - { - "name": "audio_vae", - "type": "VAE", - "link": 22 - } - ], - "outputs": [ - { - "name": "LATENT", - "type": "LATENT", - "links": [ - 803 - ] - }, - { - "name": "Latent", - "type": "LATENT", - "links": [ - 12 - ] - } - ], - "properties": { - "proxyWidgets": [ - [ - "-1", - "length" - ] - ], - "cnr_id": "comfy-core", - "ver": "0.17.2" - }, - "widgets_values": [ - 1 - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 376, - "type": "LTXSequencer", - "pos": [ - 3920, - 5950 - ], - "size": [ - 240, - 200 - ], - "flags": {}, - "order": 8, - "mode": 0, - "inputs": [ - { - "name": "positive", - "type": "CONDITIONING", - "link": 796 - }, - { - "name": "negative", - "type": "CONDITIONING", - "link": 797 - }, - { - "name": "vae", - "type": "VAE", - "link": 798 - }, - { - "name": "latent", - "type": "LATENT", - "link": 803 - }, - { - "name": "multi_input", - "type": "IMAGE", - "link": 1047 - } - ], - "outputs": [ - { - "name": "positive", - "type": "CONDITIONING", - "links": [ - 799 - ] - }, - { - "name": "negative", - "type": "CONDITIONING", - "links": [ - 800 - ] - }, - { - "name": "latent", - "type": "LATENT", - "links": [ - 802 - ] - } - ], - "properties": { - "cnr_id": "WhatDreamsCost-ComfyUI", - "ver": "ecd00e6b404f14b07c7b54b0b0033e8bccb32d24", - "Node name for S&R": "LTXSequencer", - "num_images": 0, - "insert_frame_1": 0, - "strength_1": 1, - "insert_frame_2": 0, - "strength_2": 1, - "insert_frame_3": 0, - "strength_3": 1, - "insert_frame_4": 0, - "strength_4": 1, - "insert_frame_5": 0, - "strength_5": 1, - "insert_frame_6": 0, - "strength_6": 1, - "insert_frame_7": 0, - "strength_7": 1, - "insert_frame_8": 0, - "strength_8": 1, - "insert_frame_9": 0, - "strength_9": 0, - "insert_frame_10": 1, - "strength_10": 1, - "insert_frame_11": 0, - "strength_11": 0, - "insert_frame_12": 1, - "strength_12": 1, - "insert_frame_13": 0, - "strength_13": 0, - "insert_frame_14": 1, - "strength_14": 1, - "insert_frame_15": 0, - "strength_15": 0, - "insert_frame_16": 1, - "strength_16": 1, - "insert_frame_17": 0, - "strength_17": 0, - "insert_frame_18": 1, - "strength_18": 1, - "insert_frame_19": 0, - "strength_19": 0, - "insert_frame_20": 1, - "strength_20": 1, - "insert_frame_21": 0, - "strength_21": 0, - "insert_frame_22": 1, - "strength_22": 1, - "insert_frame_23": 0, - "strength_23": 0, - "insert_frame_24": 1, - "strength_24": 1, - "insert_frame_25": 0, - "strength_25": 0, - "insert_frame_26": 1, - "strength_26": 1, - "insert_frame_27": 0, - "strength_27": 0, - "insert_frame_28": 1, - "strength_28": 1, - "insert_frame_29": 0, - "strength_29": 0, - "insert_frame_30": 1, - "strength_30": 1, - "insert_frame_31": 0, - "strength_31": 0, - "insert_frame_32": 1, - "strength_32": 1, - "insert_frame_33": 0, - "strength_33": 1, - "insert_frame_34": 0, - "strength_34": 1, - "insert_frame_35": 0, - "strength_35": 1, - "insert_frame_36": 0, - "strength_36": 1, - "insert_frame_37": 0, - "strength_37": 1, - "insert_frame_38": 0, - "strength_38": 1, - "insert_frame_39": 0, - "strength_39": 1, - "insert_frame_40": 0, - "strength_40": 1, - "insert_frame_41": 0, - "strength_41": 1, - "insert_frame_42": 0, - "strength_42": 1, - "insert_frame_43": 0, - "strength_43": 1, - "insert_frame_44": 0, - "strength_44": 1, - "insert_frame_45": 0, - "strength_45": 1, - "insert_frame_46": 0, - "strength_46": 1, - "insert_frame_47": 0, - "strength_47": 1, - "insert_frame_48": 0, - "strength_48": 1, - "insert_frame_49": 0, - "strength_49": 1, - "insert_frame_50": 0, - "strength_50": 1, - "insert_mode": "frames", - "frame_rate": 24, - "insert_second_1": 0, - "insert_second_2": 0, - "insert_second_3": 0, - "insert_second_4": 0, - "insert_second_5": 0, - "insert_second_6": 0, - "insert_second_7": 0, - "insert_second_8": 0, - "insert_second_9": 1, - "insert_second_10": 0, - "insert_second_11": 1, - "insert_second_12": 0, - "insert_second_13": 1, - "insert_second_14": 0, - "insert_second_15": 1, - "insert_second_16": 0, - "insert_second_17": 1, - "insert_second_18": 0, - "insert_second_19": 1, - "insert_second_20": 0, - "insert_second_21": 1, - "insert_second_22": 0, - "insert_second_23": 1, - "insert_second_24": 0, - "insert_second_25": 1, - "insert_second_26": 0, - "insert_second_27": 1, - "insert_second_28": 0, - "insert_second_29": 1, - "insert_second_30": 0, - "insert_second_31": 1, - "insert_second_32": 0, - "insert_second_33": 1, - "insert_second_34": 0, - "insert_second_35": 0, - "insert_second_36": 0, - "insert_second_37": 0, - "insert_second_38": 0, - "insert_second_39": 0, - "insert_second_40": 0, - "insert_second_41": 0, - "insert_second_42": 0, - "insert_second_43": 0, - "insert_second_44": 0, - "insert_second_45": 0, - "insert_second_46": 0, - "insert_second_47": 0, - "insert_second_48": 0, - "insert_second_49": 0, - "insert_second_50": 0, - "insert_images_using": "frames", - "video_length": 1, - "insert_percentage_1": 0, - "insert_percentage_2": 1, - "insert_percentage_3": 0, - "insert_percentage_4": 0, - "insert_percentage_5": 0, - "insert_percentage_6": 0, - "insert_percentage_7": 0, - "insert_percentage_8": 0, - "insert_percentage_9": 0, - "insert_percentage_10": 0, - "insert_percentage_11": 0, - "insert_percentage_12": 0, - "insert_percentage_13": 0, - "insert_percentage_14": 0, - "insert_percentage_15": 0, - "insert_percentage_16": 0, - "insert_percentage_17": 0, - "insert_percentage_18": 0, - "insert_percentage_19": 0, - "insert_percentage_20": 0, - "insert_percentage_21": 0, - "insert_percentage_22": 0, - "insert_percentage_23": 0, - "insert_percentage_24": 0, - "insert_percentage_25": 0, - "insert_percentage_26": 0, - "insert_percentage_27": 0, - "insert_percentage_28": 0, - "insert_percentage_29": 0, - "insert_percentage_30": 0, - "insert_percentage_31": 0, - "insert_percentage_32": 0, - "insert_percentage_33": 0, - "insert_percentage_34": 0, - "insert_percentage_35": 0, - "insert_percentage_36": 0, - "insert_percentage_37": 0, - "insert_percentage_38": 0, - "insert_percentage_39": 0, - "insert_percentage_40": 0, - "insert_percentage_41": 0, - "insert_percentage_42": 0, - "insert_percentage_43": 0, - "insert_percentage_44": 0, - "insert_percentage_45": 0, - "insert_percentage_46": 0, - "insert_percentage_47": 0, - "insert_percentage_48": 0, - "insert_percentage_49": 0, - "insert_percentage_50": 0, - "insert_percent_1": 0, - "insert_percent_2": 50, - "insert_percent_3": 100, - "insert_percent_4": 0, - "insert_percent_5": 0, - "insert_percent_6": 0, - "insert_percent_7": 0, - "insert_percent_8": 0, - "insert_percent_9": 0, - "insert_percent_10": 0, - "insert_percent_11": 0, - "insert_percent_12": 0, - "insert_percent_13": 0, - "insert_percent_14": 0, - "insert_percent_15": 0, - "insert_percent_16": 0, - "insert_percent_17": 0, - "insert_percent_18": 0, - "insert_percent_19": 0, - "insert_percent_20": 0, - "insert_percent_21": 0, - "insert_percent_22": 0, - "insert_percent_23": 0, - "insert_percent_24": 0, - "insert_percent_25": 0, - "insert_percent_26": 0, - "insert_percent_27": 0, - "insert_percent_28": 0, - "insert_percent_29": 0, - "insert_percent_30": 0, - "insert_percent_31": 0, - "insert_percent_32": 0, - "insert_percent_33": 0, - "insert_percent_34": 0, - "insert_percent_35": 0, - "insert_percent_36": 0, - "insert_percent_37": 0, - "insert_percent_38": 0, - "insert_percent_39": 0, - "insert_percent_40": 0, - "insert_percent_41": 0, - "insert_percent_42": 0, - "insert_percent_43": 0, - "insert_percent_44": 0, - "insert_percent_45": 0, - "insert_percent_46": 0, - "insert_percent_47": 0, - "insert_percent_48": 0, - "insert_percent_49": 0, - "insert_percent_50": 0 - }, - "widgets_values": [ - 0, - "frames", - 24, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1 - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 15, - "type": "Note", - "pos": [ - 3920, - 5740 - ], - "size": [ - 250, - 150 - ], - "flags": {}, - "order": 6, - "mode": 0, - "inputs": [], - "outputs": [], - "title": "LTX Sequencer Note", - "properties": {}, - "widgets_values": [ - "Use one image at full strength for regular image to video.\n\nIf frame_insert is -1, it will be inserted at the last frame.\n\nThe insert_mode allows to switch between inserting images by setting the exact frame or by setting the exact second." - ], - "color": "#223", - "bgcolor": "#335" - }, - { - "id": 11, - "type": "360d64f7-96cd-42a6-9b13-6eb0387e17d9", - "pos": [ - 4410, - 5950 - ], - "size": [ - 246.46875, - 322.233642578125 - ], - "flags": {}, - "order": 10, - "mode": 0, - "inputs": [ - { - "name": "model", - "type": "MODEL", - "link": 40 - }, - { - "name": "positive", - "type": "CONDITIONING", - "link": 799 - }, - { - "name": "negative", - "type": "CONDITIONING", - "link": 800 - }, - { - "name": "latent_image", - "type": "LATENT", - "link": 804 - }, - { - "name": "noise", - "type": "NOISE", - "link": 44 - }, - { - "label": "video_vae", - "name": "vae", - "type": "VAE", - "link": 45 - }, - { - "name": "image", - "type": "IMAGE", - "link": null - }, - { - "label": "Upscale 2x on Stage 3", - "name": "value", - "type": "BOOLEAN", - "widget": { - "name": "value" - }, - "link": null - }, - { - "name": "multi_input", - "type": "IMAGE", - "link": 1046 - }, - { - "label": "2x_spatial_upscale_model", - "name": "upscale_model", - "type": "LATENT_UPSCALE_MODEL", - "link": 1037 - } - ], - "outputs": [ - { - "label": "av_output", - "name": "output", - "type": "LATENT", - "links": [ - 47 - ] - } - ], - "properties": { - "proxyWidgets": [ - [ - "-1", - "value" - ] - ], - "cnr_id": "comfy-core", - "ver": "0.17.2" - }, - "widgets_values": [ - true - ], - "color": "#222", - "bgcolor": "#000" - } - ], - "links": [ - [ - 2, - 10, - 2, - 1, - 1, - "VAE" - ], - [ - 12, - 7, - 1, - 4, - 1, - "LATENT" - ], - [ - 13, - 1, - 0, - 5, - 0, - "VIDEO" - ], - [ - 21, - 10, - 5, - 7, - 1, - "INT" - ], - [ - 22, - 10, - 2, - 7, - 2, - "VAE" - ], - [ - 31, - 10, - 1, - 1, - 2, - "VAE" - ], - [ - 40, - 10, - 0, - 11, - 0, - "MODEL" - ], - [ - 44, - 10, - 6, - 11, - 4, - "NOISE" - ], - [ - 45, - 10, - 1, - 11, - 5, - "VAE" - ], - [ - 47, - 11, - 0, - 1, - 0, - "LATENT" - ], - [ - 796, - 10, - 3, - 376, - 0, - "CONDITIONING" - ], - [ - 797, - 10, - 4, - 376, - 1, - "CONDITIONING" - ], - [ - 798, - 10, - 1, - 376, - 2, - "VAE" - ], - [ - 799, - 376, - 0, - 11, - 1, - "CONDITIONING" - ], - [ - 800, - 376, - 1, - 11, - 2, - "CONDITIONING" - ], - [ - 802, - 376, - 2, - 4, - 0, - "LATENT" - ], - [ - 803, - 7, - 0, - 376, - 3, - "LATENT" - ], - [ - 804, - 4, - 0, - 11, - 3, - "LATENT" - ], - [ - 1037, - 10, - 7, - 11, - 9, - "LATENT_UPSCALE_MODEL" - ], - [ - 1046, - 381, - 0, - 11, - 8, - "IMAGE" - ], - [ - 1047, - 381, - 0, - 376, - 4, - "IMAGE" - ], - [ - 1048, - 381, - 0, - 7, - 0, - "IMAGE" - ] - ], - "groups": [], - "definitions": { - "subgraphs": [ - { - "id": "34d63160-e149-4935-ab1f-892ffe617fec", - "version": 1, - "state": { - "lastGroupId": 33, - "lastNodeId": 443, - "lastLinkId": 1080, - "lastRerouteId": 0 - }, - "revision": 0, - "config": {}, - "name": "Decode", - "inputNode": { - "id": -10, - "bounding": [ - 1610, - 7190, - 180.98828125, - 120 - ] - }, - "outputNode": { - "id": -20, - "bounding": [ - 2970, - 7320, - 120, - 60 - ] - }, - "inputs": [ - { - "id": "1d567d10-9078-49fc-a4a1-67e6d4b5744f", - "name": "av_latent", - "type": "LATENT", - "linkIds": [ - 692 - ], - "pos": [ - 1770.98828125, - 7210 - ] - }, - { - "id": "bef9341f-4429-46af-9bca-11e025bec4d0", - "name": "audio_vae", - "type": "VAE", - "linkIds": [ - 693 - ], - "pos": [ - 1770.98828125, - 7230 - ] - }, - { - "id": "4588ee3d-96a4-46a8-b829-a78181f6c305", - "name": "vae", - "type": "VAE", - "linkIds": [ - 703, - 1060 - ], - "label": "video_vae", - "pos": [ - 1770.98828125, - 7250 - ] - }, - { - "id": "1bd3842b-729d-4c3d-9927-cd508bccb1b1", - "name": "value", - "type": "BOOLEAN", - "linkIds": [ - 708 - ], - "label": "Use Optimized Decoding", - "pos": [ - 1770.98828125, - 7270 - ] - } - ], - "outputs": [ - { - "id": "7e889c1c-e185-47d7-a0f7-c5f1d971c6fe", - "name": "VIDEO", - "type": "VIDEO", - "linkIds": [ - 704 - ], - "pos": [ - 2990, - 7340 - ] - } - ], - "widgets": [], - "nodes": [ - { - "id": 337, - "type": "LazySwitchKJ", - "pos": [ - 2450, - 7340 - ], - "size": [ - 270, - 78 - ], - "flags": {}, - "order": 3, - "mode": 0, - "inputs": [ - { - "localized_name": "on_false", - "name": "on_false", - "type": "*", - "link": 701 - }, - { - "localized_name": "on_true", - "name": "on_true", - "type": "*", - "link": 1058 - }, - { - "localized_name": "switch", - "name": "switch", - "type": "BOOLEAN", - "widget": { - "name": "switch" - }, - "link": 707 - } - ], - "outputs": [ - { - "localized_name": "*", - "name": "*", - "type": "*", - "links": [ - 698 - ] - } - ], - "properties": { - "cnr_id": "comfyui-kjnodes", - "ver": "1.3.4", - "Node name for S&R": "LazySwitchKJ" - }, - "widgets_values": [ - false - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 336, - "type": "CreateVideo", - "pos": [ - 2740, - 7340 - ], - "size": [ - 210, - 78 - ], - "flags": {}, - "order": 2, - "mode": 0, - "inputs": [ - { - "localized_name": "images", - "name": "images", - "type": "IMAGE", - "link": 698 - }, - { - "localized_name": "audio", - "name": "audio", - "shape": 7, - "type": "AUDIO", - "link": 697 - } - ], - "outputs": [ - { - "localized_name": "VIDEO", - "name": "VIDEO", - "type": "VIDEO", - "links": [ - 704 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.5.1", - "Node name for S&R": "CreateVideo", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [ - 24 - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 341, - "type": "PrimitiveBoolean", - "pos": [ - 2450, - 7230 - ], - "size": [ - 230, - 58 - ], - "flags": {}, - "order": 5, - "mode": 0, - "inputs": [ - { - "localized_name": "value", - "name": "value", - "type": "BOOLEAN", - "widget": { - "name": "value" - }, - "link": 708 - } - ], - "outputs": [ - { - "localized_name": "BOOLEAN", - "name": "BOOLEAN", - "type": "BOOLEAN", - "links": [ - 707 - ] - } - ], - "title": "Use Optimized Decoding", - "properties": { - "cnr_id": "comfy-core", - "ver": "0.17.2", - "Node name for S&R": "PrimitiveBoolean" - }, - "widgets_values": [ - false - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 334, - "type": "LTXVSeparateAVLatent", - "pos": [ - 1820, - 7190 - ], - "size": [ - 240, - 46 - ], - "flags": {}, - "order": 1, - "mode": 0, - "inputs": [ - { - "localized_name": "av_latent", - "name": "av_latent", - "type": "LATENT", - "link": 692 - } - ], - "outputs": [ - { - "localized_name": "video_latent", - "name": "video_latent", - "type": "LATENT", - "links": [ - 702, - 1059 - ] - }, - { - "localized_name": "audio_latent", - "name": "audio_latent", - "type": "LATENT", - "links": [ - 687 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.5.1", - "Node name for S&R": "LTXVSeparateAVLatent", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 330, - "type": "LTXVAudioVAEDecode", - "pos": [ - 1820, - 7280 - ], - "size": [ - 240, - 46 - ], - "flags": {}, - "order": 0, - "mode": 0, - "inputs": [ - { - "localized_name": "samples", - "name": "samples", - "type": "LATENT", - "link": 687 - }, - { - "label": "Audio VAE", - "localized_name": "audio_vae", - "name": "audio_vae", - "type": "VAE", - "link": 693 - } - ], - "outputs": [ - { - "localized_name": "Audio", - "name": "Audio", - "type": "AUDIO", - "links": [ - 697 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.7.0", - "Node name for S&R": "LTXVAudioVAEDecode", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 340, - "type": "VAEDecode", - "pos": [ - 2110, - 7190 - ], - "size": [ - 150, - 50 - ], - "flags": {}, - "order": 4, - "mode": 0, - "inputs": [ - { - "localized_name": "samples", - "name": "samples", - "type": "LATENT", - "link": 702 - }, - { - "localized_name": "vae", - "name": "vae", - "type": "VAE", - "link": 703 - } - ], - "outputs": [ - { - "localized_name": "IMAGE", - "name": "IMAGE", - "type": "IMAGE", - "links": [ - 701 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.17.2", - "Node name for S&R": "VAEDecode" - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 432, - "type": "VAEDecodeTiled", - "pos": [ - 2110, - 7320 - ], - "size": [ - 270, - 150 - ], - "flags": {}, - "order": 6, - "mode": 0, - "inputs": [ - { - "localized_name": "samples", - "name": "samples", - "type": "LATENT", - "link": 1059 - }, - { - "localized_name": "vae", - "name": "vae", - "type": "VAE", - "link": 1060 - } - ], - "outputs": [ - { - "localized_name": "IMAGE", - "name": "IMAGE", - "type": "IMAGE", - "links": [ - 1058 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.17.2", - "Node name for S&R": "VAEDecodeTiled" - }, - "widgets_values": [ - 768, - 128, - 128, - 8 - ], - "color": "#222", - "bgcolor": "#000" - } - ], - "groups": [], - "links": [ - { - "id": 687, - "origin_id": 334, - "origin_slot": 1, - "target_id": 330, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 692, - "origin_id": -10, - "origin_slot": 0, - "target_id": 334, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 693, - "origin_id": -10, - "origin_slot": 1, - "target_id": 330, - "target_slot": 1, - "type": "VAE" - }, - { - "id": 697, - "origin_id": 330, - "origin_slot": 0, - "target_id": 336, - "target_slot": 1, - "type": "AUDIO" - }, - { - "id": 698, - "origin_id": 337, - "origin_slot": 0, - "target_id": 336, - "target_slot": 0, - "type": "IMAGE" - }, - { - "id": 701, - "origin_id": 340, - "origin_slot": 0, - "target_id": 337, - "target_slot": 0, - "type": "IMAGE" - }, - { - "id": 702, - "origin_id": 334, - "origin_slot": 0, - "target_id": 340, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 703, - "origin_id": -10, - "origin_slot": 2, - "target_id": 340, - "target_slot": 1, - "type": "VAE" - }, - { - "id": 704, - "origin_id": 336, - "origin_slot": 0, - "target_id": -20, - "target_slot": 0, - "type": "VIDEO" - }, - { - "id": 707, - "origin_id": 341, - "origin_slot": 0, - "target_id": 337, - "target_slot": 2, - "type": "BOOLEAN" - }, - { - "id": 708, - "origin_id": -10, - "origin_slot": 3, - "target_id": 341, - "target_slot": 0, - "type": "BOOLEAN" - }, - { - "id": 1058, - "origin_id": 432, - "origin_slot": 0, - "target_id": 337, - "target_slot": 1, - "type": "IMAGE" - }, - { - "id": 1059, - "origin_id": 334, - "origin_slot": 0, - "target_id": 432, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 1060, - "origin_id": -10, - "origin_slot": 2, - "target_id": 432, - "target_slot": 1, - "type": "VAE" - } - ], - "extra": { - "workflowRendererVersion": "LG" - } - }, - { - "id": "afa29944-eb58-40a5-8e76-484f2503fa0c", - "version": 1, - "state": { - "lastGroupId": 33, - "lastNodeId": 443, - "lastLinkId": 1080, - "lastRerouteId": 0 - }, - "revision": 0, - "config": {}, - "name": "Stage #1", - "inputNode": { - "id": -10, - "bounding": [ - 280, - 7113, - 120, - 140 - ] - }, - "outputNode": { - "id": -20, - "bounding": [ - 1150, - 6970, - 120, - 80 - ] - }, - "inputs": [ - { - "id": "e760d60a-9b49-4b40-8f39-3c81f29c08d8", - "name": "model", - "type": "MODEL", - "linkIds": [ - 683, - 686 - ], - "pos": [ - 380, - 7133 - ] - }, - { - "id": "6ab6641e-8318-4330-8b01-43ce43648dcf", - "name": "positive", - "type": "CONDITIONING", - "linkIds": [ - 786 - ], - "pos": [ - 380, - 7153 - ] - }, - { - "id": "e1e7e2f6-49fb-4754-92ab-b71442b03da7", - "name": "negative", - "type": "CONDITIONING", - "linkIds": [ - 787 - ], - "pos": [ - 380, - 7173 - ] - }, - { - "id": "5c9d4c86-ec46-4099-b7e1-41a9288a00d0", - "name": "latent_image", - "type": "LATENT", - "linkIds": [ - 687 - ], - "pos": [ - 380, - 7193 - ] - }, - { - "id": "0a89214b-81f0-4fa9-86fa-5f77079ddb7b", - "name": "noise", - "type": "NOISE", - "linkIds": [ - 688 - ], - "pos": [ - 380, - 7213 - ] - } - ], - "outputs": [ - { - "id": "b6f80403-e98c-41ad-a692-270973783381", - "name": "video_latent", - "type": "LATENT", - "linkIds": [ - 689 - ], - "pos": [ - 1170, - 6990 - ] - }, - { - "id": "f54a00fd-f0db-4e4e-9205-9c9d3e955032", - "name": "audio_latent", - "type": "LATENT", - "linkIds": [ - 690 - ], - "pos": [ - 1170, - 7010 - ] - } - ], - "widgets": [], - "nodes": [ - { - "id": 325, - "type": "KSamplerSelect", - "pos": [ - 460, - 7120 - ], - "size": [ - 210, - 60 - ], - "flags": {}, - "order": 0, - "mode": 0, - "inputs": [], - "outputs": [ - { - "localized_name": "SAMPLER", - "name": "SAMPLER", - "type": "SAMPLER", - "links": [ - 680 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.56", - "Node name for S&R": "KSamplerSelect", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [ - "euler" - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 326, - "type": "BasicScheduler", - "pos": [ - 460, - 7220 - ], - "size": [ - 210, - 106 - ], - "flags": {}, - "order": 4, - "mode": 0, - "inputs": [ - { - "localized_name": "model", - "name": "model", - "type": "MODEL", - "link": 686 - } - ], - "outputs": [ - { - "localized_name": "SIGMAS", - "name": "SIGMAS", - "type": "SIGMAS", - "links": [ - 681 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.16.4", - "Node name for S&R": "BasicScheduler" - }, - "widgets_values": [ - "linear_quadratic", - 8, - 1 - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 322, - "type": "SamplerCustomAdvanced", - "pos": [ - 690, - 6980 - ], - "size": [ - 212.3638671875, - 204.64497833251954 - ], - "flags": {}, - "order": 1, - "mode": 0, - "inputs": [ - { - "localized_name": "noise", - "name": "noise", - "type": "NOISE", - "link": 688 - }, - { - "localized_name": "guider", - "name": "guider", - "type": "GUIDER", - "link": 679 - }, - { - "localized_name": "sampler", - "name": "sampler", - "type": "SAMPLER", - "link": 680 - }, - { - "localized_name": "sigmas", - "name": "sigmas", - "type": "SIGMAS", - "link": 681 - }, - { - "localized_name": "latent_image", - "name": "latent_image", - "type": "LATENT", - "link": 687 - } - ], - "outputs": [ - { - "localized_name": "output", - "name": "output", - "type": "LATENT", - "links": [ - 682 - ] - }, - { - "localized_name": "denoised_output", - "name": "denoised_output", - "type": "LATENT", - "links": [] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.60", - "Node name for S&R": "SamplerCustomAdvanced", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 323, - "type": "LTXVSeparateAVLatent", - "pos": [ - 920, - 6980 - ], - "size": [ - 193.2916015625, - 46 - ], - "flags": {}, - "order": 2, - "mode": 0, - "inputs": [ - { - "localized_name": "av_latent", - "name": "av_latent", - "type": "LATENT", - "link": 682 - } - ], - "outputs": [ - { - "localized_name": "video_latent", - "name": "video_latent", - "type": "LATENT", - "links": [ - 689 - ] - }, - { - "localized_name": "audio_latent", - "name": "audio_latent", - "type": "LATENT", - "links": [ - 690 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.5.1", - "Node name for S&R": "LTXVSeparateAVLatent", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 324, - "type": "CFGGuider", - "pos": [ - 460, - 6990 - ], - "size": [ - 210, - 98 - ], - "flags": {}, - "order": 3, - "mode": 0, - "inputs": [ - { - "localized_name": "model", - "name": "model", - "type": "MODEL", - "link": 683 - }, - { - "localized_name": "positive", - "name": "positive", - "type": "CONDITIONING", - "link": 786 - }, - { - "localized_name": "negative", - "name": "negative", - "type": "CONDITIONING", - "link": 787 - } - ], - "outputs": [ - { - "localized_name": "GUIDER", - "name": "GUIDER", - "type": "GUIDER", - "links": [ - 679 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.64", - "Node name for S&R": "CFGGuider", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [ - 1 - ], - "color": "#222", - "bgcolor": "#000" - } - ], - "groups": [], - "links": [ - { - "id": 679, - "origin_id": 324, - "origin_slot": 0, - "target_id": 322, - "target_slot": 1, - "type": "GUIDER" - }, - { - "id": 680, - "origin_id": 325, - "origin_slot": 0, - "target_id": 322, - "target_slot": 2, - "type": "SAMPLER" - }, - { - "id": 681, - "origin_id": 326, - "origin_slot": 0, - "target_id": 322, - "target_slot": 3, - "type": "SIGMAS" - }, - { - "id": 682, - "origin_id": 322, - "origin_slot": 0, - "target_id": 323, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 683, - "origin_id": -10, - "origin_slot": 0, - "target_id": 324, - "target_slot": 0, - "type": "MODEL" - }, - { - "id": 686, - "origin_id": -10, - "origin_slot": 0, - "target_id": 326, - "target_slot": 0, - "type": "MODEL" - }, - { - "id": 687, - "origin_id": -10, - "origin_slot": 3, - "target_id": 322, - "target_slot": 4, - "type": "LATENT" - }, - { - "id": 688, - "origin_id": -10, - "origin_slot": 4, - "target_id": 322, - "target_slot": 0, - "type": "NOISE" - }, - { - "id": 689, - "origin_id": 323, - "origin_slot": 0, - "target_id": -20, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 690, - "origin_id": 323, - "origin_slot": 1, - "target_id": -20, - "target_slot": 1, - "type": "LATENT" - }, - { - "id": 786, - "origin_id": -10, - "origin_slot": 1, - "target_id": 324, - "target_slot": 1, - "type": "CONDITIONING" - }, - { - "id": 787, - "origin_id": -10, - "origin_slot": 2, - "target_id": 324, - "target_slot": 2, - "type": "CONDITIONING" - } - ], - "extra": { - "workflowRendererVersion": "LG" - } - }, - { - "id": "ce8f7842-cb03-41b4-9b1e-8d032b2a4e72", - "version": 1, - "state": { - "lastGroupId": 33, - "lastNodeId": 443, - "lastLinkId": 1080, - "lastRerouteId": 0 - }, - "revision": 0, - "config": {}, - "name": "Stage #2", - "inputNode": { - "id": -10, - "bounding": [ - 280, - 7113, - 125.224609375, - 240 - ] - }, - "outputNode": { - "id": -20, - "bounding": [ - 1820, - 7130, - 120, - 120 - ] - }, - "inputs": [ - { - "id": "e760d60a-9b49-4b40-8f39-3c81f29c08d8", - "name": "model", - "type": "MODEL", - "linkIds": [ - 683, - 686 - ], - "pos": [ - 385.224609375, - 7133 - ] - }, - { - "id": "6ab6641e-8318-4330-8b01-43ce43648dcf", - "name": "positive", - "type": "CONDITIONING", - "linkIds": [ - 703 - ], - "pos": [ - 385.224609375, - 7153 - ] - }, - { - "id": "e1e7e2f6-49fb-4754-92ab-b71442b03da7", - "name": "negative", - "type": "CONDITIONING", - "linkIds": [ - 704 - ], - "pos": [ - 385.224609375, - 7173 - ] - }, - { - "id": "0a89214b-81f0-4fa9-86fa-5f77079ddb7b", - "name": "noise", - "type": "NOISE", - "linkIds": [ - 688 - ], - "pos": [ - 385.224609375, - 7193 - ] - }, - { - "id": "d8824343-6b21-42aa-9eaf-65d1b267662d", - "name": "vae", - "type": "VAE", - "linkIds": [ - 706, - 990 - ], - "label": "video_vae", - "pos": [ - 385.224609375, - 7213 - ] - }, - { - "id": "6ea10719-25f1-449a-9d2d-bb395d89609d", - "name": "image", - "type": "IMAGE", - "linkIds": [], - "pos": [ - 385.224609375, - 7233 - ] - }, - { - "id": "6c54f923-7ccc-4832-86ca-38d39cfcc656", - "name": "samples", - "type": "LATENT", - "linkIds": [ - 705 - ], - "label": "video_latent", - "pos": [ - 385.224609375, - 7253 - ] - }, - { - "id": "5ce3a92e-d38a-4078-bc1f-7426a38fa1ff", - "name": "audio_latent", - "type": "LATENT", - "linkIds": [ - 700 - ], - "pos": [ - 385.224609375, - 7273 - ] - }, - { - "id": "6a8ebcb1-8f4b-4558-893c-1b6be5c462ec", - "name": "multi_input", - "type": "IMAGE", - "linkIds": [ - 1004 - ], - "pos": [ - 385.224609375, - 7293 - ] - }, - { - "id": "7443af4e-2549-4707-813a-a7e7efa13046", - "name": "upscale_model", - "type": "LATENT_UPSCALE_MODEL", - "linkIds": [ - 1035 - ], - "pos": [ - 385.224609375, - 7313 - ] - } - ], - "outputs": [ - { - "id": "b6f80403-e98c-41ad-a692-270973783381", - "name": "video_latent", - "type": "LATENT", - "linkIds": [ - 843 - ], - "pos": [ - 1840, - 7150 - ] - }, - { - "id": "f54a00fd-f0db-4e4e-9205-9c9d3e955032", - "name": "audio_latent", - "type": "LATENT", - "linkIds": [ - 690 - ], - "pos": [ - 1840, - 7170 - ] - }, - { - "id": "3ecaa920-05d1-4c39-8ea0-b7b21e2df49b", - "name": "positive", - "type": "CONDITIONING", - "linkIds": [ - 985 - ], - "pos": [ - 1840, - 7190 - ] - }, - { - "id": "d1c64c87-487c-4677-9f80-d4a7ef6cdcc6", - "name": "negative", - "type": "CONDITIONING", - "linkIds": [ - 986 - ], - "pos": [ - 1840, - 7210 - ] - } - ], - "widgets": [], - "nodes": [ - { - "id": 326, - "type": "BasicScheduler", - "pos": [ - 480, - 7210 - ], - "size": [ - 210, - 106 - ], - "flags": {}, - "order": 4, - "mode": 0, - "inputs": [ - { - "localized_name": "model", - "name": "model", - "type": "MODEL", - "link": 686 - } - ], - "outputs": [ - { - "localized_name": "SIGMAS", - "name": "SIGMAS", - "type": "SIGMAS", - "links": [ - 681 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.16.4", - "Node name for S&R": "BasicScheduler" - }, - "widgets_values": [ - "linear_quadratic", - 6, - 0.42 - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 325, - "type": "KSamplerSelect", - "pos": [ - 480, - 7110 - ], - "size": [ - 210, - 60 - ], - "flags": {}, - "order": 0, - "mode": 0, - "inputs": [], - "outputs": [ - { - "localized_name": "SAMPLER", - "name": "SAMPLER", - "type": "SAMPLER", - "links": [ - 680 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.56", - "Node name for S&R": "KSamplerSelect", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [ - "euler" - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 322, - "type": "SamplerCustomAdvanced", - "pos": [ - 1370, - 7130 - ], - "size": [ - 212.3638671875, - 204.64497833251954 - ], - "flags": {}, - "order": 1, - "mode": 0, - "inputs": [ - { - "localized_name": "noise", - "name": "noise", - "type": "NOISE", - "link": 688 - }, - { - "localized_name": "guider", - "name": "guider", - "type": "GUIDER", - "link": 679 - }, - { - "localized_name": "sampler", - "name": "sampler", - "type": "SAMPLER", - "link": 680 - }, - { - "localized_name": "sigmas", - "name": "sigmas", - "type": "SIGMAS", - "link": 681 - }, - { - "localized_name": "latent_image", - "name": "latent_image", - "type": "LATENT", - "link": 697 - } - ], - "outputs": [ - { - "localized_name": "output", - "name": "output", - "type": "LATENT", - "links": [ - 682 - ] - }, - { - "localized_name": "denoised_output", - "name": "denoised_output", - "type": "LATENT", - "links": [] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.60", - "Node name for S&R": "SamplerCustomAdvanced", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 323, - "type": "LTXVSeparateAVLatent", - "pos": [ - 1600, - 7140 - ], - "size": [ - 193.2916015625, - 46 - ], - "flags": {}, - "order": 2, - "mode": 0, - "inputs": [ - { - "localized_name": "av_latent", - "name": "av_latent", - "type": "LATENT", - "link": 682 - } - ], - "outputs": [ - { - "localized_name": "video_latent", - "name": "video_latent", - "type": "LATENT", - "links": [ - 843 - ] - }, - { - "localized_name": "audio_latent", - "name": "audio_latent", - "type": "LATENT", - "links": [ - 690 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.5.1", - "Node name for S&R": "LTXVSeparateAVLatent", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 409, - "type": "LTXSequencer", - "pos": [ - 850, - 7140 - ], - "size": [ - 210, - 200 - ], - "flags": {}, - "order": 8, - "mode": 0, - "inputs": [ - { - "localized_name": "positive", - "name": "positive", - "type": "CONDITIONING", - "link": 988 - }, - { - "localized_name": "negative", - "name": "negative", - "type": "CONDITIONING", - "link": 989 - }, - { - "localized_name": "vae", - "name": "vae", - "type": "VAE", - "link": 990 - }, - { - "localized_name": "latent", - "name": "latent", - "type": "LATENT", - "link": 991 - }, - { - "localized_name": "multi_input", - "name": "multi_input", - "type": "IMAGE", - "link": 1004 - } - ], - "outputs": [ - { - "localized_name": "positive", - "name": "positive", - "type": "CONDITIONING", - "links": [ - 983, - 985 - ] - }, - { - "localized_name": "negative", - "name": "negative", - "type": "CONDITIONING", - "links": [ - 984, - 986 - ] - }, - { - "localized_name": "latent", - "name": "latent", - "type": "LATENT", - "links": [ - 987 - ] - } - ], - "properties": { - "cnr_id": "WhatDreamsCost-ComfyUI", - "ver": "ecd00e6b404f14b07c7b54b0b0033e8bccb32d24", - "Node name for S&R": "LTXSequencer", - "num_images": 0, - "insert_frame_1": 0, - "strength_1": 1, - "insert_frame_2": 0, - "strength_2": 1, - "insert_frame_3": 0, - "strength_3": 1, - "insert_frame_4": 0, - "strength_4": 1, - "insert_frame_5": 0, - "strength_5": 1, - "insert_frame_6": 0, - "strength_6": 1, - "insert_frame_7": 0, - "strength_7": 1, - "insert_frame_8": 0, - "strength_8": 1, - "insert_frame_9": 0, - "strength_9": 0, - "insert_frame_10": 1, - "strength_10": 1, - "insert_frame_11": 0, - "strength_11": 0, - "insert_frame_12": 1, - "strength_12": 1, - "insert_frame_13": 0, - "strength_13": 0, - "insert_frame_14": 1, - "strength_14": 1, - "insert_frame_15": 0, - "strength_15": 0, - "insert_frame_16": 1, - "strength_16": 1, - "insert_frame_17": 0, - "strength_17": 0, - "insert_frame_18": 1, - "strength_18": 1, - "insert_frame_19": 0, - "strength_19": 0, - "insert_frame_20": 1, - "strength_20": 1, - "insert_frame_21": 0, - "strength_21": 0, - "insert_frame_22": 1, - "strength_22": 1, - "insert_frame_23": 0, - "strength_23": 0, - "insert_frame_24": 1, - "strength_24": 1, - "insert_frame_25": 0, - "strength_25": 0, - "insert_frame_26": 1, - "strength_26": 1, - "insert_frame_27": 0, - "strength_27": 0, - "insert_frame_28": 1, - "strength_28": 1, - "insert_frame_29": 0, - "strength_29": 0, - "insert_frame_30": 1, - "strength_30": 1, - "insert_frame_31": 0, - "strength_31": 0, - "insert_frame_32": 1, - "strength_32": 1, - "insert_frame_33": 0, - "strength_33": 1, - "insert_frame_34": 0, - "strength_34": 1, - "insert_frame_35": 0, - "strength_35": 1, - "insert_frame_36": 0, - "strength_36": 1, - "insert_frame_37": 0, - "strength_37": 1, - "insert_frame_38": 0, - "strength_38": 1, - "insert_frame_39": 0, - "strength_39": 1, - "insert_frame_40": 0, - "strength_40": 1, - "insert_frame_41": 0, - "strength_41": 1, - "insert_frame_42": 0, - "strength_42": 1, - "insert_frame_43": 0, - "strength_43": 1, - "insert_frame_44": 0, - "strength_44": 1, - "insert_frame_45": 0, - "strength_45": 1, - "insert_frame_46": 0, - "strength_46": 1, - "insert_frame_47": 0, - "strength_47": 1, - "insert_frame_48": 0, - "strength_48": 1, - "insert_frame_49": 0, - "strength_49": 1, - "insert_frame_50": 0, - "strength_50": 1, - "insert_mode": "frames", - "frame_rate": 24, - "insert_second_1": 0, - "insert_second_2": 0, - "insert_second_3": 0, - "insert_second_4": 0, - "insert_second_5": 0, - "insert_second_6": 0, - "insert_second_7": 0, - "insert_second_8": 0, - "insert_second_9": 1, - "insert_second_10": 0, - "insert_second_11": 1, - "insert_second_12": 0, - "insert_second_13": 1, - "insert_second_14": 0, - "insert_second_15": 1, - "insert_second_16": 0, - "insert_second_17": 1, - "insert_second_18": 0, - "insert_second_19": 1, - "insert_second_20": 0, - "insert_second_21": 1, - "insert_second_22": 0, - "insert_second_23": 1, - "insert_second_24": 0, - "insert_second_25": 1, - "insert_second_26": 0, - "insert_second_27": 1, - "insert_second_28": 0, - "insert_second_29": 1, - "insert_second_30": 0, - "insert_second_31": 1, - "insert_second_32": 0, - "insert_second_33": 1, - "insert_second_34": 0, - "insert_second_35": 0, - "insert_second_36": 0, - "insert_second_37": 0, - "insert_second_38": 0, - "insert_second_39": 0, - "insert_second_40": 0, - "insert_second_41": 0, - "insert_second_42": 0, - "insert_second_43": 0, - "insert_second_44": 0, - "insert_second_45": 0, - "insert_second_46": 0, - "insert_second_47": 0, - "insert_second_48": 0, - "insert_second_49": 0, - "insert_second_50": 0, - "insert_images_using": "frames", - "video_length": 1, - "insert_percentage_1": 0, - "insert_percentage_2": 1, - "insert_percentage_3": 0, - "insert_percentage_4": 0, - "insert_percentage_5": 0, - "insert_percentage_6": 0, - "insert_percentage_7": 0, - "insert_percentage_8": 0, - "insert_percentage_9": 0, - "insert_percentage_10": 0, - "insert_percentage_11": 0, - "insert_percentage_12": 0, - "insert_percentage_13": 0, - "insert_percentage_14": 0, - "insert_percentage_15": 0, - "insert_percentage_16": 0, - "insert_percentage_17": 0, - "insert_percentage_18": 0, - "insert_percentage_19": 0, - "insert_percentage_20": 0, - "insert_percentage_21": 0, - "insert_percentage_22": 0, - "insert_percentage_23": 0, - "insert_percentage_24": 0, - "insert_percentage_25": 0, - "insert_percentage_26": 0, - "insert_percentage_27": 0, - "insert_percentage_28": 0, - "insert_percentage_29": 0, - "insert_percentage_30": 0, - "insert_percentage_31": 0, - "insert_percentage_32": 0, - "insert_percentage_33": 0, - "insert_percentage_34": 0, - "insert_percentage_35": 0, - "insert_percentage_36": 0, - "insert_percentage_37": 0, - "insert_percentage_38": 0, - "insert_percentage_39": 0, - "insert_percentage_40": 0, - "insert_percentage_41": 0, - "insert_percentage_42": 0, - "insert_percentage_43": 0, - "insert_percentage_44": 0, - "insert_percentage_45": 0, - "insert_percentage_46": 0, - "insert_percentage_47": 0, - "insert_percentage_48": 0, - "insert_percentage_49": 0, - "insert_percentage_50": 0, - "insert_percent_1": 0, - "insert_percent_2": 50, - "insert_percent_3": 100, - "insert_percent_4": 0, - "insert_percent_5": 0, - "insert_percent_6": 0, - "insert_percent_7": 0, - "insert_percent_8": 0, - "insert_percent_9": 0, - "insert_percent_10": 0, - "insert_percent_11": 0, - "insert_percent_12": 0, - "insert_percent_13": 0, - "insert_percent_14": 0, - "insert_percent_15": 0, - "insert_percent_16": 0, - "insert_percent_17": 0, - "insert_percent_18": 0, - "insert_percent_19": 0, - "insert_percent_20": 0, - "insert_percent_21": 0, - "insert_percent_22": 0, - "insert_percent_23": 0, - "insert_percent_24": 0, - "insert_percent_25": 0, - "insert_percent_26": 0, - "insert_percent_27": 0, - "insert_percent_28": 0, - "insert_percent_29": 0, - "insert_percent_30": 0, - "insert_percent_31": 0, - "insert_percent_32": 0, - "insert_percent_33": 0, - "insert_percent_34": 0, - "insert_percent_35": 0, - "insert_percent_36": 0, - "insert_percent_37": 0, - "insert_percent_38": 0, - "insert_percent_39": 0, - "insert_percent_40": 0, - "insert_percent_41": 0, - "insert_percent_42": 0, - "insert_percent_43": 0, - "insert_percent_44": 0, - "insert_percent_45": 0, - "insert_percent_46": 0, - "insert_percent_47": 0, - "insert_percent_48": 0, - "insert_percent_49": 0, - "insert_percent_50": 0 - }, - "widgets_values": [ - 0, - "frames", - 24, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1 - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 331, - "type": "LTXVCropGuides", - "pos": [ - 490, - 7000 - ], - "size": [ - 152.7134765625, - 66 - ], - "flags": {}, - "order": 7, - "mode": 0, - "inputs": [ - { - "localized_name": "positive", - "name": "positive", - "type": "CONDITIONING", - "link": 703 - }, - { - "localized_name": "negative", - "name": "negative", - "type": "CONDITIONING", - "link": 704 - }, - { - "localized_name": "latent", - "name": "latent", - "type": "LATENT", - "link": 705 - } - ], - "outputs": [ - { - "localized_name": "positive", - "name": "positive", - "type": "CONDITIONING", - "links": [ - 828, - 846, - 988 - ] - }, - { - "localized_name": "negative", - "name": "negative", - "type": "CONDITIONING", - "links": [ - 829, - 847, - 989 - ] - }, - { - "localized_name": "latent", - "name": "latent", - "type": "LATENT", - "links": [ - 850 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.16.4", - "Node name for S&R": "LTXVCropGuides" - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 329, - "type": "LTXVConcatAVLatent", - "pos": [ - 1140, - 7270 - ], - "size": [ - 187.5, - 60 - ], - "flags": {}, - "order": 6, - "mode": 0, - "inputs": [ - { - "localized_name": "video_latent", - "name": "video_latent", - "type": "LATENT", - "link": 987 - }, - { - "localized_name": "audio_latent", - "name": "audio_latent", - "type": "LATENT", - "link": 700 - } - ], - "outputs": [ - { - "localized_name": "latent", - "name": "latent", - "type": "LATENT", - "links": [ - 697 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.7.0", - "Node name for S&R": "LTXVConcatAVLatent", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 324, - "type": "CFGGuider", - "pos": [ - 1140, - 7130 - ], - "size": [ - 210, - 98 - ], - "flags": {}, - "order": 3, - "mode": 0, - "inputs": [ - { - "localized_name": "model", - "name": "model", - "type": "MODEL", - "link": 683 - }, - { - "localized_name": "positive", - "name": "positive", - "type": "CONDITIONING", - "link": 983 - }, - { - "localized_name": "negative", - "name": "negative", - "type": "CONDITIONING", - "link": 984 - } - ], - "outputs": [ - { - "localized_name": "GUIDER", - "name": "GUIDER", - "type": "GUIDER", - "links": [ - 679 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.64", - "Node name for S&R": "CFGGuider", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [ - 1 - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 328, - "type": "LTXVLatentUpsampler", - "pos": [ - 480, - 7370 - ], - "size": [ - 320, - 66 - ], - "flags": {}, - "order": 5, - "mode": 0, - "inputs": [ - { - "localized_name": "samples", - "name": "samples", - "type": "LATENT", - "link": 850 - }, - { - "localized_name": "upscale_model", - "name": "upscale_model", - "type": "LATENT_UPSCALE_MODEL", - "link": 1035 - }, - { - "localized_name": "vae", - "name": "vae", - "type": "VAE", - "link": 706 - } - ], - "outputs": [ - { - "localized_name": "LATENT", - "name": "LATENT", - "type": "LATENT", - "links": [ - 991 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.14.1", - "Node name for S&R": "LTXVLatentUpsampler", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - } - ], - "groups": [], - "links": [ - { - "id": 679, - "origin_id": 324, - "origin_slot": 0, - "target_id": 322, - "target_slot": 1, - "type": "GUIDER" - }, - { - "id": 680, - "origin_id": 325, - "origin_slot": 0, - "target_id": 322, - "target_slot": 2, - "type": "SAMPLER" - }, - { - "id": 681, - "origin_id": 326, - "origin_slot": 0, - "target_id": 322, - "target_slot": 3, - "type": "SIGMAS" - }, - { - "id": 682, - "origin_id": 322, - "origin_slot": 0, - "target_id": 323, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 683, - "origin_id": -10, - "origin_slot": 0, - "target_id": 324, - "target_slot": 0, - "type": "MODEL" - }, - { - "id": 686, - "origin_id": -10, - "origin_slot": 0, - "target_id": 326, - "target_slot": 0, - "type": "MODEL" - }, - { - "id": 688, - "origin_id": -10, - "origin_slot": 3, - "target_id": 322, - "target_slot": 0, - "type": "NOISE" - }, - { - "id": 690, - "origin_id": 323, - "origin_slot": 1, - "target_id": -20, - "target_slot": 1, - "type": "LATENT" - }, - { - "id": 697, - "origin_id": 329, - "origin_slot": 0, - "target_id": 322, - "target_slot": 4, - "type": "LATENT" - }, - { - "id": 700, - "origin_id": -10, - "origin_slot": 7, - "target_id": 329, - "target_slot": 1, - "type": "LATENT" - }, - { - "id": 703, - "origin_id": -10, - "origin_slot": 1, - "target_id": 331, - "target_slot": 0, - "type": "CONDITIONING" - }, - { - "id": 704, - "origin_id": -10, - "origin_slot": 2, - "target_id": 331, - "target_slot": 1, - "type": "CONDITIONING" - }, - { - "id": 705, - "origin_id": -10, - "origin_slot": 6, - "target_id": 331, - "target_slot": 2, - "type": "LATENT" - }, - { - "id": 706, - "origin_id": -10, - "origin_slot": 4, - "target_id": 328, - "target_slot": 2, - "type": "VAE" - }, - { - "id": 843, - "origin_id": 323, - "origin_slot": 0, - "target_id": -20, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 850, - "origin_id": 331, - "origin_slot": 2, - "target_id": 328, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 983, - "origin_id": 409, - "origin_slot": 0, - "target_id": 324, - "target_slot": 1, - "type": "CONDITIONING" - }, - { - "id": 984, - "origin_id": 409, - "origin_slot": 1, - "target_id": 324, - "target_slot": 2, - "type": "CONDITIONING" - }, - { - "id": 985, - "origin_id": 409, - "origin_slot": 0, - "target_id": -20, - "target_slot": 2, - "type": "CONDITIONING" - }, - { - "id": 986, - "origin_id": 409, - "origin_slot": 1, - "target_id": -20, - "target_slot": 3, - "type": "CONDITIONING" - }, - { - "id": 987, - "origin_id": 409, - "origin_slot": 2, - "target_id": 329, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 988, - "origin_id": 331, - "origin_slot": 0, - "target_id": 409, - "target_slot": 0, - "type": "CONDITIONING" - }, - { - "id": 989, - "origin_id": 331, - "origin_slot": 1, - "target_id": 409, - "target_slot": 1, - "type": "CONDITIONING" - }, - { - "id": 990, - "origin_id": -10, - "origin_slot": 4, - "target_id": 409, - "target_slot": 2, - "type": "VAE" - }, - { - "id": 991, - "origin_id": 328, - "origin_slot": 0, - "target_id": 409, - "target_slot": 3, - "type": "LATENT" - }, - { - "id": 1004, - "origin_id": -10, - "origin_slot": 8, - "target_id": 409, - "target_slot": 4, - "type": "IMAGE" - }, - { - "id": 1035, - "origin_id": -10, - "origin_slot": 9, - "target_id": 328, - "target_slot": 1, - "type": "LATENT_UPSCALE_MODEL" - } - ], - "extra": { - "workflowRendererVersion": "LG" - } - }, - { - "id": "2979f3eb-1c4c-45ff-8387-f1f4addb72f4", - "version": 1, - "state": { - "lastGroupId": 33, - "lastNodeId": 443, - "lastLinkId": 1080, - "lastRerouteId": 0 - }, - "revision": 0, - "config": {}, - "name": "Process Latents", - "inputNode": { - "id": -10, - "bounding": [ - 2630, - 6303, - 128.998046875, - 100 - ] - }, - "outputNode": { - "id": -20, - "bounding": [ - 3500, - 6303, - 120, - 80 - ] - }, - "inputs": [ - { - "id": "f9d3f39d-4628-4ef2-a464-ffb98c1fb4bc", - "name": "image", - "type": "IMAGE", - "linkIds": [ - 713 - ], - "localized_name": "image", - "pos": [ - 2738.998046875, - 6323 - ] - }, - { - "id": "6c61b79c-c6a2-4cd9-96a7-72073d3603c1", - "name": "length", - "type": "INT", - "linkIds": [ - 708, - 709 - ], - "localized_name": "length", - "label": "length_seconds", - "pos": [ - 2738.998046875, - 6343 - ] - }, - { - "id": "610c02c9-497c-45ac-b603-ea5bd91aec4c", - "name": "audio_vae", - "type": "VAE", - "linkIds": [ - 714 - ], - "pos": [ - 2738.998046875, - 6363 - ] - } - ], - "outputs": [ - { - "id": "2cf8b0ee-4e7a-4045-a1ee-bbd4b878068e", - "name": "LATENT", - "type": "LATENT", - "linkIds": [ - 710 - ], - "localized_name": "LATENT", - "pos": [ - 3520, - 6323 - ] - }, - { - "id": "44e92917-679f-4cc3-aa57-51b4a6ef24a8", - "name": "Latent", - "type": "LATENT", - "linkIds": [ - 705 - ], - "localized_name": "Latent", - "pos": [ - 3520, - 6343 - ] - } - ], - "widgets": [], - "nodes": [ - { - "id": 356, - "type": "GetImageSize", - "pos": [ - 3030, - 6210 - ], - "size": [ - 170, - 70 - ], - "flags": {}, - "order": 2, - "mode": 0, - "inputs": [ - { - "localized_name": "image", - "name": "image", - "type": "IMAGE", - "link": 711 - } - ], - "outputs": [ - { - "localized_name": "width", - "name": "width", - "type": "INT", - "links": [ - 706 - ] - }, - { - "localized_name": "height", - "name": "height", - "type": "INT", - "links": [ - 707 - ] - }, - { - "localized_name": "batch_size", - "name": "batch_size", - "type": "INT", - "links": null - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.7.0", - "Node name for S&R": "GetImageSize", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 352, - "type": "EmptyLTXVLatentVideo", - "pos": [ - 3210, - 6210 - ], - "size": [ - 210, - 130 - ], - "flags": {}, - "order": 0, - "mode": 0, - "inputs": [ - { - "localized_name": "width", - "name": "width", - "type": "INT", - "widget": { - "name": "width" - }, - "link": 706 - }, - { - "localized_name": "height", - "name": "height", - "type": "INT", - "widget": { - "name": "height" - }, - "link": 707 - }, - { - "localized_name": "length", - "name": "length", - "type": "INT", - "widget": { - "name": "length" - }, - "link": 708 - } - ], - "outputs": [ - { - "localized_name": "LATENT", - "name": "LATENT", - "type": "LATENT", - "links": [ - 710 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.60", - "Node name for S&R": "EmptyLTXVLatentVideo", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [ - 768, - 512, - 1, - 1 - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 353, - "type": "LTXVEmptyLatentAudio", - "pos": [ - 3210, - 6380 - ], - "size": [ - 210, - 106 - ], - "flags": {}, - "order": 1, - "mode": 0, - "inputs": [ - { - "localized_name": "audio_vae", - "name": "audio_vae", - "type": "VAE", - "link": 714 - }, - { - "localized_name": "frames_number", - "name": "frames_number", - "type": "INT", - "widget": { - "name": "frames_number" - }, - "link": 709 - } - ], - "outputs": [ - { - "localized_name": "Latent", - "name": "Latent", - "type": "LATENT", - "links": [ - 705 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.68", - "Node name for S&R": "LTXVEmptyLatentAudio", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [ - 1, - 24, - 1 - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 357, - "type": "ImageScaleBy", - "pos": [ - 2810, - 6210 - ], - "size": [ - 210, - 82 - ], - "flags": {}, - "order": 3, - "mode": 0, - "inputs": [ - { - "localized_name": "image", - "name": "image", - "type": "IMAGE", - "link": 713 - } - ], - "outputs": [ - { - "localized_name": "IMAGE", - "name": "IMAGE", - "type": "IMAGE", - "links": [ - 711 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.5.1", - "Node name for S&R": "ImageScaleBy", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65, - "ue_properties": { - "version": "7.0.1", - "widget_ue_connectable": {} - } - }, - "widgets_values": [ - "bilinear", - 0.5 - ], - "color": "#222", - "bgcolor": "#000" - } - ], - "groups": [], - "links": [ - { - "id": 711, - "origin_id": 357, - "origin_slot": 0, - "target_id": 356, - "target_slot": 0, - "type": "IMAGE" - }, - { - "id": 706, - "origin_id": 356, - "origin_slot": 0, - "target_id": 352, - "target_slot": 0, - "type": "INT" - }, - { - "id": 707, - "origin_id": 356, - "origin_slot": 1, - "target_id": 352, - "target_slot": 1, - "type": "INT" - }, - { - "id": 713, - "origin_id": -10, - "origin_slot": 0, - "target_id": 357, - "target_slot": 0, - "type": "IMAGE" - }, - { - "id": 708, - "origin_id": -10, - "origin_slot": 1, - "target_id": 352, - "target_slot": 2, - "type": "INT" - }, - { - "id": 709, - "origin_id": -10, - "origin_slot": 1, - "target_id": 353, - "target_slot": 1, - "type": "INT" - }, - { - "id": 710, - "origin_id": 352, - "origin_slot": 0, - "target_id": -20, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 705, - "origin_id": 353, - "origin_slot": 0, - "target_id": -20, - "target_slot": 1, - "type": "LATENT" - }, - { - "id": 714, - "origin_id": -10, - "origin_slot": 2, - "target_id": 353, - "target_slot": 0, - "type": "VAE" - } - ], - "extra": { - "workflowRendererVersion": "LG" - } - }, - { - "id": "535e663f-da59-47c1-aab5-c78ff202cb16", - "version": 1, - "state": { - "lastGroupId": 33, - "lastNodeId": 443, - "lastLinkId": 1080, - "lastRerouteId": 0 - }, - "revision": 0, - "config": {}, - "name": "Stage #3", - "inputNode": { - "id": -10, - "bounding": [ - -370, - 7030, - 144.56640625, - 260 - ] - }, - "outputNode": { - "id": -20, - "bounding": [ - 1680, - 7090, - 120, - 80 - ] - }, - "inputs": [ - { - "id": "e760d60a-9b49-4b40-8f39-3c81f29c08d8", - "name": "model", - "type": "MODEL", - "linkIds": [ - 683, - 686 - ], - "pos": [ - -245.43359375, - 7050 - ] - }, - { - "id": "6ab6641e-8318-4330-8b01-43ce43648dcf", - "name": "positive", - "type": "CONDITIONING", - "linkIds": [ - 919 - ], - "pos": [ - -245.43359375, - 7070 - ] - }, - { - "id": "e1e7e2f6-49fb-4754-92ab-b71442b03da7", - "name": "negative", - "type": "CONDITIONING", - "linkIds": [ - 920 - ], - "pos": [ - -245.43359375, - 7090 - ] - }, - { - "id": "0a89214b-81f0-4fa9-86fa-5f77079ddb7b", - "name": "noise", - "type": "NOISE", - "linkIds": [ - 688 - ], - "pos": [ - -245.43359375, - 7110 - ] - }, - { - "id": "d8824343-6b21-42aa-9eaf-65d1b267662d", - "name": "vae", - "type": "VAE", - "linkIds": [ - 716, - 717, - 1002 - ], - "label": "video_vae", - "pos": [ - -245.43359375, - 7130 - ] - }, - { - "id": "6ea10719-25f1-449a-9d2d-bb395d89609d", - "name": "image", - "type": "IMAGE", - "linkIds": [], - "pos": [ - -245.43359375, - 7150 - ] - }, - { - "id": "d57f8c01-0dc1-48da-b7a2-166839b09d2b", - "name": "samples", - "type": "LATENT", - "linkIds": [ - 708 - ], - "label": "video_latent", - "pos": [ - -245.43359375, - 7170 - ] - }, - { - "id": "9f9730e3-9739-4813-953f-4c27da077f21", - "name": "audio_latent", - "type": "LATENT", - "linkIds": [ - 700 - ], - "pos": [ - -245.43359375, - 7190 - ] - }, - { - "id": "6f44ac03-cf13-4dc0-be74-7ec92e492d88", - "name": "value", - "type": "BOOLEAN", - "linkIds": [ - 719 - ], - "label": "use 2x on stage 3", - "pos": [ - -245.43359375, - 7210 - ] - }, - { - "id": "9951f766-4866-4ed1-bab3-39a03ca4ae6a", - "name": "multi_input", - "type": "IMAGE", - "linkIds": [ - 1005 - ], - "pos": [ - -245.43359375, - 7230 - ] - }, - { - "id": "265429c2-c78b-4c5d-80d7-6e272aa96a36", - "name": "upscale_model", - "type": "LATENT_UPSCALE_MODEL", - "linkIds": [ - 1038 - ], - "label": "2x_upscale_model", - "pos": [ - -245.43359375, - 7250 - ] - } - ], - "outputs": [ - { - "id": "7b177753-a22f-47a0-9ff8-7f4022516b5c", - "name": "output", - "type": "LATENT", - "linkIds": [ - 1024 - ], - "label": "video_latent", - "pos": [ - 1700, - 7110 - ] - }, - { - "id": "51232fd0-325e-4090-bcc9-4ecbdb0b9e27", - "name": "audio_latent", - "type": "LATENT", - "linkIds": [ - 1025 - ], - "pos": [ - 1700, - 7130 - ] - } - ], - "widgets": [], - "nodes": [ - { - "id": 331, - "type": "LTXVCropGuides", - "pos": [ - -190, - 6940 - ], - "size": [ - 152.7134765625, - 66 - ], - "flags": {}, - "order": 6, - "mode": 0, - "inputs": [ - { - "localized_name": "positive", - "name": "positive", - "type": "CONDITIONING", - "link": 919 - }, - { - "localized_name": "negative", - "name": "negative", - "type": "CONDITIONING", - "link": 920 - }, - { - "localized_name": "latent", - "name": "latent", - "type": "LATENT", - "link": 708 - } - ], - "outputs": [ - { - "localized_name": "positive", - "name": "positive", - "type": "CONDITIONING", - "links": [ - 999 - ] - }, - { - "localized_name": "negative", - "name": "negative", - "type": "CONDITIONING", - "links": [ - 1000 - ] - }, - { - "localized_name": "latent", - "name": "latent", - "type": "LATENT", - "links": [ - 877, - 878 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.16.4", - "Node name for S&R": "LTXVCropGuides" - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 325, - "type": "KSamplerSelect", - "pos": [ - -190, - 7050 - ], - "size": [ - 210, - 60 - ], - "flags": {}, - "order": 0, - "mode": 0, - "inputs": [], - "outputs": [ - { - "localized_name": "SAMPLER", - "name": "SAMPLER", - "type": "SAMPLER", - "links": [ - 680 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.56", - "Node name for S&R": "KSamplerSelect", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [ - "euler" - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 326, - "type": "BasicScheduler", - "pos": [ - -190, - 7150 - ], - "size": [ - 210, - 106 - ], - "flags": {}, - "order": 4, - "mode": 0, - "inputs": [ - { - "localized_name": "model", - "name": "model", - "type": "MODEL", - "link": 686 - } - ], - "outputs": [ - { - "localized_name": "SIGMAS", - "name": "SIGMAS", - "type": "SIGMAS", - "links": [ - 681 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.16.4", - "Node name for S&R": "BasicScheduler" - }, - "widgets_values": [ - "linear_quadratic", - 4, - 0.42 - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 334, - "type": "LTXVLatentUpsampler", - "pos": [ - 160, - 7300 - ], - "size": [ - 320, - 66 - ], - "flags": {}, - "order": 8, - "mode": 0, - "inputs": [ - { - "localized_name": "samples", - "name": "samples", - "type": "LATENT", - "link": 877 - }, - { - "localized_name": "upscale_model", - "name": "upscale_model", - "type": "LATENT_UPSCALE_MODEL", - "link": 710 - }, - { - "localized_name": "vae", - "name": "vae", - "type": "VAE", - "link": 716 - } - ], - "outputs": [ - { - "localized_name": "LATENT", - "name": "LATENT", - "type": "LATENT", - "links": [ - 712 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.14.1", - "Node name for S&R": "LTXVLatentUpsampler", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 332, - "type": "LazySwitchKJ", - "pos": [ - 500, - 7360 - ], - "size": [ - 210, - 78 - ], - "flags": {}, - "order": 7, - "mode": 0, - "inputs": [ - { - "localized_name": "on_false", - "name": "on_false", - "type": "*", - "link": 712 - }, - { - "localized_name": "on_true", - "name": "on_true", - "type": "*", - "link": 713 - }, - { - "localized_name": "switch", - "name": "switch", - "type": "BOOLEAN", - "widget": { - "name": "switch" - }, - "link": 718 - } - ], - "outputs": [ - { - "localized_name": "*", - "name": "*", - "type": "*", - "links": [ - 1001 - ] - } - ], - "properties": { - "cnr_id": "comfyui-kjnodes", - "ver": "1.3.4", - "Node name for S&R": "LazySwitchKJ" - }, - "widgets_values": [ - false - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 411, - "type": "LTXSequencer", - "pos": [ - 720, - 7110 - ], - "size": [ - 210, - 200 - ], - "flags": {}, - "order": 11, - "mode": 0, - "inputs": [ - { - "localized_name": "positive", - "name": "positive", - "type": "CONDITIONING", - "link": 999 - }, - { - "localized_name": "negative", - "name": "negative", - "type": "CONDITIONING", - "link": 1000 - }, - { - "localized_name": "vae", - "name": "vae", - "type": "VAE", - "link": 1002 - }, - { - "localized_name": "latent", - "name": "latent", - "type": "LATENT", - "link": 1001 - }, - { - "localized_name": "multi_input", - "name": "multi_input", - "type": "IMAGE", - "link": 1005 - } - ], - "outputs": [ - { - "localized_name": "positive", - "name": "positive", - "type": "CONDITIONING", - "links": [ - 996 - ] - }, - { - "localized_name": "negative", - "name": "negative", - "type": "CONDITIONING", - "links": [ - 997 - ] - }, - { - "localized_name": "latent", - "name": "latent", - "type": "LATENT", - "links": [ - 998 - ] - } - ], - "properties": { - "cnr_id": "WhatDreamsCost-ComfyUI", - "ver": "ecd00e6b404f14b07c7b54b0b0033e8bccb32d24", - "Node name for S&R": "LTXSequencer", - "num_images": 0, - "insert_frame_1": 0, - "strength_1": 1, - "insert_frame_2": 0, - "strength_2": 1, - "insert_frame_3": 0, - "strength_3": 1, - "insert_frame_4": 0, - "strength_4": 1, - "insert_frame_5": 0, - "strength_5": 1, - "insert_frame_6": 0, - "strength_6": 1, - "insert_frame_7": 0, - "strength_7": 1, - "insert_frame_8": 0, - "strength_8": 1, - "insert_frame_9": 0, - "strength_9": 0, - "insert_frame_10": 1, - "strength_10": 1, - "insert_frame_11": 0, - "strength_11": 0, - "insert_frame_12": 1, - "strength_12": 1, - "insert_frame_13": 0, - "strength_13": 0, - "insert_frame_14": 1, - "strength_14": 1, - "insert_frame_15": 0, - "strength_15": 0, - "insert_frame_16": 1, - "strength_16": 1, - "insert_frame_17": 0, - "strength_17": 0, - "insert_frame_18": 1, - "strength_18": 1, - "insert_frame_19": 0, - "strength_19": 0, - "insert_frame_20": 1, - "strength_20": 1, - "insert_frame_21": 0, - "strength_21": 0, - "insert_frame_22": 1, - "strength_22": 1, - "insert_frame_23": 0, - "strength_23": 0, - "insert_frame_24": 1, - "strength_24": 1, - "insert_frame_25": 0, - "strength_25": 0, - "insert_frame_26": 1, - "strength_26": 1, - "insert_frame_27": 0, - "strength_27": 0, - "insert_frame_28": 1, - "strength_28": 1, - "insert_frame_29": 0, - "strength_29": 0, - "insert_frame_30": 1, - "strength_30": 1, - "insert_frame_31": 0, - "strength_31": 0, - "insert_frame_32": 1, - "strength_32": 1, - "insert_frame_33": 0, - "strength_33": 1, - "insert_frame_34": 0, - "strength_34": 1, - "insert_frame_35": 0, - "strength_35": 1, - "insert_frame_36": 0, - "strength_36": 1, - "insert_frame_37": 0, - "strength_37": 1, - "insert_frame_38": 0, - "strength_38": 1, - "insert_frame_39": 0, - "strength_39": 1, - "insert_frame_40": 0, - "strength_40": 1, - "insert_frame_41": 0, - "strength_41": 1, - "insert_frame_42": 0, - "strength_42": 1, - "insert_frame_43": 0, - "strength_43": 1, - "insert_frame_44": 0, - "strength_44": 1, - "insert_frame_45": 0, - "strength_45": 1, - "insert_frame_46": 0, - "strength_46": 1, - "insert_frame_47": 0, - "strength_47": 1, - "insert_frame_48": 0, - "strength_48": 1, - "insert_frame_49": 0, - "strength_49": 1, - "insert_frame_50": 0, - "strength_50": 1, - "insert_mode": "frames", - "frame_rate": 24, - "insert_second_1": 0, - "insert_second_2": 0, - "insert_second_3": 0, - "insert_second_4": 0, - "insert_second_5": 0, - "insert_second_6": 0, - "insert_second_7": 0, - "insert_second_8": 0, - "insert_second_9": 1, - "insert_second_10": 0, - "insert_second_11": 1, - "insert_second_12": 0, - "insert_second_13": 1, - "insert_second_14": 0, - "insert_second_15": 1, - "insert_second_16": 0, - "insert_second_17": 1, - "insert_second_18": 0, - "insert_second_19": 1, - "insert_second_20": 0, - "insert_second_21": 1, - "insert_second_22": 0, - "insert_second_23": 1, - "insert_second_24": 0, - "insert_second_25": 1, - "insert_second_26": 0, - "insert_second_27": 1, - "insert_second_28": 0, - "insert_second_29": 1, - "insert_second_30": 0, - "insert_second_31": 1, - "insert_second_32": 0, - "insert_second_33": 1, - "insert_second_34": 0, - "insert_second_35": 0, - "insert_second_36": 0, - "insert_second_37": 0, - "insert_second_38": 0, - "insert_second_39": 0, - "insert_second_40": 0, - "insert_second_41": 0, - "insert_second_42": 0, - "insert_second_43": 0, - "insert_second_44": 0, - "insert_second_45": 0, - "insert_second_46": 0, - "insert_second_47": 0, - "insert_second_48": 0, - "insert_second_49": 0, - "insert_second_50": 0, - "insert_images_using": "frames", - "video_length": 1, - "insert_percentage_1": 0, - "insert_percentage_2": 1, - "insert_percentage_3": 0, - "insert_percentage_4": 0, - "insert_percentage_5": 0, - "insert_percentage_6": 0, - "insert_percentage_7": 0, - "insert_percentage_8": 0, - "insert_percentage_9": 0, - "insert_percentage_10": 0, - "insert_percentage_11": 0, - "insert_percentage_12": 0, - "insert_percentage_13": 0, - "insert_percentage_14": 0, - "insert_percentage_15": 0, - "insert_percentage_16": 0, - "insert_percentage_17": 0, - "insert_percentage_18": 0, - "insert_percentage_19": 0, - "insert_percentage_20": 0, - "insert_percentage_21": 0, - "insert_percentage_22": 0, - "insert_percentage_23": 0, - "insert_percentage_24": 0, - "insert_percentage_25": 0, - "insert_percentage_26": 0, - "insert_percentage_27": 0, - "insert_percentage_28": 0, - "insert_percentage_29": 0, - "insert_percentage_30": 0, - "insert_percentage_31": 0, - "insert_percentage_32": 0, - "insert_percentage_33": 0, - "insert_percentage_34": 0, - "insert_percentage_35": 0, - "insert_percentage_36": 0, - "insert_percentage_37": 0, - "insert_percentage_38": 0, - "insert_percentage_39": 0, - "insert_percentage_40": 0, - "insert_percentage_41": 0, - "insert_percentage_42": 0, - "insert_percentage_43": 0, - "insert_percentage_44": 0, - "insert_percentage_45": 0, - "insert_percentage_46": 0, - "insert_percentage_47": 0, - "insert_percentage_48": 0, - "insert_percentage_49": 0, - "insert_percentage_50": 0, - "insert_percent_1": 0, - "insert_percent_2": 50, - "insert_percent_3": 100, - "insert_percent_4": 0, - "insert_percent_5": 0, - "insert_percent_6": 0, - "insert_percent_7": 0, - "insert_percent_8": 0, - "insert_percent_9": 0, - "insert_percent_10": 0, - "insert_percent_11": 0, - "insert_percent_12": 0, - "insert_percent_13": 0, - "insert_percent_14": 0, - "insert_percent_15": 0, - "insert_percent_16": 0, - "insert_percent_17": 0, - "insert_percent_18": 0, - "insert_percent_19": 0, - "insert_percent_20": 0, - "insert_percent_21": 0, - "insert_percent_22": 0, - "insert_percent_23": 0, - "insert_percent_24": 0, - "insert_percent_25": 0, - "insert_percent_26": 0, - "insert_percent_27": 0, - "insert_percent_28": 0, - "insert_percent_29": 0, - "insert_percent_30": 0, - "insert_percent_31": 0, - "insert_percent_32": 0, - "insert_percent_33": 0, - "insert_percent_34": 0, - "insert_percent_35": 0, - "insert_percent_36": 0, - "insert_percent_37": 0, - "insert_percent_38": 0, - "insert_percent_39": 0, - "insert_percent_40": 0, - "insert_percent_41": 0, - "insert_percent_42": 0, - "insert_percent_43": 0, - "insert_percent_44": 0, - "insert_percent_45": 0, - "insert_percent_46": 0, - "insert_percent_47": 0, - "insert_percent_48": 0, - "insert_percent_49": 0, - "insert_percent_50": 0 - }, - "widgets_values": [ - 0, - "frames", - 24, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1 - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 324, - "type": "CFGGuider", - "pos": [ - 1010, - 7110 - ], - "size": [ - 210, - 98 - ], - "flags": {}, - "order": 3, - "mode": 0, - "inputs": [ - { - "localized_name": "model", - "name": "model", - "type": "MODEL", - "link": 683 - }, - { - "localized_name": "positive", - "name": "positive", - "type": "CONDITIONING", - "link": 996 - }, - { - "localized_name": "negative", - "name": "negative", - "type": "CONDITIONING", - "link": 997 - } - ], - "outputs": [ - { - "localized_name": "GUIDER", - "name": "GUIDER", - "type": "GUIDER", - "links": [ - 679 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.64", - "Node name for S&R": "CFGGuider", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [ - 1 - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 329, - "type": "LTXVConcatAVLatent", - "pos": [ - 1010, - 7250 - ], - "size": [ - 187.5, - 60 - ], - "flags": {}, - "order": 5, - "mode": 0, - "inputs": [ - { - "localized_name": "video_latent", - "name": "video_latent", - "type": "LATENT", - "link": 998 - }, - { - "localized_name": "audio_latent", - "name": "audio_latent", - "type": "LATENT", - "link": 700 - } - ], - "outputs": [ - { - "localized_name": "latent", - "name": "latent", - "type": "LATENT", - "links": [ - 697 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.7.0", - "Node name for S&R": "LTXVConcatAVLatent", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 322, - "type": "SamplerCustomAdvanced", - "pos": [ - 1230, - 7100 - ], - "size": [ - 212.3638671875, - 204.64497833251954 - ], - "flags": {}, - "order": 2, - "mode": 0, - "inputs": [ - { - "localized_name": "noise", - "name": "noise", - "type": "NOISE", - "link": 688 - }, - { - "localized_name": "guider", - "name": "guider", - "type": "GUIDER", - "link": 679 - }, - { - "localized_name": "sampler", - "name": "sampler", - "type": "SAMPLER", - "link": 680 - }, - { - "localized_name": "sigmas", - "name": "sigmas", - "type": "SIGMAS", - "link": 681 - }, - { - "localized_name": "latent_image", - "name": "latent_image", - "type": "LATENT", - "link": 697 - } - ], - "outputs": [ - { - "localized_name": "output", - "name": "output", - "type": "LATENT", - "links": [ - 1023 - ] - }, - { - "localized_name": "denoised_output", - "name": "denoised_output", - "type": "LATENT", - "links": [] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.60", - "Node name for S&R": "SamplerCustomAdvanced", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 417, - "type": "LTXVSeparateAVLatent", - "pos": [ - 1450, - 7100 - ], - "size": [ - 193.2916015625, - 46 - ], - "flags": {}, - "order": 12, - "mode": 0, - "inputs": [ - { - "localized_name": "av_latent", - "name": "av_latent", - "type": "LATENT", - "link": 1023 - } - ], - "outputs": [ - { - "localized_name": "video_latent", - "name": "video_latent", - "type": "LATENT", - "links": [ - 1024 - ] - }, - { - "localized_name": "audio_latent", - "name": "audio_latent", - "type": "LATENT", - "links": [ - 1025 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.17.2", - "Node name for S&R": "LTXVSeparateAVLatent" - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 336, - "type": "LTXVLatentUpsampler", - "pos": [ - 160, - 7410 - ], - "size": [ - 320, - 66 - ], - "flags": {}, - "order": 9, - "mode": 0, - "inputs": [ - { - "localized_name": "samples", - "name": "samples", - "type": "LATENT", - "link": 878 - }, - { - "localized_name": "upscale_model", - "name": "upscale_model", - "type": "LATENT_UPSCALE_MODEL", - "link": 1038 - }, - { - "localized_name": "vae", - "name": "vae", - "type": "VAE", - "link": 717 - } - ], - "outputs": [ - { - "localized_name": "LATENT", - "name": "LATENT", - "type": "LATENT", - "links": [ - 713 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.14.1", - "Node name for S&R": "LTXVLatentUpsampler", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 333, - "type": "LatentUpscaleModelLoader", - "pos": [ - -200, - 7300 - ], - "size": [ - 330, - 58 - ], - "flags": {}, - "order": 1, - "mode": 0, - "inputs": [], - "outputs": [ - { - "localized_name": "LATENT_UPSCALE_MODEL", - "name": "LATENT_UPSCALE_MODEL", - "type": "LATENT_UPSCALE_MODEL", - "links": [ - 710 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.16.4", - "Node name for S&R": "LatentUpscaleModelLoader" - }, - "widgets_values": [ - "ltx-2.3-spatial-upscaler-x1.5-1.0.safetensors" - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 339, - "type": "PrimitiveBoolean", - "pos": [ - 160, - 7520 - ], - "size": [ - 290, - 60 - ], - "flags": {}, - "order": 10, - "mode": 0, - "inputs": [ - { - "localized_name": "value", - "name": "value", - "type": "BOOLEAN", - "widget": { - "name": "value" - }, - "link": 719 - } - ], - "outputs": [ - { - "localized_name": "BOOLEAN", - "name": "BOOLEAN", - "type": "BOOLEAN", - "links": [ - 718 - ] - } - ], - "title": "3rd Stage: False = 1.5x, True = 2x", - "properties": { - "cnr_id": "comfy-core", - "ver": "0.17.2", - "Node name for S&R": "PrimitiveBoolean" - }, - "widgets_values": [ - true - ], - "color": "#222", - "bgcolor": "#000" - } - ], - "groups": [], - "links": [ - { - "id": 679, - "origin_id": 324, - "origin_slot": 0, - "target_id": 322, - "target_slot": 1, - "type": "GUIDER" - }, - { - "id": 680, - "origin_id": 325, - "origin_slot": 0, - "target_id": 322, - "target_slot": 2, - "type": "SAMPLER" - }, - { - "id": 681, - "origin_id": 326, - "origin_slot": 0, - "target_id": 322, - "target_slot": 3, - "type": "SIGMAS" - }, - { - "id": 683, - "origin_id": -10, - "origin_slot": 0, - "target_id": 324, - "target_slot": 0, - "type": "MODEL" - }, - { - "id": 686, - "origin_id": -10, - "origin_slot": 0, - "target_id": 326, - "target_slot": 0, - "type": "MODEL" - }, - { - "id": 688, - "origin_id": -10, - "origin_slot": 3, - "target_id": 322, - "target_slot": 0, - "type": "NOISE" - }, - { - "id": 697, - "origin_id": 329, - "origin_slot": 0, - "target_id": 322, - "target_slot": 4, - "type": "LATENT" - }, - { - "id": 700, - "origin_id": -10, - "origin_slot": 7, - "target_id": 329, - "target_slot": 1, - "type": "LATENT" - }, - { - "id": 708, - "origin_id": -10, - "origin_slot": 6, - "target_id": 331, - "target_slot": 2, - "type": "LATENT" - }, - { - "id": 710, - "origin_id": 333, - "origin_slot": 0, - "target_id": 334, - "target_slot": 1, - "type": "LATENT_UPSCALE_MODEL" - }, - { - "id": 712, - "origin_id": 334, - "origin_slot": 0, - "target_id": 332, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 713, - "origin_id": 336, - "origin_slot": 0, - "target_id": 332, - "target_slot": 1, - "type": "LATENT" - }, - { - "id": 716, - "origin_id": -10, - "origin_slot": 4, - "target_id": 334, - "target_slot": 2, - "type": "VAE" - }, - { - "id": 717, - "origin_id": -10, - "origin_slot": 4, - "target_id": 336, - "target_slot": 2, - "type": "VAE" - }, - { - "id": 718, - "origin_id": 339, - "origin_slot": 0, - "target_id": 332, - "target_slot": 2, - "type": "BOOLEAN" - }, - { - "id": 719, - "origin_id": -10, - "origin_slot": 8, - "target_id": 339, - "target_slot": 0, - "type": "BOOLEAN" - }, - { - "id": 877, - "origin_id": 331, - "origin_slot": 2, - "target_id": 334, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 878, - "origin_id": 331, - "origin_slot": 2, - "target_id": 336, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 919, - "origin_id": -10, - "origin_slot": 1, - "target_id": 331, - "target_slot": 0, - "type": "CONDITIONING" - }, - { - "id": 920, - "origin_id": -10, - "origin_slot": 2, - "target_id": 331, - "target_slot": 1, - "type": "CONDITIONING" - }, - { - "id": 996, - "origin_id": 411, - "origin_slot": 0, - "target_id": 324, - "target_slot": 1, - "type": "CONDITIONING" - }, - { - "id": 997, - "origin_id": 411, - "origin_slot": 1, - "target_id": 324, - "target_slot": 2, - "type": "CONDITIONING" - }, - { - "id": 998, - "origin_id": 411, - "origin_slot": 2, - "target_id": 329, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 999, - "origin_id": 331, - "origin_slot": 0, - "target_id": 411, - "target_slot": 0, - "type": "CONDITIONING" - }, - { - "id": 1000, - "origin_id": 331, - "origin_slot": 1, - "target_id": 411, - "target_slot": 1, - "type": "CONDITIONING" - }, - { - "id": 1001, - "origin_id": 332, - "origin_slot": 0, - "target_id": 411, - "target_slot": 3, - "type": "LATENT" - }, - { - "id": 1002, - "origin_id": -10, - "origin_slot": 4, - "target_id": 411, - "target_slot": 2, - "type": "VAE" - }, - { - "id": 1005, - "origin_id": -10, - "origin_slot": 9, - "target_id": 411, - "target_slot": 4, - "type": "IMAGE" - }, - { - "id": 1023, - "origin_id": 322, - "origin_slot": 0, - "target_id": 417, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 1024, - "origin_id": 417, - "origin_slot": 0, - "target_id": -20, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 1025, - "origin_id": 417, - "origin_slot": 1, - "target_id": -20, - "target_slot": 1, - "type": "LATENT" - }, - { - "id": 1038, - "origin_id": -10, - "origin_slot": 10, - "target_id": 336, - "target_slot": 1, - "type": "LATENT_UPSCALE_MODEL" - } - ], - "extra": { - "workflowRendererVersion": "LG" - } - }, - { - "id": "958b2c7a-1f75-4a30-84a8-892bddb9e426", - "version": 1, - "state": { - "lastGroupId": 33, - "lastNodeId": 443, - "lastLinkId": 1080, - "lastRerouteId": 0 - }, - "revision": 0, - "config": {}, - "name": "Model Loader", - "inputNode": { - "id": -10, - "bounding": [ - 2340, - 6080, - 135.654296875, - 80 - ] - }, - "outputNode": { - "id": -20, - "bounding": [ - 3560, - 5580, - 187.052734375, - 200 - ] - }, - "inputs": [ - { - "id": "9ae347e5-8a40-4407-bec5-7781d672dd85", - "name": "value", - "type": "STRING", - "linkIds": [ - 1064 - ], - "label": "Prompt", - "pos": [ - 2455.654296875, - 6100 - ] - }, - { - "id": "32b8ff31-4396-4336-aeaf-ff84d858a4d9", - "name": "value_1", - "type": "INT", - "linkIds": [ - 1018 - ], - "label": "length (seconds)", - "pos": [ - 2455.654296875, - 6120 - ] - } - ], - "outputs": [ - { - "id": "a72640aa-8055-45bb-8cbb-f68ec7413515", - "name": "optimized_model", - "type": "MODEL", - "linkIds": [ - 1067 - ], - "pos": [ - 3580, - 5600 - ] - }, - { - "id": "cfb48567-9b75-46f5-8b71-385f3388173c", - "name": "VAE", - "type": "VAE", - "linkIds": [ - 721 - ], - "label": "video_vae", - "pos": [ - 3580, - 5620 - ] - }, - { - "id": "4e095394-8a92-47e4-8993-49fb4ce73bd8", - "name": "VAE_1", - "type": "VAE", - "linkIds": [ - 722 - ], - "label": "audio_vae", - "pos": [ - 3580, - 5640 - ] - }, - { - "id": "8bbe34b1-d094-40a3-9c04-5a19cd7b2af2", - "name": "positive", - "type": "CONDITIONING", - "linkIds": [ - 726 - ], - "pos": [ - 3580, - 5660 - ] - }, - { - "id": "dac0ad69-373b-4e47-b0cd-eb8fd18e1c07", - "name": "negative", - "type": "CONDITIONING", - "linkIds": [ - 727 - ], - "pos": [ - 3580, - 5680 - ] - }, - { - "id": "b4d0868d-86e8-42ba-83fc-44c5324ac35d", - "name": "INT", - "type": "INT", - "linkIds": [ - 729 - ], - "label": "length_seconds", - "pos": [ - 3580, - 5700 - ] - }, - { - "id": "12a111ff-cd34-4b59-bca3-4c5f24626a62", - "name": "NOISE", - "type": "NOISE", - "linkIds": [ - 730 - ], - "label": "noise", - "pos": [ - 3580, - 5720 - ] - }, - { - "id": "9f03af49-218d-4431-a2e9-891d16a8eb05", - "name": "LATENT_UPSCALE_MODEL", - "type": "LATENT_UPSCALE_MODEL", - "linkIds": [ - 1034 - ], - "label": "2x_spatial_upscale_model", - "pos": [ - 3580, - 5740 - ] - } - ], - "widgets": [], - "nodes": [ - { - "id": 338, - "type": "VAELoaderKJ", - "pos": [ - 2560, - 5780 - ], - "size": [ - 270, - 106 - ], - "flags": {}, - "order": 0, - "mode": 0, - "inputs": [], - "outputs": [ - { - "localized_name": "VAE", - "name": "VAE", - "type": "VAE", - "links": [ - 722 - ] - } - ], - "title": "Audio VAELoader KJ", - "properties": { - "cnr_id": "comfyui-kjnodes", - "ver": "487c7d86a0230aae3d8c0a37d517159b73834f85", - "Node name for S&R": "VAELoaderKJ" - }, - "widgets_values": [ - "LTX23_audio_vae_bf16.safetensors", - "main_device", - "bf16" - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 337, - "type": "VAELoaderKJ", - "pos": [ - 2560, - 5630 - ], - "size": [ - 270, - 106 - ], - "flags": {}, - "order": 1, - "mode": 0, - "inputs": [], - "outputs": [ - { - "localized_name": "VAE", - "name": "VAE", - "type": "VAE", - "links": [ - 721 - ] - } - ], - "title": "Video VAELoader KJ", - "properties": { - "cnr_id": "comfyui-kjnodes", - "ver": "1.2.5", - "Node name for S&R": "VAELoaderKJ" - }, - "widgets_values": [ - "LTX23_video_vae_bf16.safetensors", - "main_device", - "bf16" - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 413, - "type": "LTXAVTextEncoderLoader", - "pos": [ - 2560, - 5940 - ], - "size": [ - 318.444921875, - 122 - ], - "flags": {}, - "order": 2, - "mode": 0, - "inputs": [], - "outputs": [ - { - "localized_name": "CLIP", - "name": "CLIP", - "type": "CLIP", - "links": [ - 1054 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.14.1", - "Node name for S&R": "LTXAVTextEncoderLoader" - }, - "widgets_values": [ - "gemma_3_12B_it_fp4_mixed.safetensors", - "ltx-2.3-22b-dev-fp8.safetensors", - "default" - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 430, - "type": "CLIPTextEncode", - "pos": [ - 2910, - 5950 - ], - "size": [ - 274.3658203125, - 88 - ], - "flags": {}, - "order": 13, - "mode": 0, - "inputs": [ - { - "localized_name": "clip", - "name": "clip", - "type": "CLIP", - "link": 1054 - }, - { - "localized_name": "text", - "name": "text", - "type": "STRING", - "widget": { - "name": "text" - }, - "link": 1064 - } - ], - "outputs": [ - { - "localized_name": "CONDITIONING", - "name": "CONDITIONING", - "type": "CONDITIONING", - "slot_index": 0, - "links": [ - 1062, - 1063 - ] - } - ], - "title": "CLIP Text Encode (Positive Prompt)", - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.28", - "Node name for S&R": "CLIPTextEncode" - }, - "widgets_values": [ - "" - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 361, - "type": "ConditioningZeroOut", - "pos": [ - 2920, - 6090 - ], - "size": [ - 204.134765625, - 26 - ], - "flags": {}, - "order": 10, - "mode": 0, - "inputs": [ - { - "localized_name": "conditioning", - "name": "conditioning", - "type": "CONDITIONING", - "link": 1063 - } - ], - "outputs": [ - { - "localized_name": "CONDITIONING", - "name": "CONDITIONING", - "type": "CONDITIONING", - "links": [ - 725 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.7.0", - "Node name for S&R": "ConditioningZeroOut", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 363, - "type": "LTXVConditioning", - "pos": [ - 3210, - 5960 - ], - "size": [ - 210, - 80 - ], - "flags": {}, - "order": 11, - "mode": 0, - "inputs": [ - { - "localized_name": "positive", - "name": "positive", - "type": "CONDITIONING", - "link": 1062 - }, - { - "localized_name": "negative", - "name": "negative", - "type": "CONDITIONING", - "link": 725 - } - ], - "outputs": [ - { - "localized_name": "positive", - "name": "positive", - "type": "CONDITIONING", - "links": [ - 726 - ] - }, - { - "localized_name": "negative", - "name": "negative", - "type": "CONDITIONING", - "links": [ - 727 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.56", - "Node name for S&R": "LTXVConditioning", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [ - 24 - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 419, - "type": "LatentUpscaleModelLoader", - "pos": [ - 2560, - 6110 - ], - "size": [ - 330, - 58 - ], - "flags": {}, - "order": 3, - "mode": 0, - "inputs": [], - "outputs": [ - { - "localized_name": "LATENT_UPSCALE_MODEL", - "name": "LATENT_UPSCALE_MODEL", - "type": "LATENT_UPSCALE_MODEL", - "links": [ - 1034 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.16.4", - "Node name for S&R": "LatentUpscaleModelLoader" - }, - "widgets_values": [ - "ltx-2.3-spatial-upscaler-x2-1.1.safetensors" - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 364, - "type": "RandomNoise", - "pos": [ - 2860, - 5750 - ], - "size": [ - 210, - 82 - ], - "flags": {}, - "order": 4, - "mode": 0, - "inputs": [ - { - "label": "noise_seed", - "localized_name": "noise_seed", - "name": "noise_seed", - "type": "INT", - "widget": { - "name": "noise_seed" - }, - "link": null - } - ], - "outputs": [ - { - "localized_name": "NOISE", - "name": "NOISE", - "type": "NOISE", - "links": [ - 730 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.56", - "Node name for S&R": "RandomNoise", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [ - 0, - "fixed" - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 365, - "type": "07609e03-45f4-49c7-a233-a933ebf95094", - "pos": [ - 2860, - 5640 - ], - "size": [ - 210, - 58 - ], - "flags": {}, - "order": 12, - "mode": 0, - "inputs": [ - { - "label": "Seconds", - "name": "value", - "type": "INT", - "widget": { - "name": "value" - }, - "link": 1018 - } - ], - "outputs": [ - { - "localized_name": "INT", - "name": "INT", - "type": "INT", - "links": [ - 729 - ] - } - ], - "properties": { - "proxyWidgets": [ - [ - "-1", - "value" - ] - ], - "cnr_id": "comfy-core", - "ver": "0.16.4" - }, - "widgets_values": [ - 5 - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 443, - "type": "VAELoaderKJ", - "pos": [ - 2560, - 5470 - ], - "size": [ - 270, - 110 - ], - "flags": {}, - "order": 5, - "mode": 0, - "inputs": [], - "outputs": [ - { - "localized_name": "VAE", - "name": "VAE", - "type": "VAE", - "links": [ - 1072 - ] - } - ], - "title": "Tiny VAELoader KJ", - "properties": { - "cnr_id": "comfyui-kjnodes", - "ver": "1.2.5", - "Node name for S&R": "VAELoaderKJ" - }, - "widgets_values": [ - "taeltx2_3.safetensors", - "main_device", - "bf16" - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 442, - "type": "LTX2SamplingPreviewOverride", - "pos": [ - 2850, - 5470 - ], - "size": [ - 270, - 100 - ], - "flags": {}, - "order": 8, - "mode": 0, - "inputs": [ - { - "localized_name": "model", - "name": "model", - "type": "MODEL", - "link": 1073 - }, - { - "localized_name": "latent_upscale_model", - "name": "latent_upscale_model", - "shape": 7, - "type": "LATENT_UPSCALE_MODEL", - "link": null - }, - { - "localized_name": "vae", - "name": "vae", - "shape": 7, - "type": "VAE", - "link": 1072 - } - ], - "outputs": [ - { - "localized_name": "MODEL", - "name": "MODEL", - "type": "MODEL", - "links": [ - 1074 - ] - } - ], - "properties": { - "cnr_id": "comfyui-kjnodes", - "ver": "1.3.4", - "Node name for S&R": "LTX2SamplingPreviewOverride" - }, - "widgets_values": [ - 16 - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 426, - "type": "CheckpointLoaderSimple", - "pos": [ - 2560, - 5330 - ], - "size": [ - 270, - 98 - ], - "flags": {}, - "order": 6, - "mode": 0, - "inputs": [], - "outputs": [ - { - "localized_name": "MODEL", - "name": "MODEL", - "type": "MODEL", - "links": [ - 1073 - ] - }, - { - "localized_name": "CLIP", - "name": "CLIP", - "type": "CLIP", - "links": null - }, - { - "localized_name": "VAE", - "name": "VAE", - "type": "VAE", - "links": null - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.17.2", - "Node name for S&R": "CheckpointLoaderSimple" - }, - "widgets_values": [ - "ltx-2.3-22b-dev-fp8.safetensors" - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 429, - "type": "LoraLoaderModelOnly", - "pos": [ - 3140, - 5480 - ], - "size": [ - 260, - 82 - ], - "flags": {}, - "order": 9, - "mode": 0, - "inputs": [ - { - "localized_name": "model", - "name": "model", - "type": "MODEL", - "link": 1074 - } - ], - "outputs": [ - { - "localized_name": "MODEL", - "name": "MODEL", - "type": "MODEL", - "links": [ - 1067 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.75", - "Node name for S&R": "LoraLoaderModelOnly", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65, - "models": [ - { - "name": "ltx-2.3-22b-distilled-lora-384.safetensors", - "url": "https://huggingface.co/Lightricks/LTX-2.3/resolve/main/ltx-2.3-22b-distilled-lora-384.safetensors", - "directory": "loras" - } - ] - }, - "widgets_values": [ - "ltx2\\ltx-2.3-22b-distilled-lora-dynamic_fro09_avg_rank_105_bf16.safetensors", - 0.5 - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 441, - "type": "MarkdownNote", - "pos": [ - 2160, - 5500 - ], - "size": [ - 370, - 320 - ], - "flags": {}, - "order": 7, - "mode": 0, - "inputs": [], - "outputs": [], - "title": "FAQ", - "properties": {}, - "widgets_values": [ - "# FAQ\n\n**How do I set the length of the video?**\n\nYou can set the length of the video in the Model Loader Subgraph →\n\n**I keep getting errors when running this workflow!**\n\nDouble check that all of the correct models loaded. This is using the newest version of 2x upscaler (v1.1). You can change it to the older one under the Model Loader.\n\n**Where do I download these models??**\n\nThere is a good list of models and download links here: \n\nAlso the latest v1.1 spatial upscaler can be found here: \n\nThe tiny vae (taeltx2_3.safetensors) is found here:\n\n\n\n\n\n\n\n\n\n" - ], - "color": "#432", - "bgcolor": "#653" - } - ], - "groups": [], - "links": [ - { - "id": 721, - "origin_id": 337, - "origin_slot": 0, - "target_id": -20, - "target_slot": 1, - "type": "VAE" - }, - { - "id": 722, - "origin_id": 338, - "origin_slot": 0, - "target_id": -20, - "target_slot": 2, - "type": "VAE" - }, - { - "id": 725, - "origin_id": 361, - "origin_slot": 0, - "target_id": 363, - "target_slot": 1, - "type": "CONDITIONING" - }, - { - "id": 726, - "origin_id": 363, - "origin_slot": 0, - "target_id": -20, - "target_slot": 3, - "type": "CONDITIONING" - }, - { - "id": 727, - "origin_id": 363, - "origin_slot": 1, - "target_id": -20, - "target_slot": 4, - "type": "CONDITIONING" - }, - { - "id": 729, - "origin_id": 365, - "origin_slot": 0, - "target_id": -20, - "target_slot": 5, - "type": "INT" - }, - { - "id": 730, - "origin_id": 364, - "origin_slot": 0, - "target_id": -20, - "target_slot": 6, - "type": "NOISE" - }, - { - "id": 1018, - "origin_id": -10, - "origin_slot": 1, - "target_id": 365, - "target_slot": 0, - "type": "INT" - }, - { - "id": 1034, - "origin_id": 419, - "origin_slot": 0, - "target_id": -20, - "target_slot": 7, - "type": "LATENT_UPSCALE_MODEL" - }, - { - "id": 1054, - "origin_id": 413, - "origin_slot": 0, - "target_id": 430, - "target_slot": 0, - "type": "CLIP" - }, - { - "id": 1062, - "origin_id": 430, - "origin_slot": 0, - "target_id": 363, - "target_slot": 0, - "type": "CONDITIONING" - }, - { - "id": 1063, - "origin_id": 430, - "origin_slot": 0, - "target_id": 361, - "target_slot": 0, - "type": "CONDITIONING" - }, - { - "id": 1064, - "origin_id": -10, - "origin_slot": 0, - "target_id": 430, - "target_slot": 1, - "type": "STRING" - }, - { - "id": 1067, - "origin_id": 429, - "origin_slot": 0, - "target_id": -20, - "target_slot": 0, - "type": "MODEL" - }, - { - "id": 1072, - "origin_id": 443, - "origin_slot": 0, - "target_id": 442, - "target_slot": 2, - "type": "VAE" - }, - { - "id": 1073, - "origin_id": 426, - "origin_slot": 0, - "target_id": 442, - "target_slot": 0, - "type": "MODEL" - }, - { - "id": 1074, - "origin_id": 442, - "origin_slot": 0, - "target_id": 429, - "target_slot": 0, - "type": "MODEL" - } - ], - "extra": { - "workflowRendererVersion": "LG" - } - }, - { - "id": "07609e03-45f4-49c7-a233-a933ebf95094", - "version": 1, - "state": { - "lastGroupId": 33, - "lastNodeId": 443, - "lastLinkId": 1080, - "lastRerouteId": 0 - }, - "revision": 0, - "config": {}, - "name": "length (seconds)", - "inputNode": { - "id": -10, - "bounding": [ - -662.1270448096031, - 4310.82566166648, - 120, - 60 - ] - }, - "outputNode": { - "id": -20, - "bounding": [ - 255.79328565979654, - 4300.82566166648, - 120, - 60 - ] - }, - "inputs": [ - { - "id": "df88c383-3299-420f-8c75-fd0d6a39a9af", - "name": "value", - "type": "INT", - "linkIds": [ - 1071 - ], - "label": "Seconds", - "pos": [ - -562.1270448096031, - 4330.82566166648 - ] - } - ], - "outputs": [ - { - "id": "9b5a4dc0-bb87-45a3-bd3d-551edc18c371", - "name": "INT", - "type": "INT", - "linkIds": [ - 359 - ], - "localized_name": "INT", - "pos": [ - 275.79328565979654, - 4320.82566166648 - ] - } - ], - "widgets": [], - "nodes": [ - { - "id": 137, - "type": "easy mathInt", - "pos": [ - -244.96788124271472, - 4296.913950056486 - ], - "size": [ - 210, - 106 - ], - "flags": {}, - "order": 0, - "mode": 0, - "inputs": [ - { - "localized_name": "a", - "name": "a", - "type": "INT", - "widget": { - "name": "a" - }, - "link": 360 - } - ], - "outputs": [ - { - "localized_name": "INT", - "name": "INT", - "type": "INT", - "links": [ - 358 - ] - } - ], - "properties": { - "cnr_id": "comfyui-easy-use", - "ver": "1.3.4", - "Node name for S&R": "easy mathInt" - }, - "widgets_values": [ - 0, - 24, - "multiply" - ] - }, - { - "id": 138, - "type": "easy mathInt", - "pos": [ - -14.206714340203394, - 4288.737373276474 - ], - "size": [ - 210, - 106 - ], - "flags": {}, - "order": 1, - "mode": 0, - "inputs": [ - { - "localized_name": "a", - "name": "a", - "type": "INT", - "widget": { - "name": "a" - }, - "link": 358 - } - ], - "outputs": [ - { - "localized_name": "INT", - "name": "INT", - "type": "INT", - "links": [ - 359 - ] - } - ], - "properties": { - "cnr_id": "comfyui-easy-use", - "ver": "1.3.4", - "Node name for S&R": "easy mathInt" - }, - "widgets_values": [ - 0, - 1, - "add" - ] - }, - { - "id": 139, - "type": "PrimitiveInt", - "pos": [ - -482.12704480960315, - 4310.54157802317 - ], - "size": [ - 210, - 87.4510511866738 - ], - "flags": {}, - "order": 2, - "mode": 0, - "inputs": [ - { - "localized_name": "value", - "name": "value", - "type": "INT", - "widget": { - "name": "value" - }, - "link": 1071 - } - ], - "outputs": [ - { - "localized_name": "INT", - "name": "INT", - "type": "INT", - "links": [ - 360 - ] - } - ], - "title": "Seconds", - "properties": { - "cnr_id": "comfy-core", - "ver": "0.16.4", - "Node name for S&R": "PrimitiveInt" - }, - "widgets_values": [ - 5, - "fixed" - ] - } - ], - "groups": [], - "links": [ - { - "id": 360, - "origin_id": 139, - "origin_slot": 0, - "target_id": 137, - "target_slot": 0, - "type": "INT" - }, - { - "id": 358, - "origin_id": 137, - "origin_slot": 0, - "target_id": 138, - "target_slot": 0, - "type": "INT" - }, - { - "id": 359, - "origin_id": 138, - "origin_slot": 0, - "target_id": -20, - "target_slot": 0, - "type": "INT" - }, - { - "id": 1071, - "origin_id": -10, - "origin_slot": 0, - "target_id": 139, - "target_slot": 0, - "type": "INT" - } - ], - "extra": { - "workflowRendererVersion": "LG" - } - }, - { - "id": "360d64f7-96cd-42a6-9b13-6eb0387e17d9", - "version": 1, - "state": { - "lastGroupId": 33, - "lastNodeId": 443, - "lastLinkId": 1080, - "lastRerouteId": 0 - }, - "revision": 0, - "config": {}, - "name": "3 Stage Upscale", - "inputNode": { - "id": -10, - "bounding": [ - 4330, - 5940, - 187.052734375, - 240 - ] - }, - "outputNode": { - "id": -20, - "bounding": [ - 5670, - 5940, - 120, - 60 - ] - }, - "inputs": [ - { - "id": "f3d52cbc-fafe-46a6-978c-2eaff2c185c3", - "name": "model", - "type": "MODEL", - "linkIds": [ - 3, - 14, - 23 - ], - "pos": [ - 4497.052734375, - 5960 - ] - }, - { - "id": "d0e8f1b7-b19b-49cd-8a2b-aa4a639857b3", - "name": "positive", - "type": "CONDITIONING", - "linkIds": [ - 4, - 15, - 871 - ], - "pos": [ - 4497.052734375, - 5980 - ] - }, - { - "id": "36e3ff32-f9b2-41d9-94de-9d8d9a91f1a6", - "name": "negative", - "type": "CONDITIONING", - "linkIds": [ - 5, - 16, - 872 - ], - "pos": [ - 4497.052734375, - 6000 - ] - }, - { - "id": "3f2a7956-c7c2-4478-99ad-2487644cbbc2", - "name": "latent_image", - "type": "LATENT", - "linkIds": [ - 6 - ], - "pos": [ - 4497.052734375, - 6020 - ] - }, - { - "id": "cc2edb71-c53a-4056-8a85-5d8199e79c72", - "name": "noise", - "type": "NOISE", - "linkIds": [ - 7, - 17, - 26 - ], - "pos": [ - 4497.052734375, - 6040 - ] - }, - { - "id": "d162c9e7-24cc-4421-a6fb-d253bfae8883", - "name": "vae", - "type": "VAE", - "linkIds": [ - 32, - 33 - ], - "label": "video_vae", - "pos": [ - 4497.052734375, - 6060 - ] - }, - { - "id": "e9cd552e-a79c-4b6e-8508-4ad3a9f4218b", - "name": "image", - "type": "IMAGE", - "linkIds": [ - 29, - 30 - ], - "pos": [ - 4497.052734375, - 6080 - ] - }, - { - "id": "46ddf61f-c9f3-48d0-9f11-6e64e8521966", - "name": "value", - "type": "BOOLEAN", - "linkIds": [ - 40 - ], - "label": "Upscale 2x on Stage 3", - "pos": [ - 4497.052734375, - 6100 - ] - }, - { - "id": "45629d5b-0676-4430-aa3d-8b0cf516de0a", - "name": "multi_input", - "type": "IMAGE", - "linkIds": [ - 1006, - 1007 - ], - "pos": [ - 4497.052734375, - 6120 - ] - }, - { - "id": "b380a19f-38a6-41ca-be93-072da1e263f3", - "name": "upscale_model", - "type": "LATENT_UPSCALE_MODEL", - "linkIds": [ - 1036, - 1039 - ], - "label": "2x_spatial_upscale_model", - "pos": [ - 4497.052734375, - 6140 - ] - } - ], - "outputs": [ - { - "id": "2fae6e6d-6667-450e-933b-b16e88c7968f", - "name": "output", - "type": "LATENT", - "linkIds": [ - 765 - ], - "label": "av_output", - "pos": [ - 5690, - 5960 - ] - } - ], - "widgets": [], - "nodes": [ - { - "id": 371, - "type": "LTXVConcatAVLatent", - "pos": [ - 5470, - 5950 - ], - "size": [ - 181.308203125, - 46 - ], - "flags": {}, - "order": 4, - "mode": 0, - "inputs": [ - { - "localized_name": "video_latent", - "name": "video_latent", - "type": "LATENT", - "link": 852 - }, - { - "localized_name": "audio_latent", - "name": "audio_latent", - "type": "LATENT", - "link": 1080 - } - ], - "outputs": [ - { - "localized_name": "latent", - "name": "latent", - "type": "LATENT", - "links": [ - 765 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.17.2", - "Node name for S&R": "LTXVConcatAVLatent" - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 383, - "type": "LTXVCropGuides", - "pos": [ - 5200, - 5950 - ], - "size": [ - 260, - 70 - ], - "flags": {}, - "order": 5, - "mode": 0, - "inputs": [ - { - "localized_name": "positive", - "name": "positive", - "type": "CONDITIONING", - "link": 871 - }, - { - "localized_name": "negative", - "name": "negative", - "type": "CONDITIONING", - "link": 872 - }, - { - "localized_name": "latent", - "name": "latent", - "type": "LATENT", - "link": 1026 - } - ], - "outputs": [ - { - "localized_name": "positive", - "name": "positive", - "type": "CONDITIONING", - "links": null - }, - { - "localized_name": "negative", - "name": "negative", - "type": "CONDITIONING", - "links": null - }, - { - "localized_name": "latent", - "name": "latent", - "type": "LATENT", - "links": [ - 852 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.17.2", - "Node name for S&R": "LTXVCropGuides" - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 2, - "type": "afa29944-eb58-40a5-8e76-484f2503fa0c", - "pos": [ - 4540, - 5950 - ], - "size": [ - 190, - 195.734375 - ], - "flags": {}, - "order": 1, - "mode": 0, - "inputs": [ - { - "name": "model", - "type": "MODEL", - "link": 3 - }, - { - "name": "positive", - "type": "CONDITIONING", - "link": 4 - }, - { - "name": "negative", - "type": "CONDITIONING", - "link": 5 - }, - { - "name": "latent_image", - "type": "LATENT", - "link": 6 - }, - { - "name": "noise", - "type": "NOISE", - "link": 7 - } - ], - "outputs": [ - { - "name": "video_latent", - "type": "LATENT", - "links": [ - 837 - ] - }, - { - "name": "audio_latent", - "type": "LATENT", - "links": [ - 794 - ] - } - ], - "properties": { - "proxyWidgets": [], - "cnr_id": "comfy-core", - "ver": "0.17.2" - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 440, - "type": "Note", - "pos": [ - 4970, - 5810 - ], - "size": [ - 210, - 88 - ], - "flags": {}, - "order": 0, - "mode": 0, - "inputs": [], - "outputs": [], - "properties": {}, - "widgets_values": [ - "Bypass Stage #3 to skip the 2nd upscale stage." - ], - "color": "#223", - "bgcolor": "#335" - }, - { - "id": 8, - "type": "535e663f-da59-47c1-aab5-c78ff202cb16", - "pos": [ - 4970, - 5950 - ], - "size": [ - 218.19140625, - 330.9668884277344 - ], - "flags": {}, - "order": 3, - "mode": 4, - "inputs": [ - { - "name": "model", - "type": "MODEL", - "link": 23 - }, - { - "name": "positive", - "type": "CONDITIONING", - "link": 875 - }, - { - "name": "negative", - "type": "CONDITIONING", - "link": 876 - }, - { - "name": "noise", - "type": "NOISE", - "link": 26 - }, - { - "label": "video_vae", - "name": "vae", - "type": "VAE", - "link": 33 - }, - { - "name": "image", - "type": "IMAGE", - "link": 30 - }, - { - "label": "video_latent", - "name": "samples", - "type": "LATENT", - "link": 27 - }, - { - "name": "audio_latent", - "type": "LATENT", - "link": 1079 - }, - { - "label": "use 2x on stage 3", - "name": "value", - "type": "BOOLEAN", - "widget": { - "name": "value" - }, - "link": 40 - }, - { - "name": "multi_input", - "type": "IMAGE", - "link": 1007 - }, - { - "label": "2x_upscale_model", - "name": "upscale_model", - "type": "LATENT_UPSCALE_MODEL", - "link": 1039 - } - ], - "outputs": [ - { - "label": "video_latent", - "name": "output", - "type": "LATENT", - "links": [ - 1026 - ] - }, - { - "name": "audio_latent", - "type": "LATENT", - "links": [] - } - ], - "properties": { - "proxyWidgets": [ - [ - "-1", - "value" - ] - ], - "cnr_id": "comfy-core", - "ver": "0.17.2" - }, - "widgets_values": [ - true - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 6, - "type": "ce8f7842-cb03-41b4-9b1e-8d032b2a4e72", - "pos": [ - 4750, - 5950 - ], - "size": [ - 200, - 299.71875 - ], - "flags": {}, - "order": 2, - "mode": 0, - "inputs": [ - { - "name": "model", - "type": "MODEL", - "link": 14 - }, - { - "name": "positive", - "type": "CONDITIONING", - "link": 15 - }, - { - "name": "negative", - "type": "CONDITIONING", - "link": 16 - }, - { - "name": "noise", - "type": "NOISE", - "link": 17 - }, - { - "label": "video_vae", - "name": "vae", - "type": "VAE", - "link": 32 - }, - { - "name": "image", - "type": "IMAGE", - "link": 29 - }, - { - "label": "video_latent", - "name": "samples", - "type": "LATENT", - "link": 837 - }, - { - "name": "audio_latent", - "type": "LATENT", - "link": 794 - }, - { - "name": "multi_input", - "type": "IMAGE", - "link": 1006 - }, - { - "name": "upscale_model", - "type": "LATENT_UPSCALE_MODEL", - "link": 1036 - } - ], - "outputs": [ - { - "name": "video_latent", - "type": "LATENT", - "links": [ - 27 - ] - }, - { - "name": "audio_latent", - "type": "LATENT", - "links": [ - 1079, - 1080 - ] - }, - { - "name": "positive", - "type": "CONDITIONING", - "links": [ - 855, - 875 - ] - }, - { - "name": "negative", - "type": "CONDITIONING", - "links": [ - 856, - 876 - ] - } - ], - "properties": { - "proxyWidgets": [], - "cnr_id": "comfy-core", - "ver": "0.17.2" - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - } - ], - "groups": [], - "links": [ - { - "id": 27, - "origin_id": 6, - "origin_slot": 0, - "target_id": 8, - "target_slot": 6, - "type": "LATENT" - }, - { - "id": 3, - "origin_id": -10, - "origin_slot": 0, - "target_id": 2, - "target_slot": 0, - "type": "MODEL" - }, - { - "id": 14, - "origin_id": -10, - "origin_slot": 0, - "target_id": 6, - "target_slot": 0, - "type": "MODEL" - }, - { - "id": 23, - "origin_id": -10, - "origin_slot": 0, - "target_id": 8, - "target_slot": 0, - "type": "MODEL" - }, - { - "id": 4, - "origin_id": -10, - "origin_slot": 1, - "target_id": 2, - "target_slot": 1, - "type": "CONDITIONING" - }, - { - "id": 15, - "origin_id": -10, - "origin_slot": 1, - "target_id": 6, - "target_slot": 1, - "type": "CONDITIONING" - }, - { - "id": 5, - "origin_id": -10, - "origin_slot": 2, - "target_id": 2, - "target_slot": 2, - "type": "CONDITIONING" - }, - { - "id": 16, - "origin_id": -10, - "origin_slot": 2, - "target_id": 6, - "target_slot": 2, - "type": "CONDITIONING" - }, - { - "id": 6, - "origin_id": -10, - "origin_slot": 3, - "target_id": 2, - "target_slot": 3, - "type": "LATENT" - }, - { - "id": 7, - "origin_id": -10, - "origin_slot": 4, - "target_id": 2, - "target_slot": 4, - "type": "NOISE" - }, - { - "id": 17, - "origin_id": -10, - "origin_slot": 4, - "target_id": 6, - "target_slot": 3, - "type": "NOISE" - }, - { - "id": 26, - "origin_id": -10, - "origin_slot": 4, - "target_id": 8, - "target_slot": 3, - "type": "NOISE" - }, - { - "id": 32, - "origin_id": -10, - "origin_slot": 5, - "target_id": 6, - "target_slot": 4, - "type": "VAE" - }, - { - "id": 33, - "origin_id": -10, - "origin_slot": 5, - "target_id": 8, - "target_slot": 4, - "type": "VAE" - }, - { - "id": 29, - "origin_id": -10, - "origin_slot": 6, - "target_id": 6, - "target_slot": 5, - "type": "IMAGE" - }, - { - "id": 30, - "origin_id": -10, - "origin_slot": 6, - "target_id": 8, - "target_slot": 5, - "type": "IMAGE" - }, - { - "id": 40, - "origin_id": -10, - "origin_slot": 7, - "target_id": 8, - "target_slot": 8, - "type": "BOOLEAN" - }, - { - "id": 765, - "origin_id": 371, - "origin_slot": 0, - "target_id": -20, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 794, - "origin_id": 2, - "origin_slot": 1, - "target_id": 6, - "target_slot": 7, - "type": "LATENT" - }, - { - "id": 837, - "origin_id": 2, - "origin_slot": 0, - "target_id": 6, - "target_slot": 6, - "type": "LATENT" - }, - { - "id": 852, - "origin_id": 383, - "origin_slot": 2, - "target_id": 371, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 871, - "origin_id": -10, - "origin_slot": 1, - "target_id": 383, - "target_slot": 0, - "type": "CONDITIONING" - }, - { - "id": 872, - "origin_id": -10, - "origin_slot": 2, - "target_id": 383, - "target_slot": 1, - "type": "CONDITIONING" - }, - { - "id": 875, - "origin_id": 6, - "origin_slot": 2, - "target_id": 8, - "target_slot": 1, - "type": "CONDITIONING" - }, - { - "id": 876, - "origin_id": 6, - "origin_slot": 3, - "target_id": 8, - "target_slot": 2, - "type": "CONDITIONING" - }, - { - "id": 1006, - "origin_id": -10, - "origin_slot": 8, - "target_id": 6, - "target_slot": 8, - "type": "IMAGE" - }, - { - "id": 1007, - "origin_id": -10, - "origin_slot": 8, - "target_id": 8, - "target_slot": 9, - "type": "IMAGE" - }, - { - "id": 1026, - "origin_id": 8, - "origin_slot": 0, - "target_id": 383, - "target_slot": 2, - "type": "LATENT" - }, - { - "id": 1036, - "origin_id": -10, - "origin_slot": 9, - "target_id": 6, - "target_slot": 9, - "type": "LATENT_UPSCALE_MODEL" - }, - { - "id": 1039, - "origin_id": -10, - "origin_slot": 9, - "target_id": 8, - "target_slot": 10, - "type": "LATENT_UPSCALE_MODEL" - }, - { - "id": 1079, - "origin_id": 6, - "origin_slot": 1, - "target_id": 8, - "target_slot": 7, - "type": "LATENT" - }, - { - "id": 1080, - "origin_id": 6, - "origin_slot": 1, - "target_id": 371, - "target_slot": 1, - "type": "LATENT" - } - ], - "extra": { - "workflowRendererVersion": "LG" - } - } - ] - }, - "config": {}, - "extra": { - "frontendVersion": "1.39.19", - "workflowRendererVersion": "LG", - "VHS_latentpreview": false, - "VHS_latentpreviewrate": 0, - "VHS_MetadataImage": true, - "VHS_KeepIntermediate": true, - "ds": { - "scale": 1, - "offset": [ - -3243, - -5621 - ] - } - }, - "version": 0.4 +{ + "id": "a41dc75e-fc80-4307-897e-f3118e4fa21b", + "revision": 0, + "last_node_id": 443, + "last_link_id": 1080, + "nodes": [ + { + "id": 4, + "type": "LTXVConcatAVLatent", + "pos": [ + 4180, + 6090 + ], + "size": [ + 210, + 60 + ], + "flags": {}, + "order": 9, + "mode": 0, + "inputs": [ + { + "name": "video_latent", + "type": "LATENT", + "link": 802 + }, + { + "name": "audio_latent", + "type": "LATENT", + "link": 12 + } + ], + "outputs": [ + { + "name": "latent", + "type": "LATENT", + "links": [ + 804 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.7.0", + "Node name for S&R": "LTXVConcatAVLatent", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 5, + "type": "SaveVideo", + "pos": [ + 4940, + 5950 + ], + "size": [ + 660, + 440 + ], + "flags": {}, + "order": 12, + "mode": 0, + "inputs": [ + { + "name": "video", + "type": "VIDEO", + "link": 13 + } + ], + "outputs": [], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.5.1", + "Node name for S&R": "SaveVideo", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [ + "video/LTX", + "auto", + "auto" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 14, + "type": "Note", + "pos": [ + 4410, + 5780 + ], + "size": [ + 210, + 110 + ], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [], + "outputs": [], + "title": "3 Stage Upscale Note", + "properties": {}, + "widgets_values": [ + "Stage 3 will default to 1.5x on the 3rd stage if false.\n\nIf you want to disable Stage 3, simply bypass the 3rd stage in the subraph below." + ], + "color": "#223", + "bgcolor": "#335" + }, + { + "id": 418, + "type": "Note", + "pos": [ + 3080, + 5820 + ], + "size": [ + 280, + 88 + ], + "flags": {}, + "order": 1, + "mode": 0, + "inputs": [], + "outputs": [], + "title": "Model Loader Note", + "properties": {}, + "widgets_values": [ + "↓ Select your own models in this subgraph ↓" + ], + "color": "#223", + "bgcolor": "#335" + }, + { + "id": 16, + "type": "Note", + "pos": [ + 4670, + 6110 + ], + "size": [ + 210, + 88 + ], + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [], + "outputs": [], + "title": "Decode Note", + "properties": {}, + "widgets_values": [ + "Enable optimized decoding to use tiled vae decoder." + ], + "color": "#223", + "bgcolor": "#335" + }, + { + "id": 439, + "type": "MarkdownNote", + "pos": [ + 2680, + 6180 + ], + "size": [ + 370, + 320 + ], + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [], + "outputs": [], + "title": "FAQ", + "properties": {}, + "widgets_values": [ + "# FAQ\n\n**How do I set the length of the video?**\n\nYou can set the length of the video in the Model Loader Subgraph →\n\n**I keep getting errors when running this workflow!**\n\nDouble check that all of the correct models loaded. This is using the newest version of 2x upscaler (v1.1). You can change it to the older one under the Model Loader.\n\n**Where do I download these models??**\n\nThere is a good list of models and download links here: \n\nAlso the latest v1.1 spatial upscaler can be found here: \n\nThe tiny vae (taeltx2_3.safetensors) is found here:\n\n\n\n\n\n\n\n\n\n" + ], + "color": "#432", + "bgcolor": "#653" + }, + { + "id": 1, + "type": "34d63160-e149-4935-ab1f-892ffe617fec", + "pos": [ + 4670, + 5950 + ], + "size": [ + 244.98828125, + 114 + ], + "flags": {}, + "order": 11, + "mode": 0, + "inputs": [ + { + "name": "av_latent", + "type": "LATENT", + "link": 47 + }, + { + "name": "audio_vae", + "type": "VAE", + "link": 2 + }, + { + "label": "video_vae", + "name": "vae", + "type": "VAE", + "link": 31 + }, + { + "label": "Use Optimized Decoding", + "name": "value", + "type": "BOOLEAN", + "widget": { + "name": "value" + }, + "link": null + } + ], + "outputs": [ + { + "name": "VIDEO", + "type": "VIDEO", + "links": [ + 13 + ] + } + ], + "properties": { + "proxyWidgets": [ + [ + "-1", + "value" + ] + ], + "cnr_id": "comfy-core", + "ver": "0.17.2" + }, + "widgets_values": [ + false + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 10, + "type": "958b2c7a-1f75-4a30-84a8-892bddb9e426", + "pos": [ + 3080, + 5950 + ], + "size": [ + 440, + 550 + ], + "flags": {}, + "order": 4, + "mode": 0, + "inputs": [ + { + "label": "Prompt", + "name": "value", + "type": "STRING", + "widget": { + "name": "value" + }, + "link": null + }, + { + "label": "length (seconds)", + "name": "value_1", + "type": "INT", + "widget": { + "name": "value_1" + }, + "link": null + } + ], + "outputs": [ + { + "name": "optimized_model", + "type": "MODEL", + "links": [ + 40 + ] + }, + { + "label": "video_vae", + "name": "VAE", + "type": "VAE", + "links": [ + 31, + 45, + 798 + ] + }, + { + "label": "audio_vae", + "name": "VAE_1", + "type": "VAE", + "links": [ + 2, + 22 + ] + }, + { + "name": "positive", + "type": "CONDITIONING", + "links": [ + 796 + ] + }, + { + "name": "negative", + "type": "CONDITIONING", + "links": [ + 797 + ] + }, + { + "label": "length_seconds", + "name": "INT", + "type": "INT", + "links": [ + 21 + ] + }, + { + "label": "noise", + "name": "NOISE", + "type": "NOISE", + "links": [ + 44 + ] + }, + { + "label": "2x_spatial_upscale_model", + "name": "LATENT_UPSCALE_MODEL", + "type": "LATENT_UPSCALE_MODEL", + "links": [ + 1037 + ] + } + ], + "properties": { + "proxyWidgets": [ + [ + "-1", + "value" + ], + [ + "-1", + "value_1" + ], + [ + "364", + "noise_seed" + ], + [ + "364", + "control_after_generate" + ] + ], + "cnr_id": "comfy-core", + "ver": "0.17.2" + }, + "widgets_values": [ + "", + 5 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 381, + "type": "MultiImageLoader", + "pos": [ + 3550, + 5950 + ], + "size": [ + 350, + 297 + ], + "flags": {}, + "order": 5, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "multi_output", + "type": "IMAGE", + "links": [ + 1046, + 1047, + 1048 + ] + } + ], + "properties": { + "cnr_id": "WhatDreamsCost-ComfyUI", + "ver": "ecd00e6b404f14b07c7b54b0b0033e8bccb32d24", + "Node name for S&R": "MultiImageLoader" + }, + "widgets_values": [ + "", + 0, + 0, + "lanczos", + "keep proportion", + 0, + 18, + "" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 7, + "type": "2979f3eb-1c4c-45ff-8387-f1f4addb72f4", + "pos": [ + 4180, + 5950 + ], + "size": [ + 210, + 100 + ], + "flags": {}, + "order": 7, + "mode": 0, + "inputs": [ + { + "name": "image", + "type": "IMAGE", + "link": 1048 + }, + { + "label": "length_seconds", + "name": "length", + "type": "INT", + "widget": { + "name": "length" + }, + "link": 21 + }, + { + "name": "audio_vae", + "type": "VAE", + "link": 22 + } + ], + "outputs": [ + { + "name": "LATENT", + "type": "LATENT", + "links": [ + 803 + ] + }, + { + "name": "Latent", + "type": "LATENT", + "links": [ + 12 + ] + } + ], + "properties": { + "proxyWidgets": [ + [ + "-1", + "length" + ] + ], + "cnr_id": "comfy-core", + "ver": "0.17.2" + }, + "widgets_values": [ + 1 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 376, + "type": "LTXSequencer", + "pos": [ + 3920, + 5950 + ], + "size": [ + 240, + 200 + ], + "flags": {}, + "order": 8, + "mode": 0, + "inputs": [ + { + "name": "positive", + "type": "CONDITIONING", + "link": 796 + }, + { + "name": "negative", + "type": "CONDITIONING", + "link": 797 + }, + { + "name": "vae", + "type": "VAE", + "link": 798 + }, + { + "name": "latent", + "type": "LATENT", + "link": 803 + }, + { + "name": "multi_input", + "type": "IMAGE", + "link": 1047 + } + ], + "outputs": [ + { + "name": "positive", + "type": "CONDITIONING", + "links": [ + 799 + ] + }, + { + "name": "negative", + "type": "CONDITIONING", + "links": [ + 800 + ] + }, + { + "name": "latent", + "type": "LATENT", + "links": [ + 802 + ] + } + ], + "properties": { + "cnr_id": "WhatDreamsCost-ComfyUI", + "ver": "ecd00e6b404f14b07c7b54b0b0033e8bccb32d24", + "Node name for S&R": "LTXSequencer", + "num_images": 0, + "insert_frame_1": 0, + "strength_1": 1, + "insert_frame_2": 0, + "strength_2": 1, + "insert_frame_3": 0, + "strength_3": 1, + "insert_frame_4": 0, + "strength_4": 1, + "insert_frame_5": 0, + "strength_5": 1, + "insert_frame_6": 0, + "strength_6": 1, + "insert_frame_7": 0, + "strength_7": 1, + "insert_frame_8": 0, + "strength_8": 1, + "insert_frame_9": 0, + "strength_9": 0, + "insert_frame_10": 1, + "strength_10": 1, + "insert_frame_11": 0, + "strength_11": 0, + "insert_frame_12": 1, + "strength_12": 1, + "insert_frame_13": 0, + "strength_13": 0, + "insert_frame_14": 1, + "strength_14": 1, + "insert_frame_15": 0, + "strength_15": 0, + "insert_frame_16": 1, + "strength_16": 1, + "insert_frame_17": 0, + "strength_17": 0, + "insert_frame_18": 1, + "strength_18": 1, + "insert_frame_19": 0, + "strength_19": 0, + "insert_frame_20": 1, + "strength_20": 1, + "insert_frame_21": 0, + "strength_21": 0, + "insert_frame_22": 1, + "strength_22": 1, + "insert_frame_23": 0, + "strength_23": 0, + "insert_frame_24": 1, + "strength_24": 1, + "insert_frame_25": 0, + "strength_25": 0, + "insert_frame_26": 1, + "strength_26": 1, + "insert_frame_27": 0, + "strength_27": 0, + "insert_frame_28": 1, + "strength_28": 1, + "insert_frame_29": 0, + "strength_29": 0, + "insert_frame_30": 1, + "strength_30": 1, + "insert_frame_31": 0, + "strength_31": 0, + "insert_frame_32": 1, + "strength_32": 1, + "insert_frame_33": 0, + "strength_33": 1, + "insert_frame_34": 0, + "strength_34": 1, + "insert_frame_35": 0, + "strength_35": 1, + "insert_frame_36": 0, + "strength_36": 1, + "insert_frame_37": 0, + "strength_37": 1, + "insert_frame_38": 0, + "strength_38": 1, + "insert_frame_39": 0, + "strength_39": 1, + "insert_frame_40": 0, + "strength_40": 1, + "insert_frame_41": 0, + "strength_41": 1, + "insert_frame_42": 0, + "strength_42": 1, + "insert_frame_43": 0, + "strength_43": 1, + "insert_frame_44": 0, + "strength_44": 1, + "insert_frame_45": 0, + "strength_45": 1, + "insert_frame_46": 0, + "strength_46": 1, + "insert_frame_47": 0, + "strength_47": 1, + "insert_frame_48": 0, + "strength_48": 1, + "insert_frame_49": 0, + "strength_49": 1, + "insert_frame_50": 0, + "strength_50": 1, + "insert_mode": "frames", + "frame_rate": 24, + "insert_second_1": 0, + "insert_second_2": 0, + "insert_second_3": 0, + "insert_second_4": 0, + "insert_second_5": 0, + "insert_second_6": 0, + "insert_second_7": 0, + "insert_second_8": 0, + "insert_second_9": 1, + "insert_second_10": 0, + "insert_second_11": 1, + "insert_second_12": 0, + "insert_second_13": 1, + "insert_second_14": 0, + "insert_second_15": 1, + "insert_second_16": 0, + "insert_second_17": 1, + "insert_second_18": 0, + "insert_second_19": 1, + "insert_second_20": 0, + "insert_second_21": 1, + "insert_second_22": 0, + "insert_second_23": 1, + "insert_second_24": 0, + "insert_second_25": 1, + "insert_second_26": 0, + "insert_second_27": 1, + "insert_second_28": 0, + "insert_second_29": 1, + "insert_second_30": 0, + "insert_second_31": 1, + "insert_second_32": 0, + "insert_second_33": 1, + "insert_second_34": 0, + "insert_second_35": 0, + "insert_second_36": 0, + "insert_second_37": 0, + "insert_second_38": 0, + "insert_second_39": 0, + "insert_second_40": 0, + "insert_second_41": 0, + "insert_second_42": 0, + "insert_second_43": 0, + "insert_second_44": 0, + "insert_second_45": 0, + "insert_second_46": 0, + "insert_second_47": 0, + "insert_second_48": 0, + "insert_second_49": 0, + "insert_second_50": 0, + "insert_images_using": "frames", + "video_length": 1, + "insert_percentage_1": 0, + "insert_percentage_2": 1, + "insert_percentage_3": 0, + "insert_percentage_4": 0, + "insert_percentage_5": 0, + "insert_percentage_6": 0, + "insert_percentage_7": 0, + "insert_percentage_8": 0, + "insert_percentage_9": 0, + "insert_percentage_10": 0, + "insert_percentage_11": 0, + "insert_percentage_12": 0, + "insert_percentage_13": 0, + "insert_percentage_14": 0, + "insert_percentage_15": 0, + "insert_percentage_16": 0, + "insert_percentage_17": 0, + "insert_percentage_18": 0, + "insert_percentage_19": 0, + "insert_percentage_20": 0, + "insert_percentage_21": 0, + "insert_percentage_22": 0, + "insert_percentage_23": 0, + "insert_percentage_24": 0, + "insert_percentage_25": 0, + "insert_percentage_26": 0, + "insert_percentage_27": 0, + "insert_percentage_28": 0, + "insert_percentage_29": 0, + "insert_percentage_30": 0, + "insert_percentage_31": 0, + "insert_percentage_32": 0, + "insert_percentage_33": 0, + "insert_percentage_34": 0, + "insert_percentage_35": 0, + "insert_percentage_36": 0, + "insert_percentage_37": 0, + "insert_percentage_38": 0, + "insert_percentage_39": 0, + "insert_percentage_40": 0, + "insert_percentage_41": 0, + "insert_percentage_42": 0, + "insert_percentage_43": 0, + "insert_percentage_44": 0, + "insert_percentage_45": 0, + "insert_percentage_46": 0, + "insert_percentage_47": 0, + "insert_percentage_48": 0, + "insert_percentage_49": 0, + "insert_percentage_50": 0, + "insert_percent_1": 0, + "insert_percent_2": 50, + "insert_percent_3": 100, + "insert_percent_4": 0, + "insert_percent_5": 0, + "insert_percent_6": 0, + "insert_percent_7": 0, + "insert_percent_8": 0, + "insert_percent_9": 0, + "insert_percent_10": 0, + "insert_percent_11": 0, + "insert_percent_12": 0, + "insert_percent_13": 0, + "insert_percent_14": 0, + "insert_percent_15": 0, + "insert_percent_16": 0, + "insert_percent_17": 0, + "insert_percent_18": 0, + "insert_percent_19": 0, + "insert_percent_20": 0, + "insert_percent_21": 0, + "insert_percent_22": 0, + "insert_percent_23": 0, + "insert_percent_24": 0, + "insert_percent_25": 0, + "insert_percent_26": 0, + "insert_percent_27": 0, + "insert_percent_28": 0, + "insert_percent_29": 0, + "insert_percent_30": 0, + "insert_percent_31": 0, + "insert_percent_32": 0, + "insert_percent_33": 0, + "insert_percent_34": 0, + "insert_percent_35": 0, + "insert_percent_36": 0, + "insert_percent_37": 0, + "insert_percent_38": 0, + "insert_percent_39": 0, + "insert_percent_40": 0, + "insert_percent_41": 0, + "insert_percent_42": 0, + "insert_percent_43": 0, + "insert_percent_44": 0, + "insert_percent_45": 0, + "insert_percent_46": 0, + "insert_percent_47": 0, + "insert_percent_48": 0, + "insert_percent_49": 0, + "insert_percent_50": 0 + }, + "widgets_values": [ + 0, + "frames", + 24, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 15, + "type": "Note", + "pos": [ + 3920, + 5740 + ], + "size": [ + 250, + 150 + ], + "flags": {}, + "order": 6, + "mode": 0, + "inputs": [], + "outputs": [], + "title": "LTX Sequencer Note", + "properties": {}, + "widgets_values": [ + "Use one image at full strength for regular image to video.\n\nIf frame_insert is -1, it will be inserted at the last frame.\n\nThe insert_mode allows to switch between inserting images by setting the exact frame or by setting the exact second." + ], + "color": "#223", + "bgcolor": "#335" + }, + { + "id": 11, + "type": "360d64f7-96cd-42a6-9b13-6eb0387e17d9", + "pos": [ + 4410, + 5950 + ], + "size": [ + 246.46875, + 322.233642578125 + ], + "flags": {}, + "order": 10, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 40 + }, + { + "name": "positive", + "type": "CONDITIONING", + "link": 799 + }, + { + "name": "negative", + "type": "CONDITIONING", + "link": 800 + }, + { + "name": "latent_image", + "type": "LATENT", + "link": 804 + }, + { + "name": "noise", + "type": "NOISE", + "link": 44 + }, + { + "label": "video_vae", + "name": "vae", + "type": "VAE", + "link": 45 + }, + { + "name": "image", + "type": "IMAGE", + "link": null + }, + { + "label": "Upscale 2x on Stage 3", + "name": "value", + "type": "BOOLEAN", + "widget": { + "name": "value" + }, + "link": null + }, + { + "name": "multi_input", + "type": "IMAGE", + "link": 1046 + }, + { + "label": "2x_spatial_upscale_model", + "name": "upscale_model", + "type": "LATENT_UPSCALE_MODEL", + "link": 1037 + } + ], + "outputs": [ + { + "label": "av_output", + "name": "output", + "type": "LATENT", + "links": [ + 47 + ] + } + ], + "properties": { + "proxyWidgets": [ + [ + "-1", + "value" + ] + ], + "cnr_id": "comfy-core", + "ver": "0.17.2" + }, + "widgets_values": [ + true + ], + "color": "#222", + "bgcolor": "#000" + } + ], + "links": [ + [ + 2, + 10, + 2, + 1, + 1, + "VAE" + ], + [ + 12, + 7, + 1, + 4, + 1, + "LATENT" + ], + [ + 13, + 1, + 0, + 5, + 0, + "VIDEO" + ], + [ + 21, + 10, + 5, + 7, + 1, + "INT" + ], + [ + 22, + 10, + 2, + 7, + 2, + "VAE" + ], + [ + 31, + 10, + 1, + 1, + 2, + "VAE" + ], + [ + 40, + 10, + 0, + 11, + 0, + "MODEL" + ], + [ + 44, + 10, + 6, + 11, + 4, + "NOISE" + ], + [ + 45, + 10, + 1, + 11, + 5, + "VAE" + ], + [ + 47, + 11, + 0, + 1, + 0, + "LATENT" + ], + [ + 796, + 10, + 3, + 376, + 0, + "CONDITIONING" + ], + [ + 797, + 10, + 4, + 376, + 1, + "CONDITIONING" + ], + [ + 798, + 10, + 1, + 376, + 2, + "VAE" + ], + [ + 799, + 376, + 0, + 11, + 1, + "CONDITIONING" + ], + [ + 800, + 376, + 1, + 11, + 2, + "CONDITIONING" + ], + [ + 802, + 376, + 2, + 4, + 0, + "LATENT" + ], + [ + 803, + 7, + 0, + 376, + 3, + "LATENT" + ], + [ + 804, + 4, + 0, + 11, + 3, + "LATENT" + ], + [ + 1037, + 10, + 7, + 11, + 9, + "LATENT_UPSCALE_MODEL" + ], + [ + 1046, + 381, + 0, + 11, + 8, + "IMAGE" + ], + [ + 1047, + 381, + 0, + 376, + 4, + "IMAGE" + ], + [ + 1048, + 381, + 0, + 7, + 0, + "IMAGE" + ] + ], + "groups": [], + "definitions": { + "subgraphs": [ + { + "id": "34d63160-e149-4935-ab1f-892ffe617fec", + "version": 1, + "state": { + "lastGroupId": 33, + "lastNodeId": 443, + "lastLinkId": 1080, + "lastRerouteId": 0 + }, + "revision": 0, + "config": {}, + "name": "Decode", + "inputNode": { + "id": -10, + "bounding": [ + 1610, + 7190, + 180.98828125, + 120 + ] + }, + "outputNode": { + "id": -20, + "bounding": [ + 2970, + 7320, + 120, + 60 + ] + }, + "inputs": [ + { + "id": "1d567d10-9078-49fc-a4a1-67e6d4b5744f", + "name": "av_latent", + "type": "LATENT", + "linkIds": [ + 692 + ], + "pos": [ + 1770.98828125, + 7210 + ] + }, + { + "id": "bef9341f-4429-46af-9bca-11e025bec4d0", + "name": "audio_vae", + "type": "VAE", + "linkIds": [ + 693 + ], + "pos": [ + 1770.98828125, + 7230 + ] + }, + { + "id": "4588ee3d-96a4-46a8-b829-a78181f6c305", + "name": "vae", + "type": "VAE", + "linkIds": [ + 703, + 1060 + ], + "label": "video_vae", + "pos": [ + 1770.98828125, + 7250 + ] + }, + { + "id": "1bd3842b-729d-4c3d-9927-cd508bccb1b1", + "name": "value", + "type": "BOOLEAN", + "linkIds": [ + 708 + ], + "label": "Use Optimized Decoding", + "pos": [ + 1770.98828125, + 7270 + ] + } + ], + "outputs": [ + { + "id": "7e889c1c-e185-47d7-a0f7-c5f1d971c6fe", + "name": "VIDEO", + "type": "VIDEO", + "linkIds": [ + 704 + ], + "pos": [ + 2990, + 7340 + ] + } + ], + "widgets": [], + "nodes": [ + { + "id": 337, + "type": "LazySwitchKJ", + "pos": [ + 2450, + 7340 + ], + "size": [ + 270, + 78 + ], + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [ + { + "localized_name": "on_false", + "name": "on_false", + "type": "*", + "link": 701 + }, + { + "localized_name": "on_true", + "name": "on_true", + "type": "*", + "link": 1058 + }, + { + "localized_name": "switch", + "name": "switch", + "type": "BOOLEAN", + "widget": { + "name": "switch" + }, + "link": 707 + } + ], + "outputs": [ + { + "localized_name": "*", + "name": "*", + "type": "*", + "links": [ + 698 + ] + } + ], + "properties": { + "cnr_id": "comfyui-kjnodes", + "ver": "1.3.4", + "Node name for S&R": "LazySwitchKJ" + }, + "widgets_values": [ + false + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 336, + "type": "CreateVideo", + "pos": [ + 2740, + 7340 + ], + "size": [ + 210, + 78 + ], + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [ + { + "localized_name": "images", + "name": "images", + "type": "IMAGE", + "link": 698 + }, + { + "localized_name": "audio", + "name": "audio", + "shape": 7, + "type": "AUDIO", + "link": 697 + } + ], + "outputs": [ + { + "localized_name": "VIDEO", + "name": "VIDEO", + "type": "VIDEO", + "links": [ + 704 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.5.1", + "Node name for S&R": "CreateVideo", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [ + 24 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 341, + "type": "PrimitiveBoolean", + "pos": [ + 2450, + 7230 + ], + "size": [ + 230, + 58 + ], + "flags": {}, + "order": 5, + "mode": 0, + "inputs": [ + { + "localized_name": "value", + "name": "value", + "type": "BOOLEAN", + "widget": { + "name": "value" + }, + "link": 708 + } + ], + "outputs": [ + { + "localized_name": "BOOLEAN", + "name": "BOOLEAN", + "type": "BOOLEAN", + "links": [ + 707 + ] + } + ], + "title": "Use Optimized Decoding", + "properties": { + "cnr_id": "comfy-core", + "ver": "0.17.2", + "Node name for S&R": "PrimitiveBoolean" + }, + "widgets_values": [ + false + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 334, + "type": "LTXVSeparateAVLatent", + "pos": [ + 1820, + 7190 + ], + "size": [ + 240, + 46 + ], + "flags": {}, + "order": 1, + "mode": 0, + "inputs": [ + { + "localized_name": "av_latent", + "name": "av_latent", + "type": "LATENT", + "link": 692 + } + ], + "outputs": [ + { + "localized_name": "video_latent", + "name": "video_latent", + "type": "LATENT", + "links": [ + 702, + 1059 + ] + }, + { + "localized_name": "audio_latent", + "name": "audio_latent", + "type": "LATENT", + "links": [ + 687 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.5.1", + "Node name for S&R": "LTXVSeparateAVLatent", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 330, + "type": "LTXVAudioVAEDecode", + "pos": [ + 1820, + 7280 + ], + "size": [ + 240, + 46 + ], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [ + { + "localized_name": "samples", + "name": "samples", + "type": "LATENT", + "link": 687 + }, + { + "label": "Audio VAE", + "localized_name": "audio_vae", + "name": "audio_vae", + "type": "VAE", + "link": 693 + } + ], + "outputs": [ + { + "localized_name": "Audio", + "name": "Audio", + "type": "AUDIO", + "links": [ + 697 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.7.0", + "Node name for S&R": "LTXVAudioVAEDecode", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 340, + "type": "VAEDecode", + "pos": [ + 2110, + 7190 + ], + "size": [ + 150, + 50 + ], + "flags": {}, + "order": 4, + "mode": 0, + "inputs": [ + { + "localized_name": "samples", + "name": "samples", + "type": "LATENT", + "link": 702 + }, + { + "localized_name": "vae", + "name": "vae", + "type": "VAE", + "link": 703 + } + ], + "outputs": [ + { + "localized_name": "IMAGE", + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 701 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.17.2", + "Node name for S&R": "VAEDecode" + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 432, + "type": "VAEDecodeTiled", + "pos": [ + 2110, + 7320 + ], + "size": [ + 270, + 150 + ], + "flags": {}, + "order": 6, + "mode": 0, + "inputs": [ + { + "localized_name": "samples", + "name": "samples", + "type": "LATENT", + "link": 1059 + }, + { + "localized_name": "vae", + "name": "vae", + "type": "VAE", + "link": 1060 + } + ], + "outputs": [ + { + "localized_name": "IMAGE", + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 1058 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.17.2", + "Node name for S&R": "VAEDecodeTiled" + }, + "widgets_values": [ + 768, + 128, + 128, + 8 + ], + "color": "#222", + "bgcolor": "#000" + } + ], + "groups": [], + "links": [ + { + "id": 687, + "origin_id": 334, + "origin_slot": 1, + "target_id": 330, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 692, + "origin_id": -10, + "origin_slot": 0, + "target_id": 334, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 693, + "origin_id": -10, + "origin_slot": 1, + "target_id": 330, + "target_slot": 1, + "type": "VAE" + }, + { + "id": 697, + "origin_id": 330, + "origin_slot": 0, + "target_id": 336, + "target_slot": 1, + "type": "AUDIO" + }, + { + "id": 698, + "origin_id": 337, + "origin_slot": 0, + "target_id": 336, + "target_slot": 0, + "type": "IMAGE" + }, + { + "id": 701, + "origin_id": 340, + "origin_slot": 0, + "target_id": 337, + "target_slot": 0, + "type": "IMAGE" + }, + { + "id": 702, + "origin_id": 334, + "origin_slot": 0, + "target_id": 340, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 703, + "origin_id": -10, + "origin_slot": 2, + "target_id": 340, + "target_slot": 1, + "type": "VAE" + }, + { + "id": 704, + "origin_id": 336, + "origin_slot": 0, + "target_id": -20, + "target_slot": 0, + "type": "VIDEO" + }, + { + "id": 707, + "origin_id": 341, + "origin_slot": 0, + "target_id": 337, + "target_slot": 2, + "type": "BOOLEAN" + }, + { + "id": 708, + "origin_id": -10, + "origin_slot": 3, + "target_id": 341, + "target_slot": 0, + "type": "BOOLEAN" + }, + { + "id": 1058, + "origin_id": 432, + "origin_slot": 0, + "target_id": 337, + "target_slot": 1, + "type": "IMAGE" + }, + { + "id": 1059, + "origin_id": 334, + "origin_slot": 0, + "target_id": 432, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 1060, + "origin_id": -10, + "origin_slot": 2, + "target_id": 432, + "target_slot": 1, + "type": "VAE" + } + ], + "extra": { + "workflowRendererVersion": "LG" + } + }, + { + "id": "afa29944-eb58-40a5-8e76-484f2503fa0c", + "version": 1, + "state": { + "lastGroupId": 33, + "lastNodeId": 443, + "lastLinkId": 1080, + "lastRerouteId": 0 + }, + "revision": 0, + "config": {}, + "name": "Stage #1", + "inputNode": { + "id": -10, + "bounding": [ + 280, + 7113, + 120, + 140 + ] + }, + "outputNode": { + "id": -20, + "bounding": [ + 1150, + 6970, + 120, + 80 + ] + }, + "inputs": [ + { + "id": "e760d60a-9b49-4b40-8f39-3c81f29c08d8", + "name": "model", + "type": "MODEL", + "linkIds": [ + 683, + 686 + ], + "pos": [ + 380, + 7133 + ] + }, + { + "id": "6ab6641e-8318-4330-8b01-43ce43648dcf", + "name": "positive", + "type": "CONDITIONING", + "linkIds": [ + 786 + ], + "pos": [ + 380, + 7153 + ] + }, + { + "id": "e1e7e2f6-49fb-4754-92ab-b71442b03da7", + "name": "negative", + "type": "CONDITIONING", + "linkIds": [ + 787 + ], + "pos": [ + 380, + 7173 + ] + }, + { + "id": "5c9d4c86-ec46-4099-b7e1-41a9288a00d0", + "name": "latent_image", + "type": "LATENT", + "linkIds": [ + 687 + ], + "pos": [ + 380, + 7193 + ] + }, + { + "id": "0a89214b-81f0-4fa9-86fa-5f77079ddb7b", + "name": "noise", + "type": "NOISE", + "linkIds": [ + 688 + ], + "pos": [ + 380, + 7213 + ] + } + ], + "outputs": [ + { + "id": "b6f80403-e98c-41ad-a692-270973783381", + "name": "video_latent", + "type": "LATENT", + "linkIds": [ + 689 + ], + "pos": [ + 1170, + 6990 + ] + }, + { + "id": "f54a00fd-f0db-4e4e-9205-9c9d3e955032", + "name": "audio_latent", + "type": "LATENT", + "linkIds": [ + 690 + ], + "pos": [ + 1170, + 7010 + ] + } + ], + "widgets": [], + "nodes": [ + { + "id": 325, + "type": "KSamplerSelect", + "pos": [ + 460, + 7120 + ], + "size": [ + 210, + 60 + ], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [], + "outputs": [ + { + "localized_name": "SAMPLER", + "name": "SAMPLER", + "type": "SAMPLER", + "links": [ + 680 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.56", + "Node name for S&R": "KSamplerSelect", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [ + "euler" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 326, + "type": "BasicScheduler", + "pos": [ + 460, + 7220 + ], + "size": [ + 210, + 106 + ], + "flags": {}, + "order": 4, + "mode": 0, + "inputs": [ + { + "localized_name": "model", + "name": "model", + "type": "MODEL", + "link": 686 + } + ], + "outputs": [ + { + "localized_name": "SIGMAS", + "name": "SIGMAS", + "type": "SIGMAS", + "links": [ + 681 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.16.4", + "Node name for S&R": "BasicScheduler" + }, + "widgets_values": [ + "linear_quadratic", + 8, + 1 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 322, + "type": "SamplerCustomAdvanced", + "pos": [ + 690, + 6980 + ], + "size": [ + 212.3638671875, + 204.64497833251954 + ], + "flags": {}, + "order": 1, + "mode": 0, + "inputs": [ + { + "localized_name": "noise", + "name": "noise", + "type": "NOISE", + "link": 688 + }, + { + "localized_name": "guider", + "name": "guider", + "type": "GUIDER", + "link": 679 + }, + { + "localized_name": "sampler", + "name": "sampler", + "type": "SAMPLER", + "link": 680 + }, + { + "localized_name": "sigmas", + "name": "sigmas", + "type": "SIGMAS", + "link": 681 + }, + { + "localized_name": "latent_image", + "name": "latent_image", + "type": "LATENT", + "link": 687 + } + ], + "outputs": [ + { + "localized_name": "output", + "name": "output", + "type": "LATENT", + "links": [ + 682 + ] + }, + { + "localized_name": "denoised_output", + "name": "denoised_output", + "type": "LATENT", + "links": [] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.60", + "Node name for S&R": "SamplerCustomAdvanced", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 323, + "type": "LTXVSeparateAVLatent", + "pos": [ + 920, + 6980 + ], + "size": [ + 193.2916015625, + 46 + ], + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [ + { + "localized_name": "av_latent", + "name": "av_latent", + "type": "LATENT", + "link": 682 + } + ], + "outputs": [ + { + "localized_name": "video_latent", + "name": "video_latent", + "type": "LATENT", + "links": [ + 689 + ] + }, + { + "localized_name": "audio_latent", + "name": "audio_latent", + "type": "LATENT", + "links": [ + 690 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.5.1", + "Node name for S&R": "LTXVSeparateAVLatent", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 324, + "type": "CFGGuider", + "pos": [ + 460, + 6990 + ], + "size": [ + 210, + 98 + ], + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [ + { + "localized_name": "model", + "name": "model", + "type": "MODEL", + "link": 683 + }, + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "link": 786 + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "link": 787 + } + ], + "outputs": [ + { + "localized_name": "GUIDER", + "name": "GUIDER", + "type": "GUIDER", + "links": [ + 679 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.64", + "Node name for S&R": "CFGGuider", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [ + 1 + ], + "color": "#222", + "bgcolor": "#000" + } + ], + "groups": [], + "links": [ + { + "id": 679, + "origin_id": 324, + "origin_slot": 0, + "target_id": 322, + "target_slot": 1, + "type": "GUIDER" + }, + { + "id": 680, + "origin_id": 325, + "origin_slot": 0, + "target_id": 322, + "target_slot": 2, + "type": "SAMPLER" + }, + { + "id": 681, + "origin_id": 326, + "origin_slot": 0, + "target_id": 322, + "target_slot": 3, + "type": "SIGMAS" + }, + { + "id": 682, + "origin_id": 322, + "origin_slot": 0, + "target_id": 323, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 683, + "origin_id": -10, + "origin_slot": 0, + "target_id": 324, + "target_slot": 0, + "type": "MODEL" + }, + { + "id": 686, + "origin_id": -10, + "origin_slot": 0, + "target_id": 326, + "target_slot": 0, + "type": "MODEL" + }, + { + "id": 687, + "origin_id": -10, + "origin_slot": 3, + "target_id": 322, + "target_slot": 4, + "type": "LATENT" + }, + { + "id": 688, + "origin_id": -10, + "origin_slot": 4, + "target_id": 322, + "target_slot": 0, + "type": "NOISE" + }, + { + "id": 689, + "origin_id": 323, + "origin_slot": 0, + "target_id": -20, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 690, + "origin_id": 323, + "origin_slot": 1, + "target_id": -20, + "target_slot": 1, + "type": "LATENT" + }, + { + "id": 786, + "origin_id": -10, + "origin_slot": 1, + "target_id": 324, + "target_slot": 1, + "type": "CONDITIONING" + }, + { + "id": 787, + "origin_id": -10, + "origin_slot": 2, + "target_id": 324, + "target_slot": 2, + "type": "CONDITIONING" + } + ], + "extra": { + "workflowRendererVersion": "LG" + } + }, + { + "id": "ce8f7842-cb03-41b4-9b1e-8d032b2a4e72", + "version": 1, + "state": { + "lastGroupId": 33, + "lastNodeId": 443, + "lastLinkId": 1080, + "lastRerouteId": 0 + }, + "revision": 0, + "config": {}, + "name": "Stage #2", + "inputNode": { + "id": -10, + "bounding": [ + 280, + 7113, + 125.224609375, + 240 + ] + }, + "outputNode": { + "id": -20, + "bounding": [ + 1820, + 7130, + 120, + 120 + ] + }, + "inputs": [ + { + "id": "e760d60a-9b49-4b40-8f39-3c81f29c08d8", + "name": "model", + "type": "MODEL", + "linkIds": [ + 683, + 686 + ], + "pos": [ + 385.224609375, + 7133 + ] + }, + { + "id": "6ab6641e-8318-4330-8b01-43ce43648dcf", + "name": "positive", + "type": "CONDITIONING", + "linkIds": [ + 703 + ], + "pos": [ + 385.224609375, + 7153 + ] + }, + { + "id": "e1e7e2f6-49fb-4754-92ab-b71442b03da7", + "name": "negative", + "type": "CONDITIONING", + "linkIds": [ + 704 + ], + "pos": [ + 385.224609375, + 7173 + ] + }, + { + "id": "0a89214b-81f0-4fa9-86fa-5f77079ddb7b", + "name": "noise", + "type": "NOISE", + "linkIds": [ + 688 + ], + "pos": [ + 385.224609375, + 7193 + ] + }, + { + "id": "d8824343-6b21-42aa-9eaf-65d1b267662d", + "name": "vae", + "type": "VAE", + "linkIds": [ + 706, + 990 + ], + "label": "video_vae", + "pos": [ + 385.224609375, + 7213 + ] + }, + { + "id": "6ea10719-25f1-449a-9d2d-bb395d89609d", + "name": "image", + "type": "IMAGE", + "linkIds": [], + "pos": [ + 385.224609375, + 7233 + ] + }, + { + "id": "6c54f923-7ccc-4832-86ca-38d39cfcc656", + "name": "samples", + "type": "LATENT", + "linkIds": [ + 705 + ], + "label": "video_latent", + "pos": [ + 385.224609375, + 7253 + ] + }, + { + "id": "5ce3a92e-d38a-4078-bc1f-7426a38fa1ff", + "name": "audio_latent", + "type": "LATENT", + "linkIds": [ + 700 + ], + "pos": [ + 385.224609375, + 7273 + ] + }, + { + "id": "6a8ebcb1-8f4b-4558-893c-1b6be5c462ec", + "name": "multi_input", + "type": "IMAGE", + "linkIds": [ + 1004 + ], + "pos": [ + 385.224609375, + 7293 + ] + }, + { + "id": "7443af4e-2549-4707-813a-a7e7efa13046", + "name": "upscale_model", + "type": "LATENT_UPSCALE_MODEL", + "linkIds": [ + 1035 + ], + "pos": [ + 385.224609375, + 7313 + ] + } + ], + "outputs": [ + { + "id": "b6f80403-e98c-41ad-a692-270973783381", + "name": "video_latent", + "type": "LATENT", + "linkIds": [ + 843 + ], + "pos": [ + 1840, + 7150 + ] + }, + { + "id": "f54a00fd-f0db-4e4e-9205-9c9d3e955032", + "name": "audio_latent", + "type": "LATENT", + "linkIds": [ + 690 + ], + "pos": [ + 1840, + 7170 + ] + }, + { + "id": "3ecaa920-05d1-4c39-8ea0-b7b21e2df49b", + "name": "positive", + "type": "CONDITIONING", + "linkIds": [ + 985 + ], + "pos": [ + 1840, + 7190 + ] + }, + { + "id": "d1c64c87-487c-4677-9f80-d4a7ef6cdcc6", + "name": "negative", + "type": "CONDITIONING", + "linkIds": [ + 986 + ], + "pos": [ + 1840, + 7210 + ] + } + ], + "widgets": [], + "nodes": [ + { + "id": 326, + "type": "BasicScheduler", + "pos": [ + 480, + 7210 + ], + "size": [ + 210, + 106 + ], + "flags": {}, + "order": 4, + "mode": 0, + "inputs": [ + { + "localized_name": "model", + "name": "model", + "type": "MODEL", + "link": 686 + } + ], + "outputs": [ + { + "localized_name": "SIGMAS", + "name": "SIGMAS", + "type": "SIGMAS", + "links": [ + 681 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.16.4", + "Node name for S&R": "BasicScheduler" + }, + "widgets_values": [ + "linear_quadratic", + 6, + 0.42 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 325, + "type": "KSamplerSelect", + "pos": [ + 480, + 7110 + ], + "size": [ + 210, + 60 + ], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [], + "outputs": [ + { + "localized_name": "SAMPLER", + "name": "SAMPLER", + "type": "SAMPLER", + "links": [ + 680 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.56", + "Node name for S&R": "KSamplerSelect", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [ + "euler" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 322, + "type": "SamplerCustomAdvanced", + "pos": [ + 1370, + 7130 + ], + "size": [ + 212.3638671875, + 204.64497833251954 + ], + "flags": {}, + "order": 1, + "mode": 0, + "inputs": [ + { + "localized_name": "noise", + "name": "noise", + "type": "NOISE", + "link": 688 + }, + { + "localized_name": "guider", + "name": "guider", + "type": "GUIDER", + "link": 679 + }, + { + "localized_name": "sampler", + "name": "sampler", + "type": "SAMPLER", + "link": 680 + }, + { + "localized_name": "sigmas", + "name": "sigmas", + "type": "SIGMAS", + "link": 681 + }, + { + "localized_name": "latent_image", + "name": "latent_image", + "type": "LATENT", + "link": 697 + } + ], + "outputs": [ + { + "localized_name": "output", + "name": "output", + "type": "LATENT", + "links": [ + 682 + ] + }, + { + "localized_name": "denoised_output", + "name": "denoised_output", + "type": "LATENT", + "links": [] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.60", + "Node name for S&R": "SamplerCustomAdvanced", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 323, + "type": "LTXVSeparateAVLatent", + "pos": [ + 1600, + 7140 + ], + "size": [ + 193.2916015625, + 46 + ], + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [ + { + "localized_name": "av_latent", + "name": "av_latent", + "type": "LATENT", + "link": 682 + } + ], + "outputs": [ + { + "localized_name": "video_latent", + "name": "video_latent", + "type": "LATENT", + "links": [ + 843 + ] + }, + { + "localized_name": "audio_latent", + "name": "audio_latent", + "type": "LATENT", + "links": [ + 690 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.5.1", + "Node name for S&R": "LTXVSeparateAVLatent", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 409, + "type": "LTXSequencer", + "pos": [ + 850, + 7140 + ], + "size": [ + 210, + 200 + ], + "flags": {}, + "order": 8, + "mode": 0, + "inputs": [ + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "link": 988 + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "link": 989 + }, + { + "localized_name": "vae", + "name": "vae", + "type": "VAE", + "link": 990 + }, + { + "localized_name": "latent", + "name": "latent", + "type": "LATENT", + "link": 991 + }, + { + "localized_name": "multi_input", + "name": "multi_input", + "type": "IMAGE", + "link": 1004 + } + ], + "outputs": [ + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "links": [ + 983, + 985 + ] + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "links": [ + 984, + 986 + ] + }, + { + "localized_name": "latent", + "name": "latent", + "type": "LATENT", + "links": [ + 987 + ] + } + ], + "properties": { + "cnr_id": "WhatDreamsCost-ComfyUI", + "ver": "ecd00e6b404f14b07c7b54b0b0033e8bccb32d24", + "Node name for S&R": "LTXSequencer", + "num_images": 0, + "insert_frame_1": 0, + "strength_1": 1, + "insert_frame_2": 0, + "strength_2": 1, + "insert_frame_3": 0, + "strength_3": 1, + "insert_frame_4": 0, + "strength_4": 1, + "insert_frame_5": 0, + "strength_5": 1, + "insert_frame_6": 0, + "strength_6": 1, + "insert_frame_7": 0, + "strength_7": 1, + "insert_frame_8": 0, + "strength_8": 1, + "insert_frame_9": 0, + "strength_9": 0, + "insert_frame_10": 1, + "strength_10": 1, + "insert_frame_11": 0, + "strength_11": 0, + "insert_frame_12": 1, + "strength_12": 1, + "insert_frame_13": 0, + "strength_13": 0, + "insert_frame_14": 1, + "strength_14": 1, + "insert_frame_15": 0, + "strength_15": 0, + "insert_frame_16": 1, + "strength_16": 1, + "insert_frame_17": 0, + "strength_17": 0, + "insert_frame_18": 1, + "strength_18": 1, + "insert_frame_19": 0, + "strength_19": 0, + "insert_frame_20": 1, + "strength_20": 1, + "insert_frame_21": 0, + "strength_21": 0, + "insert_frame_22": 1, + "strength_22": 1, + "insert_frame_23": 0, + "strength_23": 0, + "insert_frame_24": 1, + "strength_24": 1, + "insert_frame_25": 0, + "strength_25": 0, + "insert_frame_26": 1, + "strength_26": 1, + "insert_frame_27": 0, + "strength_27": 0, + "insert_frame_28": 1, + "strength_28": 1, + "insert_frame_29": 0, + "strength_29": 0, + "insert_frame_30": 1, + "strength_30": 1, + "insert_frame_31": 0, + "strength_31": 0, + "insert_frame_32": 1, + "strength_32": 1, + "insert_frame_33": 0, + "strength_33": 1, + "insert_frame_34": 0, + "strength_34": 1, + "insert_frame_35": 0, + "strength_35": 1, + "insert_frame_36": 0, + "strength_36": 1, + "insert_frame_37": 0, + "strength_37": 1, + "insert_frame_38": 0, + "strength_38": 1, + "insert_frame_39": 0, + "strength_39": 1, + "insert_frame_40": 0, + "strength_40": 1, + "insert_frame_41": 0, + "strength_41": 1, + "insert_frame_42": 0, + "strength_42": 1, + "insert_frame_43": 0, + "strength_43": 1, + "insert_frame_44": 0, + "strength_44": 1, + "insert_frame_45": 0, + "strength_45": 1, + "insert_frame_46": 0, + "strength_46": 1, + "insert_frame_47": 0, + "strength_47": 1, + "insert_frame_48": 0, + "strength_48": 1, + "insert_frame_49": 0, + "strength_49": 1, + "insert_frame_50": 0, + "strength_50": 1, + "insert_mode": "frames", + "frame_rate": 24, + "insert_second_1": 0, + "insert_second_2": 0, + "insert_second_3": 0, + "insert_second_4": 0, + "insert_second_5": 0, + "insert_second_6": 0, + "insert_second_7": 0, + "insert_second_8": 0, + "insert_second_9": 1, + "insert_second_10": 0, + "insert_second_11": 1, + "insert_second_12": 0, + "insert_second_13": 1, + "insert_second_14": 0, + "insert_second_15": 1, + "insert_second_16": 0, + "insert_second_17": 1, + "insert_second_18": 0, + "insert_second_19": 1, + "insert_second_20": 0, + "insert_second_21": 1, + "insert_second_22": 0, + "insert_second_23": 1, + "insert_second_24": 0, + "insert_second_25": 1, + "insert_second_26": 0, + "insert_second_27": 1, + "insert_second_28": 0, + "insert_second_29": 1, + "insert_second_30": 0, + "insert_second_31": 1, + "insert_second_32": 0, + "insert_second_33": 1, + "insert_second_34": 0, + "insert_second_35": 0, + "insert_second_36": 0, + "insert_second_37": 0, + "insert_second_38": 0, + "insert_second_39": 0, + "insert_second_40": 0, + "insert_second_41": 0, + "insert_second_42": 0, + "insert_second_43": 0, + "insert_second_44": 0, + "insert_second_45": 0, + "insert_second_46": 0, + "insert_second_47": 0, + "insert_second_48": 0, + "insert_second_49": 0, + "insert_second_50": 0, + "insert_images_using": "frames", + "video_length": 1, + "insert_percentage_1": 0, + "insert_percentage_2": 1, + "insert_percentage_3": 0, + "insert_percentage_4": 0, + "insert_percentage_5": 0, + "insert_percentage_6": 0, + "insert_percentage_7": 0, + "insert_percentage_8": 0, + "insert_percentage_9": 0, + "insert_percentage_10": 0, + "insert_percentage_11": 0, + "insert_percentage_12": 0, + "insert_percentage_13": 0, + "insert_percentage_14": 0, + "insert_percentage_15": 0, + "insert_percentage_16": 0, + "insert_percentage_17": 0, + "insert_percentage_18": 0, + "insert_percentage_19": 0, + "insert_percentage_20": 0, + "insert_percentage_21": 0, + "insert_percentage_22": 0, + "insert_percentage_23": 0, + "insert_percentage_24": 0, + "insert_percentage_25": 0, + "insert_percentage_26": 0, + "insert_percentage_27": 0, + "insert_percentage_28": 0, + "insert_percentage_29": 0, + "insert_percentage_30": 0, + "insert_percentage_31": 0, + "insert_percentage_32": 0, + "insert_percentage_33": 0, + "insert_percentage_34": 0, + "insert_percentage_35": 0, + "insert_percentage_36": 0, + "insert_percentage_37": 0, + "insert_percentage_38": 0, + "insert_percentage_39": 0, + "insert_percentage_40": 0, + "insert_percentage_41": 0, + "insert_percentage_42": 0, + "insert_percentage_43": 0, + "insert_percentage_44": 0, + "insert_percentage_45": 0, + "insert_percentage_46": 0, + "insert_percentage_47": 0, + "insert_percentage_48": 0, + "insert_percentage_49": 0, + "insert_percentage_50": 0, + "insert_percent_1": 0, + "insert_percent_2": 50, + "insert_percent_3": 100, + "insert_percent_4": 0, + "insert_percent_5": 0, + "insert_percent_6": 0, + "insert_percent_7": 0, + "insert_percent_8": 0, + "insert_percent_9": 0, + "insert_percent_10": 0, + "insert_percent_11": 0, + "insert_percent_12": 0, + "insert_percent_13": 0, + "insert_percent_14": 0, + "insert_percent_15": 0, + "insert_percent_16": 0, + "insert_percent_17": 0, + "insert_percent_18": 0, + "insert_percent_19": 0, + "insert_percent_20": 0, + "insert_percent_21": 0, + "insert_percent_22": 0, + "insert_percent_23": 0, + "insert_percent_24": 0, + "insert_percent_25": 0, + "insert_percent_26": 0, + "insert_percent_27": 0, + "insert_percent_28": 0, + "insert_percent_29": 0, + "insert_percent_30": 0, + "insert_percent_31": 0, + "insert_percent_32": 0, + "insert_percent_33": 0, + "insert_percent_34": 0, + "insert_percent_35": 0, + "insert_percent_36": 0, + "insert_percent_37": 0, + "insert_percent_38": 0, + "insert_percent_39": 0, + "insert_percent_40": 0, + "insert_percent_41": 0, + "insert_percent_42": 0, + "insert_percent_43": 0, + "insert_percent_44": 0, + "insert_percent_45": 0, + "insert_percent_46": 0, + "insert_percent_47": 0, + "insert_percent_48": 0, + "insert_percent_49": 0, + "insert_percent_50": 0 + }, + "widgets_values": [ + 0, + "frames", + 24, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 331, + "type": "LTXVCropGuides", + "pos": [ + 490, + 7000 + ], + "size": [ + 152.7134765625, + 66 + ], + "flags": {}, + "order": 7, + "mode": 0, + "inputs": [ + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "link": 703 + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "link": 704 + }, + { + "localized_name": "latent", + "name": "latent", + "type": "LATENT", + "link": 705 + } + ], + "outputs": [ + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "links": [ + 828, + 846, + 988 + ] + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "links": [ + 829, + 847, + 989 + ] + }, + { + "localized_name": "latent", + "name": "latent", + "type": "LATENT", + "links": [ + 850 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.16.4", + "Node name for S&R": "LTXVCropGuides" + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 329, + "type": "LTXVConcatAVLatent", + "pos": [ + 1140, + 7270 + ], + "size": [ + 187.5, + 60 + ], + "flags": {}, + "order": 6, + "mode": 0, + "inputs": [ + { + "localized_name": "video_latent", + "name": "video_latent", + "type": "LATENT", + "link": 987 + }, + { + "localized_name": "audio_latent", + "name": "audio_latent", + "type": "LATENT", + "link": 700 + } + ], + "outputs": [ + { + "localized_name": "latent", + "name": "latent", + "type": "LATENT", + "links": [ + 697 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.7.0", + "Node name for S&R": "LTXVConcatAVLatent", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 324, + "type": "CFGGuider", + "pos": [ + 1140, + 7130 + ], + "size": [ + 210, + 98 + ], + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [ + { + "localized_name": "model", + "name": "model", + "type": "MODEL", + "link": 683 + }, + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "link": 983 + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "link": 984 + } + ], + "outputs": [ + { + "localized_name": "GUIDER", + "name": "GUIDER", + "type": "GUIDER", + "links": [ + 679 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.64", + "Node name for S&R": "CFGGuider", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [ + 1 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 328, + "type": "LTXVLatentUpsampler", + "pos": [ + 480, + 7370 + ], + "size": [ + 320, + 66 + ], + "flags": {}, + "order": 5, + "mode": 0, + "inputs": [ + { + "localized_name": "samples", + "name": "samples", + "type": "LATENT", + "link": 850 + }, + { + "localized_name": "upscale_model", + "name": "upscale_model", + "type": "LATENT_UPSCALE_MODEL", + "link": 1035 + }, + { + "localized_name": "vae", + "name": "vae", + "type": "VAE", + "link": 706 + } + ], + "outputs": [ + { + "localized_name": "LATENT", + "name": "LATENT", + "type": "LATENT", + "links": [ + 991 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.14.1", + "Node name for S&R": "LTXVLatentUpsampler", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + } + ], + "groups": [], + "links": [ + { + "id": 679, + "origin_id": 324, + "origin_slot": 0, + "target_id": 322, + "target_slot": 1, + "type": "GUIDER" + }, + { + "id": 680, + "origin_id": 325, + "origin_slot": 0, + "target_id": 322, + "target_slot": 2, + "type": "SAMPLER" + }, + { + "id": 681, + "origin_id": 326, + "origin_slot": 0, + "target_id": 322, + "target_slot": 3, + "type": "SIGMAS" + }, + { + "id": 682, + "origin_id": 322, + "origin_slot": 0, + "target_id": 323, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 683, + "origin_id": -10, + "origin_slot": 0, + "target_id": 324, + "target_slot": 0, + "type": "MODEL" + }, + { + "id": 686, + "origin_id": -10, + "origin_slot": 0, + "target_id": 326, + "target_slot": 0, + "type": "MODEL" + }, + { + "id": 688, + "origin_id": -10, + "origin_slot": 3, + "target_id": 322, + "target_slot": 0, + "type": "NOISE" + }, + { + "id": 690, + "origin_id": 323, + "origin_slot": 1, + "target_id": -20, + "target_slot": 1, + "type": "LATENT" + }, + { + "id": 697, + "origin_id": 329, + "origin_slot": 0, + "target_id": 322, + "target_slot": 4, + "type": "LATENT" + }, + { + "id": 700, + "origin_id": -10, + "origin_slot": 7, + "target_id": 329, + "target_slot": 1, + "type": "LATENT" + }, + { + "id": 703, + "origin_id": -10, + "origin_slot": 1, + "target_id": 331, + "target_slot": 0, + "type": "CONDITIONING" + }, + { + "id": 704, + "origin_id": -10, + "origin_slot": 2, + "target_id": 331, + "target_slot": 1, + "type": "CONDITIONING" + }, + { + "id": 705, + "origin_id": -10, + "origin_slot": 6, + "target_id": 331, + "target_slot": 2, + "type": "LATENT" + }, + { + "id": 706, + "origin_id": -10, + "origin_slot": 4, + "target_id": 328, + "target_slot": 2, + "type": "VAE" + }, + { + "id": 843, + "origin_id": 323, + "origin_slot": 0, + "target_id": -20, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 850, + "origin_id": 331, + "origin_slot": 2, + "target_id": 328, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 983, + "origin_id": 409, + "origin_slot": 0, + "target_id": 324, + "target_slot": 1, + "type": "CONDITIONING" + }, + { + "id": 984, + "origin_id": 409, + "origin_slot": 1, + "target_id": 324, + "target_slot": 2, + "type": "CONDITIONING" + }, + { + "id": 985, + "origin_id": 409, + "origin_slot": 0, + "target_id": -20, + "target_slot": 2, + "type": "CONDITIONING" + }, + { + "id": 986, + "origin_id": 409, + "origin_slot": 1, + "target_id": -20, + "target_slot": 3, + "type": "CONDITIONING" + }, + { + "id": 987, + "origin_id": 409, + "origin_slot": 2, + "target_id": 329, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 988, + "origin_id": 331, + "origin_slot": 0, + "target_id": 409, + "target_slot": 0, + "type": "CONDITIONING" + }, + { + "id": 989, + "origin_id": 331, + "origin_slot": 1, + "target_id": 409, + "target_slot": 1, + "type": "CONDITIONING" + }, + { + "id": 990, + "origin_id": -10, + "origin_slot": 4, + "target_id": 409, + "target_slot": 2, + "type": "VAE" + }, + { + "id": 991, + "origin_id": 328, + "origin_slot": 0, + "target_id": 409, + "target_slot": 3, + "type": "LATENT" + }, + { + "id": 1004, + "origin_id": -10, + "origin_slot": 8, + "target_id": 409, + "target_slot": 4, + "type": "IMAGE" + }, + { + "id": 1035, + "origin_id": -10, + "origin_slot": 9, + "target_id": 328, + "target_slot": 1, + "type": "LATENT_UPSCALE_MODEL" + } + ], + "extra": { + "workflowRendererVersion": "LG" + } + }, + { + "id": "2979f3eb-1c4c-45ff-8387-f1f4addb72f4", + "version": 1, + "state": { + "lastGroupId": 33, + "lastNodeId": 443, + "lastLinkId": 1080, + "lastRerouteId": 0 + }, + "revision": 0, + "config": {}, + "name": "Process Latents", + "inputNode": { + "id": -10, + "bounding": [ + 2630, + 6303, + 128.998046875, + 100 + ] + }, + "outputNode": { + "id": -20, + "bounding": [ + 3500, + 6303, + 120, + 80 + ] + }, + "inputs": [ + { + "id": "f9d3f39d-4628-4ef2-a464-ffb98c1fb4bc", + "name": "image", + "type": "IMAGE", + "linkIds": [ + 713 + ], + "localized_name": "image", + "pos": [ + 2738.998046875, + 6323 + ] + }, + { + "id": "6c61b79c-c6a2-4cd9-96a7-72073d3603c1", + "name": "length", + "type": "INT", + "linkIds": [ + 708, + 709 + ], + "localized_name": "length", + "label": "length_seconds", + "pos": [ + 2738.998046875, + 6343 + ] + }, + { + "id": "610c02c9-497c-45ac-b603-ea5bd91aec4c", + "name": "audio_vae", + "type": "VAE", + "linkIds": [ + 714 + ], + "pos": [ + 2738.998046875, + 6363 + ] + } + ], + "outputs": [ + { + "id": "2cf8b0ee-4e7a-4045-a1ee-bbd4b878068e", + "name": "LATENT", + "type": "LATENT", + "linkIds": [ + 710 + ], + "localized_name": "LATENT", + "pos": [ + 3520, + 6323 + ] + }, + { + "id": "44e92917-679f-4cc3-aa57-51b4a6ef24a8", + "name": "Latent", + "type": "LATENT", + "linkIds": [ + 705 + ], + "localized_name": "Latent", + "pos": [ + 3520, + 6343 + ] + } + ], + "widgets": [], + "nodes": [ + { + "id": 356, + "type": "GetImageSize", + "pos": [ + 3030, + 6210 + ], + "size": [ + 170, + 70 + ], + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [ + { + "localized_name": "image", + "name": "image", + "type": "IMAGE", + "link": 711 + } + ], + "outputs": [ + { + "localized_name": "width", + "name": "width", + "type": "INT", + "links": [ + 706 + ] + }, + { + "localized_name": "height", + "name": "height", + "type": "INT", + "links": [ + 707 + ] + }, + { + "localized_name": "batch_size", + "name": "batch_size", + "type": "INT", + "links": null + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.7.0", + "Node name for S&R": "GetImageSize", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 352, + "type": "EmptyLTXVLatentVideo", + "pos": [ + 3210, + 6210 + ], + "size": [ + 210, + 130 + ], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [ + { + "localized_name": "width", + "name": "width", + "type": "INT", + "widget": { + "name": "width" + }, + "link": 706 + }, + { + "localized_name": "height", + "name": "height", + "type": "INT", + "widget": { + "name": "height" + }, + "link": 707 + }, + { + "localized_name": "length", + "name": "length", + "type": "INT", + "widget": { + "name": "length" + }, + "link": 708 + } + ], + "outputs": [ + { + "localized_name": "LATENT", + "name": "LATENT", + "type": "LATENT", + "links": [ + 710 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.60", + "Node name for S&R": "EmptyLTXVLatentVideo", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [ + 768, + 512, + 1, + 1 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 353, + "type": "LTXVEmptyLatentAudio", + "pos": [ + 3210, + 6380 + ], + "size": [ + 210, + 106 + ], + "flags": {}, + "order": 1, + "mode": 0, + "inputs": [ + { + "localized_name": "audio_vae", + "name": "audio_vae", + "type": "VAE", + "link": 714 + }, + { + "localized_name": "frames_number", + "name": "frames_number", + "type": "INT", + "widget": { + "name": "frames_number" + }, + "link": 709 + } + ], + "outputs": [ + { + "localized_name": "Latent", + "name": "Latent", + "type": "LATENT", + "links": [ + 705 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.68", + "Node name for S&R": "LTXVEmptyLatentAudio", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [ + 1, + 24, + 1 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 357, + "type": "ImageScaleBy", + "pos": [ + 2810, + 6210 + ], + "size": [ + 210, + 82 + ], + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [ + { + "localized_name": "image", + "name": "image", + "type": "IMAGE", + "link": 713 + } + ], + "outputs": [ + { + "localized_name": "IMAGE", + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 711 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.5.1", + "Node name for S&R": "ImageScaleBy", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65, + "ue_properties": { + "version": "7.0.1", + "widget_ue_connectable": {} + } + }, + "widgets_values": [ + "bilinear", + 0.5 + ], + "color": "#222", + "bgcolor": "#000" + } + ], + "groups": [], + "links": [ + { + "id": 711, + "origin_id": 357, + "origin_slot": 0, + "target_id": 356, + "target_slot": 0, + "type": "IMAGE" + }, + { + "id": 706, + "origin_id": 356, + "origin_slot": 0, + "target_id": 352, + "target_slot": 0, + "type": "INT" + }, + { + "id": 707, + "origin_id": 356, + "origin_slot": 1, + "target_id": 352, + "target_slot": 1, + "type": "INT" + }, + { + "id": 713, + "origin_id": -10, + "origin_slot": 0, + "target_id": 357, + "target_slot": 0, + "type": "IMAGE" + }, + { + "id": 708, + "origin_id": -10, + "origin_slot": 1, + "target_id": 352, + "target_slot": 2, + "type": "INT" + }, + { + "id": 709, + "origin_id": -10, + "origin_slot": 1, + "target_id": 353, + "target_slot": 1, + "type": "INT" + }, + { + "id": 710, + "origin_id": 352, + "origin_slot": 0, + "target_id": -20, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 705, + "origin_id": 353, + "origin_slot": 0, + "target_id": -20, + "target_slot": 1, + "type": "LATENT" + }, + { + "id": 714, + "origin_id": -10, + "origin_slot": 2, + "target_id": 353, + "target_slot": 0, + "type": "VAE" + } + ], + "extra": { + "workflowRendererVersion": "LG" + } + }, + { + "id": "535e663f-da59-47c1-aab5-c78ff202cb16", + "version": 1, + "state": { + "lastGroupId": 33, + "lastNodeId": 443, + "lastLinkId": 1080, + "lastRerouteId": 0 + }, + "revision": 0, + "config": {}, + "name": "Stage #3", + "inputNode": { + "id": -10, + "bounding": [ + -370, + 7030, + 144.56640625, + 260 + ] + }, + "outputNode": { + "id": -20, + "bounding": [ + 1680, + 7090, + 120, + 80 + ] + }, + "inputs": [ + { + "id": "e760d60a-9b49-4b40-8f39-3c81f29c08d8", + "name": "model", + "type": "MODEL", + "linkIds": [ + 683, + 686 + ], + "pos": [ + -245.43359375, + 7050 + ] + }, + { + "id": "6ab6641e-8318-4330-8b01-43ce43648dcf", + "name": "positive", + "type": "CONDITIONING", + "linkIds": [ + 919 + ], + "pos": [ + -245.43359375, + 7070 + ] + }, + { + "id": "e1e7e2f6-49fb-4754-92ab-b71442b03da7", + "name": "negative", + "type": "CONDITIONING", + "linkIds": [ + 920 + ], + "pos": [ + -245.43359375, + 7090 + ] + }, + { + "id": "0a89214b-81f0-4fa9-86fa-5f77079ddb7b", + "name": "noise", + "type": "NOISE", + "linkIds": [ + 688 + ], + "pos": [ + -245.43359375, + 7110 + ] + }, + { + "id": "d8824343-6b21-42aa-9eaf-65d1b267662d", + "name": "vae", + "type": "VAE", + "linkIds": [ + 716, + 717, + 1002 + ], + "label": "video_vae", + "pos": [ + -245.43359375, + 7130 + ] + }, + { + "id": "6ea10719-25f1-449a-9d2d-bb395d89609d", + "name": "image", + "type": "IMAGE", + "linkIds": [], + "pos": [ + -245.43359375, + 7150 + ] + }, + { + "id": "d57f8c01-0dc1-48da-b7a2-166839b09d2b", + "name": "samples", + "type": "LATENT", + "linkIds": [ + 708 + ], + "label": "video_latent", + "pos": [ + -245.43359375, + 7170 + ] + }, + { + "id": "9f9730e3-9739-4813-953f-4c27da077f21", + "name": "audio_latent", + "type": "LATENT", + "linkIds": [ + 700 + ], + "pos": [ + -245.43359375, + 7190 + ] + }, + { + "id": "6f44ac03-cf13-4dc0-be74-7ec92e492d88", + "name": "value", + "type": "BOOLEAN", + "linkIds": [ + 719 + ], + "label": "use 2x on stage 3", + "pos": [ + -245.43359375, + 7210 + ] + }, + { + "id": "9951f766-4866-4ed1-bab3-39a03ca4ae6a", + "name": "multi_input", + "type": "IMAGE", + "linkIds": [ + 1005 + ], + "pos": [ + -245.43359375, + 7230 + ] + }, + { + "id": "265429c2-c78b-4c5d-80d7-6e272aa96a36", + "name": "upscale_model", + "type": "LATENT_UPSCALE_MODEL", + "linkIds": [ + 1038 + ], + "label": "2x_upscale_model", + "pos": [ + -245.43359375, + 7250 + ] + } + ], + "outputs": [ + { + "id": "7b177753-a22f-47a0-9ff8-7f4022516b5c", + "name": "output", + "type": "LATENT", + "linkIds": [ + 1024 + ], + "label": "video_latent", + "pos": [ + 1700, + 7110 + ] + }, + { + "id": "51232fd0-325e-4090-bcc9-4ecbdb0b9e27", + "name": "audio_latent", + "type": "LATENT", + "linkIds": [ + 1025 + ], + "pos": [ + 1700, + 7130 + ] + } + ], + "widgets": [], + "nodes": [ + { + "id": 331, + "type": "LTXVCropGuides", + "pos": [ + -190, + 6940 + ], + "size": [ + 152.7134765625, + 66 + ], + "flags": {}, + "order": 6, + "mode": 0, + "inputs": [ + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "link": 919 + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "link": 920 + }, + { + "localized_name": "latent", + "name": "latent", + "type": "LATENT", + "link": 708 + } + ], + "outputs": [ + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "links": [ + 999 + ] + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "links": [ + 1000 + ] + }, + { + "localized_name": "latent", + "name": "latent", + "type": "LATENT", + "links": [ + 877, + 878 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.16.4", + "Node name for S&R": "LTXVCropGuides" + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 325, + "type": "KSamplerSelect", + "pos": [ + -190, + 7050 + ], + "size": [ + 210, + 60 + ], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [], + "outputs": [ + { + "localized_name": "SAMPLER", + "name": "SAMPLER", + "type": "SAMPLER", + "links": [ + 680 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.56", + "Node name for S&R": "KSamplerSelect", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [ + "euler" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 326, + "type": "BasicScheduler", + "pos": [ + -190, + 7150 + ], + "size": [ + 210, + 106 + ], + "flags": {}, + "order": 4, + "mode": 0, + "inputs": [ + { + "localized_name": "model", + "name": "model", + "type": "MODEL", + "link": 686 + } + ], + "outputs": [ + { + "localized_name": "SIGMAS", + "name": "SIGMAS", + "type": "SIGMAS", + "links": [ + 681 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.16.4", + "Node name for S&R": "BasicScheduler" + }, + "widgets_values": [ + "linear_quadratic", + 4, + 0.42 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 334, + "type": "LTXVLatentUpsampler", + "pos": [ + 160, + 7300 + ], + "size": [ + 320, + 66 + ], + "flags": {}, + "order": 8, + "mode": 0, + "inputs": [ + { + "localized_name": "samples", + "name": "samples", + "type": "LATENT", + "link": 877 + }, + { + "localized_name": "upscale_model", + "name": "upscale_model", + "type": "LATENT_UPSCALE_MODEL", + "link": 710 + }, + { + "localized_name": "vae", + "name": "vae", + "type": "VAE", + "link": 716 + } + ], + "outputs": [ + { + "localized_name": "LATENT", + "name": "LATENT", + "type": "LATENT", + "links": [ + 712 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.14.1", + "Node name for S&R": "LTXVLatentUpsampler", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 332, + "type": "LazySwitchKJ", + "pos": [ + 500, + 7360 + ], + "size": [ + 210, + 78 + ], + "flags": {}, + "order": 7, + "mode": 0, + "inputs": [ + { + "localized_name": "on_false", + "name": "on_false", + "type": "*", + "link": 712 + }, + { + "localized_name": "on_true", + "name": "on_true", + "type": "*", + "link": 713 + }, + { + "localized_name": "switch", + "name": "switch", + "type": "BOOLEAN", + "widget": { + "name": "switch" + }, + "link": 718 + } + ], + "outputs": [ + { + "localized_name": "*", + "name": "*", + "type": "*", + "links": [ + 1001 + ] + } + ], + "properties": { + "cnr_id": "comfyui-kjnodes", + "ver": "1.3.4", + "Node name for S&R": "LazySwitchKJ" + }, + "widgets_values": [ + false + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 411, + "type": "LTXSequencer", + "pos": [ + 720, + 7110 + ], + "size": [ + 210, + 200 + ], + "flags": {}, + "order": 11, + "mode": 0, + "inputs": [ + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "link": 999 + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "link": 1000 + }, + { + "localized_name": "vae", + "name": "vae", + "type": "VAE", + "link": 1002 + }, + { + "localized_name": "latent", + "name": "latent", + "type": "LATENT", + "link": 1001 + }, + { + "localized_name": "multi_input", + "name": "multi_input", + "type": "IMAGE", + "link": 1005 + } + ], + "outputs": [ + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "links": [ + 996 + ] + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "links": [ + 997 + ] + }, + { + "localized_name": "latent", + "name": "latent", + "type": "LATENT", + "links": [ + 998 + ] + } + ], + "properties": { + "cnr_id": "WhatDreamsCost-ComfyUI", + "ver": "ecd00e6b404f14b07c7b54b0b0033e8bccb32d24", + "Node name for S&R": "LTXSequencer", + "num_images": 0, + "insert_frame_1": 0, + "strength_1": 1, + "insert_frame_2": 0, + "strength_2": 1, + "insert_frame_3": 0, + "strength_3": 1, + "insert_frame_4": 0, + "strength_4": 1, + "insert_frame_5": 0, + "strength_5": 1, + "insert_frame_6": 0, + "strength_6": 1, + "insert_frame_7": 0, + "strength_7": 1, + "insert_frame_8": 0, + "strength_8": 1, + "insert_frame_9": 0, + "strength_9": 0, + "insert_frame_10": 1, + "strength_10": 1, + "insert_frame_11": 0, + "strength_11": 0, + "insert_frame_12": 1, + "strength_12": 1, + "insert_frame_13": 0, + "strength_13": 0, + "insert_frame_14": 1, + "strength_14": 1, + "insert_frame_15": 0, + "strength_15": 0, + "insert_frame_16": 1, + "strength_16": 1, + "insert_frame_17": 0, + "strength_17": 0, + "insert_frame_18": 1, + "strength_18": 1, + "insert_frame_19": 0, + "strength_19": 0, + "insert_frame_20": 1, + "strength_20": 1, + "insert_frame_21": 0, + "strength_21": 0, + "insert_frame_22": 1, + "strength_22": 1, + "insert_frame_23": 0, + "strength_23": 0, + "insert_frame_24": 1, + "strength_24": 1, + "insert_frame_25": 0, + "strength_25": 0, + "insert_frame_26": 1, + "strength_26": 1, + "insert_frame_27": 0, + "strength_27": 0, + "insert_frame_28": 1, + "strength_28": 1, + "insert_frame_29": 0, + "strength_29": 0, + "insert_frame_30": 1, + "strength_30": 1, + "insert_frame_31": 0, + "strength_31": 0, + "insert_frame_32": 1, + "strength_32": 1, + "insert_frame_33": 0, + "strength_33": 1, + "insert_frame_34": 0, + "strength_34": 1, + "insert_frame_35": 0, + "strength_35": 1, + "insert_frame_36": 0, + "strength_36": 1, + "insert_frame_37": 0, + "strength_37": 1, + "insert_frame_38": 0, + "strength_38": 1, + "insert_frame_39": 0, + "strength_39": 1, + "insert_frame_40": 0, + "strength_40": 1, + "insert_frame_41": 0, + "strength_41": 1, + "insert_frame_42": 0, + "strength_42": 1, + "insert_frame_43": 0, + "strength_43": 1, + "insert_frame_44": 0, + "strength_44": 1, + "insert_frame_45": 0, + "strength_45": 1, + "insert_frame_46": 0, + "strength_46": 1, + "insert_frame_47": 0, + "strength_47": 1, + "insert_frame_48": 0, + "strength_48": 1, + "insert_frame_49": 0, + "strength_49": 1, + "insert_frame_50": 0, + "strength_50": 1, + "insert_mode": "frames", + "frame_rate": 24, + "insert_second_1": 0, + "insert_second_2": 0, + "insert_second_3": 0, + "insert_second_4": 0, + "insert_second_5": 0, + "insert_second_6": 0, + "insert_second_7": 0, + "insert_second_8": 0, + "insert_second_9": 1, + "insert_second_10": 0, + "insert_second_11": 1, + "insert_second_12": 0, + "insert_second_13": 1, + "insert_second_14": 0, + "insert_second_15": 1, + "insert_second_16": 0, + "insert_second_17": 1, + "insert_second_18": 0, + "insert_second_19": 1, + "insert_second_20": 0, + "insert_second_21": 1, + "insert_second_22": 0, + "insert_second_23": 1, + "insert_second_24": 0, + "insert_second_25": 1, + "insert_second_26": 0, + "insert_second_27": 1, + "insert_second_28": 0, + "insert_second_29": 1, + "insert_second_30": 0, + "insert_second_31": 1, + "insert_second_32": 0, + "insert_second_33": 1, + "insert_second_34": 0, + "insert_second_35": 0, + "insert_second_36": 0, + "insert_second_37": 0, + "insert_second_38": 0, + "insert_second_39": 0, + "insert_second_40": 0, + "insert_second_41": 0, + "insert_second_42": 0, + "insert_second_43": 0, + "insert_second_44": 0, + "insert_second_45": 0, + "insert_second_46": 0, + "insert_second_47": 0, + "insert_second_48": 0, + "insert_second_49": 0, + "insert_second_50": 0, + "insert_images_using": "frames", + "video_length": 1, + "insert_percentage_1": 0, + "insert_percentage_2": 1, + "insert_percentage_3": 0, + "insert_percentage_4": 0, + "insert_percentage_5": 0, + "insert_percentage_6": 0, + "insert_percentage_7": 0, + "insert_percentage_8": 0, + "insert_percentage_9": 0, + "insert_percentage_10": 0, + "insert_percentage_11": 0, + "insert_percentage_12": 0, + "insert_percentage_13": 0, + "insert_percentage_14": 0, + "insert_percentage_15": 0, + "insert_percentage_16": 0, + "insert_percentage_17": 0, + "insert_percentage_18": 0, + "insert_percentage_19": 0, + "insert_percentage_20": 0, + "insert_percentage_21": 0, + "insert_percentage_22": 0, + "insert_percentage_23": 0, + "insert_percentage_24": 0, + "insert_percentage_25": 0, + "insert_percentage_26": 0, + "insert_percentage_27": 0, + "insert_percentage_28": 0, + "insert_percentage_29": 0, + "insert_percentage_30": 0, + "insert_percentage_31": 0, + "insert_percentage_32": 0, + "insert_percentage_33": 0, + "insert_percentage_34": 0, + "insert_percentage_35": 0, + "insert_percentage_36": 0, + "insert_percentage_37": 0, + "insert_percentage_38": 0, + "insert_percentage_39": 0, + "insert_percentage_40": 0, + "insert_percentage_41": 0, + "insert_percentage_42": 0, + "insert_percentage_43": 0, + "insert_percentage_44": 0, + "insert_percentage_45": 0, + "insert_percentage_46": 0, + "insert_percentage_47": 0, + "insert_percentage_48": 0, + "insert_percentage_49": 0, + "insert_percentage_50": 0, + "insert_percent_1": 0, + "insert_percent_2": 50, + "insert_percent_3": 100, + "insert_percent_4": 0, + "insert_percent_5": 0, + "insert_percent_6": 0, + "insert_percent_7": 0, + "insert_percent_8": 0, + "insert_percent_9": 0, + "insert_percent_10": 0, + "insert_percent_11": 0, + "insert_percent_12": 0, + "insert_percent_13": 0, + "insert_percent_14": 0, + "insert_percent_15": 0, + "insert_percent_16": 0, + "insert_percent_17": 0, + "insert_percent_18": 0, + "insert_percent_19": 0, + "insert_percent_20": 0, + "insert_percent_21": 0, + "insert_percent_22": 0, + "insert_percent_23": 0, + "insert_percent_24": 0, + "insert_percent_25": 0, + "insert_percent_26": 0, + "insert_percent_27": 0, + "insert_percent_28": 0, + "insert_percent_29": 0, + "insert_percent_30": 0, + "insert_percent_31": 0, + "insert_percent_32": 0, + "insert_percent_33": 0, + "insert_percent_34": 0, + "insert_percent_35": 0, + "insert_percent_36": 0, + "insert_percent_37": 0, + "insert_percent_38": 0, + "insert_percent_39": 0, + "insert_percent_40": 0, + "insert_percent_41": 0, + "insert_percent_42": 0, + "insert_percent_43": 0, + "insert_percent_44": 0, + "insert_percent_45": 0, + "insert_percent_46": 0, + "insert_percent_47": 0, + "insert_percent_48": 0, + "insert_percent_49": 0, + "insert_percent_50": 0 + }, + "widgets_values": [ + 0, + "frames", + 24, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 324, + "type": "CFGGuider", + "pos": [ + 1010, + 7110 + ], + "size": [ + 210, + 98 + ], + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [ + { + "localized_name": "model", + "name": "model", + "type": "MODEL", + "link": 683 + }, + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "link": 996 + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "link": 997 + } + ], + "outputs": [ + { + "localized_name": "GUIDER", + "name": "GUIDER", + "type": "GUIDER", + "links": [ + 679 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.64", + "Node name for S&R": "CFGGuider", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [ + 1 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 329, + "type": "LTXVConcatAVLatent", + "pos": [ + 1010, + 7250 + ], + "size": [ + 187.5, + 60 + ], + "flags": {}, + "order": 5, + "mode": 0, + "inputs": [ + { + "localized_name": "video_latent", + "name": "video_latent", + "type": "LATENT", + "link": 998 + }, + { + "localized_name": "audio_latent", + "name": "audio_latent", + "type": "LATENT", + "link": 700 + } + ], + "outputs": [ + { + "localized_name": "latent", + "name": "latent", + "type": "LATENT", + "links": [ + 697 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.7.0", + "Node name for S&R": "LTXVConcatAVLatent", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 322, + "type": "SamplerCustomAdvanced", + "pos": [ + 1230, + 7100 + ], + "size": [ + 212.3638671875, + 204.64497833251954 + ], + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [ + { + "localized_name": "noise", + "name": "noise", + "type": "NOISE", + "link": 688 + }, + { + "localized_name": "guider", + "name": "guider", + "type": "GUIDER", + "link": 679 + }, + { + "localized_name": "sampler", + "name": "sampler", + "type": "SAMPLER", + "link": 680 + }, + { + "localized_name": "sigmas", + "name": "sigmas", + "type": "SIGMAS", + "link": 681 + }, + { + "localized_name": "latent_image", + "name": "latent_image", + "type": "LATENT", + "link": 697 + } + ], + "outputs": [ + { + "localized_name": "output", + "name": "output", + "type": "LATENT", + "links": [ + 1023 + ] + }, + { + "localized_name": "denoised_output", + "name": "denoised_output", + "type": "LATENT", + "links": [] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.60", + "Node name for S&R": "SamplerCustomAdvanced", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 417, + "type": "LTXVSeparateAVLatent", + "pos": [ + 1450, + 7100 + ], + "size": [ + 193.2916015625, + 46 + ], + "flags": {}, + "order": 12, + "mode": 0, + "inputs": [ + { + "localized_name": "av_latent", + "name": "av_latent", + "type": "LATENT", + "link": 1023 + } + ], + "outputs": [ + { + "localized_name": "video_latent", + "name": "video_latent", + "type": "LATENT", + "links": [ + 1024 + ] + }, + { + "localized_name": "audio_latent", + "name": "audio_latent", + "type": "LATENT", + "links": [ + 1025 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.17.2", + "Node name for S&R": "LTXVSeparateAVLatent" + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 336, + "type": "LTXVLatentUpsampler", + "pos": [ + 160, + 7410 + ], + "size": [ + 320, + 66 + ], + "flags": {}, + "order": 9, + "mode": 0, + "inputs": [ + { + "localized_name": "samples", + "name": "samples", + "type": "LATENT", + "link": 878 + }, + { + "localized_name": "upscale_model", + "name": "upscale_model", + "type": "LATENT_UPSCALE_MODEL", + "link": 1038 + }, + { + "localized_name": "vae", + "name": "vae", + "type": "VAE", + "link": 717 + } + ], + "outputs": [ + { + "localized_name": "LATENT", + "name": "LATENT", + "type": "LATENT", + "links": [ + 713 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.14.1", + "Node name for S&R": "LTXVLatentUpsampler", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 333, + "type": "LatentUpscaleModelLoader", + "pos": [ + -200, + 7300 + ], + "size": [ + 330, + 58 + ], + "flags": {}, + "order": 1, + "mode": 0, + "inputs": [], + "outputs": [ + { + "localized_name": "LATENT_UPSCALE_MODEL", + "name": "LATENT_UPSCALE_MODEL", + "type": "LATENT_UPSCALE_MODEL", + "links": [ + 710 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.16.4", + "Node name for S&R": "LatentUpscaleModelLoader" + }, + "widgets_values": [ + "ltx-2.3-spatial-upscaler-x1.5-1.0.safetensors" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 339, + "type": "PrimitiveBoolean", + "pos": [ + 160, + 7520 + ], + "size": [ + 290, + 60 + ], + "flags": {}, + "order": 10, + "mode": 0, + "inputs": [ + { + "localized_name": "value", + "name": "value", + "type": "BOOLEAN", + "widget": { + "name": "value" + }, + "link": 719 + } + ], + "outputs": [ + { + "localized_name": "BOOLEAN", + "name": "BOOLEAN", + "type": "BOOLEAN", + "links": [ + 718 + ] + } + ], + "title": "3rd Stage: False = 1.5x, True = 2x", + "properties": { + "cnr_id": "comfy-core", + "ver": "0.17.2", + "Node name for S&R": "PrimitiveBoolean" + }, + "widgets_values": [ + true + ], + "color": "#222", + "bgcolor": "#000" + } + ], + "groups": [], + "links": [ + { + "id": 679, + "origin_id": 324, + "origin_slot": 0, + "target_id": 322, + "target_slot": 1, + "type": "GUIDER" + }, + { + "id": 680, + "origin_id": 325, + "origin_slot": 0, + "target_id": 322, + "target_slot": 2, + "type": "SAMPLER" + }, + { + "id": 681, + "origin_id": 326, + "origin_slot": 0, + "target_id": 322, + "target_slot": 3, + "type": "SIGMAS" + }, + { + "id": 683, + "origin_id": -10, + "origin_slot": 0, + "target_id": 324, + "target_slot": 0, + "type": "MODEL" + }, + { + "id": 686, + "origin_id": -10, + "origin_slot": 0, + "target_id": 326, + "target_slot": 0, + "type": "MODEL" + }, + { + "id": 688, + "origin_id": -10, + "origin_slot": 3, + "target_id": 322, + "target_slot": 0, + "type": "NOISE" + }, + { + "id": 697, + "origin_id": 329, + "origin_slot": 0, + "target_id": 322, + "target_slot": 4, + "type": "LATENT" + }, + { + "id": 700, + "origin_id": -10, + "origin_slot": 7, + "target_id": 329, + "target_slot": 1, + "type": "LATENT" + }, + { + "id": 708, + "origin_id": -10, + "origin_slot": 6, + "target_id": 331, + "target_slot": 2, + "type": "LATENT" + }, + { + "id": 710, + "origin_id": 333, + "origin_slot": 0, + "target_id": 334, + "target_slot": 1, + "type": "LATENT_UPSCALE_MODEL" + }, + { + "id": 712, + "origin_id": 334, + "origin_slot": 0, + "target_id": 332, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 713, + "origin_id": 336, + "origin_slot": 0, + "target_id": 332, + "target_slot": 1, + "type": "LATENT" + }, + { + "id": 716, + "origin_id": -10, + "origin_slot": 4, + "target_id": 334, + "target_slot": 2, + "type": "VAE" + }, + { + "id": 717, + "origin_id": -10, + "origin_slot": 4, + "target_id": 336, + "target_slot": 2, + "type": "VAE" + }, + { + "id": 718, + "origin_id": 339, + "origin_slot": 0, + "target_id": 332, + "target_slot": 2, + "type": "BOOLEAN" + }, + { + "id": 719, + "origin_id": -10, + "origin_slot": 8, + "target_id": 339, + "target_slot": 0, + "type": "BOOLEAN" + }, + { + "id": 877, + "origin_id": 331, + "origin_slot": 2, + "target_id": 334, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 878, + "origin_id": 331, + "origin_slot": 2, + "target_id": 336, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 919, + "origin_id": -10, + "origin_slot": 1, + "target_id": 331, + "target_slot": 0, + "type": "CONDITIONING" + }, + { + "id": 920, + "origin_id": -10, + "origin_slot": 2, + "target_id": 331, + "target_slot": 1, + "type": "CONDITIONING" + }, + { + "id": 996, + "origin_id": 411, + "origin_slot": 0, + "target_id": 324, + "target_slot": 1, + "type": "CONDITIONING" + }, + { + "id": 997, + "origin_id": 411, + "origin_slot": 1, + "target_id": 324, + "target_slot": 2, + "type": "CONDITIONING" + }, + { + "id": 998, + "origin_id": 411, + "origin_slot": 2, + "target_id": 329, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 999, + "origin_id": 331, + "origin_slot": 0, + "target_id": 411, + "target_slot": 0, + "type": "CONDITIONING" + }, + { + "id": 1000, + "origin_id": 331, + "origin_slot": 1, + "target_id": 411, + "target_slot": 1, + "type": "CONDITIONING" + }, + { + "id": 1001, + "origin_id": 332, + "origin_slot": 0, + "target_id": 411, + "target_slot": 3, + "type": "LATENT" + }, + { + "id": 1002, + "origin_id": -10, + "origin_slot": 4, + "target_id": 411, + "target_slot": 2, + "type": "VAE" + }, + { + "id": 1005, + "origin_id": -10, + "origin_slot": 9, + "target_id": 411, + "target_slot": 4, + "type": "IMAGE" + }, + { + "id": 1023, + "origin_id": 322, + "origin_slot": 0, + "target_id": 417, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 1024, + "origin_id": 417, + "origin_slot": 0, + "target_id": -20, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 1025, + "origin_id": 417, + "origin_slot": 1, + "target_id": -20, + "target_slot": 1, + "type": "LATENT" + }, + { + "id": 1038, + "origin_id": -10, + "origin_slot": 10, + "target_id": 336, + "target_slot": 1, + "type": "LATENT_UPSCALE_MODEL" + } + ], + "extra": { + "workflowRendererVersion": "LG" + } + }, + { + "id": "958b2c7a-1f75-4a30-84a8-892bddb9e426", + "version": 1, + "state": { + "lastGroupId": 33, + "lastNodeId": 443, + "lastLinkId": 1080, + "lastRerouteId": 0 + }, + "revision": 0, + "config": {}, + "name": "Model Loader", + "inputNode": { + "id": -10, + "bounding": [ + 2340, + 6080, + 135.654296875, + 80 + ] + }, + "outputNode": { + "id": -20, + "bounding": [ + 3560, + 5580, + 187.052734375, + 200 + ] + }, + "inputs": [ + { + "id": "9ae347e5-8a40-4407-bec5-7781d672dd85", + "name": "value", + "type": "STRING", + "linkIds": [ + 1064 + ], + "label": "Prompt", + "pos": [ + 2455.654296875, + 6100 + ] + }, + { + "id": "32b8ff31-4396-4336-aeaf-ff84d858a4d9", + "name": "value_1", + "type": "INT", + "linkIds": [ + 1018 + ], + "label": "length (seconds)", + "pos": [ + 2455.654296875, + 6120 + ] + } + ], + "outputs": [ + { + "id": "a72640aa-8055-45bb-8cbb-f68ec7413515", + "name": "optimized_model", + "type": "MODEL", + "linkIds": [ + 1067 + ], + "pos": [ + 3580, + 5600 + ] + }, + { + "id": "cfb48567-9b75-46f5-8b71-385f3388173c", + "name": "VAE", + "type": "VAE", + "linkIds": [ + 721 + ], + "label": "video_vae", + "pos": [ + 3580, + 5620 + ] + }, + { + "id": "4e095394-8a92-47e4-8993-49fb4ce73bd8", + "name": "VAE_1", + "type": "VAE", + "linkIds": [ + 722 + ], + "label": "audio_vae", + "pos": [ + 3580, + 5640 + ] + }, + { + "id": "8bbe34b1-d094-40a3-9c04-5a19cd7b2af2", + "name": "positive", + "type": "CONDITIONING", + "linkIds": [ + 726 + ], + "pos": [ + 3580, + 5660 + ] + }, + { + "id": "dac0ad69-373b-4e47-b0cd-eb8fd18e1c07", + "name": "negative", + "type": "CONDITIONING", + "linkIds": [ + 727 + ], + "pos": [ + 3580, + 5680 + ] + }, + { + "id": "b4d0868d-86e8-42ba-83fc-44c5324ac35d", + "name": "INT", + "type": "INT", + "linkIds": [ + 729 + ], + "label": "length_seconds", + "pos": [ + 3580, + 5700 + ] + }, + { + "id": "12a111ff-cd34-4b59-bca3-4c5f24626a62", + "name": "NOISE", + "type": "NOISE", + "linkIds": [ + 730 + ], + "label": "noise", + "pos": [ + 3580, + 5720 + ] + }, + { + "id": "9f03af49-218d-4431-a2e9-891d16a8eb05", + "name": "LATENT_UPSCALE_MODEL", + "type": "LATENT_UPSCALE_MODEL", + "linkIds": [ + 1034 + ], + "label": "2x_spatial_upscale_model", + "pos": [ + 3580, + 5740 + ] + } + ], + "widgets": [], + "nodes": [ + { + "id": 338, + "type": "VAELoaderKJ", + "pos": [ + 2560, + 5780 + ], + "size": [ + 270, + 106 + ], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [], + "outputs": [ + { + "localized_name": "VAE", + "name": "VAE", + "type": "VAE", + "links": [ + 722 + ] + } + ], + "title": "Audio VAELoader KJ", + "properties": { + "cnr_id": "comfyui-kjnodes", + "ver": "487c7d86a0230aae3d8c0a37d517159b73834f85", + "Node name for S&R": "VAELoaderKJ" + }, + "widgets_values": [ + "LTX23_audio_vae_bf16.safetensors", + "main_device", + "bf16" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 337, + "type": "VAELoaderKJ", + "pos": [ + 2560, + 5630 + ], + "size": [ + 270, + 106 + ], + "flags": {}, + "order": 1, + "mode": 0, + "inputs": [], + "outputs": [ + { + "localized_name": "VAE", + "name": "VAE", + "type": "VAE", + "links": [ + 721 + ] + } + ], + "title": "Video VAELoader KJ", + "properties": { + "cnr_id": "comfyui-kjnodes", + "ver": "1.2.5", + "Node name for S&R": "VAELoaderKJ" + }, + "widgets_values": [ + "LTX23_video_vae_bf16.safetensors", + "main_device", + "bf16" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 413, + "type": "LTXAVTextEncoderLoader", + "pos": [ + 2560, + 5940 + ], + "size": [ + 318.444921875, + 122 + ], + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [], + "outputs": [ + { + "localized_name": "CLIP", + "name": "CLIP", + "type": "CLIP", + "links": [ + 1054 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.14.1", + "Node name for S&R": "LTXAVTextEncoderLoader" + }, + "widgets_values": [ + "gemma_3_12B_it_fp4_mixed.safetensors", + "ltx-2.3-22b-dev-fp8.safetensors", + "default" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 430, + "type": "CLIPTextEncode", + "pos": [ + 2910, + 5950 + ], + "size": [ + 274.3658203125, + 88 + ], + "flags": {}, + "order": 13, + "mode": 0, + "inputs": [ + { + "localized_name": "clip", + "name": "clip", + "type": "CLIP", + "link": 1054 + }, + { + "localized_name": "text", + "name": "text", + "type": "STRING", + "widget": { + "name": "text" + }, + "link": 1064 + } + ], + "outputs": [ + { + "localized_name": "CONDITIONING", + "name": "CONDITIONING", + "type": "CONDITIONING", + "slot_index": 0, + "links": [ + 1062, + 1063 + ] + } + ], + "title": "CLIP Text Encode (Positive Prompt)", + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.28", + "Node name for S&R": "CLIPTextEncode" + }, + "widgets_values": [ + "" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 361, + "type": "ConditioningZeroOut", + "pos": [ + 2920, + 6090 + ], + "size": [ + 204.134765625, + 26 + ], + "flags": {}, + "order": 10, + "mode": 0, + "inputs": [ + { + "localized_name": "conditioning", + "name": "conditioning", + "type": "CONDITIONING", + "link": 1063 + } + ], + "outputs": [ + { + "localized_name": "CONDITIONING", + "name": "CONDITIONING", + "type": "CONDITIONING", + "links": [ + 725 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.7.0", + "Node name for S&R": "ConditioningZeroOut", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 363, + "type": "LTXVConditioning", + "pos": [ + 3210, + 5960 + ], + "size": [ + 210, + 80 + ], + "flags": {}, + "order": 11, + "mode": 0, + "inputs": [ + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "link": 1062 + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "link": 725 + } + ], + "outputs": [ + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "links": [ + 726 + ] + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "links": [ + 727 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.56", + "Node name for S&R": "LTXVConditioning", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [ + 24 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 419, + "type": "LatentUpscaleModelLoader", + "pos": [ + 2560, + 6110 + ], + "size": [ + 330, + 58 + ], + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [], + "outputs": [ + { + "localized_name": "LATENT_UPSCALE_MODEL", + "name": "LATENT_UPSCALE_MODEL", + "type": "LATENT_UPSCALE_MODEL", + "links": [ + 1034 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.16.4", + "Node name for S&R": "LatentUpscaleModelLoader" + }, + "widgets_values": [ + "ltx-2.3-spatial-upscaler-x2-1.1.safetensors" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 364, + "type": "RandomNoise", + "pos": [ + 2860, + 5750 + ], + "size": [ + 210, + 82 + ], + "flags": {}, + "order": 4, + "mode": 0, + "inputs": [ + { + "label": "noise_seed", + "localized_name": "noise_seed", + "name": "noise_seed", + "type": "INT", + "widget": { + "name": "noise_seed" + }, + "link": null + } + ], + "outputs": [ + { + "localized_name": "NOISE", + "name": "NOISE", + "type": "NOISE", + "links": [ + 730 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.56", + "Node name for S&R": "RandomNoise", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [ + 0, + "fixed" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 365, + "type": "07609e03-45f4-49c7-a233-a933ebf95094", + "pos": [ + 2860, + 5640 + ], + "size": [ + 210, + 58 + ], + "flags": {}, + "order": 12, + "mode": 0, + "inputs": [ + { + "label": "Seconds", + "name": "value", + "type": "INT", + "widget": { + "name": "value" + }, + "link": 1018 + } + ], + "outputs": [ + { + "localized_name": "INT", + "name": "INT", + "type": "INT", + "links": [ + 729 + ] + } + ], + "properties": { + "proxyWidgets": [ + [ + "-1", + "value" + ] + ], + "cnr_id": "comfy-core", + "ver": "0.16.4" + }, + "widgets_values": [ + 5 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 443, + "type": "VAELoaderKJ", + "pos": [ + 2560, + 5470 + ], + "size": [ + 270, + 110 + ], + "flags": {}, + "order": 5, + "mode": 0, + "inputs": [], + "outputs": [ + { + "localized_name": "VAE", + "name": "VAE", + "type": "VAE", + "links": [ + 1072 + ] + } + ], + "title": "Tiny VAELoader KJ", + "properties": { + "cnr_id": "comfyui-kjnodes", + "ver": "1.2.5", + "Node name for S&R": "VAELoaderKJ" + }, + "widgets_values": [ + "taeltx2_3.safetensors", + "main_device", + "bf16" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 442, + "type": "LTX2SamplingPreviewOverride", + "pos": [ + 2850, + 5470 + ], + "size": [ + 270, + 100 + ], + "flags": {}, + "order": 8, + "mode": 0, + "inputs": [ + { + "localized_name": "model", + "name": "model", + "type": "MODEL", + "link": 1073 + }, + { + "localized_name": "latent_upscale_model", + "name": "latent_upscale_model", + "shape": 7, + "type": "LATENT_UPSCALE_MODEL", + "link": null + }, + { + "localized_name": "vae", + "name": "vae", + "shape": 7, + "type": "VAE", + "link": 1072 + } + ], + "outputs": [ + { + "localized_name": "MODEL", + "name": "MODEL", + "type": "MODEL", + "links": [ + 1074 + ] + } + ], + "properties": { + "cnr_id": "comfyui-kjnodes", + "ver": "1.3.4", + "Node name for S&R": "LTX2SamplingPreviewOverride" + }, + "widgets_values": [ + 16 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 426, + "type": "CheckpointLoaderSimple", + "pos": [ + 2560, + 5330 + ], + "size": [ + 270, + 98 + ], + "flags": {}, + "order": 6, + "mode": 0, + "inputs": [], + "outputs": [ + { + "localized_name": "MODEL", + "name": "MODEL", + "type": "MODEL", + "links": [ + 1073 + ] + }, + { + "localized_name": "CLIP", + "name": "CLIP", + "type": "CLIP", + "links": null + }, + { + "localized_name": "VAE", + "name": "VAE", + "type": "VAE", + "links": null + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.17.2", + "Node name for S&R": "CheckpointLoaderSimple" + }, + "widgets_values": [ + "ltx-2.3-22b-dev-fp8.safetensors" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 429, + "type": "LoraLoaderModelOnly", + "pos": [ + 3140, + 5480 + ], + "size": [ + 260, + 82 + ], + "flags": {}, + "order": 9, + "mode": 0, + "inputs": [ + { + "localized_name": "model", + "name": "model", + "type": "MODEL", + "link": 1074 + } + ], + "outputs": [ + { + "localized_name": "MODEL", + "name": "MODEL", + "type": "MODEL", + "links": [ + 1067 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.75", + "Node name for S&R": "LoraLoaderModelOnly", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65, + "models": [ + { + "name": "ltx-2.3-22b-distilled-lora-384.safetensors", + "url": "https://huggingface.co/Lightricks/LTX-2.3/resolve/main/ltx-2.3-22b-distilled-lora-384.safetensors", + "directory": "loras" + } + ] + }, + "widgets_values": [ + "ltx2\\ltx-2.3-22b-distilled-lora-dynamic_fro09_avg_rank_105_bf16.safetensors", + 0.5 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 441, + "type": "MarkdownNote", + "pos": [ + 2160, + 5500 + ], + "size": [ + 370, + 320 + ], + "flags": {}, + "order": 7, + "mode": 0, + "inputs": [], + "outputs": [], + "title": "FAQ", + "properties": {}, + "widgets_values": [ + "# FAQ\n\n**How do I set the length of the video?**\n\nYou can set the length of the video in the Model Loader Subgraph →\n\n**I keep getting errors when running this workflow!**\n\nDouble check that all of the correct models loaded. This is using the newest version of 2x upscaler (v1.1). You can change it to the older one under the Model Loader.\n\n**Where do I download these models??**\n\nThere is a good list of models and download links here: \n\nAlso the latest v1.1 spatial upscaler can be found here: \n\nThe tiny vae (taeltx2_3.safetensors) is found here:\n\n\n\n\n\n\n\n\n\n" + ], + "color": "#432", + "bgcolor": "#653" + } + ], + "groups": [], + "links": [ + { + "id": 721, + "origin_id": 337, + "origin_slot": 0, + "target_id": -20, + "target_slot": 1, + "type": "VAE" + }, + { + "id": 722, + "origin_id": 338, + "origin_slot": 0, + "target_id": -20, + "target_slot": 2, + "type": "VAE" + }, + { + "id": 725, + "origin_id": 361, + "origin_slot": 0, + "target_id": 363, + "target_slot": 1, + "type": "CONDITIONING" + }, + { + "id": 726, + "origin_id": 363, + "origin_slot": 0, + "target_id": -20, + "target_slot": 3, + "type": "CONDITIONING" + }, + { + "id": 727, + "origin_id": 363, + "origin_slot": 1, + "target_id": -20, + "target_slot": 4, + "type": "CONDITIONING" + }, + { + "id": 729, + "origin_id": 365, + "origin_slot": 0, + "target_id": -20, + "target_slot": 5, + "type": "INT" + }, + { + "id": 730, + "origin_id": 364, + "origin_slot": 0, + "target_id": -20, + "target_slot": 6, + "type": "NOISE" + }, + { + "id": 1018, + "origin_id": -10, + "origin_slot": 1, + "target_id": 365, + "target_slot": 0, + "type": "INT" + }, + { + "id": 1034, + "origin_id": 419, + "origin_slot": 0, + "target_id": -20, + "target_slot": 7, + "type": "LATENT_UPSCALE_MODEL" + }, + { + "id": 1054, + "origin_id": 413, + "origin_slot": 0, + "target_id": 430, + "target_slot": 0, + "type": "CLIP" + }, + { + "id": 1062, + "origin_id": 430, + "origin_slot": 0, + "target_id": 363, + "target_slot": 0, + "type": "CONDITIONING" + }, + { + "id": 1063, + "origin_id": 430, + "origin_slot": 0, + "target_id": 361, + "target_slot": 0, + "type": "CONDITIONING" + }, + { + "id": 1064, + "origin_id": -10, + "origin_slot": 0, + "target_id": 430, + "target_slot": 1, + "type": "STRING" + }, + { + "id": 1067, + "origin_id": 429, + "origin_slot": 0, + "target_id": -20, + "target_slot": 0, + "type": "MODEL" + }, + { + "id": 1072, + "origin_id": 443, + "origin_slot": 0, + "target_id": 442, + "target_slot": 2, + "type": "VAE" + }, + { + "id": 1073, + "origin_id": 426, + "origin_slot": 0, + "target_id": 442, + "target_slot": 0, + "type": "MODEL" + }, + { + "id": 1074, + "origin_id": 442, + "origin_slot": 0, + "target_id": 429, + "target_slot": 0, + "type": "MODEL" + } + ], + "extra": { + "workflowRendererVersion": "LG" + } + }, + { + "id": "07609e03-45f4-49c7-a233-a933ebf95094", + "version": 1, + "state": { + "lastGroupId": 33, + "lastNodeId": 443, + "lastLinkId": 1080, + "lastRerouteId": 0 + }, + "revision": 0, + "config": {}, + "name": "length (seconds)", + "inputNode": { + "id": -10, + "bounding": [ + -662.1270448096031, + 4310.82566166648, + 120, + 60 + ] + }, + "outputNode": { + "id": -20, + "bounding": [ + 255.79328565979654, + 4300.82566166648, + 120, + 60 + ] + }, + "inputs": [ + { + "id": "df88c383-3299-420f-8c75-fd0d6a39a9af", + "name": "value", + "type": "INT", + "linkIds": [ + 1071 + ], + "label": "Seconds", + "pos": [ + -562.1270448096031, + 4330.82566166648 + ] + } + ], + "outputs": [ + { + "id": "9b5a4dc0-bb87-45a3-bd3d-551edc18c371", + "name": "INT", + "type": "INT", + "linkIds": [ + 359 + ], + "localized_name": "INT", + "pos": [ + 275.79328565979654, + 4320.82566166648 + ] + } + ], + "widgets": [], + "nodes": [ + { + "id": 137, + "type": "easy mathInt", + "pos": [ + -244.96788124271472, + 4296.913950056486 + ], + "size": [ + 210, + 106 + ], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [ + { + "localized_name": "a", + "name": "a", + "type": "INT", + "widget": { + "name": "a" + }, + "link": 360 + } + ], + "outputs": [ + { + "localized_name": "INT", + "name": "INT", + "type": "INT", + "links": [ + 358 + ] + } + ], + "properties": { + "cnr_id": "comfyui-easy-use", + "ver": "1.3.4", + "Node name for S&R": "easy mathInt" + }, + "widgets_values": [ + 0, + 24, + "multiply" + ] + }, + { + "id": 138, + "type": "easy mathInt", + "pos": [ + -14.206714340203394, + 4288.737373276474 + ], + "size": [ + 210, + 106 + ], + "flags": {}, + "order": 1, + "mode": 0, + "inputs": [ + { + "localized_name": "a", + "name": "a", + "type": "INT", + "widget": { + "name": "a" + }, + "link": 358 + } + ], + "outputs": [ + { + "localized_name": "INT", + "name": "INT", + "type": "INT", + "links": [ + 359 + ] + } + ], + "properties": { + "cnr_id": "comfyui-easy-use", + "ver": "1.3.4", + "Node name for S&R": "easy mathInt" + }, + "widgets_values": [ + 0, + 1, + "add" + ] + }, + { + "id": 139, + "type": "PrimitiveInt", + "pos": [ + -482.12704480960315, + 4310.54157802317 + ], + "size": [ + 210, + 87.4510511866738 + ], + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [ + { + "localized_name": "value", + "name": "value", + "type": "INT", + "widget": { + "name": "value" + }, + "link": 1071 + } + ], + "outputs": [ + { + "localized_name": "INT", + "name": "INT", + "type": "INT", + "links": [ + 360 + ] + } + ], + "title": "Seconds", + "properties": { + "cnr_id": "comfy-core", + "ver": "0.16.4", + "Node name for S&R": "PrimitiveInt" + }, + "widgets_values": [ + 5, + "fixed" + ] + } + ], + "groups": [], + "links": [ + { + "id": 360, + "origin_id": 139, + "origin_slot": 0, + "target_id": 137, + "target_slot": 0, + "type": "INT" + }, + { + "id": 358, + "origin_id": 137, + "origin_slot": 0, + "target_id": 138, + "target_slot": 0, + "type": "INT" + }, + { + "id": 359, + "origin_id": 138, + "origin_slot": 0, + "target_id": -20, + "target_slot": 0, + "type": "INT" + }, + { + "id": 1071, + "origin_id": -10, + "origin_slot": 0, + "target_id": 139, + "target_slot": 0, + "type": "INT" + } + ], + "extra": { + "workflowRendererVersion": "LG" + } + }, + { + "id": "360d64f7-96cd-42a6-9b13-6eb0387e17d9", + "version": 1, + "state": { + "lastGroupId": 33, + "lastNodeId": 443, + "lastLinkId": 1080, + "lastRerouteId": 0 + }, + "revision": 0, + "config": {}, + "name": "3 Stage Upscale", + "inputNode": { + "id": -10, + "bounding": [ + 4330, + 5940, + 187.052734375, + 240 + ] + }, + "outputNode": { + "id": -20, + "bounding": [ + 5670, + 5940, + 120, + 60 + ] + }, + "inputs": [ + { + "id": "f3d52cbc-fafe-46a6-978c-2eaff2c185c3", + "name": "model", + "type": "MODEL", + "linkIds": [ + 3, + 14, + 23 + ], + "pos": [ + 4497.052734375, + 5960 + ] + }, + { + "id": "d0e8f1b7-b19b-49cd-8a2b-aa4a639857b3", + "name": "positive", + "type": "CONDITIONING", + "linkIds": [ + 4, + 15, + 871 + ], + "pos": [ + 4497.052734375, + 5980 + ] + }, + { + "id": "36e3ff32-f9b2-41d9-94de-9d8d9a91f1a6", + "name": "negative", + "type": "CONDITIONING", + "linkIds": [ + 5, + 16, + 872 + ], + "pos": [ + 4497.052734375, + 6000 + ] + }, + { + "id": "3f2a7956-c7c2-4478-99ad-2487644cbbc2", + "name": "latent_image", + "type": "LATENT", + "linkIds": [ + 6 + ], + "pos": [ + 4497.052734375, + 6020 + ] + }, + { + "id": "cc2edb71-c53a-4056-8a85-5d8199e79c72", + "name": "noise", + "type": "NOISE", + "linkIds": [ + 7, + 17, + 26 + ], + "pos": [ + 4497.052734375, + 6040 + ] + }, + { + "id": "d162c9e7-24cc-4421-a6fb-d253bfae8883", + "name": "vae", + "type": "VAE", + "linkIds": [ + 32, + 33 + ], + "label": "video_vae", + "pos": [ + 4497.052734375, + 6060 + ] + }, + { + "id": "e9cd552e-a79c-4b6e-8508-4ad3a9f4218b", + "name": "image", + "type": "IMAGE", + "linkIds": [ + 29, + 30 + ], + "pos": [ + 4497.052734375, + 6080 + ] + }, + { + "id": "46ddf61f-c9f3-48d0-9f11-6e64e8521966", + "name": "value", + "type": "BOOLEAN", + "linkIds": [ + 40 + ], + "label": "Upscale 2x on Stage 3", + "pos": [ + 4497.052734375, + 6100 + ] + }, + { + "id": "45629d5b-0676-4430-aa3d-8b0cf516de0a", + "name": "multi_input", + "type": "IMAGE", + "linkIds": [ + 1006, + 1007 + ], + "pos": [ + 4497.052734375, + 6120 + ] + }, + { + "id": "b380a19f-38a6-41ca-be93-072da1e263f3", + "name": "upscale_model", + "type": "LATENT_UPSCALE_MODEL", + "linkIds": [ + 1036, + 1039 + ], + "label": "2x_spatial_upscale_model", + "pos": [ + 4497.052734375, + 6140 + ] + } + ], + "outputs": [ + { + "id": "2fae6e6d-6667-450e-933b-b16e88c7968f", + "name": "output", + "type": "LATENT", + "linkIds": [ + 765 + ], + "label": "av_output", + "pos": [ + 5690, + 5960 + ] + } + ], + "widgets": [], + "nodes": [ + { + "id": 371, + "type": "LTXVConcatAVLatent", + "pos": [ + 5470, + 5950 + ], + "size": [ + 181.308203125, + 46 + ], + "flags": {}, + "order": 4, + "mode": 0, + "inputs": [ + { + "localized_name": "video_latent", + "name": "video_latent", + "type": "LATENT", + "link": 852 + }, + { + "localized_name": "audio_latent", + "name": "audio_latent", + "type": "LATENT", + "link": 1080 + } + ], + "outputs": [ + { + "localized_name": "latent", + "name": "latent", + "type": "LATENT", + "links": [ + 765 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.17.2", + "Node name for S&R": "LTXVConcatAVLatent" + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 383, + "type": "LTXVCropGuides", + "pos": [ + 5200, + 5950 + ], + "size": [ + 260, + 70 + ], + "flags": {}, + "order": 5, + "mode": 0, + "inputs": [ + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "link": 871 + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "link": 872 + }, + { + "localized_name": "latent", + "name": "latent", + "type": "LATENT", + "link": 1026 + } + ], + "outputs": [ + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "links": null + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "links": null + }, + { + "localized_name": "latent", + "name": "latent", + "type": "LATENT", + "links": [ + 852 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.17.2", + "Node name for S&R": "LTXVCropGuides" + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 2, + "type": "afa29944-eb58-40a5-8e76-484f2503fa0c", + "pos": [ + 4540, + 5950 + ], + "size": [ + 190, + 195.734375 + ], + "flags": {}, + "order": 1, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 3 + }, + { + "name": "positive", + "type": "CONDITIONING", + "link": 4 + }, + { + "name": "negative", + "type": "CONDITIONING", + "link": 5 + }, + { + "name": "latent_image", + "type": "LATENT", + "link": 6 + }, + { + "name": "noise", + "type": "NOISE", + "link": 7 + } + ], + "outputs": [ + { + "name": "video_latent", + "type": "LATENT", + "links": [ + 837 + ] + }, + { + "name": "audio_latent", + "type": "LATENT", + "links": [ + 794 + ] + } + ], + "properties": { + "proxyWidgets": [], + "cnr_id": "comfy-core", + "ver": "0.17.2" + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 440, + "type": "Note", + "pos": [ + 4970, + 5810 + ], + "size": [ + 210, + 88 + ], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [], + "outputs": [], + "properties": {}, + "widgets_values": [ + "Bypass Stage #3 to skip the 2nd upscale stage." + ], + "color": "#223", + "bgcolor": "#335" + }, + { + "id": 8, + "type": "535e663f-da59-47c1-aab5-c78ff202cb16", + "pos": [ + 4970, + 5950 + ], + "size": [ + 218.19140625, + 330.9668884277344 + ], + "flags": {}, + "order": 3, + "mode": 4, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 23 + }, + { + "name": "positive", + "type": "CONDITIONING", + "link": 875 + }, + { + "name": "negative", + "type": "CONDITIONING", + "link": 876 + }, + { + "name": "noise", + "type": "NOISE", + "link": 26 + }, + { + "label": "video_vae", + "name": "vae", + "type": "VAE", + "link": 33 + }, + { + "name": "image", + "type": "IMAGE", + "link": 30 + }, + { + "label": "video_latent", + "name": "samples", + "type": "LATENT", + "link": 27 + }, + { + "name": "audio_latent", + "type": "LATENT", + "link": 1079 + }, + { + "label": "use 2x on stage 3", + "name": "value", + "type": "BOOLEAN", + "widget": { + "name": "value" + }, + "link": 40 + }, + { + "name": "multi_input", + "type": "IMAGE", + "link": 1007 + }, + { + "label": "2x_upscale_model", + "name": "upscale_model", + "type": "LATENT_UPSCALE_MODEL", + "link": 1039 + } + ], + "outputs": [ + { + "label": "video_latent", + "name": "output", + "type": "LATENT", + "links": [ + 1026 + ] + }, + { + "name": "audio_latent", + "type": "LATENT", + "links": [] + } + ], + "properties": { + "proxyWidgets": [ + [ + "-1", + "value" + ] + ], + "cnr_id": "comfy-core", + "ver": "0.17.2" + }, + "widgets_values": [ + true + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 6, + "type": "ce8f7842-cb03-41b4-9b1e-8d032b2a4e72", + "pos": [ + 4750, + 5950 + ], + "size": [ + 200, + 299.71875 + ], + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 14 + }, + { + "name": "positive", + "type": "CONDITIONING", + "link": 15 + }, + { + "name": "negative", + "type": "CONDITIONING", + "link": 16 + }, + { + "name": "noise", + "type": "NOISE", + "link": 17 + }, + { + "label": "video_vae", + "name": "vae", + "type": "VAE", + "link": 32 + }, + { + "name": "image", + "type": "IMAGE", + "link": 29 + }, + { + "label": "video_latent", + "name": "samples", + "type": "LATENT", + "link": 837 + }, + { + "name": "audio_latent", + "type": "LATENT", + "link": 794 + }, + { + "name": "multi_input", + "type": "IMAGE", + "link": 1006 + }, + { + "name": "upscale_model", + "type": "LATENT_UPSCALE_MODEL", + "link": 1036 + } + ], + "outputs": [ + { + "name": "video_latent", + "type": "LATENT", + "links": [ + 27 + ] + }, + { + "name": "audio_latent", + "type": "LATENT", + "links": [ + 1079, + 1080 + ] + }, + { + "name": "positive", + "type": "CONDITIONING", + "links": [ + 855, + 875 + ] + }, + { + "name": "negative", + "type": "CONDITIONING", + "links": [ + 856, + 876 + ] + } + ], + "properties": { + "proxyWidgets": [], + "cnr_id": "comfy-core", + "ver": "0.17.2" + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + } + ], + "groups": [], + "links": [ + { + "id": 27, + "origin_id": 6, + "origin_slot": 0, + "target_id": 8, + "target_slot": 6, + "type": "LATENT" + }, + { + "id": 3, + "origin_id": -10, + "origin_slot": 0, + "target_id": 2, + "target_slot": 0, + "type": "MODEL" + }, + { + "id": 14, + "origin_id": -10, + "origin_slot": 0, + "target_id": 6, + "target_slot": 0, + "type": "MODEL" + }, + { + "id": 23, + "origin_id": -10, + "origin_slot": 0, + "target_id": 8, + "target_slot": 0, + "type": "MODEL" + }, + { + "id": 4, + "origin_id": -10, + "origin_slot": 1, + "target_id": 2, + "target_slot": 1, + "type": "CONDITIONING" + }, + { + "id": 15, + "origin_id": -10, + "origin_slot": 1, + "target_id": 6, + "target_slot": 1, + "type": "CONDITIONING" + }, + { + "id": 5, + "origin_id": -10, + "origin_slot": 2, + "target_id": 2, + "target_slot": 2, + "type": "CONDITIONING" + }, + { + "id": 16, + "origin_id": -10, + "origin_slot": 2, + "target_id": 6, + "target_slot": 2, + "type": "CONDITIONING" + }, + { + "id": 6, + "origin_id": -10, + "origin_slot": 3, + "target_id": 2, + "target_slot": 3, + "type": "LATENT" + }, + { + "id": 7, + "origin_id": -10, + "origin_slot": 4, + "target_id": 2, + "target_slot": 4, + "type": "NOISE" + }, + { + "id": 17, + "origin_id": -10, + "origin_slot": 4, + "target_id": 6, + "target_slot": 3, + "type": "NOISE" + }, + { + "id": 26, + "origin_id": -10, + "origin_slot": 4, + "target_id": 8, + "target_slot": 3, + "type": "NOISE" + }, + { + "id": 32, + "origin_id": -10, + "origin_slot": 5, + "target_id": 6, + "target_slot": 4, + "type": "VAE" + }, + { + "id": 33, + "origin_id": -10, + "origin_slot": 5, + "target_id": 8, + "target_slot": 4, + "type": "VAE" + }, + { + "id": 29, + "origin_id": -10, + "origin_slot": 6, + "target_id": 6, + "target_slot": 5, + "type": "IMAGE" + }, + { + "id": 30, + "origin_id": -10, + "origin_slot": 6, + "target_id": 8, + "target_slot": 5, + "type": "IMAGE" + }, + { + "id": 40, + "origin_id": -10, + "origin_slot": 7, + "target_id": 8, + "target_slot": 8, + "type": "BOOLEAN" + }, + { + "id": 765, + "origin_id": 371, + "origin_slot": 0, + "target_id": -20, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 794, + "origin_id": 2, + "origin_slot": 1, + "target_id": 6, + "target_slot": 7, + "type": "LATENT" + }, + { + "id": 837, + "origin_id": 2, + "origin_slot": 0, + "target_id": 6, + "target_slot": 6, + "type": "LATENT" + }, + { + "id": 852, + "origin_id": 383, + "origin_slot": 2, + "target_id": 371, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 871, + "origin_id": -10, + "origin_slot": 1, + "target_id": 383, + "target_slot": 0, + "type": "CONDITIONING" + }, + { + "id": 872, + "origin_id": -10, + "origin_slot": 2, + "target_id": 383, + "target_slot": 1, + "type": "CONDITIONING" + }, + { + "id": 875, + "origin_id": 6, + "origin_slot": 2, + "target_id": 8, + "target_slot": 1, + "type": "CONDITIONING" + }, + { + "id": 876, + "origin_id": 6, + "origin_slot": 3, + "target_id": 8, + "target_slot": 2, + "type": "CONDITIONING" + }, + { + "id": 1006, + "origin_id": -10, + "origin_slot": 8, + "target_id": 6, + "target_slot": 8, + "type": "IMAGE" + }, + { + "id": 1007, + "origin_id": -10, + "origin_slot": 8, + "target_id": 8, + "target_slot": 9, + "type": "IMAGE" + }, + { + "id": 1026, + "origin_id": 8, + "origin_slot": 0, + "target_id": 383, + "target_slot": 2, + "type": "LATENT" + }, + { + "id": 1036, + "origin_id": -10, + "origin_slot": 9, + "target_id": 6, + "target_slot": 9, + "type": "LATENT_UPSCALE_MODEL" + }, + { + "id": 1039, + "origin_id": -10, + "origin_slot": 9, + "target_id": 8, + "target_slot": 10, + "type": "LATENT_UPSCALE_MODEL" + }, + { + "id": 1079, + "origin_id": 6, + "origin_slot": 1, + "target_id": 8, + "target_slot": 7, + "type": "LATENT" + }, + { + "id": 1080, + "origin_id": 6, + "origin_slot": 1, + "target_id": 371, + "target_slot": 1, + "type": "LATENT" + } + ], + "extra": { + "workflowRendererVersion": "LG" + } + } + ] + }, + "config": {}, + "extra": { + "frontendVersion": "1.39.19", + "workflowRendererVersion": "LG", + "VHS_latentpreview": false, + "VHS_latentpreviewrate": 0, + "VHS_MetadataImage": true, + "VHS_KeepIntermediate": true, + "ds": { + "scale": 1, + "offset": [ + -3243, + -5621 + ] + } + }, + "version": 0.4 } \ No newline at end of file diff --git a/example_workflows/LTX I2V First Last Frame 3 Stage Workflow v6.json b/example_workflows/LTX I2V First Last Frame 3 Stage Workflow v6.json index 735c6d8..132c292 100644 --- a/example_workflows/LTX I2V First Last Frame 3 Stage Workflow v6.json +++ b/example_workflows/LTX I2V First Last Frame 3 Stage Workflow v6.json @@ -1,7916 +1,7916 @@ -{ - "id": "a41dc75e-fc80-4307-897e-f3118e4fa21b", - "revision": 0, - "last_node_id": 443, - "last_link_id": 1079, - "nodes": [ - { - "id": 4, - "type": "LTXVConcatAVLatent", - "pos": [ - 4180, - 6090 - ], - "size": [ - 210, - 60 - ], - "flags": {}, - "order": 9, - "mode": 0, - "inputs": [ - { - "name": "video_latent", - "type": "LATENT", - "link": 802 - }, - { - "name": "audio_latent", - "type": "LATENT", - "link": 12 - } - ], - "outputs": [ - { - "name": "latent", - "type": "LATENT", - "links": [ - 804 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.7.0", - "Node name for S&R": "LTXVConcatAVLatent", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 5, - "type": "SaveVideo", - "pos": [ - 4940, - 5950 - ], - "size": [ - 660, - 440 - ], - "flags": {}, - "order": 12, - "mode": 0, - "inputs": [ - { - "name": "video", - "type": "VIDEO", - "link": 13 - } - ], - "outputs": [], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.5.1", - "Node name for S&R": "SaveVideo", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [ - "video/LTX", - "auto", - "auto" - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 14, - "type": "Note", - "pos": [ - 4410, - 5780 - ], - "size": [ - 210, - 110 - ], - "flags": {}, - "order": 0, - "mode": 0, - "inputs": [], - "outputs": [], - "title": "3 Stage Upscale Note", - "properties": {}, - "widgets_values": [ - "Stage 3 will default to 1.5x on the 3rd stage if false.\n\nIf you want to disable Stage 3, simply bypass the 3rd stage in the subraph below." - ], - "color": "#223", - "bgcolor": "#335" - }, - { - "id": 418, - "type": "Note", - "pos": [ - 3080, - 5820 - ], - "size": [ - 280, - 88 - ], - "flags": {}, - "order": 1, - "mode": 0, - "inputs": [], - "outputs": [], - "title": "Model Loader Note", - "properties": {}, - "widgets_values": [ - "↓ Select your own models in this subgraph ↓" - ], - "color": "#223", - "bgcolor": "#335" - }, - { - "id": 16, - "type": "Note", - "pos": [ - 4670, - 6110 - ], - "size": [ - 210, - 88 - ], - "flags": {}, - "order": 2, - "mode": 0, - "inputs": [], - "outputs": [], - "title": "Decode Note", - "properties": {}, - "widgets_values": [ - "Enable optimized decoding to use tiled vae decoder." - ], - "color": "#223", - "bgcolor": "#335" - }, - { - "id": 439, - "type": "MarkdownNote", - "pos": [ - 2680, - 6180 - ], - "size": [ - 370, - 320 - ], - "flags": {}, - "order": 3, - "mode": 0, - "inputs": [], - "outputs": [], - "title": "FAQ", - "properties": {}, - "widgets_values": [ - "# FAQ\n\n**How do I set the length of the video?**\n\nYou can set the length of the video in the Model Loader Subgraph →\n\n**I keep getting errors when running this workflow!**\n\nDouble check that all of the correct models loaded. This is using the newest version of 2x upscaler (v1.1). You can change it to the older one under the Model Loader.\n\n**Where do I download these models??**\n\nThere is a good list of models and download links here: \n\nAlso the latest v1.1 spatial upscaler can be found here: \n\nThe tiny vae (taeltx2_3.safetensors) is found here:\n\n\n\n\n\n\n\n\n\n" - ], - "color": "#432", - "bgcolor": "#653" - }, - { - "id": 1, - "type": "34d63160-e149-4935-ab1f-892ffe617fec", - "pos": [ - 4670, - 5950 - ], - "size": [ - 244.98828125, - 114 - ], - "flags": {}, - "order": 11, - "mode": 0, - "inputs": [ - { - "name": "av_latent", - "type": "LATENT", - "link": 47 - }, - { - "name": "audio_vae", - "type": "VAE", - "link": 2 - }, - { - "label": "video_vae", - "name": "vae", - "type": "VAE", - "link": 31 - }, - { - "label": "Use Optimized Decoding", - "name": "value", - "type": "BOOLEAN", - "widget": { - "name": "value" - }, - "link": null - } - ], - "outputs": [ - { - "name": "VIDEO", - "type": "VIDEO", - "links": [ - 13 - ] - } - ], - "properties": { - "proxyWidgets": [ - [ - "-1", - "value" - ] - ], - "cnr_id": "comfy-core", - "ver": "0.17.2" - }, - "widgets_values": [ - false - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 10, - "type": "958b2c7a-1f75-4a30-84a8-892bddb9e426", - "pos": [ - 3080, - 5950 - ], - "size": [ - 440, - 550 - ], - "flags": {}, - "order": 4, - "mode": 0, - "inputs": [ - { - "label": "Prompt", - "name": "value", - "type": "STRING", - "widget": { - "name": "value" - }, - "link": null - }, - { - "label": "length (seconds)", - "name": "value_1", - "type": "INT", - "widget": { - "name": "value_1" - }, - "link": null - } - ], - "outputs": [ - { - "name": "optimized_model", - "type": "MODEL", - "links": [ - 40 - ] - }, - { - "label": "video_vae", - "name": "VAE", - "type": "VAE", - "links": [ - 31, - 45, - 798 - ] - }, - { - "label": "audio_vae", - "name": "VAE_1", - "type": "VAE", - "links": [ - 2, - 22 - ] - }, - { - "name": "positive", - "type": "CONDITIONING", - "links": [ - 796 - ] - }, - { - "name": "negative", - "type": "CONDITIONING", - "links": [ - 797 - ] - }, - { - "label": "length_seconds", - "name": "INT", - "type": "INT", - "links": [ - 21 - ] - }, - { - "label": "noise", - "name": "NOISE", - "type": "NOISE", - "links": [ - 44 - ] - }, - { - "label": "2x_spatial_upscale_model", - "name": "LATENT_UPSCALE_MODEL", - "type": "LATENT_UPSCALE_MODEL", - "links": [ - 1037 - ] - } - ], - "properties": { - "proxyWidgets": [ - [ - "-1", - "value" - ], - [ - "-1", - "value_1" - ], - [ - "364", - "noise_seed" - ], - [ - "364", - "control_after_generate" - ] - ], - "cnr_id": "comfy-core", - "ver": "0.17.2" - }, - "widgets_values": [ - "", - 5 - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 381, - "type": "MultiImageLoader", - "pos": [ - 3550, - 5950 - ], - "size": [ - 350, - 297 - ], - "flags": {}, - "order": 5, - "mode": 0, - "inputs": [], - "outputs": [ - { - "name": "multi_output", - "type": "IMAGE", - "links": [ - 1046, - 1047, - 1048 - ] - } - ], - "properties": { - "cnr_id": "WhatDreamsCost-ComfyUI", - "ver": "ecd00e6b404f14b07c7b54b0b0033e8bccb32d24", - "Node name for S&R": "MultiImageLoader" - }, - "widgets_values": [ - "", - 0, - 0, - "lanczos", - "keep proportion", - 0, - 18, - "" - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 11, - "type": "360d64f7-96cd-42a6-9b13-6eb0387e17d9", - "pos": [ - 4410, - 5950 - ], - "size": [ - 246.46875, - 322.233642578125 - ], - "flags": {}, - "order": 10, - "mode": 0, - "inputs": [ - { - "name": "model", - "type": "MODEL", - "link": 40 - }, - { - "name": "positive", - "type": "CONDITIONING", - "link": 799 - }, - { - "name": "negative", - "type": "CONDITIONING", - "link": 800 - }, - { - "name": "latent_image", - "type": "LATENT", - "link": 804 - }, - { - "name": "noise", - "type": "NOISE", - "link": 44 - }, - { - "label": "video_vae", - "name": "vae", - "type": "VAE", - "link": 45 - }, - { - "name": "image", - "type": "IMAGE", - "link": null - }, - { - "label": "Upscale 2x on Stage 3", - "name": "value", - "type": "BOOLEAN", - "widget": { - "name": "value" - }, - "link": null - }, - { - "name": "multi_input", - "type": "IMAGE", - "link": 1046 - }, - { - "label": "2x_spatial_upscale_model", - "name": "upscale_model", - "type": "LATENT_UPSCALE_MODEL", - "link": 1037 - } - ], - "outputs": [ - { - "label": "av_output", - "name": "output", - "type": "LATENT", - "links": [ - 47 - ] - } - ], - "properties": { - "proxyWidgets": [ - [ - "-1", - "value" - ] - ], - "cnr_id": "comfy-core", - "ver": "0.17.2" - }, - "widgets_values": [ - true - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 7, - "type": "2979f3eb-1c4c-45ff-8387-f1f4addb72f4", - "pos": [ - 4180, - 5950 - ], - "size": [ - 210, - 100 - ], - "flags": {}, - "order": 7, - "mode": 0, - "inputs": [ - { - "name": "image", - "type": "IMAGE", - "link": 1048 - }, - { - "label": "length_seconds", - "name": "length", - "type": "INT", - "widget": { - "name": "length" - }, - "link": 21 - }, - { - "name": "audio_vae", - "type": "VAE", - "link": 22 - } - ], - "outputs": [ - { - "name": "LATENT", - "type": "LATENT", - "links": [ - 803 - ] - }, - { - "name": "Latent", - "type": "LATENT", - "links": [ - 12 - ] - } - ], - "properties": { - "proxyWidgets": [ - [ - "-1", - "length" - ] - ], - "cnr_id": "comfy-core", - "ver": "0.17.2" - }, - "widgets_values": [ - 1 - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 376, - "type": "LTXSequencer", - "pos": [ - 3920, - 5950 - ], - "size": [ - 240, - 200 - ], - "flags": {}, - "order": 8, - "mode": 0, - "inputs": [ - { - "name": "positive", - "type": "CONDITIONING", - "link": 796 - }, - { - "name": "negative", - "type": "CONDITIONING", - "link": 797 - }, - { - "name": "vae", - "type": "VAE", - "link": 798 - }, - { - "name": "latent", - "type": "LATENT", - "link": 803 - }, - { - "name": "multi_input", - "type": "IMAGE", - "link": 1047 - } - ], - "outputs": [ - { - "name": "positive", - "type": "CONDITIONING", - "links": [ - 799 - ] - }, - { - "name": "negative", - "type": "CONDITIONING", - "links": [ - 800 - ] - }, - { - "name": "latent", - "type": "LATENT", - "links": [ - 802 - ] - } - ], - "properties": { - "cnr_id": "WhatDreamsCost-ComfyUI", - "ver": "ecd00e6b404f14b07c7b54b0b0033e8bccb32d24", - "Node name for S&R": "LTXSequencer", - "num_images": 0, - "insert_frame_1": 0, - "strength_1": 1, - "insert_frame_2": 0, - "strength_2": 1, - "insert_frame_3": 0, - "strength_3": 1, - "insert_frame_4": 0, - "strength_4": 1, - "insert_frame_5": 0, - "strength_5": 1, - "insert_frame_6": 0, - "strength_6": 1, - "insert_frame_7": 0, - "strength_7": 1, - "insert_frame_8": 0, - "strength_8": 1, - "insert_frame_9": 0, - "strength_9": 0, - "insert_frame_10": 1, - "strength_10": 1, - "insert_frame_11": 0, - "strength_11": 0, - "insert_frame_12": 1, - "strength_12": 1, - "insert_frame_13": 0, - "strength_13": 0, - "insert_frame_14": 1, - "strength_14": 1, - "insert_frame_15": 0, - "strength_15": 0, - "insert_frame_16": 1, - "strength_16": 1, - "insert_frame_17": 0, - "strength_17": 0, - "insert_frame_18": 1, - "strength_18": 1, - "insert_frame_19": 0, - "strength_19": 0, - "insert_frame_20": 1, - "strength_20": 1, - "insert_frame_21": 0, - "strength_21": 0, - "insert_frame_22": 1, - "strength_22": 1, - "insert_frame_23": 0, - "strength_23": 0, - "insert_frame_24": 1, - "strength_24": 1, - "insert_frame_25": 0, - "strength_25": 0, - "insert_frame_26": 1, - "strength_26": 1, - "insert_frame_27": 0, - "strength_27": 0, - "insert_frame_28": 1, - "strength_28": 1, - "insert_frame_29": 0, - "strength_29": 0, - "insert_frame_30": 1, - "strength_30": 1, - "insert_frame_31": 0, - "strength_31": 0, - "insert_frame_32": 1, - "strength_32": 1, - "insert_frame_33": 0, - "strength_33": 1, - "insert_frame_34": 0, - "strength_34": 1, - "insert_frame_35": 0, - "strength_35": 1, - "insert_frame_36": 0, - "strength_36": 1, - "insert_frame_37": 0, - "strength_37": 1, - "insert_frame_38": 0, - "strength_38": 1, - "insert_frame_39": 0, - "strength_39": 1, - "insert_frame_40": 0, - "strength_40": 1, - "insert_frame_41": 0, - "strength_41": 1, - "insert_frame_42": 0, - "strength_42": 1, - "insert_frame_43": 0, - "strength_43": 1, - "insert_frame_44": 0, - "strength_44": 1, - "insert_frame_45": 0, - "strength_45": 1, - "insert_frame_46": 0, - "strength_46": 1, - "insert_frame_47": 0, - "strength_47": 1, - "insert_frame_48": 0, - "strength_48": 1, - "insert_frame_49": 0, - "strength_49": 1, - "insert_frame_50": 0, - "strength_50": 1, - "insert_mode": "frames", - "frame_rate": 24, - "insert_second_1": 0, - "insert_second_2": 0, - "insert_second_3": 0, - "insert_second_4": 0, - "insert_second_5": 0, - "insert_second_6": 0, - "insert_second_7": 0, - "insert_second_8": 0, - "insert_second_9": 1, - "insert_second_10": 0, - "insert_second_11": 1, - "insert_second_12": 0, - "insert_second_13": 1, - "insert_second_14": 0, - "insert_second_15": 1, - "insert_second_16": 0, - "insert_second_17": 1, - "insert_second_18": 0, - "insert_second_19": 1, - "insert_second_20": 0, - "insert_second_21": 1, - "insert_second_22": 0, - "insert_second_23": 1, - "insert_second_24": 0, - "insert_second_25": 1, - "insert_second_26": 0, - "insert_second_27": 1, - "insert_second_28": 0, - "insert_second_29": 1, - "insert_second_30": 0, - "insert_second_31": 1, - "insert_second_32": 0, - "insert_second_33": 1, - "insert_second_34": 0, - "insert_second_35": 0, - "insert_second_36": 0, - "insert_second_37": 0, - "insert_second_38": 0, - "insert_second_39": 0, - "insert_second_40": 0, - "insert_second_41": 0, - "insert_second_42": 0, - "insert_second_43": 0, - "insert_second_44": 0, - "insert_second_45": 0, - "insert_second_46": 0, - "insert_second_47": 0, - "insert_second_48": 0, - "insert_second_49": 0, - "insert_second_50": 0, - "insert_images_using": "frames", - "video_length": 1, - "insert_percentage_1": 0, - "insert_percentage_2": 1, - "insert_percentage_3": 0, - "insert_percentage_4": 0, - "insert_percentage_5": 0, - "insert_percentage_6": 0, - "insert_percentage_7": 0, - "insert_percentage_8": 0, - "insert_percentage_9": 0, - "insert_percentage_10": 0, - "insert_percentage_11": 0, - "insert_percentage_12": 0, - "insert_percentage_13": 0, - "insert_percentage_14": 0, - "insert_percentage_15": 0, - "insert_percentage_16": 0, - "insert_percentage_17": 0, - "insert_percentage_18": 0, - "insert_percentage_19": 0, - "insert_percentage_20": 0, - "insert_percentage_21": 0, - "insert_percentage_22": 0, - "insert_percentage_23": 0, - "insert_percentage_24": 0, - "insert_percentage_25": 0, - "insert_percentage_26": 0, - "insert_percentage_27": 0, - "insert_percentage_28": 0, - "insert_percentage_29": 0, - "insert_percentage_30": 0, - "insert_percentage_31": 0, - "insert_percentage_32": 0, - "insert_percentage_33": 0, - "insert_percentage_34": 0, - "insert_percentage_35": 0, - "insert_percentage_36": 0, - "insert_percentage_37": 0, - "insert_percentage_38": 0, - "insert_percentage_39": 0, - "insert_percentage_40": 0, - "insert_percentage_41": 0, - "insert_percentage_42": 0, - "insert_percentage_43": 0, - "insert_percentage_44": 0, - "insert_percentage_45": 0, - "insert_percentage_46": 0, - "insert_percentage_47": 0, - "insert_percentage_48": 0, - "insert_percentage_49": 0, - "insert_percentage_50": 0, - "insert_percent_1": 0, - "insert_percent_2": 50, - "insert_percent_3": 100, - "insert_percent_4": 0, - "insert_percent_5": 0, - "insert_percent_6": 0, - "insert_percent_7": 0, - "insert_percent_8": 0, - "insert_percent_9": 0, - "insert_percent_10": 0, - "insert_percent_11": 0, - "insert_percent_12": 0, - "insert_percent_13": 0, - "insert_percent_14": 0, - "insert_percent_15": 0, - "insert_percent_16": 0, - "insert_percent_17": 0, - "insert_percent_18": 0, - "insert_percent_19": 0, - "insert_percent_20": 0, - "insert_percent_21": 0, - "insert_percent_22": 0, - "insert_percent_23": 0, - "insert_percent_24": 0, - "insert_percent_25": 0, - "insert_percent_26": 0, - "insert_percent_27": 0, - "insert_percent_28": 0, - "insert_percent_29": 0, - "insert_percent_30": 0, - "insert_percent_31": 0, - "insert_percent_32": 0, - "insert_percent_33": 0, - "insert_percent_34": 0, - "insert_percent_35": 0, - "insert_percent_36": 0, - "insert_percent_37": 0, - "insert_percent_38": 0, - "insert_percent_39": 0, - "insert_percent_40": 0, - "insert_percent_41": 0, - "insert_percent_42": 0, - "insert_percent_43": 0, - "insert_percent_44": 0, - "insert_percent_45": 0, - "insert_percent_46": 0, - "insert_percent_47": 0, - "insert_percent_48": 0, - "insert_percent_49": 0, - "insert_percent_50": 0 - }, - "widgets_values": [ - 0, - "frames", - 24, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1 - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 15, - "type": "Note", - "pos": [ - 3920, - 5740 - ], - "size": [ - 250, - 150 - ], - "flags": {}, - "order": 6, - "mode": 0, - "inputs": [], - "outputs": [], - "title": "LTX Sequencer Note", - "properties": {}, - "widgets_values": [ - "Use one image at full strength for regular image to video.\n\nIf frame_insert is -1, it will be inserted at the last frame.\n\nThe insert_mode allows to switch between inserting images by setting the exact frame or by setting the exact second." - ], - "color": "#223", - "bgcolor": "#335" - } - ], - "links": [ - [ - 2, - 10, - 2, - 1, - 1, - "VAE" - ], - [ - 12, - 7, - 1, - 4, - 1, - "LATENT" - ], - [ - 13, - 1, - 0, - 5, - 0, - "VIDEO" - ], - [ - 21, - 10, - 5, - 7, - 1, - "INT" - ], - [ - 22, - 10, - 2, - 7, - 2, - "VAE" - ], - [ - 31, - 10, - 1, - 1, - 2, - "VAE" - ], - [ - 40, - 10, - 0, - 11, - 0, - "MODEL" - ], - [ - 44, - 10, - 6, - 11, - 4, - "NOISE" - ], - [ - 45, - 10, - 1, - 11, - 5, - "VAE" - ], - [ - 47, - 11, - 0, - 1, - 0, - "LATENT" - ], - [ - 796, - 10, - 3, - 376, - 0, - "CONDITIONING" - ], - [ - 797, - 10, - 4, - 376, - 1, - "CONDITIONING" - ], - [ - 798, - 10, - 1, - 376, - 2, - "VAE" - ], - [ - 799, - 376, - 0, - 11, - 1, - "CONDITIONING" - ], - [ - 800, - 376, - 1, - 11, - 2, - "CONDITIONING" - ], - [ - 802, - 376, - 2, - 4, - 0, - "LATENT" - ], - [ - 803, - 7, - 0, - 376, - 3, - "LATENT" - ], - [ - 804, - 4, - 0, - 11, - 3, - "LATENT" - ], - [ - 1037, - 10, - 7, - 11, - 9, - "LATENT_UPSCALE_MODEL" - ], - [ - 1046, - 381, - 0, - 11, - 8, - "IMAGE" - ], - [ - 1047, - 381, - 0, - 376, - 4, - "IMAGE" - ], - [ - 1048, - 381, - 0, - 7, - 0, - "IMAGE" - ] - ], - "groups": [], - "definitions": { - "subgraphs": [ - { - "id": "34d63160-e149-4935-ab1f-892ffe617fec", - "version": 1, - "state": { - "lastGroupId": 33, - "lastNodeId": 443, - "lastLinkId": 1079, - "lastRerouteId": 0 - }, - "revision": 0, - "config": {}, - "name": "Decode", - "inputNode": { - "id": -10, - "bounding": [ - 1610, - 7190, - 180.98828125, - 120 - ] - }, - "outputNode": { - "id": -20, - "bounding": [ - 2970, - 7320, - 120, - 60 - ] - }, - "inputs": [ - { - "id": "1d567d10-9078-49fc-a4a1-67e6d4b5744f", - "name": "av_latent", - "type": "LATENT", - "linkIds": [ - 692 - ], - "pos": [ - 1770.98828125, - 7210 - ] - }, - { - "id": "bef9341f-4429-46af-9bca-11e025bec4d0", - "name": "audio_vae", - "type": "VAE", - "linkIds": [ - 693 - ], - "pos": [ - 1770.98828125, - 7230 - ] - }, - { - "id": "4588ee3d-96a4-46a8-b829-a78181f6c305", - "name": "vae", - "type": "VAE", - "linkIds": [ - 703, - 1060 - ], - "label": "video_vae", - "pos": [ - 1770.98828125, - 7250 - ] - }, - { - "id": "1bd3842b-729d-4c3d-9927-cd508bccb1b1", - "name": "value", - "type": "BOOLEAN", - "linkIds": [ - 708 - ], - "label": "Use Optimized Decoding", - "pos": [ - 1770.98828125, - 7270 - ] - } - ], - "outputs": [ - { - "id": "7e889c1c-e185-47d7-a0f7-c5f1d971c6fe", - "name": "VIDEO", - "type": "VIDEO", - "linkIds": [ - 704 - ], - "pos": [ - 2990, - 7340 - ] - } - ], - "widgets": [], - "nodes": [ - { - "id": 337, - "type": "LazySwitchKJ", - "pos": [ - 2450, - 7340 - ], - "size": [ - 270, - 78 - ], - "flags": {}, - "order": 3, - "mode": 0, - "inputs": [ - { - "localized_name": "on_false", - "name": "on_false", - "type": "*", - "link": 701 - }, - { - "localized_name": "on_true", - "name": "on_true", - "type": "*", - "link": 1058 - }, - { - "localized_name": "switch", - "name": "switch", - "type": "BOOLEAN", - "widget": { - "name": "switch" - }, - "link": 707 - } - ], - "outputs": [ - { - "localized_name": "*", - "name": "*", - "type": "*", - "links": [ - 698 - ] - } - ], - "properties": { - "cnr_id": "comfyui-kjnodes", - "ver": "1.3.4", - "Node name for S&R": "LazySwitchKJ" - }, - "widgets_values": [ - false - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 336, - "type": "CreateVideo", - "pos": [ - 2740, - 7340 - ], - "size": [ - 210, - 78 - ], - "flags": {}, - "order": 2, - "mode": 0, - "inputs": [ - { - "localized_name": "images", - "name": "images", - "type": "IMAGE", - "link": 698 - }, - { - "localized_name": "audio", - "name": "audio", - "shape": 7, - "type": "AUDIO", - "link": 697 - } - ], - "outputs": [ - { - "localized_name": "VIDEO", - "name": "VIDEO", - "type": "VIDEO", - "links": [ - 704 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.5.1", - "Node name for S&R": "CreateVideo", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [ - 24 - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 341, - "type": "PrimitiveBoolean", - "pos": [ - 2450, - 7230 - ], - "size": [ - 230, - 58 - ], - "flags": {}, - "order": 5, - "mode": 0, - "inputs": [ - { - "localized_name": "value", - "name": "value", - "type": "BOOLEAN", - "widget": { - "name": "value" - }, - "link": 708 - } - ], - "outputs": [ - { - "localized_name": "BOOLEAN", - "name": "BOOLEAN", - "type": "BOOLEAN", - "links": [ - 707 - ] - } - ], - "title": "Use Optimized Decoding", - "properties": { - "cnr_id": "comfy-core", - "ver": "0.17.2", - "Node name for S&R": "PrimitiveBoolean" - }, - "widgets_values": [ - false - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 334, - "type": "LTXVSeparateAVLatent", - "pos": [ - 1820, - 7190 - ], - "size": [ - 240, - 46 - ], - "flags": {}, - "order": 1, - "mode": 0, - "inputs": [ - { - "localized_name": "av_latent", - "name": "av_latent", - "type": "LATENT", - "link": 692 - } - ], - "outputs": [ - { - "localized_name": "video_latent", - "name": "video_latent", - "type": "LATENT", - "links": [ - 702, - 1059 - ] - }, - { - "localized_name": "audio_latent", - "name": "audio_latent", - "type": "LATENT", - "links": [ - 687 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.5.1", - "Node name for S&R": "LTXVSeparateAVLatent", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 330, - "type": "LTXVAudioVAEDecode", - "pos": [ - 1820, - 7280 - ], - "size": [ - 240, - 46 - ], - "flags": {}, - "order": 0, - "mode": 0, - "inputs": [ - { - "localized_name": "samples", - "name": "samples", - "type": "LATENT", - "link": 687 - }, - { - "label": "Audio VAE", - "localized_name": "audio_vae", - "name": "audio_vae", - "type": "VAE", - "link": 693 - } - ], - "outputs": [ - { - "localized_name": "Audio", - "name": "Audio", - "type": "AUDIO", - "links": [ - 697 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.7.0", - "Node name for S&R": "LTXVAudioVAEDecode", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 340, - "type": "VAEDecode", - "pos": [ - 2110, - 7190 - ], - "size": [ - 150, - 50 - ], - "flags": {}, - "order": 4, - "mode": 0, - "inputs": [ - { - "localized_name": "samples", - "name": "samples", - "type": "LATENT", - "link": 702 - }, - { - "localized_name": "vae", - "name": "vae", - "type": "VAE", - "link": 703 - } - ], - "outputs": [ - { - "localized_name": "IMAGE", - "name": "IMAGE", - "type": "IMAGE", - "links": [ - 701 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.17.2", - "Node name for S&R": "VAEDecode" - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 432, - "type": "VAEDecodeTiled", - "pos": [ - 2110, - 7320 - ], - "size": [ - 270, - 150 - ], - "flags": {}, - "order": 6, - "mode": 0, - "inputs": [ - { - "localized_name": "samples", - "name": "samples", - "type": "LATENT", - "link": 1059 - }, - { - "localized_name": "vae", - "name": "vae", - "type": "VAE", - "link": 1060 - } - ], - "outputs": [ - { - "localized_name": "IMAGE", - "name": "IMAGE", - "type": "IMAGE", - "links": [ - 1058 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.17.2", - "Node name for S&R": "VAEDecodeTiled" - }, - "widgets_values": [ - 768, - 128, - 128, - 8 - ], - "color": "#222", - "bgcolor": "#000" - } - ], - "groups": [], - "links": [ - { - "id": 687, - "origin_id": 334, - "origin_slot": 1, - "target_id": 330, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 692, - "origin_id": -10, - "origin_slot": 0, - "target_id": 334, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 693, - "origin_id": -10, - "origin_slot": 1, - "target_id": 330, - "target_slot": 1, - "type": "VAE" - }, - { - "id": 697, - "origin_id": 330, - "origin_slot": 0, - "target_id": 336, - "target_slot": 1, - "type": "AUDIO" - }, - { - "id": 698, - "origin_id": 337, - "origin_slot": 0, - "target_id": 336, - "target_slot": 0, - "type": "IMAGE" - }, - { - "id": 701, - "origin_id": 340, - "origin_slot": 0, - "target_id": 337, - "target_slot": 0, - "type": "IMAGE" - }, - { - "id": 702, - "origin_id": 334, - "origin_slot": 0, - "target_id": 340, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 703, - "origin_id": -10, - "origin_slot": 2, - "target_id": 340, - "target_slot": 1, - "type": "VAE" - }, - { - "id": 704, - "origin_id": 336, - "origin_slot": 0, - "target_id": -20, - "target_slot": 0, - "type": "VIDEO" - }, - { - "id": 707, - "origin_id": 341, - "origin_slot": 0, - "target_id": 337, - "target_slot": 2, - "type": "BOOLEAN" - }, - { - "id": 708, - "origin_id": -10, - "origin_slot": 3, - "target_id": 341, - "target_slot": 0, - "type": "BOOLEAN" - }, - { - "id": 1058, - "origin_id": 432, - "origin_slot": 0, - "target_id": 337, - "target_slot": 1, - "type": "IMAGE" - }, - { - "id": 1059, - "origin_id": 334, - "origin_slot": 0, - "target_id": 432, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 1060, - "origin_id": -10, - "origin_slot": 2, - "target_id": 432, - "target_slot": 1, - "type": "VAE" - } - ], - "extra": { - "workflowRendererVersion": "LG" - } - }, - { - "id": "afa29944-eb58-40a5-8e76-484f2503fa0c", - "version": 1, - "state": { - "lastGroupId": 33, - "lastNodeId": 443, - "lastLinkId": 1079, - "lastRerouteId": 0 - }, - "revision": 0, - "config": {}, - "name": "Stage #1", - "inputNode": { - "id": -10, - "bounding": [ - 280, - 7113, - 120, - 140 - ] - }, - "outputNode": { - "id": -20, - "bounding": [ - 1150, - 6970, - 120, - 80 - ] - }, - "inputs": [ - { - "id": "e760d60a-9b49-4b40-8f39-3c81f29c08d8", - "name": "model", - "type": "MODEL", - "linkIds": [ - 683, - 686 - ], - "pos": [ - 380, - 7133 - ] - }, - { - "id": "6ab6641e-8318-4330-8b01-43ce43648dcf", - "name": "positive", - "type": "CONDITIONING", - "linkIds": [ - 786 - ], - "pos": [ - 380, - 7153 - ] - }, - { - "id": "e1e7e2f6-49fb-4754-92ab-b71442b03da7", - "name": "negative", - "type": "CONDITIONING", - "linkIds": [ - 787 - ], - "pos": [ - 380, - 7173 - ] - }, - { - "id": "5c9d4c86-ec46-4099-b7e1-41a9288a00d0", - "name": "latent_image", - "type": "LATENT", - "linkIds": [ - 687 - ], - "pos": [ - 380, - 7193 - ] - }, - { - "id": "0a89214b-81f0-4fa9-86fa-5f77079ddb7b", - "name": "noise", - "type": "NOISE", - "linkIds": [ - 688 - ], - "pos": [ - 380, - 7213 - ] - } - ], - "outputs": [ - { - "id": "b6f80403-e98c-41ad-a692-270973783381", - "name": "video_latent", - "type": "LATENT", - "linkIds": [ - 689 - ], - "pos": [ - 1170, - 6990 - ] - }, - { - "id": "f54a00fd-f0db-4e4e-9205-9c9d3e955032", - "name": "audio_latent", - "type": "LATENT", - "linkIds": [ - 690 - ], - "pos": [ - 1170, - 7010 - ] - } - ], - "widgets": [], - "nodes": [ - { - "id": 325, - "type": "KSamplerSelect", - "pos": [ - 460, - 7120 - ], - "size": [ - 210, - 60 - ], - "flags": {}, - "order": 0, - "mode": 0, - "inputs": [], - "outputs": [ - { - "localized_name": "SAMPLER", - "name": "SAMPLER", - "type": "SAMPLER", - "links": [ - 680 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.56", - "Node name for S&R": "KSamplerSelect", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [ - "euler" - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 326, - "type": "BasicScheduler", - "pos": [ - 460, - 7220 - ], - "size": [ - 210, - 106 - ], - "flags": {}, - "order": 4, - "mode": 0, - "inputs": [ - { - "localized_name": "model", - "name": "model", - "type": "MODEL", - "link": 686 - } - ], - "outputs": [ - { - "localized_name": "SIGMAS", - "name": "SIGMAS", - "type": "SIGMAS", - "links": [ - 681 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.16.4", - "Node name for S&R": "BasicScheduler" - }, - "widgets_values": [ - "linear_quadratic", - 8, - 1 - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 322, - "type": "SamplerCustomAdvanced", - "pos": [ - 690, - 6980 - ], - "size": [ - 212.3638671875, - 204.64497833251954 - ], - "flags": {}, - "order": 1, - "mode": 0, - "inputs": [ - { - "localized_name": "noise", - "name": "noise", - "type": "NOISE", - "link": 688 - }, - { - "localized_name": "guider", - "name": "guider", - "type": "GUIDER", - "link": 679 - }, - { - "localized_name": "sampler", - "name": "sampler", - "type": "SAMPLER", - "link": 680 - }, - { - "localized_name": "sigmas", - "name": "sigmas", - "type": "SIGMAS", - "link": 681 - }, - { - "localized_name": "latent_image", - "name": "latent_image", - "type": "LATENT", - "link": 687 - } - ], - "outputs": [ - { - "localized_name": "output", - "name": "output", - "type": "LATENT", - "links": [ - 682 - ] - }, - { - "localized_name": "denoised_output", - "name": "denoised_output", - "type": "LATENT", - "links": [] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.60", - "Node name for S&R": "SamplerCustomAdvanced", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 323, - "type": "LTXVSeparateAVLatent", - "pos": [ - 920, - 6980 - ], - "size": [ - 193.2916015625, - 46 - ], - "flags": {}, - "order": 2, - "mode": 0, - "inputs": [ - { - "localized_name": "av_latent", - "name": "av_latent", - "type": "LATENT", - "link": 682 - } - ], - "outputs": [ - { - "localized_name": "video_latent", - "name": "video_latent", - "type": "LATENT", - "links": [ - 689 - ] - }, - { - "localized_name": "audio_latent", - "name": "audio_latent", - "type": "LATENT", - "links": [ - 690 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.5.1", - "Node name for S&R": "LTXVSeparateAVLatent", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 324, - "type": "CFGGuider", - "pos": [ - 460, - 6990 - ], - "size": [ - 210, - 98 - ], - "flags": {}, - "order": 3, - "mode": 0, - "inputs": [ - { - "localized_name": "model", - "name": "model", - "type": "MODEL", - "link": 683 - }, - { - "localized_name": "positive", - "name": "positive", - "type": "CONDITIONING", - "link": 786 - }, - { - "localized_name": "negative", - "name": "negative", - "type": "CONDITIONING", - "link": 787 - } - ], - "outputs": [ - { - "localized_name": "GUIDER", - "name": "GUIDER", - "type": "GUIDER", - "links": [ - 679 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.64", - "Node name for S&R": "CFGGuider", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [ - 1 - ], - "color": "#222", - "bgcolor": "#000" - } - ], - "groups": [], - "links": [ - { - "id": 679, - "origin_id": 324, - "origin_slot": 0, - "target_id": 322, - "target_slot": 1, - "type": "GUIDER" - }, - { - "id": 680, - "origin_id": 325, - "origin_slot": 0, - "target_id": 322, - "target_slot": 2, - "type": "SAMPLER" - }, - { - "id": 681, - "origin_id": 326, - "origin_slot": 0, - "target_id": 322, - "target_slot": 3, - "type": "SIGMAS" - }, - { - "id": 682, - "origin_id": 322, - "origin_slot": 0, - "target_id": 323, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 683, - "origin_id": -10, - "origin_slot": 0, - "target_id": 324, - "target_slot": 0, - "type": "MODEL" - }, - { - "id": 686, - "origin_id": -10, - "origin_slot": 0, - "target_id": 326, - "target_slot": 0, - "type": "MODEL" - }, - { - "id": 687, - "origin_id": -10, - "origin_slot": 3, - "target_id": 322, - "target_slot": 4, - "type": "LATENT" - }, - { - "id": 688, - "origin_id": -10, - "origin_slot": 4, - "target_id": 322, - "target_slot": 0, - "type": "NOISE" - }, - { - "id": 689, - "origin_id": 323, - "origin_slot": 0, - "target_id": -20, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 690, - "origin_id": 323, - "origin_slot": 1, - "target_id": -20, - "target_slot": 1, - "type": "LATENT" - }, - { - "id": 786, - "origin_id": -10, - "origin_slot": 1, - "target_id": 324, - "target_slot": 1, - "type": "CONDITIONING" - }, - { - "id": 787, - "origin_id": -10, - "origin_slot": 2, - "target_id": 324, - "target_slot": 2, - "type": "CONDITIONING" - } - ], - "extra": { - "workflowRendererVersion": "LG" - } - }, - { - "id": "ce8f7842-cb03-41b4-9b1e-8d032b2a4e72", - "version": 1, - "state": { - "lastGroupId": 33, - "lastNodeId": 443, - "lastLinkId": 1079, - "lastRerouteId": 0 - }, - "revision": 0, - "config": {}, - "name": "Stage #2", - "inputNode": { - "id": -10, - "bounding": [ - 280, - 7113, - 125.224609375, - 240 - ] - }, - "outputNode": { - "id": -20, - "bounding": [ - 1820, - 7130, - 120, - 120 - ] - }, - "inputs": [ - { - "id": "e760d60a-9b49-4b40-8f39-3c81f29c08d8", - "name": "model", - "type": "MODEL", - "linkIds": [ - 683, - 686 - ], - "pos": [ - 385.224609375, - 7133 - ] - }, - { - "id": "6ab6641e-8318-4330-8b01-43ce43648dcf", - "name": "positive", - "type": "CONDITIONING", - "linkIds": [ - 703 - ], - "pos": [ - 385.224609375, - 7153 - ] - }, - { - "id": "e1e7e2f6-49fb-4754-92ab-b71442b03da7", - "name": "negative", - "type": "CONDITIONING", - "linkIds": [ - 704 - ], - "pos": [ - 385.224609375, - 7173 - ] - }, - { - "id": "0a89214b-81f0-4fa9-86fa-5f77079ddb7b", - "name": "noise", - "type": "NOISE", - "linkIds": [ - 688 - ], - "pos": [ - 385.224609375, - 7193 - ] - }, - { - "id": "d8824343-6b21-42aa-9eaf-65d1b267662d", - "name": "vae", - "type": "VAE", - "linkIds": [ - 706, - 990 - ], - "label": "video_vae", - "pos": [ - 385.224609375, - 7213 - ] - }, - { - "id": "6ea10719-25f1-449a-9d2d-bb395d89609d", - "name": "image", - "type": "IMAGE", - "linkIds": [], - "pos": [ - 385.224609375, - 7233 - ] - }, - { - "id": "6c54f923-7ccc-4832-86ca-38d39cfcc656", - "name": "samples", - "type": "LATENT", - "linkIds": [ - 705 - ], - "label": "video_latent", - "pos": [ - 385.224609375, - 7253 - ] - }, - { - "id": "5ce3a92e-d38a-4078-bc1f-7426a38fa1ff", - "name": "audio_latent", - "type": "LATENT", - "linkIds": [ - 700 - ], - "pos": [ - 385.224609375, - 7273 - ] - }, - { - "id": "6a8ebcb1-8f4b-4558-893c-1b6be5c462ec", - "name": "multi_input", - "type": "IMAGE", - "linkIds": [ - 1004 - ], - "pos": [ - 385.224609375, - 7293 - ] - }, - { - "id": "7443af4e-2549-4707-813a-a7e7efa13046", - "name": "upscale_model", - "type": "LATENT_UPSCALE_MODEL", - "linkIds": [ - 1035 - ], - "pos": [ - 385.224609375, - 7313 - ] - } - ], - "outputs": [ - { - "id": "b6f80403-e98c-41ad-a692-270973783381", - "name": "video_latent", - "type": "LATENT", - "linkIds": [ - 843 - ], - "pos": [ - 1840, - 7150 - ] - }, - { - "id": "f54a00fd-f0db-4e4e-9205-9c9d3e955032", - "name": "audio_latent", - "type": "LATENT", - "linkIds": [ - 690 - ], - "pos": [ - 1840, - 7170 - ] - }, - { - "id": "3ecaa920-05d1-4c39-8ea0-b7b21e2df49b", - "name": "positive", - "type": "CONDITIONING", - "linkIds": [ - 985 - ], - "pos": [ - 1840, - 7190 - ] - }, - { - "id": "d1c64c87-487c-4677-9f80-d4a7ef6cdcc6", - "name": "negative", - "type": "CONDITIONING", - "linkIds": [ - 986 - ], - "pos": [ - 1840, - 7210 - ] - } - ], - "widgets": [], - "nodes": [ - { - "id": 326, - "type": "BasicScheduler", - "pos": [ - 480, - 7210 - ], - "size": [ - 210, - 106 - ], - "flags": {}, - "order": 4, - "mode": 0, - "inputs": [ - { - "localized_name": "model", - "name": "model", - "type": "MODEL", - "link": 686 - } - ], - "outputs": [ - { - "localized_name": "SIGMAS", - "name": "SIGMAS", - "type": "SIGMAS", - "links": [ - 681 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.16.4", - "Node name for S&R": "BasicScheduler" - }, - "widgets_values": [ - "linear_quadratic", - 6, - 0.42 - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 325, - "type": "KSamplerSelect", - "pos": [ - 480, - 7110 - ], - "size": [ - 210, - 60 - ], - "flags": {}, - "order": 0, - "mode": 0, - "inputs": [], - "outputs": [ - { - "localized_name": "SAMPLER", - "name": "SAMPLER", - "type": "SAMPLER", - "links": [ - 680 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.56", - "Node name for S&R": "KSamplerSelect", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [ - "euler" - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 322, - "type": "SamplerCustomAdvanced", - "pos": [ - 1370, - 7130 - ], - "size": [ - 212.3638671875, - 204.64497833251954 - ], - "flags": {}, - "order": 1, - "mode": 0, - "inputs": [ - { - "localized_name": "noise", - "name": "noise", - "type": "NOISE", - "link": 688 - }, - { - "localized_name": "guider", - "name": "guider", - "type": "GUIDER", - "link": 679 - }, - { - "localized_name": "sampler", - "name": "sampler", - "type": "SAMPLER", - "link": 680 - }, - { - "localized_name": "sigmas", - "name": "sigmas", - "type": "SIGMAS", - "link": 681 - }, - { - "localized_name": "latent_image", - "name": "latent_image", - "type": "LATENT", - "link": 697 - } - ], - "outputs": [ - { - "localized_name": "output", - "name": "output", - "type": "LATENT", - "links": [ - 682 - ] - }, - { - "localized_name": "denoised_output", - "name": "denoised_output", - "type": "LATENT", - "links": [] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.60", - "Node name for S&R": "SamplerCustomAdvanced", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 323, - "type": "LTXVSeparateAVLatent", - "pos": [ - 1600, - 7140 - ], - "size": [ - 193.2916015625, - 46 - ], - "flags": {}, - "order": 2, - "mode": 0, - "inputs": [ - { - "localized_name": "av_latent", - "name": "av_latent", - "type": "LATENT", - "link": 682 - } - ], - "outputs": [ - { - "localized_name": "video_latent", - "name": "video_latent", - "type": "LATENT", - "links": [ - 843 - ] - }, - { - "localized_name": "audio_latent", - "name": "audio_latent", - "type": "LATENT", - "links": [ - 690 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.5.1", - "Node name for S&R": "LTXVSeparateAVLatent", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 409, - "type": "LTXSequencer", - "pos": [ - 850, - 7140 - ], - "size": [ - 210, - 200 - ], - "flags": {}, - "order": 8, - "mode": 0, - "inputs": [ - { - "localized_name": "positive", - "name": "positive", - "type": "CONDITIONING", - "link": 988 - }, - { - "localized_name": "negative", - "name": "negative", - "type": "CONDITIONING", - "link": 989 - }, - { - "localized_name": "vae", - "name": "vae", - "type": "VAE", - "link": 990 - }, - { - "localized_name": "latent", - "name": "latent", - "type": "LATENT", - "link": 991 - }, - { - "localized_name": "multi_input", - "name": "multi_input", - "type": "IMAGE", - "link": 1004 - } - ], - "outputs": [ - { - "localized_name": "positive", - "name": "positive", - "type": "CONDITIONING", - "links": [ - 983, - 985 - ] - }, - { - "localized_name": "negative", - "name": "negative", - "type": "CONDITIONING", - "links": [ - 984, - 986 - ] - }, - { - "localized_name": "latent", - "name": "latent", - "type": "LATENT", - "links": [ - 987 - ] - } - ], - "properties": { - "cnr_id": "WhatDreamsCost-ComfyUI", - "ver": "ecd00e6b404f14b07c7b54b0b0033e8bccb32d24", - "Node name for S&R": "LTXSequencer", - "num_images": 0, - "insert_frame_1": 0, - "strength_1": 1, - "insert_frame_2": 0, - "strength_2": 1, - "insert_frame_3": 0, - "strength_3": 1, - "insert_frame_4": 0, - "strength_4": 1, - "insert_frame_5": 0, - "strength_5": 1, - "insert_frame_6": 0, - "strength_6": 1, - "insert_frame_7": 0, - "strength_7": 1, - "insert_frame_8": 0, - "strength_8": 1, - "insert_frame_9": 0, - "strength_9": 0, - "insert_frame_10": 1, - "strength_10": 1, - "insert_frame_11": 0, - "strength_11": 0, - "insert_frame_12": 1, - "strength_12": 1, - "insert_frame_13": 0, - "strength_13": 0, - "insert_frame_14": 1, - "strength_14": 1, - "insert_frame_15": 0, - "strength_15": 0, - "insert_frame_16": 1, - "strength_16": 1, - "insert_frame_17": 0, - "strength_17": 0, - "insert_frame_18": 1, - "strength_18": 1, - "insert_frame_19": 0, - "strength_19": 0, - "insert_frame_20": 1, - "strength_20": 1, - "insert_frame_21": 0, - "strength_21": 0, - "insert_frame_22": 1, - "strength_22": 1, - "insert_frame_23": 0, - "strength_23": 0, - "insert_frame_24": 1, - "strength_24": 1, - "insert_frame_25": 0, - "strength_25": 0, - "insert_frame_26": 1, - "strength_26": 1, - "insert_frame_27": 0, - "strength_27": 0, - "insert_frame_28": 1, - "strength_28": 1, - "insert_frame_29": 0, - "strength_29": 0, - "insert_frame_30": 1, - "strength_30": 1, - "insert_frame_31": 0, - "strength_31": 0, - "insert_frame_32": 1, - "strength_32": 1, - "insert_frame_33": 0, - "strength_33": 1, - "insert_frame_34": 0, - "strength_34": 1, - "insert_frame_35": 0, - "strength_35": 1, - "insert_frame_36": 0, - "strength_36": 1, - "insert_frame_37": 0, - "strength_37": 1, - "insert_frame_38": 0, - "strength_38": 1, - "insert_frame_39": 0, - "strength_39": 1, - "insert_frame_40": 0, - "strength_40": 1, - "insert_frame_41": 0, - "strength_41": 1, - "insert_frame_42": 0, - "strength_42": 1, - "insert_frame_43": 0, - "strength_43": 1, - "insert_frame_44": 0, - "strength_44": 1, - "insert_frame_45": 0, - "strength_45": 1, - "insert_frame_46": 0, - "strength_46": 1, - "insert_frame_47": 0, - "strength_47": 1, - "insert_frame_48": 0, - "strength_48": 1, - "insert_frame_49": 0, - "strength_49": 1, - "insert_frame_50": 0, - "strength_50": 1, - "insert_mode": "frames", - "frame_rate": 24, - "insert_second_1": 0, - "insert_second_2": 0, - "insert_second_3": 0, - "insert_second_4": 0, - "insert_second_5": 0, - "insert_second_6": 0, - "insert_second_7": 0, - "insert_second_8": 0, - "insert_second_9": 1, - "insert_second_10": 0, - "insert_second_11": 1, - "insert_second_12": 0, - "insert_second_13": 1, - "insert_second_14": 0, - "insert_second_15": 1, - "insert_second_16": 0, - "insert_second_17": 1, - "insert_second_18": 0, - "insert_second_19": 1, - "insert_second_20": 0, - "insert_second_21": 1, - "insert_second_22": 0, - "insert_second_23": 1, - "insert_second_24": 0, - "insert_second_25": 1, - "insert_second_26": 0, - "insert_second_27": 1, - "insert_second_28": 0, - "insert_second_29": 1, - "insert_second_30": 0, - "insert_second_31": 1, - "insert_second_32": 0, - "insert_second_33": 1, - "insert_second_34": 0, - "insert_second_35": 0, - "insert_second_36": 0, - "insert_second_37": 0, - "insert_second_38": 0, - "insert_second_39": 0, - "insert_second_40": 0, - "insert_second_41": 0, - "insert_second_42": 0, - "insert_second_43": 0, - "insert_second_44": 0, - "insert_second_45": 0, - "insert_second_46": 0, - "insert_second_47": 0, - "insert_second_48": 0, - "insert_second_49": 0, - "insert_second_50": 0, - "insert_images_using": "frames", - "video_length": 1, - "insert_percentage_1": 0, - "insert_percentage_2": 1, - "insert_percentage_3": 0, - "insert_percentage_4": 0, - "insert_percentage_5": 0, - "insert_percentage_6": 0, - "insert_percentage_7": 0, - "insert_percentage_8": 0, - "insert_percentage_9": 0, - "insert_percentage_10": 0, - "insert_percentage_11": 0, - "insert_percentage_12": 0, - "insert_percentage_13": 0, - "insert_percentage_14": 0, - "insert_percentage_15": 0, - "insert_percentage_16": 0, - "insert_percentage_17": 0, - "insert_percentage_18": 0, - "insert_percentage_19": 0, - "insert_percentage_20": 0, - "insert_percentage_21": 0, - "insert_percentage_22": 0, - "insert_percentage_23": 0, - "insert_percentage_24": 0, - "insert_percentage_25": 0, - "insert_percentage_26": 0, - "insert_percentage_27": 0, - "insert_percentage_28": 0, - "insert_percentage_29": 0, - "insert_percentage_30": 0, - "insert_percentage_31": 0, - "insert_percentage_32": 0, - "insert_percentage_33": 0, - "insert_percentage_34": 0, - "insert_percentage_35": 0, - "insert_percentage_36": 0, - "insert_percentage_37": 0, - "insert_percentage_38": 0, - "insert_percentage_39": 0, - "insert_percentage_40": 0, - "insert_percentage_41": 0, - "insert_percentage_42": 0, - "insert_percentage_43": 0, - "insert_percentage_44": 0, - "insert_percentage_45": 0, - "insert_percentage_46": 0, - "insert_percentage_47": 0, - "insert_percentage_48": 0, - "insert_percentage_49": 0, - "insert_percentage_50": 0, - "insert_percent_1": 0, - "insert_percent_2": 50, - "insert_percent_3": 100, - "insert_percent_4": 0, - "insert_percent_5": 0, - "insert_percent_6": 0, - "insert_percent_7": 0, - "insert_percent_8": 0, - "insert_percent_9": 0, - "insert_percent_10": 0, - "insert_percent_11": 0, - "insert_percent_12": 0, - "insert_percent_13": 0, - "insert_percent_14": 0, - "insert_percent_15": 0, - "insert_percent_16": 0, - "insert_percent_17": 0, - "insert_percent_18": 0, - "insert_percent_19": 0, - "insert_percent_20": 0, - "insert_percent_21": 0, - "insert_percent_22": 0, - "insert_percent_23": 0, - "insert_percent_24": 0, - "insert_percent_25": 0, - "insert_percent_26": 0, - "insert_percent_27": 0, - "insert_percent_28": 0, - "insert_percent_29": 0, - "insert_percent_30": 0, - "insert_percent_31": 0, - "insert_percent_32": 0, - "insert_percent_33": 0, - "insert_percent_34": 0, - "insert_percent_35": 0, - "insert_percent_36": 0, - "insert_percent_37": 0, - "insert_percent_38": 0, - "insert_percent_39": 0, - "insert_percent_40": 0, - "insert_percent_41": 0, - "insert_percent_42": 0, - "insert_percent_43": 0, - "insert_percent_44": 0, - "insert_percent_45": 0, - "insert_percent_46": 0, - "insert_percent_47": 0, - "insert_percent_48": 0, - "insert_percent_49": 0, - "insert_percent_50": 0 - }, - "widgets_values": [ - 0, - "frames", - 24, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1 - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 331, - "type": "LTXVCropGuides", - "pos": [ - 490, - 7000 - ], - "size": [ - 152.7134765625, - 66 - ], - "flags": {}, - "order": 7, - "mode": 0, - "inputs": [ - { - "localized_name": "positive", - "name": "positive", - "type": "CONDITIONING", - "link": 703 - }, - { - "localized_name": "negative", - "name": "negative", - "type": "CONDITIONING", - "link": 704 - }, - { - "localized_name": "latent", - "name": "latent", - "type": "LATENT", - "link": 705 - } - ], - "outputs": [ - { - "localized_name": "positive", - "name": "positive", - "type": "CONDITIONING", - "links": [ - 828, - 846, - 988 - ] - }, - { - "localized_name": "negative", - "name": "negative", - "type": "CONDITIONING", - "links": [ - 829, - 847, - 989 - ] - }, - { - "localized_name": "latent", - "name": "latent", - "type": "LATENT", - "links": [ - 850 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.16.4", - "Node name for S&R": "LTXVCropGuides" - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 329, - "type": "LTXVConcatAVLatent", - "pos": [ - 1140, - 7270 - ], - "size": [ - 187.5, - 60 - ], - "flags": {}, - "order": 6, - "mode": 0, - "inputs": [ - { - "localized_name": "video_latent", - "name": "video_latent", - "type": "LATENT", - "link": 987 - }, - { - "localized_name": "audio_latent", - "name": "audio_latent", - "type": "LATENT", - "link": 700 - } - ], - "outputs": [ - { - "localized_name": "latent", - "name": "latent", - "type": "LATENT", - "links": [ - 697 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.7.0", - "Node name for S&R": "LTXVConcatAVLatent", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 324, - "type": "CFGGuider", - "pos": [ - 1140, - 7130 - ], - "size": [ - 210, - 98 - ], - "flags": {}, - "order": 3, - "mode": 0, - "inputs": [ - { - "localized_name": "model", - "name": "model", - "type": "MODEL", - "link": 683 - }, - { - "localized_name": "positive", - "name": "positive", - "type": "CONDITIONING", - "link": 983 - }, - { - "localized_name": "negative", - "name": "negative", - "type": "CONDITIONING", - "link": 984 - } - ], - "outputs": [ - { - "localized_name": "GUIDER", - "name": "GUIDER", - "type": "GUIDER", - "links": [ - 679 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.64", - "Node name for S&R": "CFGGuider", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [ - 1 - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 328, - "type": "LTXVLatentUpsampler", - "pos": [ - 480, - 7370 - ], - "size": [ - 320, - 66 - ], - "flags": {}, - "order": 5, - "mode": 0, - "inputs": [ - { - "localized_name": "samples", - "name": "samples", - "type": "LATENT", - "link": 850 - }, - { - "localized_name": "upscale_model", - "name": "upscale_model", - "type": "LATENT_UPSCALE_MODEL", - "link": 1035 - }, - { - "localized_name": "vae", - "name": "vae", - "type": "VAE", - "link": 706 - } - ], - "outputs": [ - { - "localized_name": "LATENT", - "name": "LATENT", - "type": "LATENT", - "links": [ - 991 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.14.1", - "Node name for S&R": "LTXVLatentUpsampler", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - } - ], - "groups": [], - "links": [ - { - "id": 679, - "origin_id": 324, - "origin_slot": 0, - "target_id": 322, - "target_slot": 1, - "type": "GUIDER" - }, - { - "id": 680, - "origin_id": 325, - "origin_slot": 0, - "target_id": 322, - "target_slot": 2, - "type": "SAMPLER" - }, - { - "id": 681, - "origin_id": 326, - "origin_slot": 0, - "target_id": 322, - "target_slot": 3, - "type": "SIGMAS" - }, - { - "id": 682, - "origin_id": 322, - "origin_slot": 0, - "target_id": 323, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 683, - "origin_id": -10, - "origin_slot": 0, - "target_id": 324, - "target_slot": 0, - "type": "MODEL" - }, - { - "id": 686, - "origin_id": -10, - "origin_slot": 0, - "target_id": 326, - "target_slot": 0, - "type": "MODEL" - }, - { - "id": 688, - "origin_id": -10, - "origin_slot": 3, - "target_id": 322, - "target_slot": 0, - "type": "NOISE" - }, - { - "id": 690, - "origin_id": 323, - "origin_slot": 1, - "target_id": -20, - "target_slot": 1, - "type": "LATENT" - }, - { - "id": 697, - "origin_id": 329, - "origin_slot": 0, - "target_id": 322, - "target_slot": 4, - "type": "LATENT" - }, - { - "id": 700, - "origin_id": -10, - "origin_slot": 7, - "target_id": 329, - "target_slot": 1, - "type": "LATENT" - }, - { - "id": 703, - "origin_id": -10, - "origin_slot": 1, - "target_id": 331, - "target_slot": 0, - "type": "CONDITIONING" - }, - { - "id": 704, - "origin_id": -10, - "origin_slot": 2, - "target_id": 331, - "target_slot": 1, - "type": "CONDITIONING" - }, - { - "id": 705, - "origin_id": -10, - "origin_slot": 6, - "target_id": 331, - "target_slot": 2, - "type": "LATENT" - }, - { - "id": 706, - "origin_id": -10, - "origin_slot": 4, - "target_id": 328, - "target_slot": 2, - "type": "VAE" - }, - { - "id": 843, - "origin_id": 323, - "origin_slot": 0, - "target_id": -20, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 850, - "origin_id": 331, - "origin_slot": 2, - "target_id": 328, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 983, - "origin_id": 409, - "origin_slot": 0, - "target_id": 324, - "target_slot": 1, - "type": "CONDITIONING" - }, - { - "id": 984, - "origin_id": 409, - "origin_slot": 1, - "target_id": 324, - "target_slot": 2, - "type": "CONDITIONING" - }, - { - "id": 985, - "origin_id": 409, - "origin_slot": 0, - "target_id": -20, - "target_slot": 2, - "type": "CONDITIONING" - }, - { - "id": 986, - "origin_id": 409, - "origin_slot": 1, - "target_id": -20, - "target_slot": 3, - "type": "CONDITIONING" - }, - { - "id": 987, - "origin_id": 409, - "origin_slot": 2, - "target_id": 329, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 988, - "origin_id": 331, - "origin_slot": 0, - "target_id": 409, - "target_slot": 0, - "type": "CONDITIONING" - }, - { - "id": 989, - "origin_id": 331, - "origin_slot": 1, - "target_id": 409, - "target_slot": 1, - "type": "CONDITIONING" - }, - { - "id": 990, - "origin_id": -10, - "origin_slot": 4, - "target_id": 409, - "target_slot": 2, - "type": "VAE" - }, - { - "id": 991, - "origin_id": 328, - "origin_slot": 0, - "target_id": 409, - "target_slot": 3, - "type": "LATENT" - }, - { - "id": 1004, - "origin_id": -10, - "origin_slot": 8, - "target_id": 409, - "target_slot": 4, - "type": "IMAGE" - }, - { - "id": 1035, - "origin_id": -10, - "origin_slot": 9, - "target_id": 328, - "target_slot": 1, - "type": "LATENT_UPSCALE_MODEL" - } - ], - "extra": { - "workflowRendererVersion": "LG" - } - }, - { - "id": "2979f3eb-1c4c-45ff-8387-f1f4addb72f4", - "version": 1, - "state": { - "lastGroupId": 33, - "lastNodeId": 443, - "lastLinkId": 1079, - "lastRerouteId": 0 - }, - "revision": 0, - "config": {}, - "name": "Process Latents", - "inputNode": { - "id": -10, - "bounding": [ - 2630, - 6303, - 128.998046875, - 100 - ] - }, - "outputNode": { - "id": -20, - "bounding": [ - 3500, - 6303, - 120, - 80 - ] - }, - "inputs": [ - { - "id": "f9d3f39d-4628-4ef2-a464-ffb98c1fb4bc", - "name": "image", - "type": "IMAGE", - "linkIds": [ - 713 - ], - "localized_name": "image", - "pos": [ - 2738.998046875, - 6323 - ] - }, - { - "id": "6c61b79c-c6a2-4cd9-96a7-72073d3603c1", - "name": "length", - "type": "INT", - "linkIds": [ - 708, - 709 - ], - "localized_name": "length", - "label": "length_seconds", - "pos": [ - 2738.998046875, - 6343 - ] - }, - { - "id": "610c02c9-497c-45ac-b603-ea5bd91aec4c", - "name": "audio_vae", - "type": "VAE", - "linkIds": [ - 714 - ], - "pos": [ - 2738.998046875, - 6363 - ] - } - ], - "outputs": [ - { - "id": "2cf8b0ee-4e7a-4045-a1ee-bbd4b878068e", - "name": "LATENT", - "type": "LATENT", - "linkIds": [ - 710 - ], - "localized_name": "LATENT", - "pos": [ - 3520, - 6323 - ] - }, - { - "id": "44e92917-679f-4cc3-aa57-51b4a6ef24a8", - "name": "Latent", - "type": "LATENT", - "linkIds": [ - 705 - ], - "localized_name": "Latent", - "pos": [ - 3520, - 6343 - ] - } - ], - "widgets": [], - "nodes": [ - { - "id": 356, - "type": "GetImageSize", - "pos": [ - 3030, - 6210 - ], - "size": [ - 170, - 70 - ], - "flags": {}, - "order": 2, - "mode": 0, - "inputs": [ - { - "localized_name": "image", - "name": "image", - "type": "IMAGE", - "link": 711 - } - ], - "outputs": [ - { - "localized_name": "width", - "name": "width", - "type": "INT", - "links": [ - 706 - ] - }, - { - "localized_name": "height", - "name": "height", - "type": "INT", - "links": [ - 707 - ] - }, - { - "localized_name": "batch_size", - "name": "batch_size", - "type": "INT", - "links": null - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.7.0", - "Node name for S&R": "GetImageSize", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 352, - "type": "EmptyLTXVLatentVideo", - "pos": [ - 3210, - 6210 - ], - "size": [ - 210, - 130 - ], - "flags": {}, - "order": 0, - "mode": 0, - "inputs": [ - { - "localized_name": "width", - "name": "width", - "type": "INT", - "widget": { - "name": "width" - }, - "link": 706 - }, - { - "localized_name": "height", - "name": "height", - "type": "INT", - "widget": { - "name": "height" - }, - "link": 707 - }, - { - "localized_name": "length", - "name": "length", - "type": "INT", - "widget": { - "name": "length" - }, - "link": 708 - } - ], - "outputs": [ - { - "localized_name": "LATENT", - "name": "LATENT", - "type": "LATENT", - "links": [ - 710 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.60", - "Node name for S&R": "EmptyLTXVLatentVideo", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [ - 768, - 512, - 1, - 1 - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 353, - "type": "LTXVEmptyLatentAudio", - "pos": [ - 3210, - 6380 - ], - "size": [ - 210, - 106 - ], - "flags": {}, - "order": 1, - "mode": 0, - "inputs": [ - { - "localized_name": "audio_vae", - "name": "audio_vae", - "type": "VAE", - "link": 714 - }, - { - "localized_name": "frames_number", - "name": "frames_number", - "type": "INT", - "widget": { - "name": "frames_number" - }, - "link": 709 - } - ], - "outputs": [ - { - "localized_name": "Latent", - "name": "Latent", - "type": "LATENT", - "links": [ - 705 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.68", - "Node name for S&R": "LTXVEmptyLatentAudio", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [ - 1, - 24, - 1 - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 357, - "type": "ImageScaleBy", - "pos": [ - 2810, - 6210 - ], - "size": [ - 210, - 82 - ], - "flags": {}, - "order": 3, - "mode": 0, - "inputs": [ - { - "localized_name": "image", - "name": "image", - "type": "IMAGE", - "link": 713 - } - ], - "outputs": [ - { - "localized_name": "IMAGE", - "name": "IMAGE", - "type": "IMAGE", - "links": [ - 711 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.5.1", - "Node name for S&R": "ImageScaleBy", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65, - "ue_properties": { - "version": "7.0.1", - "widget_ue_connectable": {} - } - }, - "widgets_values": [ - "bilinear", - 0.5 - ], - "color": "#222", - "bgcolor": "#000" - } - ], - "groups": [], - "links": [ - { - "id": 711, - "origin_id": 357, - "origin_slot": 0, - "target_id": 356, - "target_slot": 0, - "type": "IMAGE" - }, - { - "id": 706, - "origin_id": 356, - "origin_slot": 0, - "target_id": 352, - "target_slot": 0, - "type": "INT" - }, - { - "id": 707, - "origin_id": 356, - "origin_slot": 1, - "target_id": 352, - "target_slot": 1, - "type": "INT" - }, - { - "id": 713, - "origin_id": -10, - "origin_slot": 0, - "target_id": 357, - "target_slot": 0, - "type": "IMAGE" - }, - { - "id": 708, - "origin_id": -10, - "origin_slot": 1, - "target_id": 352, - "target_slot": 2, - "type": "INT" - }, - { - "id": 709, - "origin_id": -10, - "origin_slot": 1, - "target_id": 353, - "target_slot": 1, - "type": "INT" - }, - { - "id": 710, - "origin_id": 352, - "origin_slot": 0, - "target_id": -20, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 705, - "origin_id": 353, - "origin_slot": 0, - "target_id": -20, - "target_slot": 1, - "type": "LATENT" - }, - { - "id": 714, - "origin_id": -10, - "origin_slot": 2, - "target_id": 353, - "target_slot": 0, - "type": "VAE" - } - ], - "extra": { - "workflowRendererVersion": "LG" - } - }, - { - "id": "535e663f-da59-47c1-aab5-c78ff202cb16", - "version": 1, - "state": { - "lastGroupId": 33, - "lastNodeId": 443, - "lastLinkId": 1079, - "lastRerouteId": 0 - }, - "revision": 0, - "config": {}, - "name": "Stage #3", - "inputNode": { - "id": -10, - "bounding": [ - -370, - 7030, - 144.56640625, - 260 - ] - }, - "outputNode": { - "id": -20, - "bounding": [ - 1680, - 7090, - 120, - 80 - ] - }, - "inputs": [ - { - "id": "e760d60a-9b49-4b40-8f39-3c81f29c08d8", - "name": "model", - "type": "MODEL", - "linkIds": [ - 683, - 686 - ], - "pos": [ - -245.43359375, - 7050 - ] - }, - { - "id": "6ab6641e-8318-4330-8b01-43ce43648dcf", - "name": "positive", - "type": "CONDITIONING", - "linkIds": [ - 919 - ], - "pos": [ - -245.43359375, - 7070 - ] - }, - { - "id": "e1e7e2f6-49fb-4754-92ab-b71442b03da7", - "name": "negative", - "type": "CONDITIONING", - "linkIds": [ - 920 - ], - "pos": [ - -245.43359375, - 7090 - ] - }, - { - "id": "0a89214b-81f0-4fa9-86fa-5f77079ddb7b", - "name": "noise", - "type": "NOISE", - "linkIds": [ - 688 - ], - "pos": [ - -245.43359375, - 7110 - ] - }, - { - "id": "d8824343-6b21-42aa-9eaf-65d1b267662d", - "name": "vae", - "type": "VAE", - "linkIds": [ - 716, - 717, - 1002 - ], - "label": "video_vae", - "pos": [ - -245.43359375, - 7130 - ] - }, - { - "id": "6ea10719-25f1-449a-9d2d-bb395d89609d", - "name": "image", - "type": "IMAGE", - "linkIds": [], - "pos": [ - -245.43359375, - 7150 - ] - }, - { - "id": "d57f8c01-0dc1-48da-b7a2-166839b09d2b", - "name": "samples", - "type": "LATENT", - "linkIds": [ - 708 - ], - "label": "video_latent", - "pos": [ - -245.43359375, - 7170 - ] - }, - { - "id": "9f9730e3-9739-4813-953f-4c27da077f21", - "name": "audio_latent", - "type": "LATENT", - "linkIds": [ - 700 - ], - "pos": [ - -245.43359375, - 7190 - ] - }, - { - "id": "6f44ac03-cf13-4dc0-be74-7ec92e492d88", - "name": "value", - "type": "BOOLEAN", - "linkIds": [ - 719 - ], - "label": "use 2x on stage 3", - "pos": [ - -245.43359375, - 7210 - ] - }, - { - "id": "9951f766-4866-4ed1-bab3-39a03ca4ae6a", - "name": "multi_input", - "type": "IMAGE", - "linkIds": [ - 1005 - ], - "pos": [ - -245.43359375, - 7230 - ] - }, - { - "id": "265429c2-c78b-4c5d-80d7-6e272aa96a36", - "name": "upscale_model", - "type": "LATENT_UPSCALE_MODEL", - "linkIds": [ - 1038 - ], - "label": "2x_upscale_model", - "pos": [ - -245.43359375, - 7250 - ] - } - ], - "outputs": [ - { - "id": "7b177753-a22f-47a0-9ff8-7f4022516b5c", - "name": "output", - "type": "LATENT", - "linkIds": [ - 1024 - ], - "label": "video_latent", - "pos": [ - 1700, - 7110 - ] - }, - { - "id": "51232fd0-325e-4090-bcc9-4ecbdb0b9e27", - "name": "audio_latent", - "type": "LATENT", - "linkIds": [ - 1025 - ], - "pos": [ - 1700, - 7130 - ] - } - ], - "widgets": [], - "nodes": [ - { - "id": 331, - "type": "LTXVCropGuides", - "pos": [ - -190, - 6940 - ], - "size": [ - 152.7134765625, - 66 - ], - "flags": {}, - "order": 6, - "mode": 0, - "inputs": [ - { - "localized_name": "positive", - "name": "positive", - "type": "CONDITIONING", - "link": 919 - }, - { - "localized_name": "negative", - "name": "negative", - "type": "CONDITIONING", - "link": 920 - }, - { - "localized_name": "latent", - "name": "latent", - "type": "LATENT", - "link": 708 - } - ], - "outputs": [ - { - "localized_name": "positive", - "name": "positive", - "type": "CONDITIONING", - "links": [ - 999 - ] - }, - { - "localized_name": "negative", - "name": "negative", - "type": "CONDITIONING", - "links": [ - 1000 - ] - }, - { - "localized_name": "latent", - "name": "latent", - "type": "LATENT", - "links": [ - 877, - 878 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.16.4", - "Node name for S&R": "LTXVCropGuides" - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 325, - "type": "KSamplerSelect", - "pos": [ - -190, - 7050 - ], - "size": [ - 210, - 60 - ], - "flags": {}, - "order": 0, - "mode": 0, - "inputs": [], - "outputs": [ - { - "localized_name": "SAMPLER", - "name": "SAMPLER", - "type": "SAMPLER", - "links": [ - 680 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.56", - "Node name for S&R": "KSamplerSelect", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [ - "euler" - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 326, - "type": "BasicScheduler", - "pos": [ - -190, - 7150 - ], - "size": [ - 210, - 106 - ], - "flags": {}, - "order": 4, - "mode": 0, - "inputs": [ - { - "localized_name": "model", - "name": "model", - "type": "MODEL", - "link": 686 - } - ], - "outputs": [ - { - "localized_name": "SIGMAS", - "name": "SIGMAS", - "type": "SIGMAS", - "links": [ - 681 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.16.4", - "Node name for S&R": "BasicScheduler" - }, - "widgets_values": [ - "linear_quadratic", - 4, - 0.42 - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 334, - "type": "LTXVLatentUpsampler", - "pos": [ - 160, - 7300 - ], - "size": [ - 320, - 66 - ], - "flags": {}, - "order": 8, - "mode": 0, - "inputs": [ - { - "localized_name": "samples", - "name": "samples", - "type": "LATENT", - "link": 877 - }, - { - "localized_name": "upscale_model", - "name": "upscale_model", - "type": "LATENT_UPSCALE_MODEL", - "link": 710 - }, - { - "localized_name": "vae", - "name": "vae", - "type": "VAE", - "link": 716 - } - ], - "outputs": [ - { - "localized_name": "LATENT", - "name": "LATENT", - "type": "LATENT", - "links": [ - 712 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.14.1", - "Node name for S&R": "LTXVLatentUpsampler", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 332, - "type": "LazySwitchKJ", - "pos": [ - 500, - 7360 - ], - "size": [ - 210, - 78 - ], - "flags": {}, - "order": 7, - "mode": 0, - "inputs": [ - { - "localized_name": "on_false", - "name": "on_false", - "type": "*", - "link": 712 - }, - { - "localized_name": "on_true", - "name": "on_true", - "type": "*", - "link": 713 - }, - { - "localized_name": "switch", - "name": "switch", - "type": "BOOLEAN", - "widget": { - "name": "switch" - }, - "link": 718 - } - ], - "outputs": [ - { - "localized_name": "*", - "name": "*", - "type": "*", - "links": [ - 1001 - ] - } - ], - "properties": { - "cnr_id": "comfyui-kjnodes", - "ver": "1.3.4", - "Node name for S&R": "LazySwitchKJ" - }, - "widgets_values": [ - false - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 411, - "type": "LTXSequencer", - "pos": [ - 720, - 7110 - ], - "size": [ - 210, - 200 - ], - "flags": {}, - "order": 11, - "mode": 0, - "inputs": [ - { - "localized_name": "positive", - "name": "positive", - "type": "CONDITIONING", - "link": 999 - }, - { - "localized_name": "negative", - "name": "negative", - "type": "CONDITIONING", - "link": 1000 - }, - { - "localized_name": "vae", - "name": "vae", - "type": "VAE", - "link": 1002 - }, - { - "localized_name": "latent", - "name": "latent", - "type": "LATENT", - "link": 1001 - }, - { - "localized_name": "multi_input", - "name": "multi_input", - "type": "IMAGE", - "link": 1005 - } - ], - "outputs": [ - { - "localized_name": "positive", - "name": "positive", - "type": "CONDITIONING", - "links": [ - 996 - ] - }, - { - "localized_name": "negative", - "name": "negative", - "type": "CONDITIONING", - "links": [ - 997 - ] - }, - { - "localized_name": "latent", - "name": "latent", - "type": "LATENT", - "links": [ - 998 - ] - } - ], - "properties": { - "cnr_id": "WhatDreamsCost-ComfyUI", - "ver": "ecd00e6b404f14b07c7b54b0b0033e8bccb32d24", - "Node name for S&R": "LTXSequencer", - "num_images": 0, - "insert_frame_1": 0, - "strength_1": 1, - "insert_frame_2": 0, - "strength_2": 1, - "insert_frame_3": 0, - "strength_3": 1, - "insert_frame_4": 0, - "strength_4": 1, - "insert_frame_5": 0, - "strength_5": 1, - "insert_frame_6": 0, - "strength_6": 1, - "insert_frame_7": 0, - "strength_7": 1, - "insert_frame_8": 0, - "strength_8": 1, - "insert_frame_9": 0, - "strength_9": 0, - "insert_frame_10": 1, - "strength_10": 1, - "insert_frame_11": 0, - "strength_11": 0, - "insert_frame_12": 1, - "strength_12": 1, - "insert_frame_13": 0, - "strength_13": 0, - "insert_frame_14": 1, - "strength_14": 1, - "insert_frame_15": 0, - "strength_15": 0, - "insert_frame_16": 1, - "strength_16": 1, - "insert_frame_17": 0, - "strength_17": 0, - "insert_frame_18": 1, - "strength_18": 1, - "insert_frame_19": 0, - "strength_19": 0, - "insert_frame_20": 1, - "strength_20": 1, - "insert_frame_21": 0, - "strength_21": 0, - "insert_frame_22": 1, - "strength_22": 1, - "insert_frame_23": 0, - "strength_23": 0, - "insert_frame_24": 1, - "strength_24": 1, - "insert_frame_25": 0, - "strength_25": 0, - "insert_frame_26": 1, - "strength_26": 1, - "insert_frame_27": 0, - "strength_27": 0, - "insert_frame_28": 1, - "strength_28": 1, - "insert_frame_29": 0, - "strength_29": 0, - "insert_frame_30": 1, - "strength_30": 1, - "insert_frame_31": 0, - "strength_31": 0, - "insert_frame_32": 1, - "strength_32": 1, - "insert_frame_33": 0, - "strength_33": 1, - "insert_frame_34": 0, - "strength_34": 1, - "insert_frame_35": 0, - "strength_35": 1, - "insert_frame_36": 0, - "strength_36": 1, - "insert_frame_37": 0, - "strength_37": 1, - "insert_frame_38": 0, - "strength_38": 1, - "insert_frame_39": 0, - "strength_39": 1, - "insert_frame_40": 0, - "strength_40": 1, - "insert_frame_41": 0, - "strength_41": 1, - "insert_frame_42": 0, - "strength_42": 1, - "insert_frame_43": 0, - "strength_43": 1, - "insert_frame_44": 0, - "strength_44": 1, - "insert_frame_45": 0, - "strength_45": 1, - "insert_frame_46": 0, - "strength_46": 1, - "insert_frame_47": 0, - "strength_47": 1, - "insert_frame_48": 0, - "strength_48": 1, - "insert_frame_49": 0, - "strength_49": 1, - "insert_frame_50": 0, - "strength_50": 1, - "insert_mode": "frames", - "frame_rate": 24, - "insert_second_1": 0, - "insert_second_2": 0, - "insert_second_3": 0, - "insert_second_4": 0, - "insert_second_5": 0, - "insert_second_6": 0, - "insert_second_7": 0, - "insert_second_8": 0, - "insert_second_9": 1, - "insert_second_10": 0, - "insert_second_11": 1, - "insert_second_12": 0, - "insert_second_13": 1, - "insert_second_14": 0, - "insert_second_15": 1, - "insert_second_16": 0, - "insert_second_17": 1, - "insert_second_18": 0, - "insert_second_19": 1, - "insert_second_20": 0, - "insert_second_21": 1, - "insert_second_22": 0, - "insert_second_23": 1, - "insert_second_24": 0, - "insert_second_25": 1, - "insert_second_26": 0, - "insert_second_27": 1, - "insert_second_28": 0, - "insert_second_29": 1, - "insert_second_30": 0, - "insert_second_31": 1, - "insert_second_32": 0, - "insert_second_33": 1, - "insert_second_34": 0, - "insert_second_35": 0, - "insert_second_36": 0, - "insert_second_37": 0, - "insert_second_38": 0, - "insert_second_39": 0, - "insert_second_40": 0, - "insert_second_41": 0, - "insert_second_42": 0, - "insert_second_43": 0, - "insert_second_44": 0, - "insert_second_45": 0, - "insert_second_46": 0, - "insert_second_47": 0, - "insert_second_48": 0, - "insert_second_49": 0, - "insert_second_50": 0, - "insert_images_using": "frames", - "video_length": 1, - "insert_percentage_1": 0, - "insert_percentage_2": 1, - "insert_percentage_3": 0, - "insert_percentage_4": 0, - "insert_percentage_5": 0, - "insert_percentage_6": 0, - "insert_percentage_7": 0, - "insert_percentage_8": 0, - "insert_percentage_9": 0, - "insert_percentage_10": 0, - "insert_percentage_11": 0, - "insert_percentage_12": 0, - "insert_percentage_13": 0, - "insert_percentage_14": 0, - "insert_percentage_15": 0, - "insert_percentage_16": 0, - "insert_percentage_17": 0, - "insert_percentage_18": 0, - "insert_percentage_19": 0, - "insert_percentage_20": 0, - "insert_percentage_21": 0, - "insert_percentage_22": 0, - "insert_percentage_23": 0, - "insert_percentage_24": 0, - "insert_percentage_25": 0, - "insert_percentage_26": 0, - "insert_percentage_27": 0, - "insert_percentage_28": 0, - "insert_percentage_29": 0, - "insert_percentage_30": 0, - "insert_percentage_31": 0, - "insert_percentage_32": 0, - "insert_percentage_33": 0, - "insert_percentage_34": 0, - "insert_percentage_35": 0, - "insert_percentage_36": 0, - "insert_percentage_37": 0, - "insert_percentage_38": 0, - "insert_percentage_39": 0, - "insert_percentage_40": 0, - "insert_percentage_41": 0, - "insert_percentage_42": 0, - "insert_percentage_43": 0, - "insert_percentage_44": 0, - "insert_percentage_45": 0, - "insert_percentage_46": 0, - "insert_percentage_47": 0, - "insert_percentage_48": 0, - "insert_percentage_49": 0, - "insert_percentage_50": 0, - "insert_percent_1": 0, - "insert_percent_2": 50, - "insert_percent_3": 100, - "insert_percent_4": 0, - "insert_percent_5": 0, - "insert_percent_6": 0, - "insert_percent_7": 0, - "insert_percent_8": 0, - "insert_percent_9": 0, - "insert_percent_10": 0, - "insert_percent_11": 0, - "insert_percent_12": 0, - "insert_percent_13": 0, - "insert_percent_14": 0, - "insert_percent_15": 0, - "insert_percent_16": 0, - "insert_percent_17": 0, - "insert_percent_18": 0, - "insert_percent_19": 0, - "insert_percent_20": 0, - "insert_percent_21": 0, - "insert_percent_22": 0, - "insert_percent_23": 0, - "insert_percent_24": 0, - "insert_percent_25": 0, - "insert_percent_26": 0, - "insert_percent_27": 0, - "insert_percent_28": 0, - "insert_percent_29": 0, - "insert_percent_30": 0, - "insert_percent_31": 0, - "insert_percent_32": 0, - "insert_percent_33": 0, - "insert_percent_34": 0, - "insert_percent_35": 0, - "insert_percent_36": 0, - "insert_percent_37": 0, - "insert_percent_38": 0, - "insert_percent_39": 0, - "insert_percent_40": 0, - "insert_percent_41": 0, - "insert_percent_42": 0, - "insert_percent_43": 0, - "insert_percent_44": 0, - "insert_percent_45": 0, - "insert_percent_46": 0, - "insert_percent_47": 0, - "insert_percent_48": 0, - "insert_percent_49": 0, - "insert_percent_50": 0 - }, - "widgets_values": [ - 0, - "frames", - 24, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1 - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 324, - "type": "CFGGuider", - "pos": [ - 1010, - 7110 - ], - "size": [ - 210, - 98 - ], - "flags": {}, - "order": 3, - "mode": 0, - "inputs": [ - { - "localized_name": "model", - "name": "model", - "type": "MODEL", - "link": 683 - }, - { - "localized_name": "positive", - "name": "positive", - "type": "CONDITIONING", - "link": 996 - }, - { - "localized_name": "negative", - "name": "negative", - "type": "CONDITIONING", - "link": 997 - } - ], - "outputs": [ - { - "localized_name": "GUIDER", - "name": "GUIDER", - "type": "GUIDER", - "links": [ - 679 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.64", - "Node name for S&R": "CFGGuider", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [ - 1 - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 329, - "type": "LTXVConcatAVLatent", - "pos": [ - 1010, - 7250 - ], - "size": [ - 187.5, - 60 - ], - "flags": {}, - "order": 5, - "mode": 0, - "inputs": [ - { - "localized_name": "video_latent", - "name": "video_latent", - "type": "LATENT", - "link": 998 - }, - { - "localized_name": "audio_latent", - "name": "audio_latent", - "type": "LATENT", - "link": 700 - } - ], - "outputs": [ - { - "localized_name": "latent", - "name": "latent", - "type": "LATENT", - "links": [ - 697 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.7.0", - "Node name for S&R": "LTXVConcatAVLatent", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 322, - "type": "SamplerCustomAdvanced", - "pos": [ - 1230, - 7100 - ], - "size": [ - 212.3638671875, - 204.64497833251954 - ], - "flags": {}, - "order": 2, - "mode": 0, - "inputs": [ - { - "localized_name": "noise", - "name": "noise", - "type": "NOISE", - "link": 688 - }, - { - "localized_name": "guider", - "name": "guider", - "type": "GUIDER", - "link": 679 - }, - { - "localized_name": "sampler", - "name": "sampler", - "type": "SAMPLER", - "link": 680 - }, - { - "localized_name": "sigmas", - "name": "sigmas", - "type": "SIGMAS", - "link": 681 - }, - { - "localized_name": "latent_image", - "name": "latent_image", - "type": "LATENT", - "link": 697 - } - ], - "outputs": [ - { - "localized_name": "output", - "name": "output", - "type": "LATENT", - "links": [ - 1023 - ] - }, - { - "localized_name": "denoised_output", - "name": "denoised_output", - "type": "LATENT", - "links": [] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.60", - "Node name for S&R": "SamplerCustomAdvanced", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 417, - "type": "LTXVSeparateAVLatent", - "pos": [ - 1450, - 7100 - ], - "size": [ - 193.2916015625, - 46 - ], - "flags": {}, - "order": 12, - "mode": 0, - "inputs": [ - { - "localized_name": "av_latent", - "name": "av_latent", - "type": "LATENT", - "link": 1023 - } - ], - "outputs": [ - { - "localized_name": "video_latent", - "name": "video_latent", - "type": "LATENT", - "links": [ - 1024 - ] - }, - { - "localized_name": "audio_latent", - "name": "audio_latent", - "type": "LATENT", - "links": [ - 1025 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.17.2", - "Node name for S&R": "LTXVSeparateAVLatent" - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 336, - "type": "LTXVLatentUpsampler", - "pos": [ - 160, - 7410 - ], - "size": [ - 320, - 66 - ], - "flags": {}, - "order": 9, - "mode": 0, - "inputs": [ - { - "localized_name": "samples", - "name": "samples", - "type": "LATENT", - "link": 878 - }, - { - "localized_name": "upscale_model", - "name": "upscale_model", - "type": "LATENT_UPSCALE_MODEL", - "link": 1038 - }, - { - "localized_name": "vae", - "name": "vae", - "type": "VAE", - "link": 717 - } - ], - "outputs": [ - { - "localized_name": "LATENT", - "name": "LATENT", - "type": "LATENT", - "links": [ - 713 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.14.1", - "Node name for S&R": "LTXVLatentUpsampler", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 333, - "type": "LatentUpscaleModelLoader", - "pos": [ - -200, - 7300 - ], - "size": [ - 330, - 58 - ], - "flags": {}, - "order": 1, - "mode": 0, - "inputs": [], - "outputs": [ - { - "localized_name": "LATENT_UPSCALE_MODEL", - "name": "LATENT_UPSCALE_MODEL", - "type": "LATENT_UPSCALE_MODEL", - "links": [ - 710 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.16.4", - "Node name for S&R": "LatentUpscaleModelLoader" - }, - "widgets_values": [ - "ltx-2.3-spatial-upscaler-x1.5-1.0.safetensors" - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 339, - "type": "PrimitiveBoolean", - "pos": [ - 160, - 7520 - ], - "size": [ - 290, - 60 - ], - "flags": {}, - "order": 10, - "mode": 0, - "inputs": [ - { - "localized_name": "value", - "name": "value", - "type": "BOOLEAN", - "widget": { - "name": "value" - }, - "link": 719 - } - ], - "outputs": [ - { - "localized_name": "BOOLEAN", - "name": "BOOLEAN", - "type": "BOOLEAN", - "links": [ - 718 - ] - } - ], - "title": "3rd Stage: False = 1.5x, True = 2x", - "properties": { - "cnr_id": "comfy-core", - "ver": "0.17.2", - "Node name for S&R": "PrimitiveBoolean" - }, - "widgets_values": [ - true - ], - "color": "#222", - "bgcolor": "#000" - } - ], - "groups": [], - "links": [ - { - "id": 679, - "origin_id": 324, - "origin_slot": 0, - "target_id": 322, - "target_slot": 1, - "type": "GUIDER" - }, - { - "id": 680, - "origin_id": 325, - "origin_slot": 0, - "target_id": 322, - "target_slot": 2, - "type": "SAMPLER" - }, - { - "id": 681, - "origin_id": 326, - "origin_slot": 0, - "target_id": 322, - "target_slot": 3, - "type": "SIGMAS" - }, - { - "id": 683, - "origin_id": -10, - "origin_slot": 0, - "target_id": 324, - "target_slot": 0, - "type": "MODEL" - }, - { - "id": 686, - "origin_id": -10, - "origin_slot": 0, - "target_id": 326, - "target_slot": 0, - "type": "MODEL" - }, - { - "id": 688, - "origin_id": -10, - "origin_slot": 3, - "target_id": 322, - "target_slot": 0, - "type": "NOISE" - }, - { - "id": 697, - "origin_id": 329, - "origin_slot": 0, - "target_id": 322, - "target_slot": 4, - "type": "LATENT" - }, - { - "id": 700, - "origin_id": -10, - "origin_slot": 7, - "target_id": 329, - "target_slot": 1, - "type": "LATENT" - }, - { - "id": 708, - "origin_id": -10, - "origin_slot": 6, - "target_id": 331, - "target_slot": 2, - "type": "LATENT" - }, - { - "id": 710, - "origin_id": 333, - "origin_slot": 0, - "target_id": 334, - "target_slot": 1, - "type": "LATENT_UPSCALE_MODEL" - }, - { - "id": 712, - "origin_id": 334, - "origin_slot": 0, - "target_id": 332, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 713, - "origin_id": 336, - "origin_slot": 0, - "target_id": 332, - "target_slot": 1, - "type": "LATENT" - }, - { - "id": 716, - "origin_id": -10, - "origin_slot": 4, - "target_id": 334, - "target_slot": 2, - "type": "VAE" - }, - { - "id": 717, - "origin_id": -10, - "origin_slot": 4, - "target_id": 336, - "target_slot": 2, - "type": "VAE" - }, - { - "id": 718, - "origin_id": 339, - "origin_slot": 0, - "target_id": 332, - "target_slot": 2, - "type": "BOOLEAN" - }, - { - "id": 719, - "origin_id": -10, - "origin_slot": 8, - "target_id": 339, - "target_slot": 0, - "type": "BOOLEAN" - }, - { - "id": 877, - "origin_id": 331, - "origin_slot": 2, - "target_id": 334, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 878, - "origin_id": 331, - "origin_slot": 2, - "target_id": 336, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 919, - "origin_id": -10, - "origin_slot": 1, - "target_id": 331, - "target_slot": 0, - "type": "CONDITIONING" - }, - { - "id": 920, - "origin_id": -10, - "origin_slot": 2, - "target_id": 331, - "target_slot": 1, - "type": "CONDITIONING" - }, - { - "id": 996, - "origin_id": 411, - "origin_slot": 0, - "target_id": 324, - "target_slot": 1, - "type": "CONDITIONING" - }, - { - "id": 997, - "origin_id": 411, - "origin_slot": 1, - "target_id": 324, - "target_slot": 2, - "type": "CONDITIONING" - }, - { - "id": 998, - "origin_id": 411, - "origin_slot": 2, - "target_id": 329, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 999, - "origin_id": 331, - "origin_slot": 0, - "target_id": 411, - "target_slot": 0, - "type": "CONDITIONING" - }, - { - "id": 1000, - "origin_id": 331, - "origin_slot": 1, - "target_id": 411, - "target_slot": 1, - "type": "CONDITIONING" - }, - { - "id": 1001, - "origin_id": 332, - "origin_slot": 0, - "target_id": 411, - "target_slot": 3, - "type": "LATENT" - }, - { - "id": 1002, - "origin_id": -10, - "origin_slot": 4, - "target_id": 411, - "target_slot": 2, - "type": "VAE" - }, - { - "id": 1005, - "origin_id": -10, - "origin_slot": 9, - "target_id": 411, - "target_slot": 4, - "type": "IMAGE" - }, - { - "id": 1023, - "origin_id": 322, - "origin_slot": 0, - "target_id": 417, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 1024, - "origin_id": 417, - "origin_slot": 0, - "target_id": -20, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 1025, - "origin_id": 417, - "origin_slot": 1, - "target_id": -20, - "target_slot": 1, - "type": "LATENT" - }, - { - "id": 1038, - "origin_id": -10, - "origin_slot": 10, - "target_id": 336, - "target_slot": 1, - "type": "LATENT_UPSCALE_MODEL" - } - ], - "extra": { - "workflowRendererVersion": "LG" - } - }, - { - "id": "958b2c7a-1f75-4a30-84a8-892bddb9e426", - "version": 1, - "state": { - "lastGroupId": 33, - "lastNodeId": 443, - "lastLinkId": 1079, - "lastRerouteId": 0 - }, - "revision": 0, - "config": {}, - "name": "Model Loader", - "inputNode": { - "id": -10, - "bounding": [ - 2340, - 6080, - 135.654296875, - 80 - ] - }, - "outputNode": { - "id": -20, - "bounding": [ - 3560, - 5580, - 187.052734375, - 200 - ] - }, - "inputs": [ - { - "id": "9ae347e5-8a40-4407-bec5-7781d672dd85", - "name": "value", - "type": "STRING", - "linkIds": [ - 1064 - ], - "label": "Prompt", - "pos": [ - 2455.654296875, - 6100 - ] - }, - { - "id": "32b8ff31-4396-4336-aeaf-ff84d858a4d9", - "name": "value_1", - "type": "INT", - "linkIds": [ - 1018 - ], - "label": "length (seconds)", - "pos": [ - 2455.654296875, - 6120 - ] - } - ], - "outputs": [ - { - "id": "a72640aa-8055-45bb-8cbb-f68ec7413515", - "name": "optimized_model", - "type": "MODEL", - "linkIds": [ - 1067 - ], - "pos": [ - 3580, - 5600 - ] - }, - { - "id": "cfb48567-9b75-46f5-8b71-385f3388173c", - "name": "VAE", - "type": "VAE", - "linkIds": [ - 721 - ], - "label": "video_vae", - "pos": [ - 3580, - 5620 - ] - }, - { - "id": "4e095394-8a92-47e4-8993-49fb4ce73bd8", - "name": "VAE_1", - "type": "VAE", - "linkIds": [ - 722 - ], - "label": "audio_vae", - "pos": [ - 3580, - 5640 - ] - }, - { - "id": "8bbe34b1-d094-40a3-9c04-5a19cd7b2af2", - "name": "positive", - "type": "CONDITIONING", - "linkIds": [ - 726 - ], - "pos": [ - 3580, - 5660 - ] - }, - { - "id": "dac0ad69-373b-4e47-b0cd-eb8fd18e1c07", - "name": "negative", - "type": "CONDITIONING", - "linkIds": [ - 727 - ], - "pos": [ - 3580, - 5680 - ] - }, - { - "id": "b4d0868d-86e8-42ba-83fc-44c5324ac35d", - "name": "INT", - "type": "INT", - "linkIds": [ - 729 - ], - "label": "length_seconds", - "pos": [ - 3580, - 5700 - ] - }, - { - "id": "12a111ff-cd34-4b59-bca3-4c5f24626a62", - "name": "NOISE", - "type": "NOISE", - "linkIds": [ - 730 - ], - "label": "noise", - "pos": [ - 3580, - 5720 - ] - }, - { - "id": "9f03af49-218d-4431-a2e9-891d16a8eb05", - "name": "LATENT_UPSCALE_MODEL", - "type": "LATENT_UPSCALE_MODEL", - "linkIds": [ - 1034 - ], - "label": "2x_spatial_upscale_model", - "pos": [ - 3580, - 5740 - ] - } - ], - "widgets": [], - "nodes": [ - { - "id": 338, - "type": "VAELoaderKJ", - "pos": [ - 2560, - 5780 - ], - "size": [ - 270, - 106 - ], - "flags": {}, - "order": 0, - "mode": 0, - "inputs": [], - "outputs": [ - { - "localized_name": "VAE", - "name": "VAE", - "type": "VAE", - "links": [ - 722 - ] - } - ], - "title": "Audio VAELoader KJ", - "properties": { - "cnr_id": "comfyui-kjnodes", - "ver": "487c7d86a0230aae3d8c0a37d517159b73834f85", - "Node name for S&R": "VAELoaderKJ" - }, - "widgets_values": [ - "LTX23_audio_vae_bf16.safetensors", - "main_device", - "bf16" - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 337, - "type": "VAELoaderKJ", - "pos": [ - 2560, - 5630 - ], - "size": [ - 270, - 106 - ], - "flags": {}, - "order": 1, - "mode": 0, - "inputs": [], - "outputs": [ - { - "localized_name": "VAE", - "name": "VAE", - "type": "VAE", - "links": [ - 721 - ] - } - ], - "title": "Video VAELoader KJ", - "properties": { - "cnr_id": "comfyui-kjnodes", - "ver": "1.2.5", - "Node name for S&R": "VAELoaderKJ" - }, - "widgets_values": [ - "LTX23_video_vae_bf16.safetensors", - "main_device", - "bf16" - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 413, - "type": "LTXAVTextEncoderLoader", - "pos": [ - 2560, - 5940 - ], - "size": [ - 318.444921875, - 122 - ], - "flags": {}, - "order": 2, - "mode": 0, - "inputs": [], - "outputs": [ - { - "localized_name": "CLIP", - "name": "CLIP", - "type": "CLIP", - "links": [ - 1054 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.14.1", - "Node name for S&R": "LTXAVTextEncoderLoader" - }, - "widgets_values": [ - "gemma_3_12B_it_fp4_mixed.safetensors", - "ltx-2.3-22b-dev-fp8.safetensors", - "default" - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 430, - "type": "CLIPTextEncode", - "pos": [ - 2910, - 5950 - ], - "size": [ - 274.3658203125, - 88 - ], - "flags": {}, - "order": 13, - "mode": 0, - "inputs": [ - { - "localized_name": "clip", - "name": "clip", - "type": "CLIP", - "link": 1054 - }, - { - "localized_name": "text", - "name": "text", - "type": "STRING", - "widget": { - "name": "text" - }, - "link": 1064 - } - ], - "outputs": [ - { - "localized_name": "CONDITIONING", - "name": "CONDITIONING", - "type": "CONDITIONING", - "slot_index": 0, - "links": [ - 1062, - 1063 - ] - } - ], - "title": "CLIP Text Encode (Positive Prompt)", - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.28", - "Node name for S&R": "CLIPTextEncode" - }, - "widgets_values": [ - "" - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 361, - "type": "ConditioningZeroOut", - "pos": [ - 2920, - 6090 - ], - "size": [ - 204.134765625, - 26 - ], - "flags": {}, - "order": 10, - "mode": 0, - "inputs": [ - { - "localized_name": "conditioning", - "name": "conditioning", - "type": "CONDITIONING", - "link": 1063 - } - ], - "outputs": [ - { - "localized_name": "CONDITIONING", - "name": "CONDITIONING", - "type": "CONDITIONING", - "links": [ - 725 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.7.0", - "Node name for S&R": "ConditioningZeroOut", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 363, - "type": "LTXVConditioning", - "pos": [ - 3210, - 5960 - ], - "size": [ - 210, - 80 - ], - "flags": {}, - "order": 11, - "mode": 0, - "inputs": [ - { - "localized_name": "positive", - "name": "positive", - "type": "CONDITIONING", - "link": 1062 - }, - { - "localized_name": "negative", - "name": "negative", - "type": "CONDITIONING", - "link": 725 - } - ], - "outputs": [ - { - "localized_name": "positive", - "name": "positive", - "type": "CONDITIONING", - "links": [ - 726 - ] - }, - { - "localized_name": "negative", - "name": "negative", - "type": "CONDITIONING", - "links": [ - 727 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.56", - "Node name for S&R": "LTXVConditioning", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [ - 24 - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 419, - "type": "LatentUpscaleModelLoader", - "pos": [ - 2560, - 6110 - ], - "size": [ - 330, - 58 - ], - "flags": {}, - "order": 3, - "mode": 0, - "inputs": [], - "outputs": [ - { - "localized_name": "LATENT_UPSCALE_MODEL", - "name": "LATENT_UPSCALE_MODEL", - "type": "LATENT_UPSCALE_MODEL", - "links": [ - 1034 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.16.4", - "Node name for S&R": "LatentUpscaleModelLoader" - }, - "widgets_values": [ - "ltx-2.3-spatial-upscaler-x2-1.1.safetensors" - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 364, - "type": "RandomNoise", - "pos": [ - 2860, - 5750 - ], - "size": [ - 210, - 82 - ], - "flags": {}, - "order": 4, - "mode": 0, - "inputs": [ - { - "label": "noise_seed", - "localized_name": "noise_seed", - "name": "noise_seed", - "type": "INT", - "widget": { - "name": "noise_seed" - }, - "link": null - } - ], - "outputs": [ - { - "localized_name": "NOISE", - "name": "NOISE", - "type": "NOISE", - "links": [ - 730 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.56", - "Node name for S&R": "RandomNoise", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65 - }, - "widgets_values": [ - 0, - "fixed" - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 365, - "type": "07609e03-45f4-49c7-a233-a933ebf95094", - "pos": [ - 2860, - 5640 - ], - "size": [ - 210, - 58 - ], - "flags": {}, - "order": 12, - "mode": 0, - "inputs": [ - { - "label": "Seconds", - "name": "value", - "type": "INT", - "widget": { - "name": "value" - }, - "link": 1018 - } - ], - "outputs": [ - { - "localized_name": "INT", - "name": "INT", - "type": "INT", - "links": [ - 729 - ] - } - ], - "properties": { - "proxyWidgets": [ - [ - "-1", - "value" - ] - ], - "cnr_id": "comfy-core", - "ver": "0.16.4" - }, - "widgets_values": [ - 5 - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 443, - "type": "VAELoaderKJ", - "pos": [ - 2560, - 5470 - ], - "size": [ - 270, - 110 - ], - "flags": {}, - "order": 5, - "mode": 0, - "inputs": [], - "outputs": [ - { - "localized_name": "VAE", - "name": "VAE", - "type": "VAE", - "links": [ - 1072 - ] - } - ], - "title": "Tiny VAELoader KJ", - "properties": { - "cnr_id": "comfyui-kjnodes", - "ver": "1.2.5", - "Node name for S&R": "VAELoaderKJ" - }, - "widgets_values": [ - "taeltx2_3.safetensors", - "main_device", - "bf16" - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 442, - "type": "LTX2SamplingPreviewOverride", - "pos": [ - 2850, - 5470 - ], - "size": [ - 270, - 100 - ], - "flags": {}, - "order": 8, - "mode": 0, - "inputs": [ - { - "localized_name": "model", - "name": "model", - "type": "MODEL", - "link": 1073 - }, - { - "localized_name": "latent_upscale_model", - "name": "latent_upscale_model", - "shape": 7, - "type": "LATENT_UPSCALE_MODEL", - "link": null - }, - { - "localized_name": "vae", - "name": "vae", - "shape": 7, - "type": "VAE", - "link": 1072 - } - ], - "outputs": [ - { - "localized_name": "MODEL", - "name": "MODEL", - "type": "MODEL", - "links": [ - 1074 - ] - } - ], - "properties": { - "cnr_id": "comfyui-kjnodes", - "ver": "1.3.4", - "Node name for S&R": "LTX2SamplingPreviewOverride" - }, - "widgets_values": [ - 16 - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 426, - "type": "CheckpointLoaderSimple", - "pos": [ - 2560, - 5330 - ], - "size": [ - 270, - 98 - ], - "flags": {}, - "order": 6, - "mode": 0, - "inputs": [], - "outputs": [ - { - "localized_name": "MODEL", - "name": "MODEL", - "type": "MODEL", - "links": [ - 1073 - ] - }, - { - "localized_name": "CLIP", - "name": "CLIP", - "type": "CLIP", - "links": null - }, - { - "localized_name": "VAE", - "name": "VAE", - "type": "VAE", - "links": null - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.17.2", - "Node name for S&R": "CheckpointLoaderSimple" - }, - "widgets_values": [ - "ltx-2.3-22b-dev-fp8.safetensors" - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 429, - "type": "LoraLoaderModelOnly", - "pos": [ - 3140, - 5480 - ], - "size": [ - 260, - 82 - ], - "flags": {}, - "order": 9, - "mode": 0, - "inputs": [ - { - "localized_name": "model", - "name": "model", - "type": "MODEL", - "link": 1074 - } - ], - "outputs": [ - { - "localized_name": "MODEL", - "name": "MODEL", - "type": "MODEL", - "links": [ - 1067 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.75", - "Node name for S&R": "LoraLoaderModelOnly", - "enableTabs": false, - "tabWidth": 65, - "tabXOffset": 10, - "hasSecondTab": false, - "secondTabText": "Send Back", - "secondTabOffset": 80, - "secondTabWidth": 65, - "models": [ - { - "name": "ltx-2.3-22b-distilled-lora-384.safetensors", - "url": "https://huggingface.co/Lightricks/LTX-2.3/resolve/main/ltx-2.3-22b-distilled-lora-384.safetensors", - "directory": "loras" - } - ] - }, - "widgets_values": [ - "ltx2\\ltx-2.3-22b-distilled-lora-dynamic_fro09_avg_rank_105_bf16.safetensors", - 0.5 - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 441, - "type": "MarkdownNote", - "pos": [ - 2160, - 5500 - ], - "size": [ - 370, - 320 - ], - "flags": {}, - "order": 7, - "mode": 0, - "inputs": [], - "outputs": [], - "title": "FAQ", - "properties": {}, - "widgets_values": [ - "# FAQ\n\n**How do I set the length of the video?**\n\nYou can set the length of the video in the Model Loader Subgraph →\n\n**I keep getting errors when running this workflow!**\n\nDouble check that all of the correct models loaded. This is using the newest version of 2x upscaler (v1.1). You can change it to the older one under the Model Loader.\n\n**Where do I download these models??**\n\nThere is a good list of models and download links here: \n\nAlso the latest v1.1 spatial upscaler can be found here: \n\nThe tiny vae (taeltx2_3.safetensors) is found here:\n\n\n\n\n\n\n\n\n\n" - ], - "color": "#432", - "bgcolor": "#653" - } - ], - "groups": [], - "links": [ - { - "id": 721, - "origin_id": 337, - "origin_slot": 0, - "target_id": -20, - "target_slot": 1, - "type": "VAE" - }, - { - "id": 722, - "origin_id": 338, - "origin_slot": 0, - "target_id": -20, - "target_slot": 2, - "type": "VAE" - }, - { - "id": 725, - "origin_id": 361, - "origin_slot": 0, - "target_id": 363, - "target_slot": 1, - "type": "CONDITIONING" - }, - { - "id": 726, - "origin_id": 363, - "origin_slot": 0, - "target_id": -20, - "target_slot": 3, - "type": "CONDITIONING" - }, - { - "id": 727, - "origin_id": 363, - "origin_slot": 1, - "target_id": -20, - "target_slot": 4, - "type": "CONDITIONING" - }, - { - "id": 729, - "origin_id": 365, - "origin_slot": 0, - "target_id": -20, - "target_slot": 5, - "type": "INT" - }, - { - "id": 730, - "origin_id": 364, - "origin_slot": 0, - "target_id": -20, - "target_slot": 6, - "type": "NOISE" - }, - { - "id": 1018, - "origin_id": -10, - "origin_slot": 1, - "target_id": 365, - "target_slot": 0, - "type": "INT" - }, - { - "id": 1034, - "origin_id": 419, - "origin_slot": 0, - "target_id": -20, - "target_slot": 7, - "type": "LATENT_UPSCALE_MODEL" - }, - { - "id": 1054, - "origin_id": 413, - "origin_slot": 0, - "target_id": 430, - "target_slot": 0, - "type": "CLIP" - }, - { - "id": 1062, - "origin_id": 430, - "origin_slot": 0, - "target_id": 363, - "target_slot": 0, - "type": "CONDITIONING" - }, - { - "id": 1063, - "origin_id": 430, - "origin_slot": 0, - "target_id": 361, - "target_slot": 0, - "type": "CONDITIONING" - }, - { - "id": 1064, - "origin_id": -10, - "origin_slot": 0, - "target_id": 430, - "target_slot": 1, - "type": "STRING" - }, - { - "id": 1067, - "origin_id": 429, - "origin_slot": 0, - "target_id": -20, - "target_slot": 0, - "type": "MODEL" - }, - { - "id": 1072, - "origin_id": 443, - "origin_slot": 0, - "target_id": 442, - "target_slot": 2, - "type": "VAE" - }, - { - "id": 1073, - "origin_id": 426, - "origin_slot": 0, - "target_id": 442, - "target_slot": 0, - "type": "MODEL" - }, - { - "id": 1074, - "origin_id": 442, - "origin_slot": 0, - "target_id": 429, - "target_slot": 0, - "type": "MODEL" - } - ], - "extra": { - "workflowRendererVersion": "LG" - } - }, - { - "id": "07609e03-45f4-49c7-a233-a933ebf95094", - "version": 1, - "state": { - "lastGroupId": 33, - "lastNodeId": 443, - "lastLinkId": 1079, - "lastRerouteId": 0 - }, - "revision": 0, - "config": {}, - "name": "length (seconds)", - "inputNode": { - "id": -10, - "bounding": [ - -662.1270448096031, - 4310.82566166648, - 120, - 60 - ] - }, - "outputNode": { - "id": -20, - "bounding": [ - 255.79328565979654, - 4300.82566166648, - 120, - 60 - ] - }, - "inputs": [ - { - "id": "df88c383-3299-420f-8c75-fd0d6a39a9af", - "name": "value", - "type": "INT", - "linkIds": [ - 1071 - ], - "label": "Seconds", - "pos": [ - -562.1270448096031, - 4330.82566166648 - ] - } - ], - "outputs": [ - { - "id": "9b5a4dc0-bb87-45a3-bd3d-551edc18c371", - "name": "INT", - "type": "INT", - "linkIds": [ - 359 - ], - "localized_name": "INT", - "pos": [ - 275.79328565979654, - 4320.82566166648 - ] - } - ], - "widgets": [], - "nodes": [ - { - "id": 137, - "type": "easy mathInt", - "pos": [ - -244.96788124271472, - 4296.913950056486 - ], - "size": [ - 210, - 106 - ], - "flags": {}, - "order": 0, - "mode": 0, - "inputs": [ - { - "localized_name": "a", - "name": "a", - "type": "INT", - "widget": { - "name": "a" - }, - "link": 360 - } - ], - "outputs": [ - { - "localized_name": "INT", - "name": "INT", - "type": "INT", - "links": [ - 358 - ] - } - ], - "properties": { - "cnr_id": "comfyui-easy-use", - "ver": "1.3.4", - "Node name for S&R": "easy mathInt" - }, - "widgets_values": [ - 0, - 24, - "multiply" - ] - }, - { - "id": 138, - "type": "easy mathInt", - "pos": [ - -14.206714340203394, - 4288.737373276474 - ], - "size": [ - 210, - 106 - ], - "flags": {}, - "order": 1, - "mode": 0, - "inputs": [ - { - "localized_name": "a", - "name": "a", - "type": "INT", - "widget": { - "name": "a" - }, - "link": 358 - } - ], - "outputs": [ - { - "localized_name": "INT", - "name": "INT", - "type": "INT", - "links": [ - 359 - ] - } - ], - "properties": { - "cnr_id": "comfyui-easy-use", - "ver": "1.3.4", - "Node name for S&R": "easy mathInt" - }, - "widgets_values": [ - 0, - 1, - "add" - ] - }, - { - "id": 139, - "type": "PrimitiveInt", - "pos": [ - -482.12704480960315, - 4310.54157802317 - ], - "size": [ - 210, - 87.4510511866738 - ], - "flags": {}, - "order": 2, - "mode": 0, - "inputs": [ - { - "localized_name": "value", - "name": "value", - "type": "INT", - "widget": { - "name": "value" - }, - "link": 1071 - } - ], - "outputs": [ - { - "localized_name": "INT", - "name": "INT", - "type": "INT", - "links": [ - 360 - ] - } - ], - "title": "Seconds", - "properties": { - "cnr_id": "comfy-core", - "ver": "0.16.4", - "Node name for S&R": "PrimitiveInt" - }, - "widgets_values": [ - 5, - "fixed" - ] - } - ], - "groups": [], - "links": [ - { - "id": 360, - "origin_id": 139, - "origin_slot": 0, - "target_id": 137, - "target_slot": 0, - "type": "INT" - }, - { - "id": 358, - "origin_id": 137, - "origin_slot": 0, - "target_id": 138, - "target_slot": 0, - "type": "INT" - }, - { - "id": 359, - "origin_id": 138, - "origin_slot": 0, - "target_id": -20, - "target_slot": 0, - "type": "INT" - }, - { - "id": 1071, - "origin_id": -10, - "origin_slot": 0, - "target_id": 139, - "target_slot": 0, - "type": "INT" - } - ], - "extra": { - "workflowRendererVersion": "LG" - } - }, - { - "id": "360d64f7-96cd-42a6-9b13-6eb0387e17d9", - "version": 1, - "state": { - "lastGroupId": 33, - "lastNodeId": 443, - "lastLinkId": 1079, - "lastRerouteId": 0 - }, - "revision": 0, - "config": {}, - "name": "3 Stage Upscale", - "inputNode": { - "id": -10, - "bounding": [ - 4330, - 5940, - 187.052734375, - 240 - ] - }, - "outputNode": { - "id": -20, - "bounding": [ - 5670, - 5940, - 120, - 60 - ] - }, - "inputs": [ - { - "id": "f3d52cbc-fafe-46a6-978c-2eaff2c185c3", - "name": "model", - "type": "MODEL", - "linkIds": [ - 3, - 14, - 23 - ], - "pos": [ - 4497.052734375, - 5960 - ] - }, - { - "id": "d0e8f1b7-b19b-49cd-8a2b-aa4a639857b3", - "name": "positive", - "type": "CONDITIONING", - "linkIds": [ - 4, - 15, - 871 - ], - "pos": [ - 4497.052734375, - 5980 - ] - }, - { - "id": "36e3ff32-f9b2-41d9-94de-9d8d9a91f1a6", - "name": "negative", - "type": "CONDITIONING", - "linkIds": [ - 5, - 16, - 872 - ], - "pos": [ - 4497.052734375, - 6000 - ] - }, - { - "id": "3f2a7956-c7c2-4478-99ad-2487644cbbc2", - "name": "latent_image", - "type": "LATENT", - "linkIds": [ - 6 - ], - "pos": [ - 4497.052734375, - 6020 - ] - }, - { - "id": "cc2edb71-c53a-4056-8a85-5d8199e79c72", - "name": "noise", - "type": "NOISE", - "linkIds": [ - 7, - 17, - 26 - ], - "pos": [ - 4497.052734375, - 6040 - ] - }, - { - "id": "d162c9e7-24cc-4421-a6fb-d253bfae8883", - "name": "vae", - "type": "VAE", - "linkIds": [ - 32, - 33 - ], - "label": "video_vae", - "pos": [ - 4497.052734375, - 6060 - ] - }, - { - "id": "e9cd552e-a79c-4b6e-8508-4ad3a9f4218b", - "name": "image", - "type": "IMAGE", - "linkIds": [ - 29, - 30 - ], - "pos": [ - 4497.052734375, - 6080 - ] - }, - { - "id": "46ddf61f-c9f3-48d0-9f11-6e64e8521966", - "name": "value", - "type": "BOOLEAN", - "linkIds": [ - 40 - ], - "label": "Upscale 2x on Stage 3", - "pos": [ - 4497.052734375, - 6100 - ] - }, - { - "id": "45629d5b-0676-4430-aa3d-8b0cf516de0a", - "name": "multi_input", - "type": "IMAGE", - "linkIds": [ - 1006, - 1007 - ], - "pos": [ - 4497.052734375, - 6120 - ] - }, - { - "id": "b380a19f-38a6-41ca-be93-072da1e263f3", - "name": "upscale_model", - "type": "LATENT_UPSCALE_MODEL", - "linkIds": [ - 1036, - 1039 - ], - "label": "2x_spatial_upscale_model", - "pos": [ - 4497.052734375, - 6140 - ] - } - ], - "outputs": [ - { - "id": "2fae6e6d-6667-450e-933b-b16e88c7968f", - "name": "output", - "type": "LATENT", - "linkIds": [ - 765 - ], - "label": "av_output", - "pos": [ - 5690, - 5960 - ] - } - ], - "widgets": [], - "nodes": [ - { - "id": 371, - "type": "LTXVConcatAVLatent", - "pos": [ - 5470, - 5950 - ], - "size": [ - 181.308203125, - 46 - ], - "flags": {}, - "order": 4, - "mode": 0, - "inputs": [ - { - "localized_name": "video_latent", - "name": "video_latent", - "type": "LATENT", - "link": 852 - }, - { - "localized_name": "audio_latent", - "name": "audio_latent", - "type": "LATENT", - "link": 1078 - } - ], - "outputs": [ - { - "localized_name": "latent", - "name": "latent", - "type": "LATENT", - "links": [ - 765 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.17.2", - "Node name for S&R": "LTXVConcatAVLatent" - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 383, - "type": "LTXVCropGuides", - "pos": [ - 5200, - 5950 - ], - "size": [ - 260, - 70 - ], - "flags": {}, - "order": 5, - "mode": 0, - "inputs": [ - { - "localized_name": "positive", - "name": "positive", - "type": "CONDITIONING", - "link": 871 - }, - { - "localized_name": "negative", - "name": "negative", - "type": "CONDITIONING", - "link": 872 - }, - { - "localized_name": "latent", - "name": "latent", - "type": "LATENT", - "link": 1026 - } - ], - "outputs": [ - { - "localized_name": "positive", - "name": "positive", - "type": "CONDITIONING", - "links": null - }, - { - "localized_name": "negative", - "name": "negative", - "type": "CONDITIONING", - "links": null - }, - { - "localized_name": "latent", - "name": "latent", - "type": "LATENT", - "links": [ - 852 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.17.2", - "Node name for S&R": "LTXVCropGuides" - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 2, - "type": "afa29944-eb58-40a5-8e76-484f2503fa0c", - "pos": [ - 4540, - 5950 - ], - "size": [ - 190, - 195.734375 - ], - "flags": {}, - "order": 1, - "mode": 0, - "inputs": [ - { - "name": "model", - "type": "MODEL", - "link": 3 - }, - { - "name": "positive", - "type": "CONDITIONING", - "link": 4 - }, - { - "name": "negative", - "type": "CONDITIONING", - "link": 5 - }, - { - "name": "latent_image", - "type": "LATENT", - "link": 6 - }, - { - "name": "noise", - "type": "NOISE", - "link": 7 - } - ], - "outputs": [ - { - "name": "video_latent", - "type": "LATENT", - "links": [ - 837 - ] - }, - { - "name": "audio_latent", - "type": "LATENT", - "links": [ - 794 - ] - } - ], - "properties": { - "proxyWidgets": [], - "cnr_id": "comfy-core", - "ver": "0.17.2" - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 440, - "type": "Note", - "pos": [ - 4970, - 5810 - ], - "size": [ - 210, - 88 - ], - "flags": {}, - "order": 0, - "mode": 0, - "inputs": [], - "outputs": [], - "properties": {}, - "widgets_values": [ - "Bypass Stage #3 to skip the 2nd upscale stage." - ], - "color": "#223", - "bgcolor": "#335" - }, - { - "id": 6, - "type": "ce8f7842-cb03-41b4-9b1e-8d032b2a4e72", - "pos": [ - 4750, - 5950 - ], - "size": [ - 200, - 299.71875 - ], - "flags": {}, - "order": 2, - "mode": 0, - "inputs": [ - { - "name": "model", - "type": "MODEL", - "link": 14 - }, - { - "name": "positive", - "type": "CONDITIONING", - "link": 15 - }, - { - "name": "negative", - "type": "CONDITIONING", - "link": 16 - }, - { - "name": "noise", - "type": "NOISE", - "link": 17 - }, - { - "label": "video_vae", - "name": "vae", - "type": "VAE", - "link": 32 - }, - { - "name": "image", - "type": "IMAGE", - "link": 29 - }, - { - "label": "video_latent", - "name": "samples", - "type": "LATENT", - "link": 837 - }, - { - "name": "audio_latent", - "type": "LATENT", - "link": 794 - }, - { - "name": "multi_input", - "type": "IMAGE", - "link": 1006 - }, - { - "name": "upscale_model", - "type": "LATENT_UPSCALE_MODEL", - "link": 1036 - } - ], - "outputs": [ - { - "name": "video_latent", - "type": "LATENT", - "links": [ - 27 - ] - }, - { - "name": "audio_latent", - "type": "LATENT", - "links": [ - 1079 - ] - }, - { - "name": "positive", - "type": "CONDITIONING", - "links": [ - 855, - 875 - ] - }, - { - "name": "negative", - "type": "CONDITIONING", - "links": [ - 856, - 876 - ] - } - ], - "properties": { - "proxyWidgets": [], - "cnr_id": "comfy-core", - "ver": "0.17.2" - }, - "widgets_values": [], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 8, - "type": "535e663f-da59-47c1-aab5-c78ff202cb16", - "pos": [ - 4970, - 5950 - ], - "size": [ - 218.19140625, - 330.9668884277344 - ], - "flags": {}, - "order": 3, - "mode": 0, - "inputs": [ - { - "name": "model", - "type": "MODEL", - "link": 23 - }, - { - "name": "positive", - "type": "CONDITIONING", - "link": 875 - }, - { - "name": "negative", - "type": "CONDITIONING", - "link": 876 - }, - { - "name": "noise", - "type": "NOISE", - "link": 26 - }, - { - "label": "video_vae", - "name": "vae", - "type": "VAE", - "link": 33 - }, - { - "name": "image", - "type": "IMAGE", - "link": 30 - }, - { - "label": "video_latent", - "name": "samples", - "type": "LATENT", - "link": 27 - }, - { - "name": "audio_latent", - "type": "LATENT", - "link": 1079 - }, - { - "label": "use 2x on stage 3", - "name": "value", - "type": "BOOLEAN", - "widget": { - "name": "value" - }, - "link": 40 - }, - { - "name": "multi_input", - "type": "IMAGE", - "link": 1007 - }, - { - "label": "2x_upscale_model", - "name": "upscale_model", - "type": "LATENT_UPSCALE_MODEL", - "link": 1039 - } - ], - "outputs": [ - { - "label": "video_latent", - "name": "output", - "type": "LATENT", - "links": [ - 1026 - ] - }, - { - "name": "audio_latent", - "type": "LATENT", - "links": [ - 1078 - ] - } - ], - "properties": { - "proxyWidgets": [ - [ - "-1", - "value" - ] - ], - "cnr_id": "comfy-core", - "ver": "0.17.2" - }, - "widgets_values": [ - true - ], - "color": "#222", - "bgcolor": "#000" - } - ], - "groups": [], - "links": [ - { - "id": 27, - "origin_id": 6, - "origin_slot": 0, - "target_id": 8, - "target_slot": 6, - "type": "LATENT" - }, - { - "id": 3, - "origin_id": -10, - "origin_slot": 0, - "target_id": 2, - "target_slot": 0, - "type": "MODEL" - }, - { - "id": 14, - "origin_id": -10, - "origin_slot": 0, - "target_id": 6, - "target_slot": 0, - "type": "MODEL" - }, - { - "id": 23, - "origin_id": -10, - "origin_slot": 0, - "target_id": 8, - "target_slot": 0, - "type": "MODEL" - }, - { - "id": 4, - "origin_id": -10, - "origin_slot": 1, - "target_id": 2, - "target_slot": 1, - "type": "CONDITIONING" - }, - { - "id": 15, - "origin_id": -10, - "origin_slot": 1, - "target_id": 6, - "target_slot": 1, - "type": "CONDITIONING" - }, - { - "id": 5, - "origin_id": -10, - "origin_slot": 2, - "target_id": 2, - "target_slot": 2, - "type": "CONDITIONING" - }, - { - "id": 16, - "origin_id": -10, - "origin_slot": 2, - "target_id": 6, - "target_slot": 2, - "type": "CONDITIONING" - }, - { - "id": 6, - "origin_id": -10, - "origin_slot": 3, - "target_id": 2, - "target_slot": 3, - "type": "LATENT" - }, - { - "id": 7, - "origin_id": -10, - "origin_slot": 4, - "target_id": 2, - "target_slot": 4, - "type": "NOISE" - }, - { - "id": 17, - "origin_id": -10, - "origin_slot": 4, - "target_id": 6, - "target_slot": 3, - "type": "NOISE" - }, - { - "id": 26, - "origin_id": -10, - "origin_slot": 4, - "target_id": 8, - "target_slot": 3, - "type": "NOISE" - }, - { - "id": 32, - "origin_id": -10, - "origin_slot": 5, - "target_id": 6, - "target_slot": 4, - "type": "VAE" - }, - { - "id": 33, - "origin_id": -10, - "origin_slot": 5, - "target_id": 8, - "target_slot": 4, - "type": "VAE" - }, - { - "id": 29, - "origin_id": -10, - "origin_slot": 6, - "target_id": 6, - "target_slot": 5, - "type": "IMAGE" - }, - { - "id": 30, - "origin_id": -10, - "origin_slot": 6, - "target_id": 8, - "target_slot": 5, - "type": "IMAGE" - }, - { - "id": 40, - "origin_id": -10, - "origin_slot": 7, - "target_id": 8, - "target_slot": 8, - "type": "BOOLEAN" - }, - { - "id": 765, - "origin_id": 371, - "origin_slot": 0, - "target_id": -20, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 794, - "origin_id": 2, - "origin_slot": 1, - "target_id": 6, - "target_slot": 7, - "type": "LATENT" - }, - { - "id": 837, - "origin_id": 2, - "origin_slot": 0, - "target_id": 6, - "target_slot": 6, - "type": "LATENT" - }, - { - "id": 852, - "origin_id": 383, - "origin_slot": 2, - "target_id": 371, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 871, - "origin_id": -10, - "origin_slot": 1, - "target_id": 383, - "target_slot": 0, - "type": "CONDITIONING" - }, - { - "id": 872, - "origin_id": -10, - "origin_slot": 2, - "target_id": 383, - "target_slot": 1, - "type": "CONDITIONING" - }, - { - "id": 875, - "origin_id": 6, - "origin_slot": 2, - "target_id": 8, - "target_slot": 1, - "type": "CONDITIONING" - }, - { - "id": 876, - "origin_id": 6, - "origin_slot": 3, - "target_id": 8, - "target_slot": 2, - "type": "CONDITIONING" - }, - { - "id": 1006, - "origin_id": -10, - "origin_slot": 8, - "target_id": 6, - "target_slot": 8, - "type": "IMAGE" - }, - { - "id": 1007, - "origin_id": -10, - "origin_slot": 8, - "target_id": 8, - "target_slot": 9, - "type": "IMAGE" - }, - { - "id": 1026, - "origin_id": 8, - "origin_slot": 0, - "target_id": 383, - "target_slot": 2, - "type": "LATENT" - }, - { - "id": 1036, - "origin_id": -10, - "origin_slot": 9, - "target_id": 6, - "target_slot": 9, - "type": "LATENT_UPSCALE_MODEL" - }, - { - "id": 1039, - "origin_id": -10, - "origin_slot": 9, - "target_id": 8, - "target_slot": 10, - "type": "LATENT_UPSCALE_MODEL" - }, - { - "id": 1078, - "origin_id": 8, - "origin_slot": 1, - "target_id": 371, - "target_slot": 1, - "type": "LATENT" - }, - { - "id": 1079, - "origin_id": 6, - "origin_slot": 1, - "target_id": 8, - "target_slot": 7, - "type": "LATENT" - } - ], - "extra": { - "workflowRendererVersion": "LG" - } - } - ] - }, - "config": {}, - "extra": { - "frontendVersion": "1.39.19", - "workflowRendererVersion": "LG", - "VHS_latentpreview": false, - "VHS_latentpreviewrate": 0, - "VHS_MetadataImage": true, - "VHS_KeepIntermediate": true, - "ds": { - "scale": 1, - "offset": [ - -2780.1305165727445, - -5466.757245059522 - ] - } - }, - "version": 0.4 +{ + "id": "a41dc75e-fc80-4307-897e-f3118e4fa21b", + "revision": 0, + "last_node_id": 443, + "last_link_id": 1079, + "nodes": [ + { + "id": 4, + "type": "LTXVConcatAVLatent", + "pos": [ + 4180, + 6090 + ], + "size": [ + 210, + 60 + ], + "flags": {}, + "order": 9, + "mode": 0, + "inputs": [ + { + "name": "video_latent", + "type": "LATENT", + "link": 802 + }, + { + "name": "audio_latent", + "type": "LATENT", + "link": 12 + } + ], + "outputs": [ + { + "name": "latent", + "type": "LATENT", + "links": [ + 804 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.7.0", + "Node name for S&R": "LTXVConcatAVLatent", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 5, + "type": "SaveVideo", + "pos": [ + 4940, + 5950 + ], + "size": [ + 660, + 440 + ], + "flags": {}, + "order": 12, + "mode": 0, + "inputs": [ + { + "name": "video", + "type": "VIDEO", + "link": 13 + } + ], + "outputs": [], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.5.1", + "Node name for S&R": "SaveVideo", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [ + "video/LTX", + "auto", + "auto" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 14, + "type": "Note", + "pos": [ + 4410, + 5780 + ], + "size": [ + 210, + 110 + ], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [], + "outputs": [], + "title": "3 Stage Upscale Note", + "properties": {}, + "widgets_values": [ + "Stage 3 will default to 1.5x on the 3rd stage if false.\n\nIf you want to disable Stage 3, simply bypass the 3rd stage in the subraph below." + ], + "color": "#223", + "bgcolor": "#335" + }, + { + "id": 418, + "type": "Note", + "pos": [ + 3080, + 5820 + ], + "size": [ + 280, + 88 + ], + "flags": {}, + "order": 1, + "mode": 0, + "inputs": [], + "outputs": [], + "title": "Model Loader Note", + "properties": {}, + "widgets_values": [ + "↓ Select your own models in this subgraph ↓" + ], + "color": "#223", + "bgcolor": "#335" + }, + { + "id": 16, + "type": "Note", + "pos": [ + 4670, + 6110 + ], + "size": [ + 210, + 88 + ], + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [], + "outputs": [], + "title": "Decode Note", + "properties": {}, + "widgets_values": [ + "Enable optimized decoding to use tiled vae decoder." + ], + "color": "#223", + "bgcolor": "#335" + }, + { + "id": 439, + "type": "MarkdownNote", + "pos": [ + 2680, + 6180 + ], + "size": [ + 370, + 320 + ], + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [], + "outputs": [], + "title": "FAQ", + "properties": {}, + "widgets_values": [ + "# FAQ\n\n**How do I set the length of the video?**\n\nYou can set the length of the video in the Model Loader Subgraph →\n\n**I keep getting errors when running this workflow!**\n\nDouble check that all of the correct models loaded. This is using the newest version of 2x upscaler (v1.1). You can change it to the older one under the Model Loader.\n\n**Where do I download these models??**\n\nThere is a good list of models and download links here: \n\nAlso the latest v1.1 spatial upscaler can be found here: \n\nThe tiny vae (taeltx2_3.safetensors) is found here:\n\n\n\n\n\n\n\n\n\n" + ], + "color": "#432", + "bgcolor": "#653" + }, + { + "id": 1, + "type": "34d63160-e149-4935-ab1f-892ffe617fec", + "pos": [ + 4670, + 5950 + ], + "size": [ + 244.98828125, + 114 + ], + "flags": {}, + "order": 11, + "mode": 0, + "inputs": [ + { + "name": "av_latent", + "type": "LATENT", + "link": 47 + }, + { + "name": "audio_vae", + "type": "VAE", + "link": 2 + }, + { + "label": "video_vae", + "name": "vae", + "type": "VAE", + "link": 31 + }, + { + "label": "Use Optimized Decoding", + "name": "value", + "type": "BOOLEAN", + "widget": { + "name": "value" + }, + "link": null + } + ], + "outputs": [ + { + "name": "VIDEO", + "type": "VIDEO", + "links": [ + 13 + ] + } + ], + "properties": { + "proxyWidgets": [ + [ + "-1", + "value" + ] + ], + "cnr_id": "comfy-core", + "ver": "0.17.2" + }, + "widgets_values": [ + false + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 10, + "type": "958b2c7a-1f75-4a30-84a8-892bddb9e426", + "pos": [ + 3080, + 5950 + ], + "size": [ + 440, + 550 + ], + "flags": {}, + "order": 4, + "mode": 0, + "inputs": [ + { + "label": "Prompt", + "name": "value", + "type": "STRING", + "widget": { + "name": "value" + }, + "link": null + }, + { + "label": "length (seconds)", + "name": "value_1", + "type": "INT", + "widget": { + "name": "value_1" + }, + "link": null + } + ], + "outputs": [ + { + "name": "optimized_model", + "type": "MODEL", + "links": [ + 40 + ] + }, + { + "label": "video_vae", + "name": "VAE", + "type": "VAE", + "links": [ + 31, + 45, + 798 + ] + }, + { + "label": "audio_vae", + "name": "VAE_1", + "type": "VAE", + "links": [ + 2, + 22 + ] + }, + { + "name": "positive", + "type": "CONDITIONING", + "links": [ + 796 + ] + }, + { + "name": "negative", + "type": "CONDITIONING", + "links": [ + 797 + ] + }, + { + "label": "length_seconds", + "name": "INT", + "type": "INT", + "links": [ + 21 + ] + }, + { + "label": "noise", + "name": "NOISE", + "type": "NOISE", + "links": [ + 44 + ] + }, + { + "label": "2x_spatial_upscale_model", + "name": "LATENT_UPSCALE_MODEL", + "type": "LATENT_UPSCALE_MODEL", + "links": [ + 1037 + ] + } + ], + "properties": { + "proxyWidgets": [ + [ + "-1", + "value" + ], + [ + "-1", + "value_1" + ], + [ + "364", + "noise_seed" + ], + [ + "364", + "control_after_generate" + ] + ], + "cnr_id": "comfy-core", + "ver": "0.17.2" + }, + "widgets_values": [ + "", + 5 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 381, + "type": "MultiImageLoader", + "pos": [ + 3550, + 5950 + ], + "size": [ + 350, + 297 + ], + "flags": {}, + "order": 5, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "multi_output", + "type": "IMAGE", + "links": [ + 1046, + 1047, + 1048 + ] + } + ], + "properties": { + "cnr_id": "WhatDreamsCost-ComfyUI", + "ver": "ecd00e6b404f14b07c7b54b0b0033e8bccb32d24", + "Node name for S&R": "MultiImageLoader" + }, + "widgets_values": [ + "", + 0, + 0, + "lanczos", + "keep proportion", + 0, + 18, + "" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 11, + "type": "360d64f7-96cd-42a6-9b13-6eb0387e17d9", + "pos": [ + 4410, + 5950 + ], + "size": [ + 246.46875, + 322.233642578125 + ], + "flags": {}, + "order": 10, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 40 + }, + { + "name": "positive", + "type": "CONDITIONING", + "link": 799 + }, + { + "name": "negative", + "type": "CONDITIONING", + "link": 800 + }, + { + "name": "latent_image", + "type": "LATENT", + "link": 804 + }, + { + "name": "noise", + "type": "NOISE", + "link": 44 + }, + { + "label": "video_vae", + "name": "vae", + "type": "VAE", + "link": 45 + }, + { + "name": "image", + "type": "IMAGE", + "link": null + }, + { + "label": "Upscale 2x on Stage 3", + "name": "value", + "type": "BOOLEAN", + "widget": { + "name": "value" + }, + "link": null + }, + { + "name": "multi_input", + "type": "IMAGE", + "link": 1046 + }, + { + "label": "2x_spatial_upscale_model", + "name": "upscale_model", + "type": "LATENT_UPSCALE_MODEL", + "link": 1037 + } + ], + "outputs": [ + { + "label": "av_output", + "name": "output", + "type": "LATENT", + "links": [ + 47 + ] + } + ], + "properties": { + "proxyWidgets": [ + [ + "-1", + "value" + ] + ], + "cnr_id": "comfy-core", + "ver": "0.17.2" + }, + "widgets_values": [ + true + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 7, + "type": "2979f3eb-1c4c-45ff-8387-f1f4addb72f4", + "pos": [ + 4180, + 5950 + ], + "size": [ + 210, + 100 + ], + "flags": {}, + "order": 7, + "mode": 0, + "inputs": [ + { + "name": "image", + "type": "IMAGE", + "link": 1048 + }, + { + "label": "length_seconds", + "name": "length", + "type": "INT", + "widget": { + "name": "length" + }, + "link": 21 + }, + { + "name": "audio_vae", + "type": "VAE", + "link": 22 + } + ], + "outputs": [ + { + "name": "LATENT", + "type": "LATENT", + "links": [ + 803 + ] + }, + { + "name": "Latent", + "type": "LATENT", + "links": [ + 12 + ] + } + ], + "properties": { + "proxyWidgets": [ + [ + "-1", + "length" + ] + ], + "cnr_id": "comfy-core", + "ver": "0.17.2" + }, + "widgets_values": [ + 1 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 376, + "type": "LTXSequencer", + "pos": [ + 3920, + 5950 + ], + "size": [ + 240, + 200 + ], + "flags": {}, + "order": 8, + "mode": 0, + "inputs": [ + { + "name": "positive", + "type": "CONDITIONING", + "link": 796 + }, + { + "name": "negative", + "type": "CONDITIONING", + "link": 797 + }, + { + "name": "vae", + "type": "VAE", + "link": 798 + }, + { + "name": "latent", + "type": "LATENT", + "link": 803 + }, + { + "name": "multi_input", + "type": "IMAGE", + "link": 1047 + } + ], + "outputs": [ + { + "name": "positive", + "type": "CONDITIONING", + "links": [ + 799 + ] + }, + { + "name": "negative", + "type": "CONDITIONING", + "links": [ + 800 + ] + }, + { + "name": "latent", + "type": "LATENT", + "links": [ + 802 + ] + } + ], + "properties": { + "cnr_id": "WhatDreamsCost-ComfyUI", + "ver": "ecd00e6b404f14b07c7b54b0b0033e8bccb32d24", + "Node name for S&R": "LTXSequencer", + "num_images": 0, + "insert_frame_1": 0, + "strength_1": 1, + "insert_frame_2": 0, + "strength_2": 1, + "insert_frame_3": 0, + "strength_3": 1, + "insert_frame_4": 0, + "strength_4": 1, + "insert_frame_5": 0, + "strength_5": 1, + "insert_frame_6": 0, + "strength_6": 1, + "insert_frame_7": 0, + "strength_7": 1, + "insert_frame_8": 0, + "strength_8": 1, + "insert_frame_9": 0, + "strength_9": 0, + "insert_frame_10": 1, + "strength_10": 1, + "insert_frame_11": 0, + "strength_11": 0, + "insert_frame_12": 1, + "strength_12": 1, + "insert_frame_13": 0, + "strength_13": 0, + "insert_frame_14": 1, + "strength_14": 1, + "insert_frame_15": 0, + "strength_15": 0, + "insert_frame_16": 1, + "strength_16": 1, + "insert_frame_17": 0, + "strength_17": 0, + "insert_frame_18": 1, + "strength_18": 1, + "insert_frame_19": 0, + "strength_19": 0, + "insert_frame_20": 1, + "strength_20": 1, + "insert_frame_21": 0, + "strength_21": 0, + "insert_frame_22": 1, + "strength_22": 1, + "insert_frame_23": 0, + "strength_23": 0, + "insert_frame_24": 1, + "strength_24": 1, + "insert_frame_25": 0, + "strength_25": 0, + "insert_frame_26": 1, + "strength_26": 1, + "insert_frame_27": 0, + "strength_27": 0, + "insert_frame_28": 1, + "strength_28": 1, + "insert_frame_29": 0, + "strength_29": 0, + "insert_frame_30": 1, + "strength_30": 1, + "insert_frame_31": 0, + "strength_31": 0, + "insert_frame_32": 1, + "strength_32": 1, + "insert_frame_33": 0, + "strength_33": 1, + "insert_frame_34": 0, + "strength_34": 1, + "insert_frame_35": 0, + "strength_35": 1, + "insert_frame_36": 0, + "strength_36": 1, + "insert_frame_37": 0, + "strength_37": 1, + "insert_frame_38": 0, + "strength_38": 1, + "insert_frame_39": 0, + "strength_39": 1, + "insert_frame_40": 0, + "strength_40": 1, + "insert_frame_41": 0, + "strength_41": 1, + "insert_frame_42": 0, + "strength_42": 1, + "insert_frame_43": 0, + "strength_43": 1, + "insert_frame_44": 0, + "strength_44": 1, + "insert_frame_45": 0, + "strength_45": 1, + "insert_frame_46": 0, + "strength_46": 1, + "insert_frame_47": 0, + "strength_47": 1, + "insert_frame_48": 0, + "strength_48": 1, + "insert_frame_49": 0, + "strength_49": 1, + "insert_frame_50": 0, + "strength_50": 1, + "insert_mode": "frames", + "frame_rate": 24, + "insert_second_1": 0, + "insert_second_2": 0, + "insert_second_3": 0, + "insert_second_4": 0, + "insert_second_5": 0, + "insert_second_6": 0, + "insert_second_7": 0, + "insert_second_8": 0, + "insert_second_9": 1, + "insert_second_10": 0, + "insert_second_11": 1, + "insert_second_12": 0, + "insert_second_13": 1, + "insert_second_14": 0, + "insert_second_15": 1, + "insert_second_16": 0, + "insert_second_17": 1, + "insert_second_18": 0, + "insert_second_19": 1, + "insert_second_20": 0, + "insert_second_21": 1, + "insert_second_22": 0, + "insert_second_23": 1, + "insert_second_24": 0, + "insert_second_25": 1, + "insert_second_26": 0, + "insert_second_27": 1, + "insert_second_28": 0, + "insert_second_29": 1, + "insert_second_30": 0, + "insert_second_31": 1, + "insert_second_32": 0, + "insert_second_33": 1, + "insert_second_34": 0, + "insert_second_35": 0, + "insert_second_36": 0, + "insert_second_37": 0, + "insert_second_38": 0, + "insert_second_39": 0, + "insert_second_40": 0, + "insert_second_41": 0, + "insert_second_42": 0, + "insert_second_43": 0, + "insert_second_44": 0, + "insert_second_45": 0, + "insert_second_46": 0, + "insert_second_47": 0, + "insert_second_48": 0, + "insert_second_49": 0, + "insert_second_50": 0, + "insert_images_using": "frames", + "video_length": 1, + "insert_percentage_1": 0, + "insert_percentage_2": 1, + "insert_percentage_3": 0, + "insert_percentage_4": 0, + "insert_percentage_5": 0, + "insert_percentage_6": 0, + "insert_percentage_7": 0, + "insert_percentage_8": 0, + "insert_percentage_9": 0, + "insert_percentage_10": 0, + "insert_percentage_11": 0, + "insert_percentage_12": 0, + "insert_percentage_13": 0, + "insert_percentage_14": 0, + "insert_percentage_15": 0, + "insert_percentage_16": 0, + "insert_percentage_17": 0, + "insert_percentage_18": 0, + "insert_percentage_19": 0, + "insert_percentage_20": 0, + "insert_percentage_21": 0, + "insert_percentage_22": 0, + "insert_percentage_23": 0, + "insert_percentage_24": 0, + "insert_percentage_25": 0, + "insert_percentage_26": 0, + "insert_percentage_27": 0, + "insert_percentage_28": 0, + "insert_percentage_29": 0, + "insert_percentage_30": 0, + "insert_percentage_31": 0, + "insert_percentage_32": 0, + "insert_percentage_33": 0, + "insert_percentage_34": 0, + "insert_percentage_35": 0, + "insert_percentage_36": 0, + "insert_percentage_37": 0, + "insert_percentage_38": 0, + "insert_percentage_39": 0, + "insert_percentage_40": 0, + "insert_percentage_41": 0, + "insert_percentage_42": 0, + "insert_percentage_43": 0, + "insert_percentage_44": 0, + "insert_percentage_45": 0, + "insert_percentage_46": 0, + "insert_percentage_47": 0, + "insert_percentage_48": 0, + "insert_percentage_49": 0, + "insert_percentage_50": 0, + "insert_percent_1": 0, + "insert_percent_2": 50, + "insert_percent_3": 100, + "insert_percent_4": 0, + "insert_percent_5": 0, + "insert_percent_6": 0, + "insert_percent_7": 0, + "insert_percent_8": 0, + "insert_percent_9": 0, + "insert_percent_10": 0, + "insert_percent_11": 0, + "insert_percent_12": 0, + "insert_percent_13": 0, + "insert_percent_14": 0, + "insert_percent_15": 0, + "insert_percent_16": 0, + "insert_percent_17": 0, + "insert_percent_18": 0, + "insert_percent_19": 0, + "insert_percent_20": 0, + "insert_percent_21": 0, + "insert_percent_22": 0, + "insert_percent_23": 0, + "insert_percent_24": 0, + "insert_percent_25": 0, + "insert_percent_26": 0, + "insert_percent_27": 0, + "insert_percent_28": 0, + "insert_percent_29": 0, + "insert_percent_30": 0, + "insert_percent_31": 0, + "insert_percent_32": 0, + "insert_percent_33": 0, + "insert_percent_34": 0, + "insert_percent_35": 0, + "insert_percent_36": 0, + "insert_percent_37": 0, + "insert_percent_38": 0, + "insert_percent_39": 0, + "insert_percent_40": 0, + "insert_percent_41": 0, + "insert_percent_42": 0, + "insert_percent_43": 0, + "insert_percent_44": 0, + "insert_percent_45": 0, + "insert_percent_46": 0, + "insert_percent_47": 0, + "insert_percent_48": 0, + "insert_percent_49": 0, + "insert_percent_50": 0 + }, + "widgets_values": [ + 0, + "frames", + 24, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 15, + "type": "Note", + "pos": [ + 3920, + 5740 + ], + "size": [ + 250, + 150 + ], + "flags": {}, + "order": 6, + "mode": 0, + "inputs": [], + "outputs": [], + "title": "LTX Sequencer Note", + "properties": {}, + "widgets_values": [ + "Use one image at full strength for regular image to video.\n\nIf frame_insert is -1, it will be inserted at the last frame.\n\nThe insert_mode allows to switch between inserting images by setting the exact frame or by setting the exact second." + ], + "color": "#223", + "bgcolor": "#335" + } + ], + "links": [ + [ + 2, + 10, + 2, + 1, + 1, + "VAE" + ], + [ + 12, + 7, + 1, + 4, + 1, + "LATENT" + ], + [ + 13, + 1, + 0, + 5, + 0, + "VIDEO" + ], + [ + 21, + 10, + 5, + 7, + 1, + "INT" + ], + [ + 22, + 10, + 2, + 7, + 2, + "VAE" + ], + [ + 31, + 10, + 1, + 1, + 2, + "VAE" + ], + [ + 40, + 10, + 0, + 11, + 0, + "MODEL" + ], + [ + 44, + 10, + 6, + 11, + 4, + "NOISE" + ], + [ + 45, + 10, + 1, + 11, + 5, + "VAE" + ], + [ + 47, + 11, + 0, + 1, + 0, + "LATENT" + ], + [ + 796, + 10, + 3, + 376, + 0, + "CONDITIONING" + ], + [ + 797, + 10, + 4, + 376, + 1, + "CONDITIONING" + ], + [ + 798, + 10, + 1, + 376, + 2, + "VAE" + ], + [ + 799, + 376, + 0, + 11, + 1, + "CONDITIONING" + ], + [ + 800, + 376, + 1, + 11, + 2, + "CONDITIONING" + ], + [ + 802, + 376, + 2, + 4, + 0, + "LATENT" + ], + [ + 803, + 7, + 0, + 376, + 3, + "LATENT" + ], + [ + 804, + 4, + 0, + 11, + 3, + "LATENT" + ], + [ + 1037, + 10, + 7, + 11, + 9, + "LATENT_UPSCALE_MODEL" + ], + [ + 1046, + 381, + 0, + 11, + 8, + "IMAGE" + ], + [ + 1047, + 381, + 0, + 376, + 4, + "IMAGE" + ], + [ + 1048, + 381, + 0, + 7, + 0, + "IMAGE" + ] + ], + "groups": [], + "definitions": { + "subgraphs": [ + { + "id": "34d63160-e149-4935-ab1f-892ffe617fec", + "version": 1, + "state": { + "lastGroupId": 33, + "lastNodeId": 443, + "lastLinkId": 1079, + "lastRerouteId": 0 + }, + "revision": 0, + "config": {}, + "name": "Decode", + "inputNode": { + "id": -10, + "bounding": [ + 1610, + 7190, + 180.98828125, + 120 + ] + }, + "outputNode": { + "id": -20, + "bounding": [ + 2970, + 7320, + 120, + 60 + ] + }, + "inputs": [ + { + "id": "1d567d10-9078-49fc-a4a1-67e6d4b5744f", + "name": "av_latent", + "type": "LATENT", + "linkIds": [ + 692 + ], + "pos": [ + 1770.98828125, + 7210 + ] + }, + { + "id": "bef9341f-4429-46af-9bca-11e025bec4d0", + "name": "audio_vae", + "type": "VAE", + "linkIds": [ + 693 + ], + "pos": [ + 1770.98828125, + 7230 + ] + }, + { + "id": "4588ee3d-96a4-46a8-b829-a78181f6c305", + "name": "vae", + "type": "VAE", + "linkIds": [ + 703, + 1060 + ], + "label": "video_vae", + "pos": [ + 1770.98828125, + 7250 + ] + }, + { + "id": "1bd3842b-729d-4c3d-9927-cd508bccb1b1", + "name": "value", + "type": "BOOLEAN", + "linkIds": [ + 708 + ], + "label": "Use Optimized Decoding", + "pos": [ + 1770.98828125, + 7270 + ] + } + ], + "outputs": [ + { + "id": "7e889c1c-e185-47d7-a0f7-c5f1d971c6fe", + "name": "VIDEO", + "type": "VIDEO", + "linkIds": [ + 704 + ], + "pos": [ + 2990, + 7340 + ] + } + ], + "widgets": [], + "nodes": [ + { + "id": 337, + "type": "LazySwitchKJ", + "pos": [ + 2450, + 7340 + ], + "size": [ + 270, + 78 + ], + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [ + { + "localized_name": "on_false", + "name": "on_false", + "type": "*", + "link": 701 + }, + { + "localized_name": "on_true", + "name": "on_true", + "type": "*", + "link": 1058 + }, + { + "localized_name": "switch", + "name": "switch", + "type": "BOOLEAN", + "widget": { + "name": "switch" + }, + "link": 707 + } + ], + "outputs": [ + { + "localized_name": "*", + "name": "*", + "type": "*", + "links": [ + 698 + ] + } + ], + "properties": { + "cnr_id": "comfyui-kjnodes", + "ver": "1.3.4", + "Node name for S&R": "LazySwitchKJ" + }, + "widgets_values": [ + false + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 336, + "type": "CreateVideo", + "pos": [ + 2740, + 7340 + ], + "size": [ + 210, + 78 + ], + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [ + { + "localized_name": "images", + "name": "images", + "type": "IMAGE", + "link": 698 + }, + { + "localized_name": "audio", + "name": "audio", + "shape": 7, + "type": "AUDIO", + "link": 697 + } + ], + "outputs": [ + { + "localized_name": "VIDEO", + "name": "VIDEO", + "type": "VIDEO", + "links": [ + 704 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.5.1", + "Node name for S&R": "CreateVideo", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [ + 24 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 341, + "type": "PrimitiveBoolean", + "pos": [ + 2450, + 7230 + ], + "size": [ + 230, + 58 + ], + "flags": {}, + "order": 5, + "mode": 0, + "inputs": [ + { + "localized_name": "value", + "name": "value", + "type": "BOOLEAN", + "widget": { + "name": "value" + }, + "link": 708 + } + ], + "outputs": [ + { + "localized_name": "BOOLEAN", + "name": "BOOLEAN", + "type": "BOOLEAN", + "links": [ + 707 + ] + } + ], + "title": "Use Optimized Decoding", + "properties": { + "cnr_id": "comfy-core", + "ver": "0.17.2", + "Node name for S&R": "PrimitiveBoolean" + }, + "widgets_values": [ + false + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 334, + "type": "LTXVSeparateAVLatent", + "pos": [ + 1820, + 7190 + ], + "size": [ + 240, + 46 + ], + "flags": {}, + "order": 1, + "mode": 0, + "inputs": [ + { + "localized_name": "av_latent", + "name": "av_latent", + "type": "LATENT", + "link": 692 + } + ], + "outputs": [ + { + "localized_name": "video_latent", + "name": "video_latent", + "type": "LATENT", + "links": [ + 702, + 1059 + ] + }, + { + "localized_name": "audio_latent", + "name": "audio_latent", + "type": "LATENT", + "links": [ + 687 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.5.1", + "Node name for S&R": "LTXVSeparateAVLatent", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 330, + "type": "LTXVAudioVAEDecode", + "pos": [ + 1820, + 7280 + ], + "size": [ + 240, + 46 + ], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [ + { + "localized_name": "samples", + "name": "samples", + "type": "LATENT", + "link": 687 + }, + { + "label": "Audio VAE", + "localized_name": "audio_vae", + "name": "audio_vae", + "type": "VAE", + "link": 693 + } + ], + "outputs": [ + { + "localized_name": "Audio", + "name": "Audio", + "type": "AUDIO", + "links": [ + 697 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.7.0", + "Node name for S&R": "LTXVAudioVAEDecode", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 340, + "type": "VAEDecode", + "pos": [ + 2110, + 7190 + ], + "size": [ + 150, + 50 + ], + "flags": {}, + "order": 4, + "mode": 0, + "inputs": [ + { + "localized_name": "samples", + "name": "samples", + "type": "LATENT", + "link": 702 + }, + { + "localized_name": "vae", + "name": "vae", + "type": "VAE", + "link": 703 + } + ], + "outputs": [ + { + "localized_name": "IMAGE", + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 701 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.17.2", + "Node name for S&R": "VAEDecode" + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 432, + "type": "VAEDecodeTiled", + "pos": [ + 2110, + 7320 + ], + "size": [ + 270, + 150 + ], + "flags": {}, + "order": 6, + "mode": 0, + "inputs": [ + { + "localized_name": "samples", + "name": "samples", + "type": "LATENT", + "link": 1059 + }, + { + "localized_name": "vae", + "name": "vae", + "type": "VAE", + "link": 1060 + } + ], + "outputs": [ + { + "localized_name": "IMAGE", + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 1058 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.17.2", + "Node name for S&R": "VAEDecodeTiled" + }, + "widgets_values": [ + 768, + 128, + 128, + 8 + ], + "color": "#222", + "bgcolor": "#000" + } + ], + "groups": [], + "links": [ + { + "id": 687, + "origin_id": 334, + "origin_slot": 1, + "target_id": 330, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 692, + "origin_id": -10, + "origin_slot": 0, + "target_id": 334, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 693, + "origin_id": -10, + "origin_slot": 1, + "target_id": 330, + "target_slot": 1, + "type": "VAE" + }, + { + "id": 697, + "origin_id": 330, + "origin_slot": 0, + "target_id": 336, + "target_slot": 1, + "type": "AUDIO" + }, + { + "id": 698, + "origin_id": 337, + "origin_slot": 0, + "target_id": 336, + "target_slot": 0, + "type": "IMAGE" + }, + { + "id": 701, + "origin_id": 340, + "origin_slot": 0, + "target_id": 337, + "target_slot": 0, + "type": "IMAGE" + }, + { + "id": 702, + "origin_id": 334, + "origin_slot": 0, + "target_id": 340, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 703, + "origin_id": -10, + "origin_slot": 2, + "target_id": 340, + "target_slot": 1, + "type": "VAE" + }, + { + "id": 704, + "origin_id": 336, + "origin_slot": 0, + "target_id": -20, + "target_slot": 0, + "type": "VIDEO" + }, + { + "id": 707, + "origin_id": 341, + "origin_slot": 0, + "target_id": 337, + "target_slot": 2, + "type": "BOOLEAN" + }, + { + "id": 708, + "origin_id": -10, + "origin_slot": 3, + "target_id": 341, + "target_slot": 0, + "type": "BOOLEAN" + }, + { + "id": 1058, + "origin_id": 432, + "origin_slot": 0, + "target_id": 337, + "target_slot": 1, + "type": "IMAGE" + }, + { + "id": 1059, + "origin_id": 334, + "origin_slot": 0, + "target_id": 432, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 1060, + "origin_id": -10, + "origin_slot": 2, + "target_id": 432, + "target_slot": 1, + "type": "VAE" + } + ], + "extra": { + "workflowRendererVersion": "LG" + } + }, + { + "id": "afa29944-eb58-40a5-8e76-484f2503fa0c", + "version": 1, + "state": { + "lastGroupId": 33, + "lastNodeId": 443, + "lastLinkId": 1079, + "lastRerouteId": 0 + }, + "revision": 0, + "config": {}, + "name": "Stage #1", + "inputNode": { + "id": -10, + "bounding": [ + 280, + 7113, + 120, + 140 + ] + }, + "outputNode": { + "id": -20, + "bounding": [ + 1150, + 6970, + 120, + 80 + ] + }, + "inputs": [ + { + "id": "e760d60a-9b49-4b40-8f39-3c81f29c08d8", + "name": "model", + "type": "MODEL", + "linkIds": [ + 683, + 686 + ], + "pos": [ + 380, + 7133 + ] + }, + { + "id": "6ab6641e-8318-4330-8b01-43ce43648dcf", + "name": "positive", + "type": "CONDITIONING", + "linkIds": [ + 786 + ], + "pos": [ + 380, + 7153 + ] + }, + { + "id": "e1e7e2f6-49fb-4754-92ab-b71442b03da7", + "name": "negative", + "type": "CONDITIONING", + "linkIds": [ + 787 + ], + "pos": [ + 380, + 7173 + ] + }, + { + "id": "5c9d4c86-ec46-4099-b7e1-41a9288a00d0", + "name": "latent_image", + "type": "LATENT", + "linkIds": [ + 687 + ], + "pos": [ + 380, + 7193 + ] + }, + { + "id": "0a89214b-81f0-4fa9-86fa-5f77079ddb7b", + "name": "noise", + "type": "NOISE", + "linkIds": [ + 688 + ], + "pos": [ + 380, + 7213 + ] + } + ], + "outputs": [ + { + "id": "b6f80403-e98c-41ad-a692-270973783381", + "name": "video_latent", + "type": "LATENT", + "linkIds": [ + 689 + ], + "pos": [ + 1170, + 6990 + ] + }, + { + "id": "f54a00fd-f0db-4e4e-9205-9c9d3e955032", + "name": "audio_latent", + "type": "LATENT", + "linkIds": [ + 690 + ], + "pos": [ + 1170, + 7010 + ] + } + ], + "widgets": [], + "nodes": [ + { + "id": 325, + "type": "KSamplerSelect", + "pos": [ + 460, + 7120 + ], + "size": [ + 210, + 60 + ], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [], + "outputs": [ + { + "localized_name": "SAMPLER", + "name": "SAMPLER", + "type": "SAMPLER", + "links": [ + 680 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.56", + "Node name for S&R": "KSamplerSelect", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [ + "euler" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 326, + "type": "BasicScheduler", + "pos": [ + 460, + 7220 + ], + "size": [ + 210, + 106 + ], + "flags": {}, + "order": 4, + "mode": 0, + "inputs": [ + { + "localized_name": "model", + "name": "model", + "type": "MODEL", + "link": 686 + } + ], + "outputs": [ + { + "localized_name": "SIGMAS", + "name": "SIGMAS", + "type": "SIGMAS", + "links": [ + 681 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.16.4", + "Node name for S&R": "BasicScheduler" + }, + "widgets_values": [ + "linear_quadratic", + 8, + 1 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 322, + "type": "SamplerCustomAdvanced", + "pos": [ + 690, + 6980 + ], + "size": [ + 212.3638671875, + 204.64497833251954 + ], + "flags": {}, + "order": 1, + "mode": 0, + "inputs": [ + { + "localized_name": "noise", + "name": "noise", + "type": "NOISE", + "link": 688 + }, + { + "localized_name": "guider", + "name": "guider", + "type": "GUIDER", + "link": 679 + }, + { + "localized_name": "sampler", + "name": "sampler", + "type": "SAMPLER", + "link": 680 + }, + { + "localized_name": "sigmas", + "name": "sigmas", + "type": "SIGMAS", + "link": 681 + }, + { + "localized_name": "latent_image", + "name": "latent_image", + "type": "LATENT", + "link": 687 + } + ], + "outputs": [ + { + "localized_name": "output", + "name": "output", + "type": "LATENT", + "links": [ + 682 + ] + }, + { + "localized_name": "denoised_output", + "name": "denoised_output", + "type": "LATENT", + "links": [] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.60", + "Node name for S&R": "SamplerCustomAdvanced", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 323, + "type": "LTXVSeparateAVLatent", + "pos": [ + 920, + 6980 + ], + "size": [ + 193.2916015625, + 46 + ], + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [ + { + "localized_name": "av_latent", + "name": "av_latent", + "type": "LATENT", + "link": 682 + } + ], + "outputs": [ + { + "localized_name": "video_latent", + "name": "video_latent", + "type": "LATENT", + "links": [ + 689 + ] + }, + { + "localized_name": "audio_latent", + "name": "audio_latent", + "type": "LATENT", + "links": [ + 690 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.5.1", + "Node name for S&R": "LTXVSeparateAVLatent", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 324, + "type": "CFGGuider", + "pos": [ + 460, + 6990 + ], + "size": [ + 210, + 98 + ], + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [ + { + "localized_name": "model", + "name": "model", + "type": "MODEL", + "link": 683 + }, + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "link": 786 + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "link": 787 + } + ], + "outputs": [ + { + "localized_name": "GUIDER", + "name": "GUIDER", + "type": "GUIDER", + "links": [ + 679 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.64", + "Node name for S&R": "CFGGuider", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [ + 1 + ], + "color": "#222", + "bgcolor": "#000" + } + ], + "groups": [], + "links": [ + { + "id": 679, + "origin_id": 324, + "origin_slot": 0, + "target_id": 322, + "target_slot": 1, + "type": "GUIDER" + }, + { + "id": 680, + "origin_id": 325, + "origin_slot": 0, + "target_id": 322, + "target_slot": 2, + "type": "SAMPLER" + }, + { + "id": 681, + "origin_id": 326, + "origin_slot": 0, + "target_id": 322, + "target_slot": 3, + "type": "SIGMAS" + }, + { + "id": 682, + "origin_id": 322, + "origin_slot": 0, + "target_id": 323, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 683, + "origin_id": -10, + "origin_slot": 0, + "target_id": 324, + "target_slot": 0, + "type": "MODEL" + }, + { + "id": 686, + "origin_id": -10, + "origin_slot": 0, + "target_id": 326, + "target_slot": 0, + "type": "MODEL" + }, + { + "id": 687, + "origin_id": -10, + "origin_slot": 3, + "target_id": 322, + "target_slot": 4, + "type": "LATENT" + }, + { + "id": 688, + "origin_id": -10, + "origin_slot": 4, + "target_id": 322, + "target_slot": 0, + "type": "NOISE" + }, + { + "id": 689, + "origin_id": 323, + "origin_slot": 0, + "target_id": -20, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 690, + "origin_id": 323, + "origin_slot": 1, + "target_id": -20, + "target_slot": 1, + "type": "LATENT" + }, + { + "id": 786, + "origin_id": -10, + "origin_slot": 1, + "target_id": 324, + "target_slot": 1, + "type": "CONDITIONING" + }, + { + "id": 787, + "origin_id": -10, + "origin_slot": 2, + "target_id": 324, + "target_slot": 2, + "type": "CONDITIONING" + } + ], + "extra": { + "workflowRendererVersion": "LG" + } + }, + { + "id": "ce8f7842-cb03-41b4-9b1e-8d032b2a4e72", + "version": 1, + "state": { + "lastGroupId": 33, + "lastNodeId": 443, + "lastLinkId": 1079, + "lastRerouteId": 0 + }, + "revision": 0, + "config": {}, + "name": "Stage #2", + "inputNode": { + "id": -10, + "bounding": [ + 280, + 7113, + 125.224609375, + 240 + ] + }, + "outputNode": { + "id": -20, + "bounding": [ + 1820, + 7130, + 120, + 120 + ] + }, + "inputs": [ + { + "id": "e760d60a-9b49-4b40-8f39-3c81f29c08d8", + "name": "model", + "type": "MODEL", + "linkIds": [ + 683, + 686 + ], + "pos": [ + 385.224609375, + 7133 + ] + }, + { + "id": "6ab6641e-8318-4330-8b01-43ce43648dcf", + "name": "positive", + "type": "CONDITIONING", + "linkIds": [ + 703 + ], + "pos": [ + 385.224609375, + 7153 + ] + }, + { + "id": "e1e7e2f6-49fb-4754-92ab-b71442b03da7", + "name": "negative", + "type": "CONDITIONING", + "linkIds": [ + 704 + ], + "pos": [ + 385.224609375, + 7173 + ] + }, + { + "id": "0a89214b-81f0-4fa9-86fa-5f77079ddb7b", + "name": "noise", + "type": "NOISE", + "linkIds": [ + 688 + ], + "pos": [ + 385.224609375, + 7193 + ] + }, + { + "id": "d8824343-6b21-42aa-9eaf-65d1b267662d", + "name": "vae", + "type": "VAE", + "linkIds": [ + 706, + 990 + ], + "label": "video_vae", + "pos": [ + 385.224609375, + 7213 + ] + }, + { + "id": "6ea10719-25f1-449a-9d2d-bb395d89609d", + "name": "image", + "type": "IMAGE", + "linkIds": [], + "pos": [ + 385.224609375, + 7233 + ] + }, + { + "id": "6c54f923-7ccc-4832-86ca-38d39cfcc656", + "name": "samples", + "type": "LATENT", + "linkIds": [ + 705 + ], + "label": "video_latent", + "pos": [ + 385.224609375, + 7253 + ] + }, + { + "id": "5ce3a92e-d38a-4078-bc1f-7426a38fa1ff", + "name": "audio_latent", + "type": "LATENT", + "linkIds": [ + 700 + ], + "pos": [ + 385.224609375, + 7273 + ] + }, + { + "id": "6a8ebcb1-8f4b-4558-893c-1b6be5c462ec", + "name": "multi_input", + "type": "IMAGE", + "linkIds": [ + 1004 + ], + "pos": [ + 385.224609375, + 7293 + ] + }, + { + "id": "7443af4e-2549-4707-813a-a7e7efa13046", + "name": "upscale_model", + "type": "LATENT_UPSCALE_MODEL", + "linkIds": [ + 1035 + ], + "pos": [ + 385.224609375, + 7313 + ] + } + ], + "outputs": [ + { + "id": "b6f80403-e98c-41ad-a692-270973783381", + "name": "video_latent", + "type": "LATENT", + "linkIds": [ + 843 + ], + "pos": [ + 1840, + 7150 + ] + }, + { + "id": "f54a00fd-f0db-4e4e-9205-9c9d3e955032", + "name": "audio_latent", + "type": "LATENT", + "linkIds": [ + 690 + ], + "pos": [ + 1840, + 7170 + ] + }, + { + "id": "3ecaa920-05d1-4c39-8ea0-b7b21e2df49b", + "name": "positive", + "type": "CONDITIONING", + "linkIds": [ + 985 + ], + "pos": [ + 1840, + 7190 + ] + }, + { + "id": "d1c64c87-487c-4677-9f80-d4a7ef6cdcc6", + "name": "negative", + "type": "CONDITIONING", + "linkIds": [ + 986 + ], + "pos": [ + 1840, + 7210 + ] + } + ], + "widgets": [], + "nodes": [ + { + "id": 326, + "type": "BasicScheduler", + "pos": [ + 480, + 7210 + ], + "size": [ + 210, + 106 + ], + "flags": {}, + "order": 4, + "mode": 0, + "inputs": [ + { + "localized_name": "model", + "name": "model", + "type": "MODEL", + "link": 686 + } + ], + "outputs": [ + { + "localized_name": "SIGMAS", + "name": "SIGMAS", + "type": "SIGMAS", + "links": [ + 681 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.16.4", + "Node name for S&R": "BasicScheduler" + }, + "widgets_values": [ + "linear_quadratic", + 6, + 0.42 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 325, + "type": "KSamplerSelect", + "pos": [ + 480, + 7110 + ], + "size": [ + 210, + 60 + ], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [], + "outputs": [ + { + "localized_name": "SAMPLER", + "name": "SAMPLER", + "type": "SAMPLER", + "links": [ + 680 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.56", + "Node name for S&R": "KSamplerSelect", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [ + "euler" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 322, + "type": "SamplerCustomAdvanced", + "pos": [ + 1370, + 7130 + ], + "size": [ + 212.3638671875, + 204.64497833251954 + ], + "flags": {}, + "order": 1, + "mode": 0, + "inputs": [ + { + "localized_name": "noise", + "name": "noise", + "type": "NOISE", + "link": 688 + }, + { + "localized_name": "guider", + "name": "guider", + "type": "GUIDER", + "link": 679 + }, + { + "localized_name": "sampler", + "name": "sampler", + "type": "SAMPLER", + "link": 680 + }, + { + "localized_name": "sigmas", + "name": "sigmas", + "type": "SIGMAS", + "link": 681 + }, + { + "localized_name": "latent_image", + "name": "latent_image", + "type": "LATENT", + "link": 697 + } + ], + "outputs": [ + { + "localized_name": "output", + "name": "output", + "type": "LATENT", + "links": [ + 682 + ] + }, + { + "localized_name": "denoised_output", + "name": "denoised_output", + "type": "LATENT", + "links": [] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.60", + "Node name for S&R": "SamplerCustomAdvanced", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 323, + "type": "LTXVSeparateAVLatent", + "pos": [ + 1600, + 7140 + ], + "size": [ + 193.2916015625, + 46 + ], + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [ + { + "localized_name": "av_latent", + "name": "av_latent", + "type": "LATENT", + "link": 682 + } + ], + "outputs": [ + { + "localized_name": "video_latent", + "name": "video_latent", + "type": "LATENT", + "links": [ + 843 + ] + }, + { + "localized_name": "audio_latent", + "name": "audio_latent", + "type": "LATENT", + "links": [ + 690 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.5.1", + "Node name for S&R": "LTXVSeparateAVLatent", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 409, + "type": "LTXSequencer", + "pos": [ + 850, + 7140 + ], + "size": [ + 210, + 200 + ], + "flags": {}, + "order": 8, + "mode": 0, + "inputs": [ + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "link": 988 + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "link": 989 + }, + { + "localized_name": "vae", + "name": "vae", + "type": "VAE", + "link": 990 + }, + { + "localized_name": "latent", + "name": "latent", + "type": "LATENT", + "link": 991 + }, + { + "localized_name": "multi_input", + "name": "multi_input", + "type": "IMAGE", + "link": 1004 + } + ], + "outputs": [ + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "links": [ + 983, + 985 + ] + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "links": [ + 984, + 986 + ] + }, + { + "localized_name": "latent", + "name": "latent", + "type": "LATENT", + "links": [ + 987 + ] + } + ], + "properties": { + "cnr_id": "WhatDreamsCost-ComfyUI", + "ver": "ecd00e6b404f14b07c7b54b0b0033e8bccb32d24", + "Node name for S&R": "LTXSequencer", + "num_images": 0, + "insert_frame_1": 0, + "strength_1": 1, + "insert_frame_2": 0, + "strength_2": 1, + "insert_frame_3": 0, + "strength_3": 1, + "insert_frame_4": 0, + "strength_4": 1, + "insert_frame_5": 0, + "strength_5": 1, + "insert_frame_6": 0, + "strength_6": 1, + "insert_frame_7": 0, + "strength_7": 1, + "insert_frame_8": 0, + "strength_8": 1, + "insert_frame_9": 0, + "strength_9": 0, + "insert_frame_10": 1, + "strength_10": 1, + "insert_frame_11": 0, + "strength_11": 0, + "insert_frame_12": 1, + "strength_12": 1, + "insert_frame_13": 0, + "strength_13": 0, + "insert_frame_14": 1, + "strength_14": 1, + "insert_frame_15": 0, + "strength_15": 0, + "insert_frame_16": 1, + "strength_16": 1, + "insert_frame_17": 0, + "strength_17": 0, + "insert_frame_18": 1, + "strength_18": 1, + "insert_frame_19": 0, + "strength_19": 0, + "insert_frame_20": 1, + "strength_20": 1, + "insert_frame_21": 0, + "strength_21": 0, + "insert_frame_22": 1, + "strength_22": 1, + "insert_frame_23": 0, + "strength_23": 0, + "insert_frame_24": 1, + "strength_24": 1, + "insert_frame_25": 0, + "strength_25": 0, + "insert_frame_26": 1, + "strength_26": 1, + "insert_frame_27": 0, + "strength_27": 0, + "insert_frame_28": 1, + "strength_28": 1, + "insert_frame_29": 0, + "strength_29": 0, + "insert_frame_30": 1, + "strength_30": 1, + "insert_frame_31": 0, + "strength_31": 0, + "insert_frame_32": 1, + "strength_32": 1, + "insert_frame_33": 0, + "strength_33": 1, + "insert_frame_34": 0, + "strength_34": 1, + "insert_frame_35": 0, + "strength_35": 1, + "insert_frame_36": 0, + "strength_36": 1, + "insert_frame_37": 0, + "strength_37": 1, + "insert_frame_38": 0, + "strength_38": 1, + "insert_frame_39": 0, + "strength_39": 1, + "insert_frame_40": 0, + "strength_40": 1, + "insert_frame_41": 0, + "strength_41": 1, + "insert_frame_42": 0, + "strength_42": 1, + "insert_frame_43": 0, + "strength_43": 1, + "insert_frame_44": 0, + "strength_44": 1, + "insert_frame_45": 0, + "strength_45": 1, + "insert_frame_46": 0, + "strength_46": 1, + "insert_frame_47": 0, + "strength_47": 1, + "insert_frame_48": 0, + "strength_48": 1, + "insert_frame_49": 0, + "strength_49": 1, + "insert_frame_50": 0, + "strength_50": 1, + "insert_mode": "frames", + "frame_rate": 24, + "insert_second_1": 0, + "insert_second_2": 0, + "insert_second_3": 0, + "insert_second_4": 0, + "insert_second_5": 0, + "insert_second_6": 0, + "insert_second_7": 0, + "insert_second_8": 0, + "insert_second_9": 1, + "insert_second_10": 0, + "insert_second_11": 1, + "insert_second_12": 0, + "insert_second_13": 1, + "insert_second_14": 0, + "insert_second_15": 1, + "insert_second_16": 0, + "insert_second_17": 1, + "insert_second_18": 0, + "insert_second_19": 1, + "insert_second_20": 0, + "insert_second_21": 1, + "insert_second_22": 0, + "insert_second_23": 1, + "insert_second_24": 0, + "insert_second_25": 1, + "insert_second_26": 0, + "insert_second_27": 1, + "insert_second_28": 0, + "insert_second_29": 1, + "insert_second_30": 0, + "insert_second_31": 1, + "insert_second_32": 0, + "insert_second_33": 1, + "insert_second_34": 0, + "insert_second_35": 0, + "insert_second_36": 0, + "insert_second_37": 0, + "insert_second_38": 0, + "insert_second_39": 0, + "insert_second_40": 0, + "insert_second_41": 0, + "insert_second_42": 0, + "insert_second_43": 0, + "insert_second_44": 0, + "insert_second_45": 0, + "insert_second_46": 0, + "insert_second_47": 0, + "insert_second_48": 0, + "insert_second_49": 0, + "insert_second_50": 0, + "insert_images_using": "frames", + "video_length": 1, + "insert_percentage_1": 0, + "insert_percentage_2": 1, + "insert_percentage_3": 0, + "insert_percentage_4": 0, + "insert_percentage_5": 0, + "insert_percentage_6": 0, + "insert_percentage_7": 0, + "insert_percentage_8": 0, + "insert_percentage_9": 0, + "insert_percentage_10": 0, + "insert_percentage_11": 0, + "insert_percentage_12": 0, + "insert_percentage_13": 0, + "insert_percentage_14": 0, + "insert_percentage_15": 0, + "insert_percentage_16": 0, + "insert_percentage_17": 0, + "insert_percentage_18": 0, + "insert_percentage_19": 0, + "insert_percentage_20": 0, + "insert_percentage_21": 0, + "insert_percentage_22": 0, + "insert_percentage_23": 0, + "insert_percentage_24": 0, + "insert_percentage_25": 0, + "insert_percentage_26": 0, + "insert_percentage_27": 0, + "insert_percentage_28": 0, + "insert_percentage_29": 0, + "insert_percentage_30": 0, + "insert_percentage_31": 0, + "insert_percentage_32": 0, + "insert_percentage_33": 0, + "insert_percentage_34": 0, + "insert_percentage_35": 0, + "insert_percentage_36": 0, + "insert_percentage_37": 0, + "insert_percentage_38": 0, + "insert_percentage_39": 0, + "insert_percentage_40": 0, + "insert_percentage_41": 0, + "insert_percentage_42": 0, + "insert_percentage_43": 0, + "insert_percentage_44": 0, + "insert_percentage_45": 0, + "insert_percentage_46": 0, + "insert_percentage_47": 0, + "insert_percentage_48": 0, + "insert_percentage_49": 0, + "insert_percentage_50": 0, + "insert_percent_1": 0, + "insert_percent_2": 50, + "insert_percent_3": 100, + "insert_percent_4": 0, + "insert_percent_5": 0, + "insert_percent_6": 0, + "insert_percent_7": 0, + "insert_percent_8": 0, + "insert_percent_9": 0, + "insert_percent_10": 0, + "insert_percent_11": 0, + "insert_percent_12": 0, + "insert_percent_13": 0, + "insert_percent_14": 0, + "insert_percent_15": 0, + "insert_percent_16": 0, + "insert_percent_17": 0, + "insert_percent_18": 0, + "insert_percent_19": 0, + "insert_percent_20": 0, + "insert_percent_21": 0, + "insert_percent_22": 0, + "insert_percent_23": 0, + "insert_percent_24": 0, + "insert_percent_25": 0, + "insert_percent_26": 0, + "insert_percent_27": 0, + "insert_percent_28": 0, + "insert_percent_29": 0, + "insert_percent_30": 0, + "insert_percent_31": 0, + "insert_percent_32": 0, + "insert_percent_33": 0, + "insert_percent_34": 0, + "insert_percent_35": 0, + "insert_percent_36": 0, + "insert_percent_37": 0, + "insert_percent_38": 0, + "insert_percent_39": 0, + "insert_percent_40": 0, + "insert_percent_41": 0, + "insert_percent_42": 0, + "insert_percent_43": 0, + "insert_percent_44": 0, + "insert_percent_45": 0, + "insert_percent_46": 0, + "insert_percent_47": 0, + "insert_percent_48": 0, + "insert_percent_49": 0, + "insert_percent_50": 0 + }, + "widgets_values": [ + 0, + "frames", + 24, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 331, + "type": "LTXVCropGuides", + "pos": [ + 490, + 7000 + ], + "size": [ + 152.7134765625, + 66 + ], + "flags": {}, + "order": 7, + "mode": 0, + "inputs": [ + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "link": 703 + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "link": 704 + }, + { + "localized_name": "latent", + "name": "latent", + "type": "LATENT", + "link": 705 + } + ], + "outputs": [ + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "links": [ + 828, + 846, + 988 + ] + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "links": [ + 829, + 847, + 989 + ] + }, + { + "localized_name": "latent", + "name": "latent", + "type": "LATENT", + "links": [ + 850 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.16.4", + "Node name for S&R": "LTXVCropGuides" + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 329, + "type": "LTXVConcatAVLatent", + "pos": [ + 1140, + 7270 + ], + "size": [ + 187.5, + 60 + ], + "flags": {}, + "order": 6, + "mode": 0, + "inputs": [ + { + "localized_name": "video_latent", + "name": "video_latent", + "type": "LATENT", + "link": 987 + }, + { + "localized_name": "audio_latent", + "name": "audio_latent", + "type": "LATENT", + "link": 700 + } + ], + "outputs": [ + { + "localized_name": "latent", + "name": "latent", + "type": "LATENT", + "links": [ + 697 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.7.0", + "Node name for S&R": "LTXVConcatAVLatent", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 324, + "type": "CFGGuider", + "pos": [ + 1140, + 7130 + ], + "size": [ + 210, + 98 + ], + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [ + { + "localized_name": "model", + "name": "model", + "type": "MODEL", + "link": 683 + }, + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "link": 983 + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "link": 984 + } + ], + "outputs": [ + { + "localized_name": "GUIDER", + "name": "GUIDER", + "type": "GUIDER", + "links": [ + 679 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.64", + "Node name for S&R": "CFGGuider", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [ + 1 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 328, + "type": "LTXVLatentUpsampler", + "pos": [ + 480, + 7370 + ], + "size": [ + 320, + 66 + ], + "flags": {}, + "order": 5, + "mode": 0, + "inputs": [ + { + "localized_name": "samples", + "name": "samples", + "type": "LATENT", + "link": 850 + }, + { + "localized_name": "upscale_model", + "name": "upscale_model", + "type": "LATENT_UPSCALE_MODEL", + "link": 1035 + }, + { + "localized_name": "vae", + "name": "vae", + "type": "VAE", + "link": 706 + } + ], + "outputs": [ + { + "localized_name": "LATENT", + "name": "LATENT", + "type": "LATENT", + "links": [ + 991 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.14.1", + "Node name for S&R": "LTXVLatentUpsampler", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + } + ], + "groups": [], + "links": [ + { + "id": 679, + "origin_id": 324, + "origin_slot": 0, + "target_id": 322, + "target_slot": 1, + "type": "GUIDER" + }, + { + "id": 680, + "origin_id": 325, + "origin_slot": 0, + "target_id": 322, + "target_slot": 2, + "type": "SAMPLER" + }, + { + "id": 681, + "origin_id": 326, + "origin_slot": 0, + "target_id": 322, + "target_slot": 3, + "type": "SIGMAS" + }, + { + "id": 682, + "origin_id": 322, + "origin_slot": 0, + "target_id": 323, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 683, + "origin_id": -10, + "origin_slot": 0, + "target_id": 324, + "target_slot": 0, + "type": "MODEL" + }, + { + "id": 686, + "origin_id": -10, + "origin_slot": 0, + "target_id": 326, + "target_slot": 0, + "type": "MODEL" + }, + { + "id": 688, + "origin_id": -10, + "origin_slot": 3, + "target_id": 322, + "target_slot": 0, + "type": "NOISE" + }, + { + "id": 690, + "origin_id": 323, + "origin_slot": 1, + "target_id": -20, + "target_slot": 1, + "type": "LATENT" + }, + { + "id": 697, + "origin_id": 329, + "origin_slot": 0, + "target_id": 322, + "target_slot": 4, + "type": "LATENT" + }, + { + "id": 700, + "origin_id": -10, + "origin_slot": 7, + "target_id": 329, + "target_slot": 1, + "type": "LATENT" + }, + { + "id": 703, + "origin_id": -10, + "origin_slot": 1, + "target_id": 331, + "target_slot": 0, + "type": "CONDITIONING" + }, + { + "id": 704, + "origin_id": -10, + "origin_slot": 2, + "target_id": 331, + "target_slot": 1, + "type": "CONDITIONING" + }, + { + "id": 705, + "origin_id": -10, + "origin_slot": 6, + "target_id": 331, + "target_slot": 2, + "type": "LATENT" + }, + { + "id": 706, + "origin_id": -10, + "origin_slot": 4, + "target_id": 328, + "target_slot": 2, + "type": "VAE" + }, + { + "id": 843, + "origin_id": 323, + "origin_slot": 0, + "target_id": -20, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 850, + "origin_id": 331, + "origin_slot": 2, + "target_id": 328, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 983, + "origin_id": 409, + "origin_slot": 0, + "target_id": 324, + "target_slot": 1, + "type": "CONDITIONING" + }, + { + "id": 984, + "origin_id": 409, + "origin_slot": 1, + "target_id": 324, + "target_slot": 2, + "type": "CONDITIONING" + }, + { + "id": 985, + "origin_id": 409, + "origin_slot": 0, + "target_id": -20, + "target_slot": 2, + "type": "CONDITIONING" + }, + { + "id": 986, + "origin_id": 409, + "origin_slot": 1, + "target_id": -20, + "target_slot": 3, + "type": "CONDITIONING" + }, + { + "id": 987, + "origin_id": 409, + "origin_slot": 2, + "target_id": 329, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 988, + "origin_id": 331, + "origin_slot": 0, + "target_id": 409, + "target_slot": 0, + "type": "CONDITIONING" + }, + { + "id": 989, + "origin_id": 331, + "origin_slot": 1, + "target_id": 409, + "target_slot": 1, + "type": "CONDITIONING" + }, + { + "id": 990, + "origin_id": -10, + "origin_slot": 4, + "target_id": 409, + "target_slot": 2, + "type": "VAE" + }, + { + "id": 991, + "origin_id": 328, + "origin_slot": 0, + "target_id": 409, + "target_slot": 3, + "type": "LATENT" + }, + { + "id": 1004, + "origin_id": -10, + "origin_slot": 8, + "target_id": 409, + "target_slot": 4, + "type": "IMAGE" + }, + { + "id": 1035, + "origin_id": -10, + "origin_slot": 9, + "target_id": 328, + "target_slot": 1, + "type": "LATENT_UPSCALE_MODEL" + } + ], + "extra": { + "workflowRendererVersion": "LG" + } + }, + { + "id": "2979f3eb-1c4c-45ff-8387-f1f4addb72f4", + "version": 1, + "state": { + "lastGroupId": 33, + "lastNodeId": 443, + "lastLinkId": 1079, + "lastRerouteId": 0 + }, + "revision": 0, + "config": {}, + "name": "Process Latents", + "inputNode": { + "id": -10, + "bounding": [ + 2630, + 6303, + 128.998046875, + 100 + ] + }, + "outputNode": { + "id": -20, + "bounding": [ + 3500, + 6303, + 120, + 80 + ] + }, + "inputs": [ + { + "id": "f9d3f39d-4628-4ef2-a464-ffb98c1fb4bc", + "name": "image", + "type": "IMAGE", + "linkIds": [ + 713 + ], + "localized_name": "image", + "pos": [ + 2738.998046875, + 6323 + ] + }, + { + "id": "6c61b79c-c6a2-4cd9-96a7-72073d3603c1", + "name": "length", + "type": "INT", + "linkIds": [ + 708, + 709 + ], + "localized_name": "length", + "label": "length_seconds", + "pos": [ + 2738.998046875, + 6343 + ] + }, + { + "id": "610c02c9-497c-45ac-b603-ea5bd91aec4c", + "name": "audio_vae", + "type": "VAE", + "linkIds": [ + 714 + ], + "pos": [ + 2738.998046875, + 6363 + ] + } + ], + "outputs": [ + { + "id": "2cf8b0ee-4e7a-4045-a1ee-bbd4b878068e", + "name": "LATENT", + "type": "LATENT", + "linkIds": [ + 710 + ], + "localized_name": "LATENT", + "pos": [ + 3520, + 6323 + ] + }, + { + "id": "44e92917-679f-4cc3-aa57-51b4a6ef24a8", + "name": "Latent", + "type": "LATENT", + "linkIds": [ + 705 + ], + "localized_name": "Latent", + "pos": [ + 3520, + 6343 + ] + } + ], + "widgets": [], + "nodes": [ + { + "id": 356, + "type": "GetImageSize", + "pos": [ + 3030, + 6210 + ], + "size": [ + 170, + 70 + ], + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [ + { + "localized_name": "image", + "name": "image", + "type": "IMAGE", + "link": 711 + } + ], + "outputs": [ + { + "localized_name": "width", + "name": "width", + "type": "INT", + "links": [ + 706 + ] + }, + { + "localized_name": "height", + "name": "height", + "type": "INT", + "links": [ + 707 + ] + }, + { + "localized_name": "batch_size", + "name": "batch_size", + "type": "INT", + "links": null + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.7.0", + "Node name for S&R": "GetImageSize", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 352, + "type": "EmptyLTXVLatentVideo", + "pos": [ + 3210, + 6210 + ], + "size": [ + 210, + 130 + ], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [ + { + "localized_name": "width", + "name": "width", + "type": "INT", + "widget": { + "name": "width" + }, + "link": 706 + }, + { + "localized_name": "height", + "name": "height", + "type": "INT", + "widget": { + "name": "height" + }, + "link": 707 + }, + { + "localized_name": "length", + "name": "length", + "type": "INT", + "widget": { + "name": "length" + }, + "link": 708 + } + ], + "outputs": [ + { + "localized_name": "LATENT", + "name": "LATENT", + "type": "LATENT", + "links": [ + 710 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.60", + "Node name for S&R": "EmptyLTXVLatentVideo", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [ + 768, + 512, + 1, + 1 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 353, + "type": "LTXVEmptyLatentAudio", + "pos": [ + 3210, + 6380 + ], + "size": [ + 210, + 106 + ], + "flags": {}, + "order": 1, + "mode": 0, + "inputs": [ + { + "localized_name": "audio_vae", + "name": "audio_vae", + "type": "VAE", + "link": 714 + }, + { + "localized_name": "frames_number", + "name": "frames_number", + "type": "INT", + "widget": { + "name": "frames_number" + }, + "link": 709 + } + ], + "outputs": [ + { + "localized_name": "Latent", + "name": "Latent", + "type": "LATENT", + "links": [ + 705 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.68", + "Node name for S&R": "LTXVEmptyLatentAudio", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [ + 1, + 24, + 1 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 357, + "type": "ImageScaleBy", + "pos": [ + 2810, + 6210 + ], + "size": [ + 210, + 82 + ], + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [ + { + "localized_name": "image", + "name": "image", + "type": "IMAGE", + "link": 713 + } + ], + "outputs": [ + { + "localized_name": "IMAGE", + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 711 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.5.1", + "Node name for S&R": "ImageScaleBy", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65, + "ue_properties": { + "version": "7.0.1", + "widget_ue_connectable": {} + } + }, + "widgets_values": [ + "bilinear", + 0.5 + ], + "color": "#222", + "bgcolor": "#000" + } + ], + "groups": [], + "links": [ + { + "id": 711, + "origin_id": 357, + "origin_slot": 0, + "target_id": 356, + "target_slot": 0, + "type": "IMAGE" + }, + { + "id": 706, + "origin_id": 356, + "origin_slot": 0, + "target_id": 352, + "target_slot": 0, + "type": "INT" + }, + { + "id": 707, + "origin_id": 356, + "origin_slot": 1, + "target_id": 352, + "target_slot": 1, + "type": "INT" + }, + { + "id": 713, + "origin_id": -10, + "origin_slot": 0, + "target_id": 357, + "target_slot": 0, + "type": "IMAGE" + }, + { + "id": 708, + "origin_id": -10, + "origin_slot": 1, + "target_id": 352, + "target_slot": 2, + "type": "INT" + }, + { + "id": 709, + "origin_id": -10, + "origin_slot": 1, + "target_id": 353, + "target_slot": 1, + "type": "INT" + }, + { + "id": 710, + "origin_id": 352, + "origin_slot": 0, + "target_id": -20, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 705, + "origin_id": 353, + "origin_slot": 0, + "target_id": -20, + "target_slot": 1, + "type": "LATENT" + }, + { + "id": 714, + "origin_id": -10, + "origin_slot": 2, + "target_id": 353, + "target_slot": 0, + "type": "VAE" + } + ], + "extra": { + "workflowRendererVersion": "LG" + } + }, + { + "id": "535e663f-da59-47c1-aab5-c78ff202cb16", + "version": 1, + "state": { + "lastGroupId": 33, + "lastNodeId": 443, + "lastLinkId": 1079, + "lastRerouteId": 0 + }, + "revision": 0, + "config": {}, + "name": "Stage #3", + "inputNode": { + "id": -10, + "bounding": [ + -370, + 7030, + 144.56640625, + 260 + ] + }, + "outputNode": { + "id": -20, + "bounding": [ + 1680, + 7090, + 120, + 80 + ] + }, + "inputs": [ + { + "id": "e760d60a-9b49-4b40-8f39-3c81f29c08d8", + "name": "model", + "type": "MODEL", + "linkIds": [ + 683, + 686 + ], + "pos": [ + -245.43359375, + 7050 + ] + }, + { + "id": "6ab6641e-8318-4330-8b01-43ce43648dcf", + "name": "positive", + "type": "CONDITIONING", + "linkIds": [ + 919 + ], + "pos": [ + -245.43359375, + 7070 + ] + }, + { + "id": "e1e7e2f6-49fb-4754-92ab-b71442b03da7", + "name": "negative", + "type": "CONDITIONING", + "linkIds": [ + 920 + ], + "pos": [ + -245.43359375, + 7090 + ] + }, + { + "id": "0a89214b-81f0-4fa9-86fa-5f77079ddb7b", + "name": "noise", + "type": "NOISE", + "linkIds": [ + 688 + ], + "pos": [ + -245.43359375, + 7110 + ] + }, + { + "id": "d8824343-6b21-42aa-9eaf-65d1b267662d", + "name": "vae", + "type": "VAE", + "linkIds": [ + 716, + 717, + 1002 + ], + "label": "video_vae", + "pos": [ + -245.43359375, + 7130 + ] + }, + { + "id": "6ea10719-25f1-449a-9d2d-bb395d89609d", + "name": "image", + "type": "IMAGE", + "linkIds": [], + "pos": [ + -245.43359375, + 7150 + ] + }, + { + "id": "d57f8c01-0dc1-48da-b7a2-166839b09d2b", + "name": "samples", + "type": "LATENT", + "linkIds": [ + 708 + ], + "label": "video_latent", + "pos": [ + -245.43359375, + 7170 + ] + }, + { + "id": "9f9730e3-9739-4813-953f-4c27da077f21", + "name": "audio_latent", + "type": "LATENT", + "linkIds": [ + 700 + ], + "pos": [ + -245.43359375, + 7190 + ] + }, + { + "id": "6f44ac03-cf13-4dc0-be74-7ec92e492d88", + "name": "value", + "type": "BOOLEAN", + "linkIds": [ + 719 + ], + "label": "use 2x on stage 3", + "pos": [ + -245.43359375, + 7210 + ] + }, + { + "id": "9951f766-4866-4ed1-bab3-39a03ca4ae6a", + "name": "multi_input", + "type": "IMAGE", + "linkIds": [ + 1005 + ], + "pos": [ + -245.43359375, + 7230 + ] + }, + { + "id": "265429c2-c78b-4c5d-80d7-6e272aa96a36", + "name": "upscale_model", + "type": "LATENT_UPSCALE_MODEL", + "linkIds": [ + 1038 + ], + "label": "2x_upscale_model", + "pos": [ + -245.43359375, + 7250 + ] + } + ], + "outputs": [ + { + "id": "7b177753-a22f-47a0-9ff8-7f4022516b5c", + "name": "output", + "type": "LATENT", + "linkIds": [ + 1024 + ], + "label": "video_latent", + "pos": [ + 1700, + 7110 + ] + }, + { + "id": "51232fd0-325e-4090-bcc9-4ecbdb0b9e27", + "name": "audio_latent", + "type": "LATENT", + "linkIds": [ + 1025 + ], + "pos": [ + 1700, + 7130 + ] + } + ], + "widgets": [], + "nodes": [ + { + "id": 331, + "type": "LTXVCropGuides", + "pos": [ + -190, + 6940 + ], + "size": [ + 152.7134765625, + 66 + ], + "flags": {}, + "order": 6, + "mode": 0, + "inputs": [ + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "link": 919 + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "link": 920 + }, + { + "localized_name": "latent", + "name": "latent", + "type": "LATENT", + "link": 708 + } + ], + "outputs": [ + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "links": [ + 999 + ] + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "links": [ + 1000 + ] + }, + { + "localized_name": "latent", + "name": "latent", + "type": "LATENT", + "links": [ + 877, + 878 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.16.4", + "Node name for S&R": "LTXVCropGuides" + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 325, + "type": "KSamplerSelect", + "pos": [ + -190, + 7050 + ], + "size": [ + 210, + 60 + ], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [], + "outputs": [ + { + "localized_name": "SAMPLER", + "name": "SAMPLER", + "type": "SAMPLER", + "links": [ + 680 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.56", + "Node name for S&R": "KSamplerSelect", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [ + "euler" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 326, + "type": "BasicScheduler", + "pos": [ + -190, + 7150 + ], + "size": [ + 210, + 106 + ], + "flags": {}, + "order": 4, + "mode": 0, + "inputs": [ + { + "localized_name": "model", + "name": "model", + "type": "MODEL", + "link": 686 + } + ], + "outputs": [ + { + "localized_name": "SIGMAS", + "name": "SIGMAS", + "type": "SIGMAS", + "links": [ + 681 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.16.4", + "Node name for S&R": "BasicScheduler" + }, + "widgets_values": [ + "linear_quadratic", + 4, + 0.42 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 334, + "type": "LTXVLatentUpsampler", + "pos": [ + 160, + 7300 + ], + "size": [ + 320, + 66 + ], + "flags": {}, + "order": 8, + "mode": 0, + "inputs": [ + { + "localized_name": "samples", + "name": "samples", + "type": "LATENT", + "link": 877 + }, + { + "localized_name": "upscale_model", + "name": "upscale_model", + "type": "LATENT_UPSCALE_MODEL", + "link": 710 + }, + { + "localized_name": "vae", + "name": "vae", + "type": "VAE", + "link": 716 + } + ], + "outputs": [ + { + "localized_name": "LATENT", + "name": "LATENT", + "type": "LATENT", + "links": [ + 712 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.14.1", + "Node name for S&R": "LTXVLatentUpsampler", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 332, + "type": "LazySwitchKJ", + "pos": [ + 500, + 7360 + ], + "size": [ + 210, + 78 + ], + "flags": {}, + "order": 7, + "mode": 0, + "inputs": [ + { + "localized_name": "on_false", + "name": "on_false", + "type": "*", + "link": 712 + }, + { + "localized_name": "on_true", + "name": "on_true", + "type": "*", + "link": 713 + }, + { + "localized_name": "switch", + "name": "switch", + "type": "BOOLEAN", + "widget": { + "name": "switch" + }, + "link": 718 + } + ], + "outputs": [ + { + "localized_name": "*", + "name": "*", + "type": "*", + "links": [ + 1001 + ] + } + ], + "properties": { + "cnr_id": "comfyui-kjnodes", + "ver": "1.3.4", + "Node name for S&R": "LazySwitchKJ" + }, + "widgets_values": [ + false + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 411, + "type": "LTXSequencer", + "pos": [ + 720, + 7110 + ], + "size": [ + 210, + 200 + ], + "flags": {}, + "order": 11, + "mode": 0, + "inputs": [ + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "link": 999 + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "link": 1000 + }, + { + "localized_name": "vae", + "name": "vae", + "type": "VAE", + "link": 1002 + }, + { + "localized_name": "latent", + "name": "latent", + "type": "LATENT", + "link": 1001 + }, + { + "localized_name": "multi_input", + "name": "multi_input", + "type": "IMAGE", + "link": 1005 + } + ], + "outputs": [ + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "links": [ + 996 + ] + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "links": [ + 997 + ] + }, + { + "localized_name": "latent", + "name": "latent", + "type": "LATENT", + "links": [ + 998 + ] + } + ], + "properties": { + "cnr_id": "WhatDreamsCost-ComfyUI", + "ver": "ecd00e6b404f14b07c7b54b0b0033e8bccb32d24", + "Node name for S&R": "LTXSequencer", + "num_images": 0, + "insert_frame_1": 0, + "strength_1": 1, + "insert_frame_2": 0, + "strength_2": 1, + "insert_frame_3": 0, + "strength_3": 1, + "insert_frame_4": 0, + "strength_4": 1, + "insert_frame_5": 0, + "strength_5": 1, + "insert_frame_6": 0, + "strength_6": 1, + "insert_frame_7": 0, + "strength_7": 1, + "insert_frame_8": 0, + "strength_8": 1, + "insert_frame_9": 0, + "strength_9": 0, + "insert_frame_10": 1, + "strength_10": 1, + "insert_frame_11": 0, + "strength_11": 0, + "insert_frame_12": 1, + "strength_12": 1, + "insert_frame_13": 0, + "strength_13": 0, + "insert_frame_14": 1, + "strength_14": 1, + "insert_frame_15": 0, + "strength_15": 0, + "insert_frame_16": 1, + "strength_16": 1, + "insert_frame_17": 0, + "strength_17": 0, + "insert_frame_18": 1, + "strength_18": 1, + "insert_frame_19": 0, + "strength_19": 0, + "insert_frame_20": 1, + "strength_20": 1, + "insert_frame_21": 0, + "strength_21": 0, + "insert_frame_22": 1, + "strength_22": 1, + "insert_frame_23": 0, + "strength_23": 0, + "insert_frame_24": 1, + "strength_24": 1, + "insert_frame_25": 0, + "strength_25": 0, + "insert_frame_26": 1, + "strength_26": 1, + "insert_frame_27": 0, + "strength_27": 0, + "insert_frame_28": 1, + "strength_28": 1, + "insert_frame_29": 0, + "strength_29": 0, + "insert_frame_30": 1, + "strength_30": 1, + "insert_frame_31": 0, + "strength_31": 0, + "insert_frame_32": 1, + "strength_32": 1, + "insert_frame_33": 0, + "strength_33": 1, + "insert_frame_34": 0, + "strength_34": 1, + "insert_frame_35": 0, + "strength_35": 1, + "insert_frame_36": 0, + "strength_36": 1, + "insert_frame_37": 0, + "strength_37": 1, + "insert_frame_38": 0, + "strength_38": 1, + "insert_frame_39": 0, + "strength_39": 1, + "insert_frame_40": 0, + "strength_40": 1, + "insert_frame_41": 0, + "strength_41": 1, + "insert_frame_42": 0, + "strength_42": 1, + "insert_frame_43": 0, + "strength_43": 1, + "insert_frame_44": 0, + "strength_44": 1, + "insert_frame_45": 0, + "strength_45": 1, + "insert_frame_46": 0, + "strength_46": 1, + "insert_frame_47": 0, + "strength_47": 1, + "insert_frame_48": 0, + "strength_48": 1, + "insert_frame_49": 0, + "strength_49": 1, + "insert_frame_50": 0, + "strength_50": 1, + "insert_mode": "frames", + "frame_rate": 24, + "insert_second_1": 0, + "insert_second_2": 0, + "insert_second_3": 0, + "insert_second_4": 0, + "insert_second_5": 0, + "insert_second_6": 0, + "insert_second_7": 0, + "insert_second_8": 0, + "insert_second_9": 1, + "insert_second_10": 0, + "insert_second_11": 1, + "insert_second_12": 0, + "insert_second_13": 1, + "insert_second_14": 0, + "insert_second_15": 1, + "insert_second_16": 0, + "insert_second_17": 1, + "insert_second_18": 0, + "insert_second_19": 1, + "insert_second_20": 0, + "insert_second_21": 1, + "insert_second_22": 0, + "insert_second_23": 1, + "insert_second_24": 0, + "insert_second_25": 1, + "insert_second_26": 0, + "insert_second_27": 1, + "insert_second_28": 0, + "insert_second_29": 1, + "insert_second_30": 0, + "insert_second_31": 1, + "insert_second_32": 0, + "insert_second_33": 1, + "insert_second_34": 0, + "insert_second_35": 0, + "insert_second_36": 0, + "insert_second_37": 0, + "insert_second_38": 0, + "insert_second_39": 0, + "insert_second_40": 0, + "insert_second_41": 0, + "insert_second_42": 0, + "insert_second_43": 0, + "insert_second_44": 0, + "insert_second_45": 0, + "insert_second_46": 0, + "insert_second_47": 0, + "insert_second_48": 0, + "insert_second_49": 0, + "insert_second_50": 0, + "insert_images_using": "frames", + "video_length": 1, + "insert_percentage_1": 0, + "insert_percentage_2": 1, + "insert_percentage_3": 0, + "insert_percentage_4": 0, + "insert_percentage_5": 0, + "insert_percentage_6": 0, + "insert_percentage_7": 0, + "insert_percentage_8": 0, + "insert_percentage_9": 0, + "insert_percentage_10": 0, + "insert_percentage_11": 0, + "insert_percentage_12": 0, + "insert_percentage_13": 0, + "insert_percentage_14": 0, + "insert_percentage_15": 0, + "insert_percentage_16": 0, + "insert_percentage_17": 0, + "insert_percentage_18": 0, + "insert_percentage_19": 0, + "insert_percentage_20": 0, + "insert_percentage_21": 0, + "insert_percentage_22": 0, + "insert_percentage_23": 0, + "insert_percentage_24": 0, + "insert_percentage_25": 0, + "insert_percentage_26": 0, + "insert_percentage_27": 0, + "insert_percentage_28": 0, + "insert_percentage_29": 0, + "insert_percentage_30": 0, + "insert_percentage_31": 0, + "insert_percentage_32": 0, + "insert_percentage_33": 0, + "insert_percentage_34": 0, + "insert_percentage_35": 0, + "insert_percentage_36": 0, + "insert_percentage_37": 0, + "insert_percentage_38": 0, + "insert_percentage_39": 0, + "insert_percentage_40": 0, + "insert_percentage_41": 0, + "insert_percentage_42": 0, + "insert_percentage_43": 0, + "insert_percentage_44": 0, + "insert_percentage_45": 0, + "insert_percentage_46": 0, + "insert_percentage_47": 0, + "insert_percentage_48": 0, + "insert_percentage_49": 0, + "insert_percentage_50": 0, + "insert_percent_1": 0, + "insert_percent_2": 50, + "insert_percent_3": 100, + "insert_percent_4": 0, + "insert_percent_5": 0, + "insert_percent_6": 0, + "insert_percent_7": 0, + "insert_percent_8": 0, + "insert_percent_9": 0, + "insert_percent_10": 0, + "insert_percent_11": 0, + "insert_percent_12": 0, + "insert_percent_13": 0, + "insert_percent_14": 0, + "insert_percent_15": 0, + "insert_percent_16": 0, + "insert_percent_17": 0, + "insert_percent_18": 0, + "insert_percent_19": 0, + "insert_percent_20": 0, + "insert_percent_21": 0, + "insert_percent_22": 0, + "insert_percent_23": 0, + "insert_percent_24": 0, + "insert_percent_25": 0, + "insert_percent_26": 0, + "insert_percent_27": 0, + "insert_percent_28": 0, + "insert_percent_29": 0, + "insert_percent_30": 0, + "insert_percent_31": 0, + "insert_percent_32": 0, + "insert_percent_33": 0, + "insert_percent_34": 0, + "insert_percent_35": 0, + "insert_percent_36": 0, + "insert_percent_37": 0, + "insert_percent_38": 0, + "insert_percent_39": 0, + "insert_percent_40": 0, + "insert_percent_41": 0, + "insert_percent_42": 0, + "insert_percent_43": 0, + "insert_percent_44": 0, + "insert_percent_45": 0, + "insert_percent_46": 0, + "insert_percent_47": 0, + "insert_percent_48": 0, + "insert_percent_49": 0, + "insert_percent_50": 0 + }, + "widgets_values": [ + 0, + "frames", + 24, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 324, + "type": "CFGGuider", + "pos": [ + 1010, + 7110 + ], + "size": [ + 210, + 98 + ], + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [ + { + "localized_name": "model", + "name": "model", + "type": "MODEL", + "link": 683 + }, + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "link": 996 + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "link": 997 + } + ], + "outputs": [ + { + "localized_name": "GUIDER", + "name": "GUIDER", + "type": "GUIDER", + "links": [ + 679 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.64", + "Node name for S&R": "CFGGuider", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [ + 1 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 329, + "type": "LTXVConcatAVLatent", + "pos": [ + 1010, + 7250 + ], + "size": [ + 187.5, + 60 + ], + "flags": {}, + "order": 5, + "mode": 0, + "inputs": [ + { + "localized_name": "video_latent", + "name": "video_latent", + "type": "LATENT", + "link": 998 + }, + { + "localized_name": "audio_latent", + "name": "audio_latent", + "type": "LATENT", + "link": 700 + } + ], + "outputs": [ + { + "localized_name": "latent", + "name": "latent", + "type": "LATENT", + "links": [ + 697 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.7.0", + "Node name for S&R": "LTXVConcatAVLatent", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 322, + "type": "SamplerCustomAdvanced", + "pos": [ + 1230, + 7100 + ], + "size": [ + 212.3638671875, + 204.64497833251954 + ], + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [ + { + "localized_name": "noise", + "name": "noise", + "type": "NOISE", + "link": 688 + }, + { + "localized_name": "guider", + "name": "guider", + "type": "GUIDER", + "link": 679 + }, + { + "localized_name": "sampler", + "name": "sampler", + "type": "SAMPLER", + "link": 680 + }, + { + "localized_name": "sigmas", + "name": "sigmas", + "type": "SIGMAS", + "link": 681 + }, + { + "localized_name": "latent_image", + "name": "latent_image", + "type": "LATENT", + "link": 697 + } + ], + "outputs": [ + { + "localized_name": "output", + "name": "output", + "type": "LATENT", + "links": [ + 1023 + ] + }, + { + "localized_name": "denoised_output", + "name": "denoised_output", + "type": "LATENT", + "links": [] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.60", + "Node name for S&R": "SamplerCustomAdvanced", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 417, + "type": "LTXVSeparateAVLatent", + "pos": [ + 1450, + 7100 + ], + "size": [ + 193.2916015625, + 46 + ], + "flags": {}, + "order": 12, + "mode": 0, + "inputs": [ + { + "localized_name": "av_latent", + "name": "av_latent", + "type": "LATENT", + "link": 1023 + } + ], + "outputs": [ + { + "localized_name": "video_latent", + "name": "video_latent", + "type": "LATENT", + "links": [ + 1024 + ] + }, + { + "localized_name": "audio_latent", + "name": "audio_latent", + "type": "LATENT", + "links": [ + 1025 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.17.2", + "Node name for S&R": "LTXVSeparateAVLatent" + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 336, + "type": "LTXVLatentUpsampler", + "pos": [ + 160, + 7410 + ], + "size": [ + 320, + 66 + ], + "flags": {}, + "order": 9, + "mode": 0, + "inputs": [ + { + "localized_name": "samples", + "name": "samples", + "type": "LATENT", + "link": 878 + }, + { + "localized_name": "upscale_model", + "name": "upscale_model", + "type": "LATENT_UPSCALE_MODEL", + "link": 1038 + }, + { + "localized_name": "vae", + "name": "vae", + "type": "VAE", + "link": 717 + } + ], + "outputs": [ + { + "localized_name": "LATENT", + "name": "LATENT", + "type": "LATENT", + "links": [ + 713 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.14.1", + "Node name for S&R": "LTXVLatentUpsampler", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 333, + "type": "LatentUpscaleModelLoader", + "pos": [ + -200, + 7300 + ], + "size": [ + 330, + 58 + ], + "flags": {}, + "order": 1, + "mode": 0, + "inputs": [], + "outputs": [ + { + "localized_name": "LATENT_UPSCALE_MODEL", + "name": "LATENT_UPSCALE_MODEL", + "type": "LATENT_UPSCALE_MODEL", + "links": [ + 710 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.16.4", + "Node name for S&R": "LatentUpscaleModelLoader" + }, + "widgets_values": [ + "ltx-2.3-spatial-upscaler-x1.5-1.0.safetensors" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 339, + "type": "PrimitiveBoolean", + "pos": [ + 160, + 7520 + ], + "size": [ + 290, + 60 + ], + "flags": {}, + "order": 10, + "mode": 0, + "inputs": [ + { + "localized_name": "value", + "name": "value", + "type": "BOOLEAN", + "widget": { + "name": "value" + }, + "link": 719 + } + ], + "outputs": [ + { + "localized_name": "BOOLEAN", + "name": "BOOLEAN", + "type": "BOOLEAN", + "links": [ + 718 + ] + } + ], + "title": "3rd Stage: False = 1.5x, True = 2x", + "properties": { + "cnr_id": "comfy-core", + "ver": "0.17.2", + "Node name for S&R": "PrimitiveBoolean" + }, + "widgets_values": [ + true + ], + "color": "#222", + "bgcolor": "#000" + } + ], + "groups": [], + "links": [ + { + "id": 679, + "origin_id": 324, + "origin_slot": 0, + "target_id": 322, + "target_slot": 1, + "type": "GUIDER" + }, + { + "id": 680, + "origin_id": 325, + "origin_slot": 0, + "target_id": 322, + "target_slot": 2, + "type": "SAMPLER" + }, + { + "id": 681, + "origin_id": 326, + "origin_slot": 0, + "target_id": 322, + "target_slot": 3, + "type": "SIGMAS" + }, + { + "id": 683, + "origin_id": -10, + "origin_slot": 0, + "target_id": 324, + "target_slot": 0, + "type": "MODEL" + }, + { + "id": 686, + "origin_id": -10, + "origin_slot": 0, + "target_id": 326, + "target_slot": 0, + "type": "MODEL" + }, + { + "id": 688, + "origin_id": -10, + "origin_slot": 3, + "target_id": 322, + "target_slot": 0, + "type": "NOISE" + }, + { + "id": 697, + "origin_id": 329, + "origin_slot": 0, + "target_id": 322, + "target_slot": 4, + "type": "LATENT" + }, + { + "id": 700, + "origin_id": -10, + "origin_slot": 7, + "target_id": 329, + "target_slot": 1, + "type": "LATENT" + }, + { + "id": 708, + "origin_id": -10, + "origin_slot": 6, + "target_id": 331, + "target_slot": 2, + "type": "LATENT" + }, + { + "id": 710, + "origin_id": 333, + "origin_slot": 0, + "target_id": 334, + "target_slot": 1, + "type": "LATENT_UPSCALE_MODEL" + }, + { + "id": 712, + "origin_id": 334, + "origin_slot": 0, + "target_id": 332, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 713, + "origin_id": 336, + "origin_slot": 0, + "target_id": 332, + "target_slot": 1, + "type": "LATENT" + }, + { + "id": 716, + "origin_id": -10, + "origin_slot": 4, + "target_id": 334, + "target_slot": 2, + "type": "VAE" + }, + { + "id": 717, + "origin_id": -10, + "origin_slot": 4, + "target_id": 336, + "target_slot": 2, + "type": "VAE" + }, + { + "id": 718, + "origin_id": 339, + "origin_slot": 0, + "target_id": 332, + "target_slot": 2, + "type": "BOOLEAN" + }, + { + "id": 719, + "origin_id": -10, + "origin_slot": 8, + "target_id": 339, + "target_slot": 0, + "type": "BOOLEAN" + }, + { + "id": 877, + "origin_id": 331, + "origin_slot": 2, + "target_id": 334, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 878, + "origin_id": 331, + "origin_slot": 2, + "target_id": 336, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 919, + "origin_id": -10, + "origin_slot": 1, + "target_id": 331, + "target_slot": 0, + "type": "CONDITIONING" + }, + { + "id": 920, + "origin_id": -10, + "origin_slot": 2, + "target_id": 331, + "target_slot": 1, + "type": "CONDITIONING" + }, + { + "id": 996, + "origin_id": 411, + "origin_slot": 0, + "target_id": 324, + "target_slot": 1, + "type": "CONDITIONING" + }, + { + "id": 997, + "origin_id": 411, + "origin_slot": 1, + "target_id": 324, + "target_slot": 2, + "type": "CONDITIONING" + }, + { + "id": 998, + "origin_id": 411, + "origin_slot": 2, + "target_id": 329, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 999, + "origin_id": 331, + "origin_slot": 0, + "target_id": 411, + "target_slot": 0, + "type": "CONDITIONING" + }, + { + "id": 1000, + "origin_id": 331, + "origin_slot": 1, + "target_id": 411, + "target_slot": 1, + "type": "CONDITIONING" + }, + { + "id": 1001, + "origin_id": 332, + "origin_slot": 0, + "target_id": 411, + "target_slot": 3, + "type": "LATENT" + }, + { + "id": 1002, + "origin_id": -10, + "origin_slot": 4, + "target_id": 411, + "target_slot": 2, + "type": "VAE" + }, + { + "id": 1005, + "origin_id": -10, + "origin_slot": 9, + "target_id": 411, + "target_slot": 4, + "type": "IMAGE" + }, + { + "id": 1023, + "origin_id": 322, + "origin_slot": 0, + "target_id": 417, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 1024, + "origin_id": 417, + "origin_slot": 0, + "target_id": -20, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 1025, + "origin_id": 417, + "origin_slot": 1, + "target_id": -20, + "target_slot": 1, + "type": "LATENT" + }, + { + "id": 1038, + "origin_id": -10, + "origin_slot": 10, + "target_id": 336, + "target_slot": 1, + "type": "LATENT_UPSCALE_MODEL" + } + ], + "extra": { + "workflowRendererVersion": "LG" + } + }, + { + "id": "958b2c7a-1f75-4a30-84a8-892bddb9e426", + "version": 1, + "state": { + "lastGroupId": 33, + "lastNodeId": 443, + "lastLinkId": 1079, + "lastRerouteId": 0 + }, + "revision": 0, + "config": {}, + "name": "Model Loader", + "inputNode": { + "id": -10, + "bounding": [ + 2340, + 6080, + 135.654296875, + 80 + ] + }, + "outputNode": { + "id": -20, + "bounding": [ + 3560, + 5580, + 187.052734375, + 200 + ] + }, + "inputs": [ + { + "id": "9ae347e5-8a40-4407-bec5-7781d672dd85", + "name": "value", + "type": "STRING", + "linkIds": [ + 1064 + ], + "label": "Prompt", + "pos": [ + 2455.654296875, + 6100 + ] + }, + { + "id": "32b8ff31-4396-4336-aeaf-ff84d858a4d9", + "name": "value_1", + "type": "INT", + "linkIds": [ + 1018 + ], + "label": "length (seconds)", + "pos": [ + 2455.654296875, + 6120 + ] + } + ], + "outputs": [ + { + "id": "a72640aa-8055-45bb-8cbb-f68ec7413515", + "name": "optimized_model", + "type": "MODEL", + "linkIds": [ + 1067 + ], + "pos": [ + 3580, + 5600 + ] + }, + { + "id": "cfb48567-9b75-46f5-8b71-385f3388173c", + "name": "VAE", + "type": "VAE", + "linkIds": [ + 721 + ], + "label": "video_vae", + "pos": [ + 3580, + 5620 + ] + }, + { + "id": "4e095394-8a92-47e4-8993-49fb4ce73bd8", + "name": "VAE_1", + "type": "VAE", + "linkIds": [ + 722 + ], + "label": "audio_vae", + "pos": [ + 3580, + 5640 + ] + }, + { + "id": "8bbe34b1-d094-40a3-9c04-5a19cd7b2af2", + "name": "positive", + "type": "CONDITIONING", + "linkIds": [ + 726 + ], + "pos": [ + 3580, + 5660 + ] + }, + { + "id": "dac0ad69-373b-4e47-b0cd-eb8fd18e1c07", + "name": "negative", + "type": "CONDITIONING", + "linkIds": [ + 727 + ], + "pos": [ + 3580, + 5680 + ] + }, + { + "id": "b4d0868d-86e8-42ba-83fc-44c5324ac35d", + "name": "INT", + "type": "INT", + "linkIds": [ + 729 + ], + "label": "length_seconds", + "pos": [ + 3580, + 5700 + ] + }, + { + "id": "12a111ff-cd34-4b59-bca3-4c5f24626a62", + "name": "NOISE", + "type": "NOISE", + "linkIds": [ + 730 + ], + "label": "noise", + "pos": [ + 3580, + 5720 + ] + }, + { + "id": "9f03af49-218d-4431-a2e9-891d16a8eb05", + "name": "LATENT_UPSCALE_MODEL", + "type": "LATENT_UPSCALE_MODEL", + "linkIds": [ + 1034 + ], + "label": "2x_spatial_upscale_model", + "pos": [ + 3580, + 5740 + ] + } + ], + "widgets": [], + "nodes": [ + { + "id": 338, + "type": "VAELoaderKJ", + "pos": [ + 2560, + 5780 + ], + "size": [ + 270, + 106 + ], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [], + "outputs": [ + { + "localized_name": "VAE", + "name": "VAE", + "type": "VAE", + "links": [ + 722 + ] + } + ], + "title": "Audio VAELoader KJ", + "properties": { + "cnr_id": "comfyui-kjnodes", + "ver": "487c7d86a0230aae3d8c0a37d517159b73834f85", + "Node name for S&R": "VAELoaderKJ" + }, + "widgets_values": [ + "LTX23_audio_vae_bf16.safetensors", + "main_device", + "bf16" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 337, + "type": "VAELoaderKJ", + "pos": [ + 2560, + 5630 + ], + "size": [ + 270, + 106 + ], + "flags": {}, + "order": 1, + "mode": 0, + "inputs": [], + "outputs": [ + { + "localized_name": "VAE", + "name": "VAE", + "type": "VAE", + "links": [ + 721 + ] + } + ], + "title": "Video VAELoader KJ", + "properties": { + "cnr_id": "comfyui-kjnodes", + "ver": "1.2.5", + "Node name for S&R": "VAELoaderKJ" + }, + "widgets_values": [ + "LTX23_video_vae_bf16.safetensors", + "main_device", + "bf16" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 413, + "type": "LTXAVTextEncoderLoader", + "pos": [ + 2560, + 5940 + ], + "size": [ + 318.444921875, + 122 + ], + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [], + "outputs": [ + { + "localized_name": "CLIP", + "name": "CLIP", + "type": "CLIP", + "links": [ + 1054 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.14.1", + "Node name for S&R": "LTXAVTextEncoderLoader" + }, + "widgets_values": [ + "gemma_3_12B_it_fp4_mixed.safetensors", + "ltx-2.3-22b-dev-fp8.safetensors", + "default" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 430, + "type": "CLIPTextEncode", + "pos": [ + 2910, + 5950 + ], + "size": [ + 274.3658203125, + 88 + ], + "flags": {}, + "order": 13, + "mode": 0, + "inputs": [ + { + "localized_name": "clip", + "name": "clip", + "type": "CLIP", + "link": 1054 + }, + { + "localized_name": "text", + "name": "text", + "type": "STRING", + "widget": { + "name": "text" + }, + "link": 1064 + } + ], + "outputs": [ + { + "localized_name": "CONDITIONING", + "name": "CONDITIONING", + "type": "CONDITIONING", + "slot_index": 0, + "links": [ + 1062, + 1063 + ] + } + ], + "title": "CLIP Text Encode (Positive Prompt)", + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.28", + "Node name for S&R": "CLIPTextEncode" + }, + "widgets_values": [ + "" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 361, + "type": "ConditioningZeroOut", + "pos": [ + 2920, + 6090 + ], + "size": [ + 204.134765625, + 26 + ], + "flags": {}, + "order": 10, + "mode": 0, + "inputs": [ + { + "localized_name": "conditioning", + "name": "conditioning", + "type": "CONDITIONING", + "link": 1063 + } + ], + "outputs": [ + { + "localized_name": "CONDITIONING", + "name": "CONDITIONING", + "type": "CONDITIONING", + "links": [ + 725 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.7.0", + "Node name for S&R": "ConditioningZeroOut", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 363, + "type": "LTXVConditioning", + "pos": [ + 3210, + 5960 + ], + "size": [ + 210, + 80 + ], + "flags": {}, + "order": 11, + "mode": 0, + "inputs": [ + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "link": 1062 + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "link": 725 + } + ], + "outputs": [ + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "links": [ + 726 + ] + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "links": [ + 727 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.56", + "Node name for S&R": "LTXVConditioning", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [ + 24 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 419, + "type": "LatentUpscaleModelLoader", + "pos": [ + 2560, + 6110 + ], + "size": [ + 330, + 58 + ], + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [], + "outputs": [ + { + "localized_name": "LATENT_UPSCALE_MODEL", + "name": "LATENT_UPSCALE_MODEL", + "type": "LATENT_UPSCALE_MODEL", + "links": [ + 1034 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.16.4", + "Node name for S&R": "LatentUpscaleModelLoader" + }, + "widgets_values": [ + "ltx-2.3-spatial-upscaler-x2-1.1.safetensors" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 364, + "type": "RandomNoise", + "pos": [ + 2860, + 5750 + ], + "size": [ + 210, + 82 + ], + "flags": {}, + "order": 4, + "mode": 0, + "inputs": [ + { + "label": "noise_seed", + "localized_name": "noise_seed", + "name": "noise_seed", + "type": "INT", + "widget": { + "name": "noise_seed" + }, + "link": null + } + ], + "outputs": [ + { + "localized_name": "NOISE", + "name": "NOISE", + "type": "NOISE", + "links": [ + 730 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.56", + "Node name for S&R": "RandomNoise", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65 + }, + "widgets_values": [ + 0, + "fixed" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 365, + "type": "07609e03-45f4-49c7-a233-a933ebf95094", + "pos": [ + 2860, + 5640 + ], + "size": [ + 210, + 58 + ], + "flags": {}, + "order": 12, + "mode": 0, + "inputs": [ + { + "label": "Seconds", + "name": "value", + "type": "INT", + "widget": { + "name": "value" + }, + "link": 1018 + } + ], + "outputs": [ + { + "localized_name": "INT", + "name": "INT", + "type": "INT", + "links": [ + 729 + ] + } + ], + "properties": { + "proxyWidgets": [ + [ + "-1", + "value" + ] + ], + "cnr_id": "comfy-core", + "ver": "0.16.4" + }, + "widgets_values": [ + 5 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 443, + "type": "VAELoaderKJ", + "pos": [ + 2560, + 5470 + ], + "size": [ + 270, + 110 + ], + "flags": {}, + "order": 5, + "mode": 0, + "inputs": [], + "outputs": [ + { + "localized_name": "VAE", + "name": "VAE", + "type": "VAE", + "links": [ + 1072 + ] + } + ], + "title": "Tiny VAELoader KJ", + "properties": { + "cnr_id": "comfyui-kjnodes", + "ver": "1.2.5", + "Node name for S&R": "VAELoaderKJ" + }, + "widgets_values": [ + "taeltx2_3.safetensors", + "main_device", + "bf16" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 442, + "type": "LTX2SamplingPreviewOverride", + "pos": [ + 2850, + 5470 + ], + "size": [ + 270, + 100 + ], + "flags": {}, + "order": 8, + "mode": 0, + "inputs": [ + { + "localized_name": "model", + "name": "model", + "type": "MODEL", + "link": 1073 + }, + { + "localized_name": "latent_upscale_model", + "name": "latent_upscale_model", + "shape": 7, + "type": "LATENT_UPSCALE_MODEL", + "link": null + }, + { + "localized_name": "vae", + "name": "vae", + "shape": 7, + "type": "VAE", + "link": 1072 + } + ], + "outputs": [ + { + "localized_name": "MODEL", + "name": "MODEL", + "type": "MODEL", + "links": [ + 1074 + ] + } + ], + "properties": { + "cnr_id": "comfyui-kjnodes", + "ver": "1.3.4", + "Node name for S&R": "LTX2SamplingPreviewOverride" + }, + "widgets_values": [ + 16 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 426, + "type": "CheckpointLoaderSimple", + "pos": [ + 2560, + 5330 + ], + "size": [ + 270, + 98 + ], + "flags": {}, + "order": 6, + "mode": 0, + "inputs": [], + "outputs": [ + { + "localized_name": "MODEL", + "name": "MODEL", + "type": "MODEL", + "links": [ + 1073 + ] + }, + { + "localized_name": "CLIP", + "name": "CLIP", + "type": "CLIP", + "links": null + }, + { + "localized_name": "VAE", + "name": "VAE", + "type": "VAE", + "links": null + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.17.2", + "Node name for S&R": "CheckpointLoaderSimple" + }, + "widgets_values": [ + "ltx-2.3-22b-dev-fp8.safetensors" + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 429, + "type": "LoraLoaderModelOnly", + "pos": [ + 3140, + 5480 + ], + "size": [ + 260, + 82 + ], + "flags": {}, + "order": 9, + "mode": 0, + "inputs": [ + { + "localized_name": "model", + "name": "model", + "type": "MODEL", + "link": 1074 + } + ], + "outputs": [ + { + "localized_name": "MODEL", + "name": "MODEL", + "type": "MODEL", + "links": [ + 1067 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.75", + "Node name for S&R": "LoraLoaderModelOnly", + "enableTabs": false, + "tabWidth": 65, + "tabXOffset": 10, + "hasSecondTab": false, + "secondTabText": "Send Back", + "secondTabOffset": 80, + "secondTabWidth": 65, + "models": [ + { + "name": "ltx-2.3-22b-distilled-lora-384.safetensors", + "url": "https://huggingface.co/Lightricks/LTX-2.3/resolve/main/ltx-2.3-22b-distilled-lora-384.safetensors", + "directory": "loras" + } + ] + }, + "widgets_values": [ + "ltx2\\ltx-2.3-22b-distilled-lora-dynamic_fro09_avg_rank_105_bf16.safetensors", + 0.5 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 441, + "type": "MarkdownNote", + "pos": [ + 2160, + 5500 + ], + "size": [ + 370, + 320 + ], + "flags": {}, + "order": 7, + "mode": 0, + "inputs": [], + "outputs": [], + "title": "FAQ", + "properties": {}, + "widgets_values": [ + "# FAQ\n\n**How do I set the length of the video?**\n\nYou can set the length of the video in the Model Loader Subgraph →\n\n**I keep getting errors when running this workflow!**\n\nDouble check that all of the correct models loaded. This is using the newest version of 2x upscaler (v1.1). You can change it to the older one under the Model Loader.\n\n**Where do I download these models??**\n\nThere is a good list of models and download links here: \n\nAlso the latest v1.1 spatial upscaler can be found here: \n\nThe tiny vae (taeltx2_3.safetensors) is found here:\n\n\n\n\n\n\n\n\n\n" + ], + "color": "#432", + "bgcolor": "#653" + } + ], + "groups": [], + "links": [ + { + "id": 721, + "origin_id": 337, + "origin_slot": 0, + "target_id": -20, + "target_slot": 1, + "type": "VAE" + }, + { + "id": 722, + "origin_id": 338, + "origin_slot": 0, + "target_id": -20, + "target_slot": 2, + "type": "VAE" + }, + { + "id": 725, + "origin_id": 361, + "origin_slot": 0, + "target_id": 363, + "target_slot": 1, + "type": "CONDITIONING" + }, + { + "id": 726, + "origin_id": 363, + "origin_slot": 0, + "target_id": -20, + "target_slot": 3, + "type": "CONDITIONING" + }, + { + "id": 727, + "origin_id": 363, + "origin_slot": 1, + "target_id": -20, + "target_slot": 4, + "type": "CONDITIONING" + }, + { + "id": 729, + "origin_id": 365, + "origin_slot": 0, + "target_id": -20, + "target_slot": 5, + "type": "INT" + }, + { + "id": 730, + "origin_id": 364, + "origin_slot": 0, + "target_id": -20, + "target_slot": 6, + "type": "NOISE" + }, + { + "id": 1018, + "origin_id": -10, + "origin_slot": 1, + "target_id": 365, + "target_slot": 0, + "type": "INT" + }, + { + "id": 1034, + "origin_id": 419, + "origin_slot": 0, + "target_id": -20, + "target_slot": 7, + "type": "LATENT_UPSCALE_MODEL" + }, + { + "id": 1054, + "origin_id": 413, + "origin_slot": 0, + "target_id": 430, + "target_slot": 0, + "type": "CLIP" + }, + { + "id": 1062, + "origin_id": 430, + "origin_slot": 0, + "target_id": 363, + "target_slot": 0, + "type": "CONDITIONING" + }, + { + "id": 1063, + "origin_id": 430, + "origin_slot": 0, + "target_id": 361, + "target_slot": 0, + "type": "CONDITIONING" + }, + { + "id": 1064, + "origin_id": -10, + "origin_slot": 0, + "target_id": 430, + "target_slot": 1, + "type": "STRING" + }, + { + "id": 1067, + "origin_id": 429, + "origin_slot": 0, + "target_id": -20, + "target_slot": 0, + "type": "MODEL" + }, + { + "id": 1072, + "origin_id": 443, + "origin_slot": 0, + "target_id": 442, + "target_slot": 2, + "type": "VAE" + }, + { + "id": 1073, + "origin_id": 426, + "origin_slot": 0, + "target_id": 442, + "target_slot": 0, + "type": "MODEL" + }, + { + "id": 1074, + "origin_id": 442, + "origin_slot": 0, + "target_id": 429, + "target_slot": 0, + "type": "MODEL" + } + ], + "extra": { + "workflowRendererVersion": "LG" + } + }, + { + "id": "07609e03-45f4-49c7-a233-a933ebf95094", + "version": 1, + "state": { + "lastGroupId": 33, + "lastNodeId": 443, + "lastLinkId": 1079, + "lastRerouteId": 0 + }, + "revision": 0, + "config": {}, + "name": "length (seconds)", + "inputNode": { + "id": -10, + "bounding": [ + -662.1270448096031, + 4310.82566166648, + 120, + 60 + ] + }, + "outputNode": { + "id": -20, + "bounding": [ + 255.79328565979654, + 4300.82566166648, + 120, + 60 + ] + }, + "inputs": [ + { + "id": "df88c383-3299-420f-8c75-fd0d6a39a9af", + "name": "value", + "type": "INT", + "linkIds": [ + 1071 + ], + "label": "Seconds", + "pos": [ + -562.1270448096031, + 4330.82566166648 + ] + } + ], + "outputs": [ + { + "id": "9b5a4dc0-bb87-45a3-bd3d-551edc18c371", + "name": "INT", + "type": "INT", + "linkIds": [ + 359 + ], + "localized_name": "INT", + "pos": [ + 275.79328565979654, + 4320.82566166648 + ] + } + ], + "widgets": [], + "nodes": [ + { + "id": 137, + "type": "easy mathInt", + "pos": [ + -244.96788124271472, + 4296.913950056486 + ], + "size": [ + 210, + 106 + ], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [ + { + "localized_name": "a", + "name": "a", + "type": "INT", + "widget": { + "name": "a" + }, + "link": 360 + } + ], + "outputs": [ + { + "localized_name": "INT", + "name": "INT", + "type": "INT", + "links": [ + 358 + ] + } + ], + "properties": { + "cnr_id": "comfyui-easy-use", + "ver": "1.3.4", + "Node name for S&R": "easy mathInt" + }, + "widgets_values": [ + 0, + 24, + "multiply" + ] + }, + { + "id": 138, + "type": "easy mathInt", + "pos": [ + -14.206714340203394, + 4288.737373276474 + ], + "size": [ + 210, + 106 + ], + "flags": {}, + "order": 1, + "mode": 0, + "inputs": [ + { + "localized_name": "a", + "name": "a", + "type": "INT", + "widget": { + "name": "a" + }, + "link": 358 + } + ], + "outputs": [ + { + "localized_name": "INT", + "name": "INT", + "type": "INT", + "links": [ + 359 + ] + } + ], + "properties": { + "cnr_id": "comfyui-easy-use", + "ver": "1.3.4", + "Node name for S&R": "easy mathInt" + }, + "widgets_values": [ + 0, + 1, + "add" + ] + }, + { + "id": 139, + "type": "PrimitiveInt", + "pos": [ + -482.12704480960315, + 4310.54157802317 + ], + "size": [ + 210, + 87.4510511866738 + ], + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [ + { + "localized_name": "value", + "name": "value", + "type": "INT", + "widget": { + "name": "value" + }, + "link": 1071 + } + ], + "outputs": [ + { + "localized_name": "INT", + "name": "INT", + "type": "INT", + "links": [ + 360 + ] + } + ], + "title": "Seconds", + "properties": { + "cnr_id": "comfy-core", + "ver": "0.16.4", + "Node name for S&R": "PrimitiveInt" + }, + "widgets_values": [ + 5, + "fixed" + ] + } + ], + "groups": [], + "links": [ + { + "id": 360, + "origin_id": 139, + "origin_slot": 0, + "target_id": 137, + "target_slot": 0, + "type": "INT" + }, + { + "id": 358, + "origin_id": 137, + "origin_slot": 0, + "target_id": 138, + "target_slot": 0, + "type": "INT" + }, + { + "id": 359, + "origin_id": 138, + "origin_slot": 0, + "target_id": -20, + "target_slot": 0, + "type": "INT" + }, + { + "id": 1071, + "origin_id": -10, + "origin_slot": 0, + "target_id": 139, + "target_slot": 0, + "type": "INT" + } + ], + "extra": { + "workflowRendererVersion": "LG" + } + }, + { + "id": "360d64f7-96cd-42a6-9b13-6eb0387e17d9", + "version": 1, + "state": { + "lastGroupId": 33, + "lastNodeId": 443, + "lastLinkId": 1079, + "lastRerouteId": 0 + }, + "revision": 0, + "config": {}, + "name": "3 Stage Upscale", + "inputNode": { + "id": -10, + "bounding": [ + 4330, + 5940, + 187.052734375, + 240 + ] + }, + "outputNode": { + "id": -20, + "bounding": [ + 5670, + 5940, + 120, + 60 + ] + }, + "inputs": [ + { + "id": "f3d52cbc-fafe-46a6-978c-2eaff2c185c3", + "name": "model", + "type": "MODEL", + "linkIds": [ + 3, + 14, + 23 + ], + "pos": [ + 4497.052734375, + 5960 + ] + }, + { + "id": "d0e8f1b7-b19b-49cd-8a2b-aa4a639857b3", + "name": "positive", + "type": "CONDITIONING", + "linkIds": [ + 4, + 15, + 871 + ], + "pos": [ + 4497.052734375, + 5980 + ] + }, + { + "id": "36e3ff32-f9b2-41d9-94de-9d8d9a91f1a6", + "name": "negative", + "type": "CONDITIONING", + "linkIds": [ + 5, + 16, + 872 + ], + "pos": [ + 4497.052734375, + 6000 + ] + }, + { + "id": "3f2a7956-c7c2-4478-99ad-2487644cbbc2", + "name": "latent_image", + "type": "LATENT", + "linkIds": [ + 6 + ], + "pos": [ + 4497.052734375, + 6020 + ] + }, + { + "id": "cc2edb71-c53a-4056-8a85-5d8199e79c72", + "name": "noise", + "type": "NOISE", + "linkIds": [ + 7, + 17, + 26 + ], + "pos": [ + 4497.052734375, + 6040 + ] + }, + { + "id": "d162c9e7-24cc-4421-a6fb-d253bfae8883", + "name": "vae", + "type": "VAE", + "linkIds": [ + 32, + 33 + ], + "label": "video_vae", + "pos": [ + 4497.052734375, + 6060 + ] + }, + { + "id": "e9cd552e-a79c-4b6e-8508-4ad3a9f4218b", + "name": "image", + "type": "IMAGE", + "linkIds": [ + 29, + 30 + ], + "pos": [ + 4497.052734375, + 6080 + ] + }, + { + "id": "46ddf61f-c9f3-48d0-9f11-6e64e8521966", + "name": "value", + "type": "BOOLEAN", + "linkIds": [ + 40 + ], + "label": "Upscale 2x on Stage 3", + "pos": [ + 4497.052734375, + 6100 + ] + }, + { + "id": "45629d5b-0676-4430-aa3d-8b0cf516de0a", + "name": "multi_input", + "type": "IMAGE", + "linkIds": [ + 1006, + 1007 + ], + "pos": [ + 4497.052734375, + 6120 + ] + }, + { + "id": "b380a19f-38a6-41ca-be93-072da1e263f3", + "name": "upscale_model", + "type": "LATENT_UPSCALE_MODEL", + "linkIds": [ + 1036, + 1039 + ], + "label": "2x_spatial_upscale_model", + "pos": [ + 4497.052734375, + 6140 + ] + } + ], + "outputs": [ + { + "id": "2fae6e6d-6667-450e-933b-b16e88c7968f", + "name": "output", + "type": "LATENT", + "linkIds": [ + 765 + ], + "label": "av_output", + "pos": [ + 5690, + 5960 + ] + } + ], + "widgets": [], + "nodes": [ + { + "id": 371, + "type": "LTXVConcatAVLatent", + "pos": [ + 5470, + 5950 + ], + "size": [ + 181.308203125, + 46 + ], + "flags": {}, + "order": 4, + "mode": 0, + "inputs": [ + { + "localized_name": "video_latent", + "name": "video_latent", + "type": "LATENT", + "link": 852 + }, + { + "localized_name": "audio_latent", + "name": "audio_latent", + "type": "LATENT", + "link": 1078 + } + ], + "outputs": [ + { + "localized_name": "latent", + "name": "latent", + "type": "LATENT", + "links": [ + 765 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.17.2", + "Node name for S&R": "LTXVConcatAVLatent" + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 383, + "type": "LTXVCropGuides", + "pos": [ + 5200, + 5950 + ], + "size": [ + 260, + 70 + ], + "flags": {}, + "order": 5, + "mode": 0, + "inputs": [ + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "link": 871 + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "link": 872 + }, + { + "localized_name": "latent", + "name": "latent", + "type": "LATENT", + "link": 1026 + } + ], + "outputs": [ + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "links": null + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "links": null + }, + { + "localized_name": "latent", + "name": "latent", + "type": "LATENT", + "links": [ + 852 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.17.2", + "Node name for S&R": "LTXVCropGuides" + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 2, + "type": "afa29944-eb58-40a5-8e76-484f2503fa0c", + "pos": [ + 4540, + 5950 + ], + "size": [ + 190, + 195.734375 + ], + "flags": {}, + "order": 1, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 3 + }, + { + "name": "positive", + "type": "CONDITIONING", + "link": 4 + }, + { + "name": "negative", + "type": "CONDITIONING", + "link": 5 + }, + { + "name": "latent_image", + "type": "LATENT", + "link": 6 + }, + { + "name": "noise", + "type": "NOISE", + "link": 7 + } + ], + "outputs": [ + { + "name": "video_latent", + "type": "LATENT", + "links": [ + 837 + ] + }, + { + "name": "audio_latent", + "type": "LATENT", + "links": [ + 794 + ] + } + ], + "properties": { + "proxyWidgets": [], + "cnr_id": "comfy-core", + "ver": "0.17.2" + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 440, + "type": "Note", + "pos": [ + 4970, + 5810 + ], + "size": [ + 210, + 88 + ], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [], + "outputs": [], + "properties": {}, + "widgets_values": [ + "Bypass Stage #3 to skip the 2nd upscale stage." + ], + "color": "#223", + "bgcolor": "#335" + }, + { + "id": 6, + "type": "ce8f7842-cb03-41b4-9b1e-8d032b2a4e72", + "pos": [ + 4750, + 5950 + ], + "size": [ + 200, + 299.71875 + ], + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 14 + }, + { + "name": "positive", + "type": "CONDITIONING", + "link": 15 + }, + { + "name": "negative", + "type": "CONDITIONING", + "link": 16 + }, + { + "name": "noise", + "type": "NOISE", + "link": 17 + }, + { + "label": "video_vae", + "name": "vae", + "type": "VAE", + "link": 32 + }, + { + "name": "image", + "type": "IMAGE", + "link": 29 + }, + { + "label": "video_latent", + "name": "samples", + "type": "LATENT", + "link": 837 + }, + { + "name": "audio_latent", + "type": "LATENT", + "link": 794 + }, + { + "name": "multi_input", + "type": "IMAGE", + "link": 1006 + }, + { + "name": "upscale_model", + "type": "LATENT_UPSCALE_MODEL", + "link": 1036 + } + ], + "outputs": [ + { + "name": "video_latent", + "type": "LATENT", + "links": [ + 27 + ] + }, + { + "name": "audio_latent", + "type": "LATENT", + "links": [ + 1079 + ] + }, + { + "name": "positive", + "type": "CONDITIONING", + "links": [ + 855, + 875 + ] + }, + { + "name": "negative", + "type": "CONDITIONING", + "links": [ + 856, + 876 + ] + } + ], + "properties": { + "proxyWidgets": [], + "cnr_id": "comfy-core", + "ver": "0.17.2" + }, + "widgets_values": [], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 8, + "type": "535e663f-da59-47c1-aab5-c78ff202cb16", + "pos": [ + 4970, + 5950 + ], + "size": [ + 218.19140625, + 330.9668884277344 + ], + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 23 + }, + { + "name": "positive", + "type": "CONDITIONING", + "link": 875 + }, + { + "name": "negative", + "type": "CONDITIONING", + "link": 876 + }, + { + "name": "noise", + "type": "NOISE", + "link": 26 + }, + { + "label": "video_vae", + "name": "vae", + "type": "VAE", + "link": 33 + }, + { + "name": "image", + "type": "IMAGE", + "link": 30 + }, + { + "label": "video_latent", + "name": "samples", + "type": "LATENT", + "link": 27 + }, + { + "name": "audio_latent", + "type": "LATENT", + "link": 1079 + }, + { + "label": "use 2x on stage 3", + "name": "value", + "type": "BOOLEAN", + "widget": { + "name": "value" + }, + "link": 40 + }, + { + "name": "multi_input", + "type": "IMAGE", + "link": 1007 + }, + { + "label": "2x_upscale_model", + "name": "upscale_model", + "type": "LATENT_UPSCALE_MODEL", + "link": 1039 + } + ], + "outputs": [ + { + "label": "video_latent", + "name": "output", + "type": "LATENT", + "links": [ + 1026 + ] + }, + { + "name": "audio_latent", + "type": "LATENT", + "links": [ + 1078 + ] + } + ], + "properties": { + "proxyWidgets": [ + [ + "-1", + "value" + ] + ], + "cnr_id": "comfy-core", + "ver": "0.17.2" + }, + "widgets_values": [ + true + ], + "color": "#222", + "bgcolor": "#000" + } + ], + "groups": [], + "links": [ + { + "id": 27, + "origin_id": 6, + "origin_slot": 0, + "target_id": 8, + "target_slot": 6, + "type": "LATENT" + }, + { + "id": 3, + "origin_id": -10, + "origin_slot": 0, + "target_id": 2, + "target_slot": 0, + "type": "MODEL" + }, + { + "id": 14, + "origin_id": -10, + "origin_slot": 0, + "target_id": 6, + "target_slot": 0, + "type": "MODEL" + }, + { + "id": 23, + "origin_id": -10, + "origin_slot": 0, + "target_id": 8, + "target_slot": 0, + "type": "MODEL" + }, + { + "id": 4, + "origin_id": -10, + "origin_slot": 1, + "target_id": 2, + "target_slot": 1, + "type": "CONDITIONING" + }, + { + "id": 15, + "origin_id": -10, + "origin_slot": 1, + "target_id": 6, + "target_slot": 1, + "type": "CONDITIONING" + }, + { + "id": 5, + "origin_id": -10, + "origin_slot": 2, + "target_id": 2, + "target_slot": 2, + "type": "CONDITIONING" + }, + { + "id": 16, + "origin_id": -10, + "origin_slot": 2, + "target_id": 6, + "target_slot": 2, + "type": "CONDITIONING" + }, + { + "id": 6, + "origin_id": -10, + "origin_slot": 3, + "target_id": 2, + "target_slot": 3, + "type": "LATENT" + }, + { + "id": 7, + "origin_id": -10, + "origin_slot": 4, + "target_id": 2, + "target_slot": 4, + "type": "NOISE" + }, + { + "id": 17, + "origin_id": -10, + "origin_slot": 4, + "target_id": 6, + "target_slot": 3, + "type": "NOISE" + }, + { + "id": 26, + "origin_id": -10, + "origin_slot": 4, + "target_id": 8, + "target_slot": 3, + "type": "NOISE" + }, + { + "id": 32, + "origin_id": -10, + "origin_slot": 5, + "target_id": 6, + "target_slot": 4, + "type": "VAE" + }, + { + "id": 33, + "origin_id": -10, + "origin_slot": 5, + "target_id": 8, + "target_slot": 4, + "type": "VAE" + }, + { + "id": 29, + "origin_id": -10, + "origin_slot": 6, + "target_id": 6, + "target_slot": 5, + "type": "IMAGE" + }, + { + "id": 30, + "origin_id": -10, + "origin_slot": 6, + "target_id": 8, + "target_slot": 5, + "type": "IMAGE" + }, + { + "id": 40, + "origin_id": -10, + "origin_slot": 7, + "target_id": 8, + "target_slot": 8, + "type": "BOOLEAN" + }, + { + "id": 765, + "origin_id": 371, + "origin_slot": 0, + "target_id": -20, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 794, + "origin_id": 2, + "origin_slot": 1, + "target_id": 6, + "target_slot": 7, + "type": "LATENT" + }, + { + "id": 837, + "origin_id": 2, + "origin_slot": 0, + "target_id": 6, + "target_slot": 6, + "type": "LATENT" + }, + { + "id": 852, + "origin_id": 383, + "origin_slot": 2, + "target_id": 371, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 871, + "origin_id": -10, + "origin_slot": 1, + "target_id": 383, + "target_slot": 0, + "type": "CONDITIONING" + }, + { + "id": 872, + "origin_id": -10, + "origin_slot": 2, + "target_id": 383, + "target_slot": 1, + "type": "CONDITIONING" + }, + { + "id": 875, + "origin_id": 6, + "origin_slot": 2, + "target_id": 8, + "target_slot": 1, + "type": "CONDITIONING" + }, + { + "id": 876, + "origin_id": 6, + "origin_slot": 3, + "target_id": 8, + "target_slot": 2, + "type": "CONDITIONING" + }, + { + "id": 1006, + "origin_id": -10, + "origin_slot": 8, + "target_id": 6, + "target_slot": 8, + "type": "IMAGE" + }, + { + "id": 1007, + "origin_id": -10, + "origin_slot": 8, + "target_id": 8, + "target_slot": 9, + "type": "IMAGE" + }, + { + "id": 1026, + "origin_id": 8, + "origin_slot": 0, + "target_id": 383, + "target_slot": 2, + "type": "LATENT" + }, + { + "id": 1036, + "origin_id": -10, + "origin_slot": 9, + "target_id": 6, + "target_slot": 9, + "type": "LATENT_UPSCALE_MODEL" + }, + { + "id": 1039, + "origin_id": -10, + "origin_slot": 9, + "target_id": 8, + "target_slot": 10, + "type": "LATENT_UPSCALE_MODEL" + }, + { + "id": 1078, + "origin_id": 8, + "origin_slot": 1, + "target_id": 371, + "target_slot": 1, + "type": "LATENT" + }, + { + "id": 1079, + "origin_id": 6, + "origin_slot": 1, + "target_id": 8, + "target_slot": 7, + "type": "LATENT" + } + ], + "extra": { + "workflowRendererVersion": "LG" + } + } + ] + }, + "config": {}, + "extra": { + "frontendVersion": "1.39.19", + "workflowRendererVersion": "LG", + "VHS_latentpreview": false, + "VHS_latentpreviewrate": 0, + "VHS_MetadataImage": true, + "VHS_KeepIntermediate": true, + "ds": { + "scale": 1, + "offset": [ + -2780.1305165727445, + -5466.757245059522 + ] + } + }, + "version": 0.4 } \ No newline at end of file diff --git a/js/load_audio_ui.js b/js/load_audio_ui.js index 5964f3c..7162bb3 100644 --- a/js/load_audio_ui.js +++ b/js/load_audio_ui.js @@ -1,522 +1,522 @@ -import { app } from "../../scripts/app.js"; -import { api } from "../../scripts/api.js"; - -app.registerExtension({ - name: "Comfy.LoadAudioUI", - async beforeRegisterNodeDef(nodeType, nodeData, app) { - if (nodeData.name === "LoadAudioUI") { - const onNodeCreated = nodeType.prototype.onNodeCreated; - const onDrawBackground = nodeType.prototype.onDrawBackground; - - // --- V1 LiteGraph Image Preview Hider --- - nodeType.prototype.onDrawBackground = function (ctx) { - if (onDrawBackground) { - onDrawBackground.apply(this, arguments); - } - }; - - nodeType.prototype.onNodeCreated = function () { - // Ensure standard creation logic runs first - const r = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined; - const node = this; // Capture the node instance - - // Track if we are in the initial loading phase to prevent resetting saved trim values - node._initializing = true; - node._should_reset_trim = false; - - // ==================================================================== - // FIX: HIDE DEFAULT COMFYUI AUDIO PLAYER SAFELY - // ==================================================================== - setTimeout(() => { - if (node.widgets) { - const nativeWidgetIndex = node.widgets.findIndex(w => w.name === "audioUI"); - if (nativeWidgetIndex !== -1) { - const w = node.widgets[nativeWidgetIndex]; - if (w.element) { - w.element.style.display = "none"; - w.element.style.height = "0px"; - w.element.style.position = "absolute"; - w.element.style.pointerEvents = "none"; - } - w.type = "hidden"; - w.hidden = true; - w.computeSize = () => [0, 0]; - - // Only update height to account for the hidden widget, - // preserving the width (whether it's the default 475 or a user-saved value). - const currentWidth = node.size[0]; - const recommendedHeight = node.computeSize()[1]; - node.setSize([currentWidth, recommendedHeight]); - - if (app.graph) { - app.graph.setDirtyCanvas(true, true); - } - } - } - }, 10); - // ==================================================================== - - - // --- THE CORE FIX FOR COMFYUI V2 --- - Object.defineProperty(node, 'imgs', { - get: function() { return undefined; }, - set: function(val) { /* Ignore attempts by ComfyUI to set an image preview */ }, - configurable: true - }); - - // Shared upload handler - const handleFileUpload = async (file) => { - if (!file.type.startsWith('audio/') && !file.type.startsWith('video/')) return false; - try { - const body = new FormData(); - body.append("image", file); - body.append("type", "input"); - body.append("subfolder", ""); - - const resp = await api.fetchApi("/upload/image", { - method: "POST", - body, - }); - - if (resp.status === 200) { - const data = await resp.json(); - const audioWidget = node.widgets && node.widgets.find(w => w.name === "audio"); - if (audioWidget) { - // Manual upload should always reset the trim range - node._should_reset_trim = true; - audioWidget.value = data.name; - if (audioWidget.options && audioWidget.options.values && !audioWidget.options.values.includes(data.name)) { - audioWidget.options.values.push(data.name); - } - if (audioWidget.callback) { - audioWidget.callback(data.name); - } - app.graph.setDirtyCanvas(true, false); - } - } - } catch (err) { - console.error("Error uploading dragged audio file:", err); - } - return true; - }; - - this.onDragDrop = function(e) { - if (e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files.length > 0) { - const file = e.dataTransfer.files[0]; - if (file.type.startsWith('audio/') || file.type.startsWith('video/')) { - handleFileUpload(file); - return true; - } - } - return false; - }; - - // 1. Build the Main Custom HTML Container - const container = document.createElement("div"); - const defaultBg = "rgba(30, 30, 30, 0.9)"; - Object.assign(container.style, { - display: "flex", - flexDirection: "column", - gap: "10px", - width: "100%", - padding: "10px", - boxSizing: "border-box", - background: defaultBg, - borderRadius: "6px", - color: "white", - fontFamily: "sans-serif", - marginTop: "8px", - flexShrink: "0", - transition: "background 0.2s" - }); - - const playerTop = document.createElement("div"); - Object.assign(playerTop.style, { - display: "flex", - justifyContent: "space-between", - alignItems: "center", - padding: "0 2px", - marginBottom: "-4px" - }); - - const playerTitle = document.createElement("span"); - playerTitle.textContent = "No audio selected"; - Object.assign(playerTitle.style, { - fontSize: "11px", - color: "#aaa", - whiteSpace: "nowrap", - overflow: "hidden", - textOverflow: "ellipsis", - maxWidth: "140px" - }); - - const trimLength = document.createElement("span"); - Object.assign(trimLength.style, { - fontSize: "11px", - color: "#38bdf8", - fontWeight: "bold", - background: "rgba(56, 189, 248, 0.1)", - padding: "3px 6px", - borderRadius: "4px", - whiteSpace: "nowrap" - }); - trimLength.textContent = "Trimmed: 0.0s"; - - playerTop.appendChild(playerTitle); - playerTop.appendChild(trimLength); - container.appendChild(playerTop); - - const audioEl = document.createElement("audio"); - audioEl.controls = true; - audioEl.style.width = "100%"; - audioEl.style.height = "40px"; - audioEl.style.outline = "none"; - container.appendChild(audioEl); - - const trimArea = document.createElement("div"); - Object.assign(trimArea.style, { - display: "flex", - flexDirection: "column", - gap: "6px", - background: "rgba(0, 0, 0, 0.35)", - padding: "12px", - borderRadius: "6px", - border: "1px solid rgba(255, 255, 255, 0.05)" - }); - - const timeRuler = document.createElement("div"); - Object.assign(timeRuler.style, { - position: "relative", - width: "100%", - height: "22px", - fontSize: "10px", - color: "#aaa", - pointerEvents: "none", - userSelect: "none" - }); - trimArea.appendChild(timeRuler); - - const sliderBox = document.createElement("div"); - Object.assign(sliderBox.style, { - position: "relative", - width: "100%", - height: "24px", - background: "#111", - borderRadius: "4px", - cursor: "pointer", - userSelect: "none", - boxShadow: "inset 0 1px 3px rgba(0,0,0,0.5)" - }); - - const fill = document.createElement("div"); - Object.assign(fill.style, { - position: "absolute", - height: "100%", - background: "rgba(14, 165, 233, 0.35)", - pointerEvents: "none" - }); - sliderBox.appendChild(fill); - - const createHandle = (color) => { - const h = document.createElement("div"); - Object.assign(h.style, { - position: "absolute", - top: "0", - width: "8px", - height: "100%", - background: color, - transform: "translateX(-50%)", - pointerEvents: "none", - boxShadow: "0 0 4px rgba(0,0,0,0.8)", - borderRadius: "2px" - }); - return h; - }; - - const startHandle = createHandle("#38bdf8"); - const endHandle = createHandle("#38bdf8"); - sliderBox.appendChild(startHandle); - sliderBox.appendChild(endHandle); - trimArea.appendChild(sliderBox); - - container.appendChild(trimArea); - - // 4. Attach container to the node UI - const widget = this.addDOMWidget("audio_ui", "audio_ui", container); - - // --- DEFAULT SIZE FOR NEW NODES --- - this.size = [475, this.computeSize()[1]]; - - widget.computeSize = function(width) { - return [width, 200]; - }; - - // 5. Bind Node Data to UI dynamically - setTimeout(() => { - const audioWidget = node.widgets && node.widgets.find(w => w.name === "audio"); - const startWidget = node.widgets && node.widgets.find(w => w.name === "start_time"); - const endWidget = node.widgets && node.widgets.find(w => w.name === "end_time"); - const durationWidget = node.widgets && node.widgets.find(w => w.name === "duration"); - - let duration = 0; - let dragging = null; - let dragOffset = 0; - let dragSelectionWidth = 0; - let isUpdatingDuration = false; // Flag to prevent infinite loops - - // Hook into the Python-generated native duration widget - if (durationWidget) { - const origCallback = durationWidget.callback; - durationWidget.callback = function(v) { - // If we're internally triggering it, ignore it - if (!duration || isUpdatingDuration) { - if (origCallback) origCallback.apply(this, arguments); - return; - } - - isUpdatingDuration = true; - let d = parseFloat(v) || 0; - if (d < 0) d = 0; - if (d > duration) d = duration; - - let s = startWidget ? parseFloat(startWidget.value) || 0 : 0; - let newStart = s; - let newEnd = s + d; - - // If adding duration pushes past the end of the audio, shift the start time back instead - if (newEnd > duration) { - newEnd = duration; - newStart = duration - d; - } - - if (startWidget) startWidget.value = parseFloat(newStart.toFixed(2)); - if (endWidget) endWidget.value = parseFloat(newEnd.toFixed(2)); - - updateUI(true); - app.graph.setDirtyCanvas(true, false); - - if (origCallback) origCallback.apply(this, arguments); - isUpdatingDuration = false; - }; - } - - if (audioWidget) { - const updateAudio = () => { - if (!audioWidget.value || audioWidget.value === "none") { - playerTitle.textContent = "No audio selected"; - return; - } - let filename = audioWidget.value; - let subfolder = ""; - if (filename.includes("/") || filename.includes("\\")) { - const sep = filename.includes("/") ? "/" : "\\"; - const parts = filename.split(sep); - filename = parts.pop(); - subfolder = parts.join("/"); - } - playerTitle.textContent = filename; - audioEl.src = api.apiURL(`/view?filename=${encodeURIComponent(filename)}&type=input&subfolder=${encodeURIComponent(subfolder)}`); - }; - audioWidget.callback = function() { - // If user manually changes the dropdown, flag for trim reset - if (!node._initializing) { - node._should_reset_trim = true; - } - updateAudio(); - }; - updateAudio(); - } - - container.ondragover = (e) => { - e.preventDefault(); - e.stopPropagation(); - container.style.background = "rgba(14, 165, 233, 0.2)"; - }; - container.ondragleave = (e) => { - e.preventDefault(); - e.stopPropagation(); - container.style.background = defaultBg; - }; - container.ondrop = async (e) => { - e.preventDefault(); - e.stopPropagation(); - container.style.background = defaultBg; - if (e.dataTransfer.files && e.dataTransfer.files.length > 0) { - handleFileUpload(e.dataTransfer.files[0]); - } - }; - - const formatTime = (secs) => { - if (secs < 60) return secs.toFixed(1) + "s"; - const m = Math.floor(secs / 60); - const s = (secs % 60).toFixed(1); - return `${m}:${s.padStart(4, '0')}`; - }; - - const updateRuler = () => { - timeRuler.innerHTML = ''; - if (!duration) return; - const numMajorTicks = 5; - const subTicks = 4; - const totalTicks = (numMajorTicks - 1) * subTicks; - for (let i = 0; i <= totalTicks; i++) { - const pct = i / totalTicks; - const t = duration * pct; - const isMajor = i % subTicks === 0; - const tickWrapper = document.createElement("div"); - Object.assign(tickWrapper.style, { - position: "absolute", left: `${pct * 100}%`, top: "0", - display: "flex", flexDirection: "column", alignItems: "center", transform: "translateX(-50%)" - }); - if (i === 0) { tickWrapper.style.transform = "none"; tickWrapper.style.alignItems = "flex-start"; } - if (i === totalTicks) { tickWrapper.style.transform = "translateX(-100%)"; tickWrapper.style.alignItems = "flex-end"; } - const line = document.createElement("div"); - Object.assign(line.style, { - width: isMajor ? "2px" : "1px", height: isMajor ? "6px" : "4px", - background: isMajor ? "#aaa" : "#555", marginBottom: "2px", borderRadius: "1px" - }); - tickWrapper.appendChild(line); - if (isMajor) { - const label = document.createElement("div"); - label.textContent = formatTime(t); - tickWrapper.appendChild(label); - } - timeRuler.appendChild(tickWrapper); - } - }; - - const updateUI = (syncPlayer = false) => { - if (!duration) return; - let s = startWidget ? parseFloat(startWidget.value) || 0 : 0; - let e = endWidget ? parseFloat(endWidget.value) || 0 : 0; - if (e === 0 || e > duration) e = duration; - if (s > e) s = e; - const sPct = (s / duration) * 100; - const ePct = (e / duration) * 100; - startHandle.style.left = `${sPct}%`; - endHandle.style.left = `${ePct}%`; - fill.style.left = `${sPct}%`; - fill.style.width = `${ePct - sPct}%`; - - // Sync native duration widget seamlessly to match UI handles - const currentDur = parseFloat((e - s).toFixed(2)); - trimLength.textContent = `Trimmed: ${currentDur}s`; - if (durationWidget && durationWidget.value !== currentDur) { - isUpdatingDuration = true; - durationWidget.value = currentDur; - isUpdatingDuration = false; - } - - if (syncPlayer && audioEl.readyState >= 1) { audioEl.currentTime = s; } - }; - - audioEl.onloadedmetadata = () => { - duration = audioEl.duration; - - // Handle trim reset for new audio selection - if (node._should_reset_trim) { - if (startWidget) startWidget.value = 0; - if (endWidget) endWidget.value = parseFloat(duration.toFixed(2)); - node._should_reset_trim = false; - } else { - // Default clamping logic for initial load or out-of-bounds saved values - let e = endWidget ? parseFloat(endWidget.value) || 0 : 0; - if (endWidget && (e === 0 || e > duration)) { - endWidget.value = parseFloat(duration.toFixed(2)); - } - } - - updateRuler(); - updateUI(); - app.graph.setDirtyCanvas(true, false); - }; - - audioEl.ontimeupdate = () => { - if (dragging || !duration) return; - let s = startWidget ? parseFloat(startWidget.value) || 0 : 0; - let e = endWidget ? parseFloat(endWidget.value) || duration : duration; - if (e === 0) e = duration; - if (audioEl.currentTime >= e) { audioEl.pause(); audioEl.currentTime = s; } - }; - - audioEl.onplay = () => { - let s = startWidget ? parseFloat(startWidget.value) || 0 : 0; - let e = endWidget ? parseFloat(endWidget.value) || duration : duration; - if (e === 0) e = duration; - if (audioEl.currentTime < s || audioEl.currentTime >= e) { audioEl.currentTime = s; } - }; - - [startWidget, endWidget].forEach(w => { - if (w) { - const orig = w.callback; - w.callback = function() { updateUI(true); if(orig) orig.apply(this, arguments); }; - } - }); - - sliderBox.onpointerdown = (e) => { - if (!duration) return; - const rect = sliderBox.getBoundingClientRect(); - const x = Math.max(0, Math.min(e.clientX - rect.left, rect.width)); - const val = (x / rect.width) * duration; - let s = startWidget ? parseFloat(startWidget.value) || 0 : 0; - let e_val = endWidget ? parseFloat(endWidget.value) || duration : duration; - - // Define a "handle tolerance" zone (approx 10px on each side) to prioritize resizing over dragging - const handleTolerance = (10 / rect.width) * duration; - - if (val > s + handleTolerance && val < e_val - handleTolerance) { - dragging = 'center'; - dragOffset = val - s; - dragSelectionWidth = e_val - s; - } else if (Math.abs(val - s) < Math.abs(val - e_val)) { - dragging = 'start'; - if(startWidget) startWidget.value = parseFloat(Math.min(val, e_val).toFixed(2)); - } else { - dragging = 'end'; - if(endWidget) endWidget.value = parseFloat(Math.max(val, s).toFixed(2)); - } - updateUI(true); app.graph.setDirtyCanvas(true, false); - sliderBox.setPointerCapture(e.pointerId); - }; - - sliderBox.onpointermove = (e) => { - if (!dragging || !duration) return; - const rect = sliderBox.getBoundingClientRect(); - const x = Math.max(0, Math.min(e.clientX - rect.left, rect.width)); - const val = (x / rect.width) * duration; - if (dragging === 'start') { - let e_val = endWidget ? parseFloat(endWidget.value) || duration : duration; - if(startWidget) startWidget.value = parseFloat(Math.min(val, e_val).toFixed(2)); - } else if (dragging === 'end') { - const s = startWidget ? parseFloat(startWidget.value) || 0 : 0; - if(endWidget) endWidget.value = parseFloat(Math.max(val, s).toFixed(2)); - } else if (dragging === 'center') { - let newStart = val - dragOffset; - let newEnd = newStart + dragSelectionWidth; - - // Clamp to bounds - if (newStart < 0) { - newStart = 0; - newEnd = dragSelectionWidth; - } else if (newEnd > duration) { - newEnd = duration; - newStart = duration - dragSelectionWidth; - } - - if(startWidget) startWidget.value = parseFloat(newStart.toFixed(2)); - if(endWidget) endWidget.value = parseFloat(newEnd.toFixed(2)); - } - updateUI(true); app.graph.setDirtyCanvas(true, false); - }; - - sliderBox.onpointerup = (e) => { dragging = null; sliderBox.releasePointerCapture(e.pointerId); }; - - // Exit initialization phase - setTimeout(() => { node._initializing = false; }, 500); - - }, 100); - return r; - } - } - } +import { app } from "../../scripts/app.js"; +import { api } from "../../scripts/api.js"; + +app.registerExtension({ + name: "Comfy.LoadAudioUI", + async beforeRegisterNodeDef(nodeType, nodeData, app) { + if (nodeData.name === "LoadAudioUI") { + const onNodeCreated = nodeType.prototype.onNodeCreated; + const onDrawBackground = nodeType.prototype.onDrawBackground; + + // --- V1 LiteGraph Image Preview Hider --- + nodeType.prototype.onDrawBackground = function (ctx) { + if (onDrawBackground) { + onDrawBackground.apply(this, arguments); + } + }; + + nodeType.prototype.onNodeCreated = function () { + // Ensure standard creation logic runs first + const r = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined; + const node = this; // Capture the node instance + + // Track if we are in the initial loading phase to prevent resetting saved trim values + node._initializing = true; + node._should_reset_trim = false; + + // ==================================================================== + // FIX: HIDE DEFAULT COMFYUI AUDIO PLAYER SAFELY + // ==================================================================== + setTimeout(() => { + if (node.widgets) { + const nativeWidgetIndex = node.widgets.findIndex(w => w.name === "audioUI"); + if (nativeWidgetIndex !== -1) { + const w = node.widgets[nativeWidgetIndex]; + if (w.element) { + w.element.style.display = "none"; + w.element.style.height = "0px"; + w.element.style.position = "absolute"; + w.element.style.pointerEvents = "none"; + } + w.type = "hidden"; + w.hidden = true; + w.computeSize = () => [0, 0]; + + // Only update height to account for the hidden widget, + // preserving the width (whether it's the default 475 or a user-saved value). + const currentWidth = node.size[0]; + const recommendedHeight = node.computeSize()[1]; + node.setSize([currentWidth, recommendedHeight]); + + if (app.graph) { + app.graph.setDirtyCanvas(true, true); + } + } + } + }, 10); + // ==================================================================== + + + // --- THE CORE FIX FOR COMFYUI V2 --- + Object.defineProperty(node, 'imgs', { + get: function() { return undefined; }, + set: function(val) { /* Ignore attempts by ComfyUI to set an image preview */ }, + configurable: true + }); + + // Shared upload handler + const handleFileUpload = async (file) => { + if (!file.type.startsWith('audio/') && !file.type.startsWith('video/')) return false; + try { + const body = new FormData(); + body.append("image", file); + body.append("type", "input"); + body.append("subfolder", ""); + + const resp = await api.fetchApi("/upload/image", { + method: "POST", + body, + }); + + if (resp.status === 200) { + const data = await resp.json(); + const audioWidget = node.widgets && node.widgets.find(w => w.name === "audio"); + if (audioWidget) { + // Manual upload should always reset the trim range + node._should_reset_trim = true; + audioWidget.value = data.name; + if (audioWidget.options && audioWidget.options.values && !audioWidget.options.values.includes(data.name)) { + audioWidget.options.values.push(data.name); + } + if (audioWidget.callback) { + audioWidget.callback(data.name); + } + app.graph.setDirtyCanvas(true, false); + } + } + } catch (err) { + console.error("Error uploading dragged audio file:", err); + } + return true; + }; + + this.onDragDrop = function(e) { + if (e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files.length > 0) { + const file = e.dataTransfer.files[0]; + if (file.type.startsWith('audio/') || file.type.startsWith('video/')) { + handleFileUpload(file); + return true; + } + } + return false; + }; + + // 1. Build the Main Custom HTML Container + const container = document.createElement("div"); + const defaultBg = "rgba(30, 30, 30, 0.9)"; + Object.assign(container.style, { + display: "flex", + flexDirection: "column", + gap: "10px", + width: "100%", + padding: "10px", + boxSizing: "border-box", + background: defaultBg, + borderRadius: "6px", + color: "white", + fontFamily: "sans-serif", + marginTop: "8px", + flexShrink: "0", + transition: "background 0.2s" + }); + + const playerTop = document.createElement("div"); + Object.assign(playerTop.style, { + display: "flex", + justifyContent: "space-between", + alignItems: "center", + padding: "0 2px", + marginBottom: "-4px" + }); + + const playerTitle = document.createElement("span"); + playerTitle.textContent = "No audio selected"; + Object.assign(playerTitle.style, { + fontSize: "11px", + color: "#aaa", + whiteSpace: "nowrap", + overflow: "hidden", + textOverflow: "ellipsis", + maxWidth: "140px" + }); + + const trimLength = document.createElement("span"); + Object.assign(trimLength.style, { + fontSize: "11px", + color: "#38bdf8", + fontWeight: "bold", + background: "rgba(56, 189, 248, 0.1)", + padding: "3px 6px", + borderRadius: "4px", + whiteSpace: "nowrap" + }); + trimLength.textContent = "Trimmed: 0.0s"; + + playerTop.appendChild(playerTitle); + playerTop.appendChild(trimLength); + container.appendChild(playerTop); + + const audioEl = document.createElement("audio"); + audioEl.controls = true; + audioEl.style.width = "100%"; + audioEl.style.height = "40px"; + audioEl.style.outline = "none"; + container.appendChild(audioEl); + + const trimArea = document.createElement("div"); + Object.assign(trimArea.style, { + display: "flex", + flexDirection: "column", + gap: "6px", + background: "rgba(0, 0, 0, 0.35)", + padding: "12px", + borderRadius: "6px", + border: "1px solid rgba(255, 255, 255, 0.05)" + }); + + const timeRuler = document.createElement("div"); + Object.assign(timeRuler.style, { + position: "relative", + width: "100%", + height: "22px", + fontSize: "10px", + color: "#aaa", + pointerEvents: "none", + userSelect: "none" + }); + trimArea.appendChild(timeRuler); + + const sliderBox = document.createElement("div"); + Object.assign(sliderBox.style, { + position: "relative", + width: "100%", + height: "24px", + background: "#111", + borderRadius: "4px", + cursor: "pointer", + userSelect: "none", + boxShadow: "inset 0 1px 3px rgba(0,0,0,0.5)" + }); + + const fill = document.createElement("div"); + Object.assign(fill.style, { + position: "absolute", + height: "100%", + background: "rgba(14, 165, 233, 0.35)", + pointerEvents: "none" + }); + sliderBox.appendChild(fill); + + const createHandle = (color) => { + const h = document.createElement("div"); + Object.assign(h.style, { + position: "absolute", + top: "0", + width: "8px", + height: "100%", + background: color, + transform: "translateX(-50%)", + pointerEvents: "none", + boxShadow: "0 0 4px rgba(0,0,0,0.8)", + borderRadius: "2px" + }); + return h; + }; + + const startHandle = createHandle("#38bdf8"); + const endHandle = createHandle("#38bdf8"); + sliderBox.appendChild(startHandle); + sliderBox.appendChild(endHandle); + trimArea.appendChild(sliderBox); + + container.appendChild(trimArea); + + // 4. Attach container to the node UI + const widget = this.addDOMWidget("audio_ui", "audio_ui", container); + + // --- DEFAULT SIZE FOR NEW NODES --- + this.size = [475, this.computeSize()[1]]; + + widget.computeSize = function(width) { + return [width, 200]; + }; + + // 5. Bind Node Data to UI dynamically + setTimeout(() => { + const audioWidget = node.widgets && node.widgets.find(w => w.name === "audio"); + const startWidget = node.widgets && node.widgets.find(w => w.name === "start_time"); + const endWidget = node.widgets && node.widgets.find(w => w.name === "end_time"); + const durationWidget = node.widgets && node.widgets.find(w => w.name === "duration"); + + let duration = 0; + let dragging = null; + let dragOffset = 0; + let dragSelectionWidth = 0; + let isUpdatingDuration = false; // Flag to prevent infinite loops + + // Hook into the Python-generated native duration widget + if (durationWidget) { + const origCallback = durationWidget.callback; + durationWidget.callback = function(v) { + // If we're internally triggering it, ignore it + if (!duration || isUpdatingDuration) { + if (origCallback) origCallback.apply(this, arguments); + return; + } + + isUpdatingDuration = true; + let d = parseFloat(v) || 0; + if (d < 0) d = 0; + if (d > duration) d = duration; + + let s = startWidget ? parseFloat(startWidget.value) || 0 : 0; + let newStart = s; + let newEnd = s + d; + + // If adding duration pushes past the end of the audio, shift the start time back instead + if (newEnd > duration) { + newEnd = duration; + newStart = duration - d; + } + + if (startWidget) startWidget.value = parseFloat(newStart.toFixed(2)); + if (endWidget) endWidget.value = parseFloat(newEnd.toFixed(2)); + + updateUI(true); + app.graph.setDirtyCanvas(true, false); + + if (origCallback) origCallback.apply(this, arguments); + isUpdatingDuration = false; + }; + } + + if (audioWidget) { + const updateAudio = () => { + if (!audioWidget.value || audioWidget.value === "none") { + playerTitle.textContent = "No audio selected"; + return; + } + let filename = audioWidget.value; + let subfolder = ""; + if (filename.includes("/") || filename.includes("\\")) { + const sep = filename.includes("/") ? "/" : "\\"; + const parts = filename.split(sep); + filename = parts.pop(); + subfolder = parts.join("/"); + } + playerTitle.textContent = filename; + audioEl.src = api.apiURL(`/view?filename=${encodeURIComponent(filename)}&type=input&subfolder=${encodeURIComponent(subfolder)}`); + }; + audioWidget.callback = function() { + // If user manually changes the dropdown, flag for trim reset + if (!node._initializing) { + node._should_reset_trim = true; + } + updateAudio(); + }; + updateAudio(); + } + + container.ondragover = (e) => { + e.preventDefault(); + e.stopPropagation(); + container.style.background = "rgba(14, 165, 233, 0.2)"; + }; + container.ondragleave = (e) => { + e.preventDefault(); + e.stopPropagation(); + container.style.background = defaultBg; + }; + container.ondrop = async (e) => { + e.preventDefault(); + e.stopPropagation(); + container.style.background = defaultBg; + if (e.dataTransfer.files && e.dataTransfer.files.length > 0) { + handleFileUpload(e.dataTransfer.files[0]); + } + }; + + const formatTime = (secs) => { + if (secs < 60) return secs.toFixed(1) + "s"; + const m = Math.floor(secs / 60); + const s = (secs % 60).toFixed(1); + return `${m}:${s.padStart(4, '0')}`; + }; + + const updateRuler = () => { + timeRuler.innerHTML = ''; + if (!duration) return; + const numMajorTicks = 5; + const subTicks = 4; + const totalTicks = (numMajorTicks - 1) * subTicks; + for (let i = 0; i <= totalTicks; i++) { + const pct = i / totalTicks; + const t = duration * pct; + const isMajor = i % subTicks === 0; + const tickWrapper = document.createElement("div"); + Object.assign(tickWrapper.style, { + position: "absolute", left: `${pct * 100}%`, top: "0", + display: "flex", flexDirection: "column", alignItems: "center", transform: "translateX(-50%)" + }); + if (i === 0) { tickWrapper.style.transform = "none"; tickWrapper.style.alignItems = "flex-start"; } + if (i === totalTicks) { tickWrapper.style.transform = "translateX(-100%)"; tickWrapper.style.alignItems = "flex-end"; } + const line = document.createElement("div"); + Object.assign(line.style, { + width: isMajor ? "2px" : "1px", height: isMajor ? "6px" : "4px", + background: isMajor ? "#aaa" : "#555", marginBottom: "2px", borderRadius: "1px" + }); + tickWrapper.appendChild(line); + if (isMajor) { + const label = document.createElement("div"); + label.textContent = formatTime(t); + tickWrapper.appendChild(label); + } + timeRuler.appendChild(tickWrapper); + } + }; + + const updateUI = (syncPlayer = false) => { + if (!duration) return; + let s = startWidget ? parseFloat(startWidget.value) || 0 : 0; + let e = endWidget ? parseFloat(endWidget.value) || 0 : 0; + if (e === 0 || e > duration) e = duration; + if (s > e) s = e; + const sPct = (s / duration) * 100; + const ePct = (e / duration) * 100; + startHandle.style.left = `${sPct}%`; + endHandle.style.left = `${ePct}%`; + fill.style.left = `${sPct}%`; + fill.style.width = `${ePct - sPct}%`; + + // Sync native duration widget seamlessly to match UI handles + const currentDur = parseFloat((e - s).toFixed(2)); + trimLength.textContent = `Trimmed: ${currentDur}s`; + if (durationWidget && durationWidget.value !== currentDur) { + isUpdatingDuration = true; + durationWidget.value = currentDur; + isUpdatingDuration = false; + } + + if (syncPlayer && audioEl.readyState >= 1) { audioEl.currentTime = s; } + }; + + audioEl.onloadedmetadata = () => { + duration = audioEl.duration; + + // Handle trim reset for new audio selection + if (node._should_reset_trim) { + if (startWidget) startWidget.value = 0; + if (endWidget) endWidget.value = parseFloat(duration.toFixed(2)); + node._should_reset_trim = false; + } else { + // Default clamping logic for initial load or out-of-bounds saved values + let e = endWidget ? parseFloat(endWidget.value) || 0 : 0; + if (endWidget && (e === 0 || e > duration)) { + endWidget.value = parseFloat(duration.toFixed(2)); + } + } + + updateRuler(); + updateUI(); + app.graph.setDirtyCanvas(true, false); + }; + + audioEl.ontimeupdate = () => { + if (dragging || !duration) return; + let s = startWidget ? parseFloat(startWidget.value) || 0 : 0; + let e = endWidget ? parseFloat(endWidget.value) || duration : duration; + if (e === 0) e = duration; + if (audioEl.currentTime >= e) { audioEl.pause(); audioEl.currentTime = s; } + }; + + audioEl.onplay = () => { + let s = startWidget ? parseFloat(startWidget.value) || 0 : 0; + let e = endWidget ? parseFloat(endWidget.value) || duration : duration; + if (e === 0) e = duration; + if (audioEl.currentTime < s || audioEl.currentTime >= e) { audioEl.currentTime = s; } + }; + + [startWidget, endWidget].forEach(w => { + if (w) { + const orig = w.callback; + w.callback = function() { updateUI(true); if(orig) orig.apply(this, arguments); }; + } + }); + + sliderBox.onpointerdown = (e) => { + if (!duration) return; + const rect = sliderBox.getBoundingClientRect(); + const x = Math.max(0, Math.min(e.clientX - rect.left, rect.width)); + const val = (x / rect.width) * duration; + let s = startWidget ? parseFloat(startWidget.value) || 0 : 0; + let e_val = endWidget ? parseFloat(endWidget.value) || duration : duration; + + // Define a "handle tolerance" zone (approx 10px on each side) to prioritize resizing over dragging + const handleTolerance = (10 / rect.width) * duration; + + if (val > s + handleTolerance && val < e_val - handleTolerance) { + dragging = 'center'; + dragOffset = val - s; + dragSelectionWidth = e_val - s; + } else if (Math.abs(val - s) < Math.abs(val - e_val)) { + dragging = 'start'; + if(startWidget) startWidget.value = parseFloat(Math.min(val, e_val).toFixed(2)); + } else { + dragging = 'end'; + if(endWidget) endWidget.value = parseFloat(Math.max(val, s).toFixed(2)); + } + updateUI(true); app.graph.setDirtyCanvas(true, false); + sliderBox.setPointerCapture(e.pointerId); + }; + + sliderBox.onpointermove = (e) => { + if (!dragging || !duration) return; + const rect = sliderBox.getBoundingClientRect(); + const x = Math.max(0, Math.min(e.clientX - rect.left, rect.width)); + const val = (x / rect.width) * duration; + if (dragging === 'start') { + let e_val = endWidget ? parseFloat(endWidget.value) || duration : duration; + if(startWidget) startWidget.value = parseFloat(Math.min(val, e_val).toFixed(2)); + } else if (dragging === 'end') { + const s = startWidget ? parseFloat(startWidget.value) || 0 : 0; + if(endWidget) endWidget.value = parseFloat(Math.max(val, s).toFixed(2)); + } else if (dragging === 'center') { + let newStart = val - dragOffset; + let newEnd = newStart + dragSelectionWidth; + + // Clamp to bounds + if (newStart < 0) { + newStart = 0; + newEnd = dragSelectionWidth; + } else if (newEnd > duration) { + newEnd = duration; + newStart = duration - dragSelectionWidth; + } + + if(startWidget) startWidget.value = parseFloat(newStart.toFixed(2)); + if(endWidget) endWidget.value = parseFloat(newEnd.toFixed(2)); + } + updateUI(true); app.graph.setDirtyCanvas(true, false); + }; + + sliderBox.onpointerup = (e) => { dragging = null; sliderBox.releasePointerCapture(e.pointerId); }; + + // Exit initialization phase + setTimeout(() => { node._initializing = false; }, 500); + + }, 100); + return r; + } + } + } }); \ No newline at end of file diff --git a/js/load_video_ui.js b/js/load_video_ui.js index 51988e7..3182957 100644 --- a/js/load_video_ui.js +++ b/js/load_video_ui.js @@ -1,1514 +1,1514 @@ -import { app } from "../../scripts/app.js"; -import { api } from "../../scripts/api.js"; - -app.registerExtension({ - name: "Comfy.LoadVideoUI", - async beforeRegisterNodeDef(nodeType, nodeData, app) { - if (nodeData.name === "LoadVideoUI") { - const onNodeCreated = nodeType.prototype.onNodeCreated; - const onConfigure = nodeType.prototype.onConfigure; - const onResize = nodeType.prototype.onResize; - const onDrawForeground = nodeType.prototype.onDrawForeground; - - // Hook into workflow loading to instantly restore the video UI - nodeType.prototype.onConfigure = function (info) { - if (onConfigure) { - onConfigure.apply(this, arguments); - } - - // Force UI synchronization - if (this.syncFramesFromTime) this.syncFramesFromTime(); - if (this.toggleWidgetVisibility) this.toggleWidgetVisibility(); - if (this.syncToggleVisual) this.syncToggleVisual(); - - if (this.widgets) { - const videoWidget = this.widgets.find(w => w.name === "video"); - if (videoWidget && videoWidget.value && this.updatePreview) { - this.updatePreview(videoWidget.value); - } - } - }; - - // Continuous frame-accurate check to guarantee exact height alignment - // even on initial graph load when the workflow reloads! - nodeType.prototype.onDrawForeground = function (ctx) { - if (onDrawForeground) onDrawForeground.apply(this, arguments); - - if (this.domWidget && this.domWidget.element && this.domWidget.last_y) { - const remainingHeight = this.size[1] - this.domWidget.last_y - 18; - const currentHeight = parseFloat(this.domWidget.element.style.height); - const targetHeight = Math.max(150, remainingHeight); - - // Only update DOM if the height has drifted by more than 1 pixel - if (isNaN(currentHeight) || Math.abs(currentHeight - targetHeight) > 1) { - this.domWidget.element.style.height = `${targetHeight}px`; - } - } - }; - - // Allow the node to scale nicely when resized by the user - nodeType.prototype.onResize = function (size) { - if (onResize) onResize.apply(this, arguments); - if (this.domWidget && this.domWidget.element) { - // Fill the exact width provided by LiteGraph's bounds natively - this.domWidget.element.style.width = "100%"; - this.domWidget.element.style.margin = "0"; - - // Fallback calc if last_y isn't ready - let yOffset = this.domWidget.last_y; - if (!yOffset) { - yOffset = 30; // Default LiteGraph Title Height - if (this.widgets) { - for (let w of this.widgets) { - if (w === this.domWidget) break; - yOffset += (w.computeSize ? w.computeSize()[1] : 20) + 4; - } - } - } - - const remainingHeight = size[1] - yOffset - 18; - this.domWidget.element.style.height = `${Math.max(150, remainingHeight)}px`; - } - }; - - nodeType.prototype.onNodeCreated = function () { - const r = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined; - const node = this; - - // Find the core widgets - const videoWidget = this.widgets.find((w) => w.name === "video"); - const frameRateWidget = this.widgets.find((w) => w.name === "frame_rate"); - const displayModeWidget = this.widgets.find((w) => w.name === "display_mode"); - - const startTimeWidget = this.widgets.find((w) => w.name === "start_time"); - const endTimeWidget = this.widgets.find((w) => w.name === "end_time"); - const durationWidget = this.widgets.find((w) => w.name === "duration"); - - const startFrameWidget = this.widgets.find((w) => w.name === "start_frame"); - const endFrameWidget = this.widgets.find((w) => w.name === "end_frame"); - const durationFramesWidget = this.widgets.find((w) => w.name === "duration_frames"); - - const cropXWidget = this.widgets.find((w) => w.name === "crop_x"); - const cropYWidget = this.widgets.find((w) => w.name === "crop_y"); - const cropWWidget = this.widgets.find((w) => w.name === "crop_w"); - const cropHWidget = this.widgets.find((w) => w.name === "crop_h"); - - // ==================================================================== - // WIDGET HIDING & SYNC ENGINE - // ==================================================================== - let isSyncing = false; - - function setWidgetVisibility(w, visible, typeStr) { - if (!w) return; - w.hidden = !visible; - if (!visible) { - w.type = "hidden"; - w.computeSize = () => [0, -4]; // Suppresses gap allocation in V1 - } else { - w.type = typeStr; - delete w.computeSize; // Restores standard ComfyUI measurement - } - } - - node.toggleWidgetVisibility = function () { - const isFrames = displayModeWidget && displayModeWidget.value === "frames"; - setWidgetVisibility(startTimeWidget, !isFrames, "FLOAT"); - setWidgetVisibility(endTimeWidget, !isFrames, "FLOAT"); - setWidgetVisibility(durationWidget, !isFrames, "FLOAT"); - setWidgetVisibility(startFrameWidget, isFrames, "INT"); - setWidgetVisibility(endFrameWidget, isFrames, "INT"); - setWidgetVisibility(durationFramesWidget, isFrames, "INT"); - setWidgetVisibility(displayModeWidget, false, "combo"); // Toggle is hidden, driven by UI - - setWidgetVisibility(cropXWidget, false, "FLOAT"); - setWidgetVisibility(cropYWidget, false, "FLOAT"); - setWidgetVisibility(cropWWidget, false, "FLOAT"); - setWidgetVisibility(cropHWidget, false, "FLOAT"); - - // Allow the node to calculate its required min size, but DO NOT overwrite - // the current user-defined width/height unless it's strictly smaller than the minimum. - const minSize = node.computeSize(); - node.size[0] = Math.max(node.size[0], minSize[0]); - node.size[1] = Math.max(node.size[1], minSize[1]); - - if (node.onResize) node.onResize(node.size); - app.graph.setDirtyCanvas(true, true); - }; - - node.syncFramesFromTime = function () { - if (isSyncing || !frameRateWidget) return; - isSyncing = true; - const fr = frameRateWidget.value || 24; - if (startTimeWidget && startFrameWidget) startFrameWidget.value = Math.round(startTimeWidget.value * fr); - if (endTimeWidget && endFrameWidget) endFrameWidget.value = Math.round(endTimeWidget.value * fr); - if (durationWidget && durationFramesWidget) durationFramesWidget.value = Math.round(durationWidget.value * fr); - isSyncing = false; - }; - - node.syncTimeFromFrames = function () { - if (isSyncing || !frameRateWidget) return; - isSyncing = true; - const fr = frameRateWidget.value || 24; - if (startTimeWidget && startFrameWidget) startTimeWidget.value = parseFloat((startFrameWidget.value / fr).toFixed(3)); - if (endTimeWidget && endFrameWidget) endTimeWidget.value = parseFloat((endFrameWidget.value / fr).toFixed(3)); - if (durationWidget && durationFramesWidget) durationFramesWidget.value = parseFloat((durationFramesWidget.value / fr).toFixed(3)); - isSyncing = false; - }; - - // Bind standard input callbacks to synchronize automatically - function bindWidget(w, isFrame, isFrameRate = false) { - if (!w) return; - const orig = w.callback; - w.callback = function () { - if (orig) orig.apply(this, arguments); - if (isFrame) node.syncTimeFromFrames(); - else node.syncFramesFromTime(); - - // Always force a ruler update if framerate changes so the timeline marks match the new rate - if (duration === 0 || isFrameRate) updateRuler(); - updateUI(true); - }; - } - - bindWidget(startTimeWidget, false); - bindWidget(endTimeWidget, false); - bindWidget(startFrameWidget, true); - bindWidget(endFrameWidget, true); - bindWidget(frameRateWidget, false, true); // Triggers re-sync of frames from time AND updates ruler - - // Bind update function to the node so onConfigure can access it - node.updatePreview = function (filename) { - if (!filename) { - return; - } - let url; - - // Check if absolute path (Starts with C:\ or /) - if (filename.match(/^[a-zA-Z]:\\/) || filename.startsWith('/')) { - url = api.apiURL(`/video_ui_custom_view?filename=${encodeURIComponent(filename)}`); - } else { - url = api.apiURL(`/view?filename=${encodeURIComponent(filename)}&type=input`); - } - - if (videoPreview) videoPreview.src = url; - }; - - if (videoWidget) { - const originalCallback = videoWidget.callback; - videoWidget.callback = function () { - if (originalCallback) originalCallback.apply(this, arguments); - if (node.updatePreview) node.updatePreview(this.value); - }; - } - - // Initialize widget visibility right away - if (displayModeWidget && !displayModeWidget.value) displayModeWidget.value = "seconds"; - node.toggleWidgetVisibility(); - - // ==================================================================== - // CHOOSE FILE BUTTON (Native ComfyUI Widget, placed below duration) - // ==================================================================== - const fileInput = document.createElement("input"); - fileInput.type = "file"; - fileInput.accept = "video/*"; - fileInput.style.display = "none"; - document.body.appendChild(fileInput); - - const btnWidget = this.addWidget("button", "choose file to upload", null, () => { - fileInput.click(); - }); - - // Define robust upload logic - const uploadFile = async (file) => { - try { - if (errorMsg) errorMsg.style.display = "none"; - - // Fast Path: If desktop environment exposes absolute file path, skip upload entirely! - if (file.path) { - videoWidget.value = file.path; - node.updatePreview(file.path); - if (startTimeWidget) startTimeWidget.value = 0; - if (endTimeWidget) endTimeWidget.value = 0; - node.syncFramesFromTime(); - return; - } - - btnWidget.name = "Uploading..."; - node.setDirtyCanvas(true, false); - - const CHUNK_SIZE = 10 * 1024 * 1024; // 10MB chunks - - if (file.size > CHUNK_SIZE) { - const totalChunks = Math.ceil(file.size / CHUNK_SIZE); - const safeFileName = file.name.replace(/[^a-zA-Z0-9.\-_]/g, '_'); - const safeName = Date.now() + "_" + safeFileName; - - for (let i = 0; i < totalChunks; i++) { - btnWidget.name = `Uploading... ${Math.round((i / totalChunks) * 100)}%`; - node.setDirtyCanvas(true, false); - - const chunk = file.slice(i * CHUNK_SIZE, (i + 1) * CHUNK_SIZE); - - const formData = new FormData(); - formData.append("file", chunk); - formData.append("filename", safeName); - formData.append("chunk_index", i); - formData.append("total_chunks", totalChunks); - - const resp = await api.fetchApi("/video_ui_upload_chunk", { - method: "POST", - body: formData, - }); - - if (resp.status !== 200) { - throw new Error("Chunk upload failed"); - } - - if (i === totalChunks - 1) { - const data = await resp.json(); - videoWidget.value = data.name; - node.updatePreview(data.name); - if (startTimeWidget) startTimeWidget.value = 0; - if (endTimeWidget) endTimeWidget.value = 0; - node.syncFramesFromTime(); - } - } - } else { - // Standard upload for small files - const body = new FormData(); - body.append("image", file); - - const resp = await api.fetchApi("/upload/image", { - method: "POST", - body: body, - }); - - if (resp.status === 413) { - throw new Error("File too large. Make sure python backend has the chunking update."); - } - - if (resp.status === 200) { - const data = await resp.json(); - videoWidget.value = data.name; - node.updatePreview(data.name); - if (startTimeWidget) startTimeWidget.value = 0; - if (endTimeWidget) endTimeWidget.value = 0; - node.syncFramesFromTime(); - } else { - throw new Error(`Upload failed: ${resp.statusText}`); - } - } - } catch (error) { - console.error("Upload failed", error); - if (errorMsg) { - errorMsg.textContent = "Upload failed. Check console."; - errorMsg.style.display = "block"; - } - } finally { - btnWidget.name = "choose file to upload"; - node.setDirtyCanvas(true, false); - fileInput.value = ""; // reset input - } - }; - - fileInput.addEventListener("change", (e) => { - if (e.target.files.length) { - uploadFile(e.target.files[0]); - } - }); - - // Attach drag & drop directly onto the LiteGraph node canvas frame - node.onDropFile = function (file) { - // Check MIME type or common video file extensions to ensure all videos are caught - if (file.type.startsWith('video/') || file.name.toLowerCase().match(/\.(mp4|webm|mkv|avi|mov|m4v|flv|wmv)$/)) { - uploadFile(file); - return true; - } - return false; - }; - - // Clean up DOM elements strictly tied to this node instance - const originalOnRemove = node.onRemoved; - node.onRemoved = function () { - if (fileInput && fileInput.parentNode) fileInput.parentNode.removeChild(fileInput); - if (originalOnRemove) originalOnRemove.apply(this, arguments); - }; - - // ==================================================================== - // UI CONTAINER (Preview & Timeline Editor) - // ==================================================================== - const container = document.createElement("div"); - const defaultBg = "rgba(30, 30, 30, 0.9)"; - Object.assign(container.style, { - display: "flex", - flexDirection: "column", - gap: "10px", - width: "100%", - margin: "0", - padding: "10px", - boxSizing: "border-box", - background: defaultBg, - borderRadius: "6px", - color: "white", - fontFamily: "sans-serif", - marginTop: "8px", - flexShrink: "0", - transition: "background 0.2s" - }); - - const errorMsg = document.createElement("div"); - Object.assign(errorMsg.style, { - color: "#ff6b6b", - fontSize: "12px", - display: "none", - marginBottom: "4px", - flexShrink: "0", - boxSizing: "border-box" - }); - container.appendChild(errorMsg); - - // Top Bar: Display Mode Toggle & Trimmed Length - const playerTop = document.createElement("div"); - Object.assign(playerTop.style, { - display: "flex", - justifyContent: "space-between", - alignItems: "center", - padding: "0 2px", - marginBottom: "-4px", - flexShrink: "0", - boxSizing: "border-box", - flexWrap: "wrap", // Prevent squishing/overflow by letting it wrap gracefully - gap: "6px", - position: "relative" - }); - - // Toggle Container UI - const toggleWrapper = document.createElement("div"); - Object.assign(toggleWrapper.style, { - display: "flex", - alignItems: "center", - gap: "6px", - background: "rgba(0, 0, 0, 0.2)", - padding: "0 8px", - borderRadius: "4px", - height: "22px", - boxSizing: "border-box" - }); - - const toggleTitle = document.createElement("span"); - toggleTitle.textContent = "Display Mode"; - Object.assign(toggleTitle.style, { - fontSize: "12px", - color: "#38bdf8", - fontWeight: "bold", - whiteSpace: "nowrap" - }); - - // Segmented pill control - const segmentedToggle = document.createElement("div"); - Object.assign(segmentedToggle.style, { - display: "flex", - alignItems: "center", - background: "rgba(0, 0, 0, 0.35)", - border: "1px solid rgba(56, 189, 248, 0.3)", - borderRadius: "4px", - overflow: "hidden", - height: "18px", - flexShrink: "0", - cursor: "pointer" - }); - - const createSegBtn = (label) => { - const btn = document.createElement("span"); - btn.textContent = label; - Object.assign(btn.style, { - fontSize: "11px", - fontWeight: "bold", - padding: "0 8px", - lineHeight: "18px", - color: "rgba(255,255,255,0.45)", - background: "transparent", - transition: "background 0.2s, color 0.2s", - userSelect: "none", - whiteSpace: "nowrap" - }); - return btn; - }; - - const segTime = createSegBtn("Time"); - const segDivider = document.createElement("span"); - segDivider.style.cssText = "width:1px;height:12px;background:rgba(56,189,248,0.25);flex-shrink:0;"; - const segFrames = createSegBtn("Frames"); - - segmentedToggle.appendChild(segTime); - segmentedToggle.appendChild(segDivider); - segmentedToggle.appendChild(segFrames); - - const applySegmentState = (frames) => { - if (frames) { - segTime.style.background = "transparent"; - segTime.style.color = "rgba(255,255,255,0.45)"; - segFrames.style.background = "rgba(37,126,235,0.85)"; - segFrames.style.color = "#fff"; - } else { - segTime.style.background = "rgba(56,189,248,0.85)"; - segTime.style.color = "#fff"; - segFrames.style.background = "transparent"; - segFrames.style.color = "rgba(255,255,255,0.45)"; - } - }; - - // Keep a reference so the init block below can call it - let isFramesMode = false; - applySegmentState(false); // Default: Time is active - - const doToggle = () => { - isFramesMode = !isFramesMode; - applySegmentState(isFramesMode); - - if (displayModeWidget) displayModeWidget.value = isFramesMode ? "frames" : "seconds"; - - // Sync values perfectly on flip - if (isFramesMode) node.syncFramesFromTime(); - else node.syncTimeFromFrames(); - - node.toggleWidgetVisibility(); - updateRuler(); - updateUI(true); - }; - - segmentedToggle.onclick = doToggle; - - // Expose the switch activation so the init requestAnimationFrame below can call it - const switchBox = { onclick: doToggle }; - - // Allow onConfigure (workflow reload) to re-sync the visual highlight - node.syncToggleVisual = function () { - const savedIsFrames = displayModeWidget && displayModeWidget.value === "frames"; - isFramesMode = savedIsFrames; - applySegmentState(savedIsFrames); - }; - - toggleWrapper.appendChild(toggleTitle); - toggleWrapper.appendChild(segmentedToggle); - - const leftContainer = document.createElement("div"); - Object.assign(leftContainer.style, { - flex: "1 1 0%", - display: "flex", - justifyContent: "flex-start", - minWidth: "max-content" - }); - leftContainer.appendChild(toggleWrapper); - playerTop.appendChild(leftContainer); - - const trimLength = document.createElement("span"); - Object.assign(trimLength.style, { - display: "flex", - alignItems: "center", - fontSize: "12px", - color: "#38bdf8", // Always remains blue - fontWeight: "bold", - background: "rgba(56, 189, 248, 0.1)", // Always remains blue - padding: "0 6px", - borderRadius: "4px", - whiteSpace: "nowrap", - height: "22px", - boxSizing: "border-box", - cursor: "pointer" - }); - trimLength.textContent = "Trimmed: 0:00"; - - const cropBtn = document.createElement("button"); - cropBtn.textContent = "Crop"; - Object.assign(cropBtn.style, { - background: "rgba(255, 255, 255, 0.1)", - color: "white", - border: "none", - borderRadius: "4px", - padding: "0 8px", - height: "22px", - fontSize: "12px", - fontWeight: "bold", - cursor: "pointer" - }); - - let isCropVisible = false; - - const cropUIContainer = document.createElement("div"); - Object.assign(cropUIContainer.style, { - display: "flex", alignItems: "center", gap: "6px", zIndex: "11" - }); - - const cropDims = document.createElement("span"); - Object.assign(cropDims.style, { - fontSize: "12px", color: "#38bdf8", fontWeight: "bold", - display: "none", padding: "0 6px", pointerEvents: "none" - }); - - const cropEditContainer = document.createElement("div"); - Object.assign(cropEditContainer.style, { - display: "none", alignItems: "center", gap: "4px" - }); - - const arSelect = document.createElement("select"); - Object.assign(arSelect.style, { - background: "#222", color: "#fff", border: "1px solid #555", - borderRadius: "3px", fontSize: "12px", padding: "2px", outline: "none", - cursor: "pointer" - }); - const ratios = [ - { name: "Freeform", val: 0 }, - { name: "Original", val: -1 }, - { name: "1:1", val: 1 }, - { name: "4:5", val: 4 / 5 }, - { name: "5:4", val: 5 / 4 }, - { name: "16:9", val: 16 / 9 }, - { name: "9:16", val: 9 / 16 }, - { name: "4:3", val: 4 / 3 }, - { name: "3:4", val: 3 / 4 }, - { name: "3:2", val: 3 / 2 }, - { name: "2:3", val: 2 / 3 }, - { name: "2:1", val: 2 }, - { name: "1:2", val: 1 / 2 } - ]; - ratios.forEach(r => { - const opt = document.createElement("option"); - opt.textContent = r.name; - opt.value = r.val; - arSelect.appendChild(opt); - }); - - const wInput = document.createElement("input"); - const hInput = document.createElement("input"); - const inputStyle = { - width: "40px", background: "rgba(0,0,0,0.5)", color: "#38bdf8", - border: "1px solid #555", borderRadius: "3px", fontSize: "12px", - textAlign: "center", padding: "2px", outline: "none" - }; - Object.assign(wInput.style, inputStyle); - Object.assign(hInput.style, inputStyle); - wInput.type = "text"; - hInput.type = "text"; - - const xSpan = document.createElement("span"); - xSpan.textContent = "x"; - xSpan.style.color = "#888"; - xSpan.style.fontSize = "12px"; - - cropEditContainer.appendChild(arSelect); - cropEditContainer.appendChild(wInput); - cropEditContainer.appendChild(xSpan); - cropEditContainer.appendChild(hInput); - - cropUIContainer.appendChild(cropDims); - cropUIContainer.appendChild(cropEditContainer); - playerTop.appendChild(cropUIContainer); - - const rightContainer = document.createElement("div"); - Object.assign(rightContainer.style, { - flex: "1 1 0%", - display: "flex", - justifyContent: "flex-end", - gap: "6px", - minWidth: "max-content" - }); - rightContainer.appendChild(cropBtn); - rightContainer.appendChild(trimLength); - playerTop.appendChild(rightContainer); - - let currentAspectRatio = 0; - - const handleManualDimensionInput = (isWidth) => { - const vw = videoPreview.videoWidth; - const vh = videoPreview.videoHeight; - if (!vw || !vh) return; - - let newW = parseInt(wInput.value) || Math.round((cropWWidget ? parseFloat(cropWWidget.value) || 1 : 1) * vw); - let newH = parseInt(hInput.value) || Math.round((cropHWidget ? parseFloat(cropHWidget.value) || 1 : 1) * vh); - - if (currentAspectRatio > 0) { - if (isWidth) { - newH = Math.round(newW / currentAspectRatio); - } else { - newW = Math.round(newH * currentAspectRatio); - } - } - - newW = Math.max(1, Math.min(newW, vw)); - newH = Math.max(1, Math.min(newH, vh)); - - let cw_val = newW / vw; - let ch_val = newH / vh; - - let cx = cropXWidget ? parseFloat(cropXWidget.value) || 0 : 0; - let cy = cropYWidget ? parseFloat(cropYWidget.value) || 0 : 0; - - if (cx + cw_val > 1) cx = 1 - cw_val; - if (cy + ch_val > 1) cy = 1 - ch_val; - - if (cropXWidget) cropXWidget.value = parseFloat(cx.toFixed(3)); - if (cropYWidget) cropYWidget.value = parseFloat(cy.toFixed(3)); - if (cropWWidget) cropWWidget.value = parseFloat(cw_val.toFixed(3)); - if (cropHWidget) cropHWidget.value = parseFloat(ch_val.toFixed(3)); - - updateCropUI(); - app.graph.setDirtyCanvas(true, false); - }; - - wInput.addEventListener("change", () => handleManualDimensionInput(true)); - hInput.addEventListener("change", () => handleManualDimensionInput(false)); - wInput.addEventListener("keydown", (e) => { if (e.key === "Enter") handleManualDimensionInput(true); }); - hInput.addEventListener("keydown", (e) => { if (e.key === "Enter") handleManualDimensionInput(false); }); - - arSelect.onchange = () => { - currentAspectRatio = parseFloat(arSelect.value); - if (currentAspectRatio === -1 && videoPreview.videoWidth) { - currentAspectRatio = videoPreview.videoWidth / videoPreview.videoHeight; - } - if (currentAspectRatio > 0 && videoPreview.videoWidth) { - const vw = videoPreview.videoWidth; - const vh = videoPreview.videoHeight; - let cw_val = cropWWidget ? parseFloat(cropWWidget.value) || 1 : 1; - let cx = cropXWidget ? parseFloat(cropXWidget.value) || 0 : 0; - let cy = cropYWidget ? parseFloat(cropYWidget.value) || 0 : 0; - - const actualW = cw_val * vw; - let actualH = actualW / currentAspectRatio; - let ch_val = actualH / vh; - - if (ch_val > 1) { - ch_val = 1; - const newActualW = vh * currentAspectRatio; - cw_val = newActualW / vw; - } - if (cy + ch_val > 1) cy = 1 - ch_val; - if (cx + cw_val > 1) cx = 1 - cw_val; - - if (cropXWidget) cropXWidget.value = parseFloat(cx.toFixed(3)); - if (cropYWidget) cropYWidget.value = parseFloat(cy.toFixed(3)); - if (cropWWidget) cropWWidget.value = parseFloat(cw_val.toFixed(3)); - if (cropHWidget) cropHWidget.value = parseFloat(ch_val.toFixed(3)); - - updateCropUI(); - app.graph.setDirtyCanvas(true, false); - } - }; - - cropBtn.onclick = () => { - isCropVisible = !isCropVisible; - cropBtn.style.background = isCropVisible ? "#38bdf8" : "rgba(255, 255, 255, 0.1)"; - cropBtn.style.color = isCropVisible ? "black" : "white"; - if (isCropVisible) { - cropBox.style.display = "block"; - cropEditContainer.style.display = "flex"; - cropDims.style.display = "none"; - } else { - cropBox.style.display = "none"; - cropEditContainer.style.display = "none"; - // updateCropUI handles cropDims visibility when off - } - if (isCropVisible) { - videoPreview.pause(); - videoPreview.controls = false; - } else { - videoPreview.controls = true; - } - updateCropUI(); - }; - - container.appendChild(playerTop); - - // Video Preview Area (Native Controls) - const videoWrapper = document.createElement("div"); - Object.assign(videoWrapper.style, { - position: "relative", - width: "100%", - flexGrow: "1", - minHeight: "0px", - display: "flex", - alignItems: "center", - justifyContent: "center", - background: "#000", - borderRadius: "4px", - overflow: "hidden" - }); - - const videoPreview = document.createElement("video"); - Object.assign(videoPreview.style, { - width: "100%", - height: "100%", - objectFit: "contain", - outline: "none", - boxSizing: "border-box" - }); - videoPreview.controls = true; - videoPreview.controlsList = "nodownload nofullscreen noremoteplayback"; - videoPreview.muted = false; // Changed from true to false so the video starts unmuted - videoWrapper.appendChild(videoPreview); - - const cropBox = document.createElement("div"); - Object.assign(cropBox.style, { - position: "absolute", - border: "2px dashed #38bdf8", - display: "none", - pointerEvents: "auto", - cursor: "move", - boxSizing: "border-box", - boxShadow: "0 0 0 9999px rgba(0, 0, 0, 0.5)", - zIndex: "10", - overflow: "hidden" - }); - - // 3x3 Grid lines - for (let i = 1; i <= 2; i++) { - const vLine = document.createElement("div"); - Object.assign(vLine.style, { - position: "absolute", left: `${i * 33.33}%`, top: "0", bottom: "0", - borderLeft: "1px dashed rgba(255,255,255,0.3)", pointerEvents: "none" - }); - const hLine = document.createElement("div"); - Object.assign(hLine.style, { - position: "absolute", top: `${i * 33.33}%`, left: "0", right: "0", - borderTop: "1px dashed rgba(255,255,255,0.3)", pointerEvents: "none" - }); - cropBox.appendChild(vLine); - cropBox.appendChild(hLine); - } - - const createCropHandle = (cursor, pos, borders) => { - const h = document.createElement("div"); - Object.assign(h.style, { - position: "absolute", - width: "20px", - height: "20px", - background: "transparent", - cursor: cursor, - pointerEvents: "auto", - ...borders, - ...pos - }); - return h; - }; - - const tlHandle = createCropHandle("nwse-resize", { top: "-3px", left: "-3px" }, { borderTop: "6px solid #38bdf8", borderLeft: "6px solid #38bdf8" }); - const trHandle = createCropHandle("nesw-resize", { top: "-3px", right: "-3px" }, { borderTop: "6px solid #38bdf8", borderRight: "6px solid #38bdf8" }); - const blHandle = createCropHandle("nesw-resize", { bottom: "-3px", left: "-3px" }, { borderBottom: "6px solid #38bdf8", borderLeft: "6px solid #38bdf8" }); - const brHandle = createCropHandle("nwse-resize", { bottom: "-3px", right: "-3px" }, { borderBottom: "6px solid #38bdf8", borderRight: "6px solid #38bdf8" }); - - const tmHandle = createCropHandle("ns-resize", { top: "-3px", left: "50%", transform: "translateX(-50%)" }, { borderTop: "6px solid #38bdf8", width: "16px", height: "10px" }); - const bmHandle = createCropHandle("ns-resize", { bottom: "-3px", left: "50%", transform: "translateX(-50%)" }, { borderBottom: "6px solid #38bdf8", width: "16px", height: "10px" }); - const lmHandle = createCropHandle("ew-resize", { top: "50%", left: "-3px", transform: "translateY(-50%)" }, { borderLeft: "6px solid #38bdf8", width: "10px", height: "16px" }); - const rmHandle = createCropHandle("ew-resize", { top: "50%", right: "-3px", transform: "translateY(-50%)" }, { borderRight: "6px solid #38bdf8", width: "10px", height: "16px" }); - - const handles = [tlHandle, trHandle, blHandle, brHandle, tmHandle, bmHandle, lmHandle, rmHandle]; - handles.forEach(h => cropBox.appendChild(h)); - videoWrapper.appendChild(cropBox); - - container.appendChild(videoWrapper); - - // Trim Area (Time Ruler & Slider) - const trimArea = document.createElement("div"); - Object.assign(trimArea.style, { - display: "flex", - flexDirection: "column", - gap: "6px", - background: "rgba(0, 0, 0, 0.35)", - padding: "12px", - borderRadius: "6px", - border: "1px solid rgba(255, 255, 255, 0.05)", - flexShrink: "0", // Prevent timeline from squishing when shrinking node - boxSizing: "border-box" - }); - - const timeRuler = document.createElement("div"); - Object.assign(timeRuler.style, { - position: "relative", - width: "100%", - height: "22px", - fontSize: "11px", - color: "#aaa", - pointerEvents: "none", - userSelect: "none", - boxSizing: "border-box" - }); - trimArea.appendChild(timeRuler); - - const sliderBox = document.createElement("div"); - Object.assign(sliderBox.style, { - position: "relative", - width: "100%", - height: "24px", - background: "#111", - borderRadius: "4px", - cursor: "pointer", - userSelect: "none", - boxShadow: "inset 0 1px 3px rgba(0,0,0,0.5)", - boxSizing: "border-box" - }); - - const fill = document.createElement("div"); - Object.assign(fill.style, { - position: "absolute", - height: "100%", - background: "rgba(14, 165, 233, 0.35)", - pointerEvents: "none" - }); - sliderBox.appendChild(fill); - - const createHandle = (color) => { - const h = document.createElement("div"); - Object.assign(h.style, { - position: "absolute", - top: "0", - width: "8px", - height: "100%", - background: color, - transform: "translateX(-50%)", - pointerEvents: "none", - boxShadow: "0 0 4px rgba(0,0,0,0.8)", - borderRadius: "2px" - }); - return h; - }; - - const startHandle = createHandle("#38bdf8"); - const endHandle = createHandle("#38bdf8"); - sliderBox.appendChild(startHandle); - sliderBox.appendChild(endHandle); - trimArea.appendChild(sliderBox); - - container.appendChild(trimArea); - - // Delay DOM Widget creation to ensure it is added after all standard widgets - setTimeout(() => { - // Add HTML widget to LiteGraph - node.domWidget = node.addDOMWidget("VideoUI", "div", container); - - // Fixed: Return a solid minimum required bounding box. - // Bumped horizontal from 200px to 360px. This natively stops LiteGraph - // from letting the node be squished too thin, completely preventing overlap. - node.domWidget.computeSize = function () { - return [360, 250]; - }; - - // Applies the default creation bounds natively, increased default height - // to match the widgets required height out of the box. - requestAnimationFrame(() => { - if (node.size[0] < 690) { - node.size[0] = 690; - } - - // INCREASE DEFAULT HEIGHT HERE: - // Change the 620 below to adjust the starting height of the node - if (node.size[1] < 740) { - node.size[1] = 740; - } - - // Trigger manual resize call so the vertical math applies instantly - if (node.onResize) node.onResize(node.size); - - // Sync visual toggle to initial data - if (displayModeWidget && displayModeWidget.value === "frames") { - isFramesMode = false; // prime for click - switchBox.onclick(); - } - - app.graph.setDirtyCanvas(true, true); - }); - }, 100); - - // ==================================================================== - // LOGIC & SYNCING - // ==================================================================== - let duration = 0; - let dragging = null; - let dragOffset = 0; - let dragSelectionWidth = 0; - let isUpdatingDuration = false; - - // Crop logic - let cropDragging = null; - let dragStartX = 0; - let dragStartY = 0; - let dragStartCropX = 0; - let dragStartCropY = 0; - let dragStartCropW = 1; - let dragStartCropH = 1; - - const updateCropUI = () => { - const vw = videoPreview.videoWidth; - const vh = videoPreview.videoHeight; - - let cx = cropXWidget ? parseFloat(cropXWidget.value) || 0 : 0; - let cy = cropYWidget ? parseFloat(cropYWidget.value) || 0 : 0; - let cw_val = cropWWidget ? parseFloat(cropWWidget.value) || 1 : 1; - let ch_val = cropHWidget ? parseFloat(cropHWidget.value) || 1 : 1; - - const actualW = vw ? Math.round(cw_val * vw) : 0; - const actualH = vh ? Math.round(ch_val * vh) : 0; - - if (!isCropVisible || !vw) { - cropBox.style.display = "none"; - cropEditContainer.style.display = "none"; - if (cw_val < 0.999 || ch_val < 0.999 || cx > 0.001 || cy > 0.001) { - cropDims.textContent = `Crop: ${actualW}x${actualH}`; - cropDims.style.display = "inline-block"; - } else { - cropDims.style.display = "none"; - } - return; - } - - cropDims.style.display = "none"; - cropEditContainer.style.display = "flex"; - cropBox.style.display = "block"; - - if (document.activeElement !== wInput) wInput.value = actualW; - if (document.activeElement !== hInput) hInput.value = actualH; - - const cw = videoPreview.clientWidth; - const ch = videoPreview.clientHeight; - - const ratio = Math.min(cw / vw, ch / vh); - const renderedW = vw * ratio; - const renderedH = vh * ratio; - const xOffset = (cw - renderedW) / 2; - const yOffset = (ch - renderedH) / 2; - - cropBox.style.left = `${xOffset + cx * renderedW}px`; - cropBox.style.top = `${yOffset + cy * renderedH}px`; - cropBox.style.width = `${cw_val * renderedW}px`; - cropBox.style.height = `${ch_val * renderedH}px`; - }; - - const onCropPointerDown = (e, handle) => { - if (!isCropVisible) return; - e.preventDefault(); - e.stopPropagation(); - cropDragging = handle; - e.target.setPointerCapture(e.pointerId); - - dragStartX = e.clientX; - dragStartY = e.clientY; - dragStartCropX = cropXWidget ? parseFloat(cropXWidget.value) || 0 : 0; - dragStartCropY = cropYWidget ? parseFloat(cropYWidget.value) || 0 : 0; - dragStartCropW = cropWWidget ? parseFloat(cropWWidget.value) || 1 : 1; - dragStartCropH = cropHWidget ? parseFloat(cropHWidget.value) || 1 : 1; - - e.target.addEventListener("pointermove", onCropPointerMove); - e.target.addEventListener("pointerup", onCropPointerUp); - }; - - const onCropPointerMove = (e) => { - if (!cropDragging) return; - e.preventDefault(); - - const vw = videoPreview.videoWidth; - const vh = videoPreview.videoHeight; - const cw = videoPreview.clientWidth; - const ch = videoPreview.clientHeight; - - const ratio = Math.min(cw / vw, ch / vh); - const renderedW = vw * ratio; - const renderedH = vh * ratio; - - const dx = (e.clientX - dragStartX) / renderedW; - const dy = (e.clientY - dragStartY) / renderedH; - - let new_cw = dragStartCropW; - let new_ch = dragStartCropH; - let new_cx = dragStartCropX; - let new_cy = dragStartCropY; - - if (cropDragging === "tl") { - new_cw = dragStartCropW - dx; - new_ch = dragStartCropH - dy; - } else if (cropDragging === "tr") { - new_cw = dragStartCropW + dx; - new_ch = dragStartCropH - dy; - } else if (cropDragging === "bl") { - new_cw = dragStartCropW - dx; - new_ch = dragStartCropH + dy; - } else if (cropDragging === "br") { - new_cw = dragStartCropW + dx; - new_ch = dragStartCropH + dy; - } else if (cropDragging === "tm") { - new_ch = dragStartCropH - dy; - } else if (cropDragging === "bm") { - new_ch = dragStartCropH + dy; - } else if (cropDragging === "lm") { - new_cw = dragStartCropW - dx; - } else if (cropDragging === "rm") { - new_cw = dragStartCropW + dx; - } - - if (currentAspectRatio > 0 && cropDragging !== "center") { - const R = currentAspectRatio * (vh / vw); - if (["tm", "bm"].includes(cropDragging)) { - new_cw = new_ch * R; - new_cx = dragStartCropX + (dragStartCropW - new_cw) / 2; - } else if (["lm", "rm"].includes(cropDragging)) { - new_ch = new_cw / R; - new_cy = dragStartCropY + (dragStartCropH - new_ch) / 2; - } else { - new_ch = new_cw / R; - } - } - - if (cropDragging === "tl") { - new_cx = dragStartCropX + dragStartCropW - new_cw; - new_cy = dragStartCropY + dragStartCropH - new_ch; - } else if (cropDragging === "tr") { - new_cx = dragStartCropX; - new_cy = dragStartCropY + dragStartCropH - new_ch; - } else if (cropDragging === "bl") { - new_cx = dragStartCropX + dragStartCropW - new_cw; - new_cy = dragStartCropY; - } else if (cropDragging === "br") { - new_cx = dragStartCropX; - new_cy = dragStartCropY; - } else if (cropDragging === "tm") { - new_cy = dragStartCropY + dragStartCropH - new_ch; - if (!(currentAspectRatio > 0)) new_cx = dragStartCropX; - } else if (cropDragging === "bm") { - new_cy = dragStartCropY; - if (!(currentAspectRatio > 0)) new_cx = dragStartCropX; - } else if (cropDragging === "lm") { - new_cx = dragStartCropX + dragStartCropW - new_cw; - if (!(currentAspectRatio > 0)) new_cy = dragStartCropY; - } else if (cropDragging === "rm") { - new_cx = dragStartCropX; - if (!(currentAspectRatio > 0)) new_cy = dragStartCropY; - } else if (cropDragging === "center") { - new_cx = dragStartCropX + dx; - new_cy = dragStartCropY + dy; - } - - if (new_cw < 0.02) { - new_cw = 0.02; - if (currentAspectRatio > 0) new_ch = new_cw / (currentAspectRatio * (vh / vw)); - } - if (new_ch < 0.02) { - new_ch = 0.02; - if (currentAspectRatio > 0) new_cw = new_ch * (currentAspectRatio * (vh / vw)); - } - - if (cropDragging === "center") { - new_cx = Math.max(0, Math.min(new_cx, 1 - new_cw)); - new_cy = Math.max(0, Math.min(new_cy, 1 - new_ch)); - } else { - if (new_cx < 0) { - if (["tl", "bl", "lm"].includes(cropDragging)) { new_cw += new_cx; new_cx = 0; } - } - if (new_cy < 0) { - if (["tl", "tr", "tm"].includes(cropDragging)) { new_ch += new_cy; new_cy = 0; } - } - if (new_cx + new_cw > 1) { - if (["tr", "br", "rm"].includes(cropDragging)) new_cw = 1 - new_cx; - } - if (new_cy + new_ch > 1) { - if (["bl", "br", "bm"].includes(cropDragging)) new_ch = 1 - new_cy; - } - - if (currentAspectRatio > 0) { - const R = currentAspectRatio * (vh / vw); - if (new_cw / new_ch > R + 0.001) { - new_cw = new_ch * R; - if (["tl", "bl", "lm"].includes(cropDragging)) new_cx = dragStartCropX + dragStartCropW - new_cw; - } else if (new_cw / new_ch < R - 0.001) { - new_ch = new_cw / R; - if (["tl", "tr", "tm"].includes(cropDragging)) new_cy = dragStartCropY + dragStartCropH - new_ch; - } - } - } - - if (cropXWidget) cropXWidget.value = parseFloat(new_cx.toFixed(3)); - if (cropYWidget) cropYWidget.value = parseFloat(new_cy.toFixed(3)); - if (cropWWidget) cropWWidget.value = parseFloat(new_cw.toFixed(3)); - if (cropHWidget) cropHWidget.value = parseFloat(new_ch.toFixed(3)); - - updateCropUI(); - app.graph.setDirtyCanvas(true, false); - }; - - const onCropPointerUp = (e) => { - cropDragging = null; - e.target.releasePointerCapture(e.pointerId); - e.target.removeEventListener("pointermove", onCropPointerMove); - e.target.removeEventListener("pointerup", onCropPointerUp); - }; - - cropBox.onpointerdown = (e) => { - if (e.target === cropBox) onCropPointerDown(e, "center"); - }; - tlHandle.onpointerdown = (e) => onCropPointerDown(e, "tl"); - trHandle.onpointerdown = (e) => onCropPointerDown(e, "tr"); - blHandle.onpointerdown = (e) => onCropPointerDown(e, "bl"); - brHandle.onpointerdown = (e) => onCropPointerDown(e, "br"); - tmHandle.onpointerdown = (e) => onCropPointerDown(e, "tm"); - bmHandle.onpointerdown = (e) => onCropPointerDown(e, "bm"); - lmHandle.onpointerdown = (e) => onCropPointerDown(e, "lm"); - rmHandle.onpointerdown = (e) => onCropPointerDown(e, "rm"); - - // Add a resize observer to the video wrapper so crop handles stay pinned - const resizeObserver = new ResizeObserver(() => { - if (isCropVisible) updateCropUI(); - }); - resizeObserver.observe(videoWrapper); - - // Ensure we clean up observer - const oldOnRemoved = node.onRemoved; - node.onRemoved = function () { - resizeObserver.disconnect(); - if (oldOnRemoved) oldOnRemoved.apply(this, arguments); - } - - // Smart helper to ensure timeline displays correctly even with no video loaded - const getActiveDuration = () => { - if (duration > 0) return duration; - let e = endTimeWidget ? parseFloat(endTimeWidget.value) || 0 : 0; - let s = startTimeWidget ? parseFloat(startTimeWidget.value) || 0 : 0; - let maxVal = Math.max(e, s); - return maxVal > 0 ? Math.max(maxVal, 1.0) : 1.0; // Default to 1.0 if completely empty - }; - - // Time Duration Hook - if (durationWidget) { - const origCallback = durationWidget.callback; - durationWidget.callback = function (v) { - if (isUpdatingDuration) { - if (origCallback) origCallback.apply(this, arguments); - return; - } - - isUpdatingDuration = true; - const activeDur = getActiveDuration(); - let d = parseFloat(v) || 0; - if (d < 0) d = 0; - if (d > activeDur) d = activeDur; - - let s = startTimeWidget ? parseFloat(startTimeWidget.value) || 0 : 0; - let newStart = s; - let newEnd = s + d; - - if (newEnd > activeDur) { - newEnd = activeDur; - newStart = activeDur - d; - } - - if (startTimeWidget) startTimeWidget.value = parseFloat(newStart.toFixed(2)); - if (endTimeWidget) endTimeWidget.value = parseFloat(newEnd.toFixed(2)); - node.syncFramesFromTime(); - - if (duration === 0) updateRuler(); - updateUI(true); - app.graph.setDirtyCanvas(true, false); - - if (origCallback) origCallback.apply(this, arguments); - isUpdatingDuration = false; - }; - } - - // Frame Duration Hook - if (durationFramesWidget) { - const origCallback = durationFramesWidget.callback; - durationFramesWidget.callback = function (v) { - if (isUpdatingDuration || !frameRateWidget) { - if (origCallback) origCallback.apply(this, arguments); - return; - } - - isUpdatingDuration = true; - const fr = frameRateWidget.value || 24; - const activeDurFrames = Math.round(getActiveDuration() * fr); - - let d = parseInt(v) || 0; - if (d < 0) d = 0; - if (d > activeDurFrames) d = activeDurFrames; - - let s = startFrameWidget ? parseInt(startFrameWidget.value) || 0 : 0; - let newStart = s; - let newEnd = s + d; - - if (newEnd > activeDurFrames) { - newEnd = activeDurFrames; - newStart = activeDurFrames - d; - } - - if (startFrameWidget) startFrameWidget.value = newStart; - if (endFrameWidget) endFrameWidget.value = newEnd; - node.syncTimeFromFrames(); - - if (duration === 0) updateRuler(); - updateUI(true); - app.graph.setDirtyCanvas(true, false); - - if (origCallback) origCallback.apply(this, arguments); - isUpdatingDuration = false; - }; - } - - // Standard Video Player Format HH:MM:SS (only shows hours if it's over an hour long) - const formatTime = (secs) => { - const h = Math.floor(secs / 3600); - const m = Math.floor((secs % 3600) / 60); - const s = Math.floor(secs % 60); - const mStr = m.toString().padStart(2, '0'); - const sStr = s.toString().padStart(2, '0'); - - if (h > 0) { - return `${h}:${mStr}:${sStr}`; - } else { - return `${m}:${sStr}`; - } - }; - - const updateRuler = () => { - timeRuler.innerHTML = ''; - const activeDur = getActiveDuration(); - const numMajorTicks = 5; - const subTicks = 4; - const totalTicks = (numMajorTicks - 1) * subTicks; - - const isFrames = displayModeWidget && displayModeWidget.value === "frames"; - const fr = frameRateWidget ? frameRateWidget.value : 24; - - for (let i = 0; i <= totalTicks; i++) { - const pct = i / totalTicks; - const t = activeDur * pct; - const isMajor = i % subTicks === 0; - const tickWrapper = document.createElement("div"); - Object.assign(tickWrapper.style, { - position: "absolute", left: `${pct * 100}%`, top: "0", - display: "flex", flexDirection: "column", alignItems: "center", transform: "translateX(-50%)" - }); - if (i === 0) { tickWrapper.style.transform = "none"; tickWrapper.style.alignItems = "flex-start"; } - if (i === totalTicks) { tickWrapper.style.transform = "translateX(-100%)"; tickWrapper.style.alignItems = "flex-end"; } - const line = document.createElement("div"); - Object.assign(line.style, { - width: isMajor ? "2px" : "1px", height: isMajor ? "6px" : "4px", - background: isMajor ? "#aaa" : "#555", marginBottom: "2px", borderRadius: "1px" - }); - tickWrapper.appendChild(line); - - if (isMajor) { - const label = document.createElement("div"); - if (isFrames) { - label.textContent = Math.round(t * fr); - } else { - label.textContent = formatTime(t); - } - tickWrapper.appendChild(label); - } - timeRuler.appendChild(tickWrapper); - } - }; - - function updateUI(syncPlayer = false) { - const activeDur = getActiveDuration(); - - let s = startTimeWidget ? parseFloat(startTimeWidget.value) || 0 : 0; - let e = endTimeWidget ? parseFloat(endTimeWidget.value) || 0 : 0; - - let visualEnd = e; - if (visualEnd === 0 || visualEnd > activeDur) visualEnd = activeDur; - if (s > visualEnd) s = visualEnd; - - let pStart = (s / activeDur) * 100; - let pEnd = (visualEnd / activeDur) * 100; - - pStart = Math.max(0, Math.min(pStart, 100)); - pEnd = Math.max(0, Math.min(pEnd, 100)); - - startHandle.style.left = `${pStart}%`; - endHandle.style.left = `${pEnd}%`; - - fill.style.left = `${pStart}%`; - fill.style.width = `${pEnd - pStart}%`; - - const currentDur = parseFloat((visualEnd - s).toFixed(2)); - const isFrames = displayModeWidget && displayModeWidget.value === "frames"; - const fr = frameRateWidget ? frameRateWidget.value : 24; - - if (isFrames) { - trimLength.textContent = `Trimmed: ${Math.round(currentDur * fr)} frames`; - // Keeps its blue styling securely - } else { - trimLength.textContent = `Trimmed: ${formatTime(currentDur)}`; - // Keeps its blue styling securely - } - - // Only automatically push data directly to durationWidget if a real video is loaded - if (duration > 0 && !isUpdatingDuration) { - isUpdatingDuration = true; - if (durationWidget && durationWidget.value !== currentDur) { - durationWidget.value = currentDur; - } - if (durationFramesWidget && durationFramesWidget.value !== Math.round(currentDur * fr)) { - durationFramesWidget.value = Math.round(currentDur * fr); - } - isUpdatingDuration = false; - } - - if (syncPlayer && duration > 0) { - videoPreview.currentTime = s; - } - } - - // Force draw default empty state on creation - setTimeout(() => { - updateRuler(); - updateUI(); - }, 50); - - videoPreview.onloadedmetadata = () => { - duration = videoPreview.duration; - if (endTimeWidget && (endTimeWidget.value === 0 || endTimeWidget.value > duration)) { - endTimeWidget.value = duration; - node.syncFramesFromTime(); - } - updateRuler(); - updateUI(); - updateCropUI(); - }; - - // Loop Trim during Native Playback - videoPreview.ontimeupdate = () => { - if (!duration || dragging) return; - - let s = startTimeWidget ? parseFloat(startTimeWidget.value) || 0 : 0; - let e = endTimeWidget ? parseFloat(endTimeWidget.value) || duration : duration; - if (e === 0) e = duration; - - if (videoPreview.currentTime >= e && e > 0) { - videoPreview.currentTime = s; - } else if (videoPreview.currentTime < s) { - videoPreview.currentTime = s; - } - }; - - // --- Timeline Drag Logic (Primary state runs in Seconds format to lock playback natively) --- - sliderBox.onpointerdown = (e) => { - const activeDur = getActiveDuration(); - const rect = sliderBox.getBoundingClientRect(); - const x = Math.max(0, Math.min(e.clientX - rect.left, rect.width)); - const val = (x / rect.width) * activeDur; - - let s = startTimeWidget ? parseFloat(startTimeWidget.value) || 0 : 0; - let e_val = endTimeWidget ? parseFloat(endTimeWidget.value) || activeDur : activeDur; - if (e_val === 0) e_val = activeDur; - - const handleTolerance = (10 / rect.width) * activeDur; - - if (val > s + handleTolerance && val < e_val - handleTolerance) { - dragging = 'center'; - dragOffset = val - s; - dragSelectionWidth = e_val - s; - } else if (Math.abs(val - s) < Math.abs(val - e_val)) { - dragging = 'start'; - if (startTimeWidget) startTimeWidget.value = parseFloat(Math.min(val, e_val).toFixed(2)); - if (duration > 0) videoPreview.currentTime = startTimeWidget.value; - } else { - dragging = 'end'; - if (endTimeWidget) endTimeWidget.value = parseFloat(Math.max(val, s).toFixed(2)); - if (duration > 0) videoPreview.currentTime = endTimeWidget.value; - } - - node.syncFramesFromTime(); - updateUI(); - app.graph.setDirtyCanvas(true, false); - sliderBox.setPointerCapture(e.pointerId); - }; - - sliderBox.onpointermove = (e) => { - if (!dragging) return; - const activeDur = getActiveDuration(); - const rect = sliderBox.getBoundingClientRect(); - const x = Math.max(0, Math.min(e.clientX - rect.left, rect.width)); - const val = (x / rect.width) * activeDur; - - if (dragging === 'start') { - let e_val = endTimeWidget ? parseFloat(endTimeWidget.value) || activeDur : activeDur; - if (e_val === 0) e_val = activeDur; - if (startTimeWidget) startTimeWidget.value = parseFloat(Math.min(val, e_val).toFixed(2)); - if (duration > 0) videoPreview.currentTime = startTimeWidget.value; - } else if (dragging === 'end') { - const s = startTimeWidget ? parseFloat(startTimeWidget.value) || 0 : 0; - if (endTimeWidget) endTimeWidget.value = parseFloat(Math.max(val, s).toFixed(2)); - if (duration > 0) videoPreview.currentTime = endTimeWidget.value; - } else if (dragging === 'center') { - let newStart = val - dragOffset; - let newEnd = newStart + dragSelectionWidth; - - if (newStart < 0) { - newStart = 0; - newEnd = dragSelectionWidth; - } else if (newEnd > activeDur) { - newEnd = activeDur; - newStart = activeDur - dragSelectionWidth; - } - - if (startTimeWidget) startTimeWidget.value = parseFloat(newStart.toFixed(2)); - if (endTimeWidget) endTimeWidget.value = parseFloat(newEnd.toFixed(2)); - if (duration > 0) videoPreview.currentTime = startTimeWidget.value; - } - - node.syncFramesFromTime(); - updateUI(); - app.graph.setDirtyCanvas(true, false); - }; - - sliderBox.onpointerup = (e) => { - dragging = null; - sliderBox.releasePointerCapture(e.pointerId); - }; - - // --- Improved Global Drag & Drop for Node Inner Content --- - let dragCounter = 0; - container.addEventListener("dragenter", (e) => { - e.preventDefault(); - dragCounter++; - if (dragCounter === 1) { - container.style.outline = "2px dashed #38bdf8"; - container.style.outlineOffset = "-2px"; - container.style.background = "rgba(14, 165, 233, 0.1)"; - } - }); - - container.addEventListener("dragover", (e) => { - e.preventDefault(); - }); - - container.addEventListener("dragleave", (e) => { - e.preventDefault(); - dragCounter--; - if (dragCounter === 0) { - container.style.outline = "none"; - container.style.background = defaultBg; - } - }); - - container.addEventListener("drop", (e) => { - e.preventDefault(); - e.stopPropagation(); - dragCounter = 0; - container.style.outline = "none"; - container.style.background = defaultBg; - if (e.dataTransfer.files && e.dataTransfer.files.length > 0) { - const file = e.dataTransfer.files[0]; - if (file.type.startsWith('video/') || file.name.toLowerCase().match(/\.(mp4|webm|mkv|avi|mov|m4v|flv|wmv)$/)) { - uploadFile(file); - } - } - }); - - if (videoWidget && videoWidget.value) { - node.updatePreview(videoWidget.value); - } - - return r; - }; - } - }, +import { app } from "../../scripts/app.js"; +import { api } from "../../scripts/api.js"; + +app.registerExtension({ + name: "Comfy.LoadVideoUI", + async beforeRegisterNodeDef(nodeType, nodeData, app) { + if (nodeData.name === "LoadVideoUI") { + const onNodeCreated = nodeType.prototype.onNodeCreated; + const onConfigure = nodeType.prototype.onConfigure; + const onResize = nodeType.prototype.onResize; + const onDrawForeground = nodeType.prototype.onDrawForeground; + + // Hook into workflow loading to instantly restore the video UI + nodeType.prototype.onConfigure = function (info) { + if (onConfigure) { + onConfigure.apply(this, arguments); + } + + // Force UI synchronization + if (this.syncFramesFromTime) this.syncFramesFromTime(); + if (this.toggleWidgetVisibility) this.toggleWidgetVisibility(); + if (this.syncToggleVisual) this.syncToggleVisual(); + + if (this.widgets) { + const videoWidget = this.widgets.find(w => w.name === "video"); + if (videoWidget && videoWidget.value && this.updatePreview) { + this.updatePreview(videoWidget.value); + } + } + }; + + // Continuous frame-accurate check to guarantee exact height alignment + // even on initial graph load when the workflow reloads! + nodeType.prototype.onDrawForeground = function (ctx) { + if (onDrawForeground) onDrawForeground.apply(this, arguments); + + if (this.domWidget && this.domWidget.element && this.domWidget.last_y) { + const remainingHeight = this.size[1] - this.domWidget.last_y - 18; + const currentHeight = parseFloat(this.domWidget.element.style.height); + const targetHeight = Math.max(150, remainingHeight); + + // Only update DOM if the height has drifted by more than 1 pixel + if (isNaN(currentHeight) || Math.abs(currentHeight - targetHeight) > 1) { + this.domWidget.element.style.height = `${targetHeight}px`; + } + } + }; + + // Allow the node to scale nicely when resized by the user + nodeType.prototype.onResize = function (size) { + if (onResize) onResize.apply(this, arguments); + if (this.domWidget && this.domWidget.element) { + // Fill the exact width provided by LiteGraph's bounds natively + this.domWidget.element.style.width = "100%"; + this.domWidget.element.style.margin = "0"; + + // Fallback calc if last_y isn't ready + let yOffset = this.domWidget.last_y; + if (!yOffset) { + yOffset = 30; // Default LiteGraph Title Height + if (this.widgets) { + for (let w of this.widgets) { + if (w === this.domWidget) break; + yOffset += (w.computeSize ? w.computeSize()[1] : 20) + 4; + } + } + } + + const remainingHeight = size[1] - yOffset - 18; + this.domWidget.element.style.height = `${Math.max(150, remainingHeight)}px`; + } + }; + + nodeType.prototype.onNodeCreated = function () { + const r = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined; + const node = this; + + // Find the core widgets + const videoWidget = this.widgets.find((w) => w.name === "video"); + const frameRateWidget = this.widgets.find((w) => w.name === "frame_rate"); + const displayModeWidget = this.widgets.find((w) => w.name === "display_mode"); + + const startTimeWidget = this.widgets.find((w) => w.name === "start_time"); + const endTimeWidget = this.widgets.find((w) => w.name === "end_time"); + const durationWidget = this.widgets.find((w) => w.name === "duration"); + + const startFrameWidget = this.widgets.find((w) => w.name === "start_frame"); + const endFrameWidget = this.widgets.find((w) => w.name === "end_frame"); + const durationFramesWidget = this.widgets.find((w) => w.name === "duration_frames"); + + const cropXWidget = this.widgets.find((w) => w.name === "crop_x"); + const cropYWidget = this.widgets.find((w) => w.name === "crop_y"); + const cropWWidget = this.widgets.find((w) => w.name === "crop_w"); + const cropHWidget = this.widgets.find((w) => w.name === "crop_h"); + + // ==================================================================== + // WIDGET HIDING & SYNC ENGINE + // ==================================================================== + let isSyncing = false; + + function setWidgetVisibility(w, visible, typeStr) { + if (!w) return; + w.hidden = !visible; + if (!visible) { + w.type = "hidden"; + w.computeSize = () => [0, -4]; // Suppresses gap allocation in V1 + } else { + w.type = typeStr; + delete w.computeSize; // Restores standard ComfyUI measurement + } + } + + node.toggleWidgetVisibility = function () { + const isFrames = displayModeWidget && displayModeWidget.value === "frames"; + setWidgetVisibility(startTimeWidget, !isFrames, "FLOAT"); + setWidgetVisibility(endTimeWidget, !isFrames, "FLOAT"); + setWidgetVisibility(durationWidget, !isFrames, "FLOAT"); + setWidgetVisibility(startFrameWidget, isFrames, "INT"); + setWidgetVisibility(endFrameWidget, isFrames, "INT"); + setWidgetVisibility(durationFramesWidget, isFrames, "INT"); + setWidgetVisibility(displayModeWidget, false, "combo"); // Toggle is hidden, driven by UI + + setWidgetVisibility(cropXWidget, false, "FLOAT"); + setWidgetVisibility(cropYWidget, false, "FLOAT"); + setWidgetVisibility(cropWWidget, false, "FLOAT"); + setWidgetVisibility(cropHWidget, false, "FLOAT"); + + // Allow the node to calculate its required min size, but DO NOT overwrite + // the current user-defined width/height unless it's strictly smaller than the minimum. + const minSize = node.computeSize(); + node.size[0] = Math.max(node.size[0], minSize[0]); + node.size[1] = Math.max(node.size[1], minSize[1]); + + if (node.onResize) node.onResize(node.size); + app.graph.setDirtyCanvas(true, true); + }; + + node.syncFramesFromTime = function () { + if (isSyncing || !frameRateWidget) return; + isSyncing = true; + const fr = frameRateWidget.value || 24; + if (startTimeWidget && startFrameWidget) startFrameWidget.value = Math.round(startTimeWidget.value * fr); + if (endTimeWidget && endFrameWidget) endFrameWidget.value = Math.round(endTimeWidget.value * fr); + if (durationWidget && durationFramesWidget) durationFramesWidget.value = Math.round(durationWidget.value * fr); + isSyncing = false; + }; + + node.syncTimeFromFrames = function () { + if (isSyncing || !frameRateWidget) return; + isSyncing = true; + const fr = frameRateWidget.value || 24; + if (startTimeWidget && startFrameWidget) startTimeWidget.value = parseFloat((startFrameWidget.value / fr).toFixed(3)); + if (endTimeWidget && endFrameWidget) endTimeWidget.value = parseFloat((endFrameWidget.value / fr).toFixed(3)); + if (durationWidget && durationFramesWidget) durationFramesWidget.value = parseFloat((durationFramesWidget.value / fr).toFixed(3)); + isSyncing = false; + }; + + // Bind standard input callbacks to synchronize automatically + function bindWidget(w, isFrame, isFrameRate = false) { + if (!w) return; + const orig = w.callback; + w.callback = function () { + if (orig) orig.apply(this, arguments); + if (isFrame) node.syncTimeFromFrames(); + else node.syncFramesFromTime(); + + // Always force a ruler update if framerate changes so the timeline marks match the new rate + if (duration === 0 || isFrameRate) updateRuler(); + updateUI(true); + }; + } + + bindWidget(startTimeWidget, false); + bindWidget(endTimeWidget, false); + bindWidget(startFrameWidget, true); + bindWidget(endFrameWidget, true); + bindWidget(frameRateWidget, false, true); // Triggers re-sync of frames from time AND updates ruler + + // Bind update function to the node so onConfigure can access it + node.updatePreview = function (filename) { + if (!filename) { + return; + } + let url; + + // Check if absolute path (Starts with C:\ or /) + if (filename.match(/^[a-zA-Z]:\\/) || filename.startsWith('/')) { + url = api.apiURL(`/video_ui_custom_view?filename=${encodeURIComponent(filename)}`); + } else { + url = api.apiURL(`/view?filename=${encodeURIComponent(filename)}&type=input`); + } + + if (videoPreview) videoPreview.src = url; + }; + + if (videoWidget) { + const originalCallback = videoWidget.callback; + videoWidget.callback = function () { + if (originalCallback) originalCallback.apply(this, arguments); + if (node.updatePreview) node.updatePreview(this.value); + }; + } + + // Initialize widget visibility right away + if (displayModeWidget && !displayModeWidget.value) displayModeWidget.value = "seconds"; + node.toggleWidgetVisibility(); + + // ==================================================================== + // CHOOSE FILE BUTTON (Native ComfyUI Widget, placed below duration) + // ==================================================================== + const fileInput = document.createElement("input"); + fileInput.type = "file"; + fileInput.accept = "video/*"; + fileInput.style.display = "none"; + document.body.appendChild(fileInput); + + const btnWidget = this.addWidget("button", "choose file to upload", null, () => { + fileInput.click(); + }); + + // Define robust upload logic + const uploadFile = async (file) => { + try { + if (errorMsg) errorMsg.style.display = "none"; + + // Fast Path: If desktop environment exposes absolute file path, skip upload entirely! + if (file.path) { + videoWidget.value = file.path; + node.updatePreview(file.path); + if (startTimeWidget) startTimeWidget.value = 0; + if (endTimeWidget) endTimeWidget.value = 0; + node.syncFramesFromTime(); + return; + } + + btnWidget.name = "Uploading..."; + node.setDirtyCanvas(true, false); + + const CHUNK_SIZE = 10 * 1024 * 1024; // 10MB chunks + + if (file.size > CHUNK_SIZE) { + const totalChunks = Math.ceil(file.size / CHUNK_SIZE); + const safeFileName = file.name.replace(/[^a-zA-Z0-9.\-_]/g, '_'); + const safeName = Date.now() + "_" + safeFileName; + + for (let i = 0; i < totalChunks; i++) { + btnWidget.name = `Uploading... ${Math.round((i / totalChunks) * 100)}%`; + node.setDirtyCanvas(true, false); + + const chunk = file.slice(i * CHUNK_SIZE, (i + 1) * CHUNK_SIZE); + + const formData = new FormData(); + formData.append("file", chunk); + formData.append("filename", safeName); + formData.append("chunk_index", i); + formData.append("total_chunks", totalChunks); + + const resp = await api.fetchApi("/video_ui_upload_chunk", { + method: "POST", + body: formData, + }); + + if (resp.status !== 200) { + throw new Error("Chunk upload failed"); + } + + if (i === totalChunks - 1) { + const data = await resp.json(); + videoWidget.value = data.name; + node.updatePreview(data.name); + if (startTimeWidget) startTimeWidget.value = 0; + if (endTimeWidget) endTimeWidget.value = 0; + node.syncFramesFromTime(); + } + } + } else { + // Standard upload for small files + const body = new FormData(); + body.append("image", file); + + const resp = await api.fetchApi("/upload/image", { + method: "POST", + body: body, + }); + + if (resp.status === 413) { + throw new Error("File too large. Make sure python backend has the chunking update."); + } + + if (resp.status === 200) { + const data = await resp.json(); + videoWidget.value = data.name; + node.updatePreview(data.name); + if (startTimeWidget) startTimeWidget.value = 0; + if (endTimeWidget) endTimeWidget.value = 0; + node.syncFramesFromTime(); + } else { + throw new Error(`Upload failed: ${resp.statusText}`); + } + } + } catch (error) { + console.error("Upload failed", error); + if (errorMsg) { + errorMsg.textContent = "Upload failed. Check console."; + errorMsg.style.display = "block"; + } + } finally { + btnWidget.name = "choose file to upload"; + node.setDirtyCanvas(true, false); + fileInput.value = ""; // reset input + } + }; + + fileInput.addEventListener("change", (e) => { + if (e.target.files.length) { + uploadFile(e.target.files[0]); + } + }); + + // Attach drag & drop directly onto the LiteGraph node canvas frame + node.onDropFile = function (file) { + // Check MIME type or common video file extensions to ensure all videos are caught + if (file.type.startsWith('video/') || file.name.toLowerCase().match(/\.(mp4|webm|mkv|avi|mov|m4v|flv|wmv)$/)) { + uploadFile(file); + return true; + } + return false; + }; + + // Clean up DOM elements strictly tied to this node instance + const originalOnRemove = node.onRemoved; + node.onRemoved = function () { + if (fileInput && fileInput.parentNode) fileInput.parentNode.removeChild(fileInput); + if (originalOnRemove) originalOnRemove.apply(this, arguments); + }; + + // ==================================================================== + // UI CONTAINER (Preview & Timeline Editor) + // ==================================================================== + const container = document.createElement("div"); + const defaultBg = "rgba(30, 30, 30, 0.9)"; + Object.assign(container.style, { + display: "flex", + flexDirection: "column", + gap: "10px", + width: "100%", + margin: "0", + padding: "10px", + boxSizing: "border-box", + background: defaultBg, + borderRadius: "6px", + color: "white", + fontFamily: "sans-serif", + marginTop: "8px", + flexShrink: "0", + transition: "background 0.2s" + }); + + const errorMsg = document.createElement("div"); + Object.assign(errorMsg.style, { + color: "#ff6b6b", + fontSize: "12px", + display: "none", + marginBottom: "4px", + flexShrink: "0", + boxSizing: "border-box" + }); + container.appendChild(errorMsg); + + // Top Bar: Display Mode Toggle & Trimmed Length + const playerTop = document.createElement("div"); + Object.assign(playerTop.style, { + display: "flex", + justifyContent: "space-between", + alignItems: "center", + padding: "0 2px", + marginBottom: "-4px", + flexShrink: "0", + boxSizing: "border-box", + flexWrap: "wrap", // Prevent squishing/overflow by letting it wrap gracefully + gap: "6px", + position: "relative" + }); + + // Toggle Container UI + const toggleWrapper = document.createElement("div"); + Object.assign(toggleWrapper.style, { + display: "flex", + alignItems: "center", + gap: "6px", + background: "rgba(0, 0, 0, 0.2)", + padding: "0 8px", + borderRadius: "4px", + height: "22px", + boxSizing: "border-box" + }); + + const toggleTitle = document.createElement("span"); + toggleTitle.textContent = "Display Mode"; + Object.assign(toggleTitle.style, { + fontSize: "12px", + color: "#38bdf8", + fontWeight: "bold", + whiteSpace: "nowrap" + }); + + // Segmented pill control + const segmentedToggle = document.createElement("div"); + Object.assign(segmentedToggle.style, { + display: "flex", + alignItems: "center", + background: "rgba(0, 0, 0, 0.35)", + border: "1px solid rgba(56, 189, 248, 0.3)", + borderRadius: "4px", + overflow: "hidden", + height: "18px", + flexShrink: "0", + cursor: "pointer" + }); + + const createSegBtn = (label) => { + const btn = document.createElement("span"); + btn.textContent = label; + Object.assign(btn.style, { + fontSize: "11px", + fontWeight: "bold", + padding: "0 8px", + lineHeight: "18px", + color: "rgba(255,255,255,0.45)", + background: "transparent", + transition: "background 0.2s, color 0.2s", + userSelect: "none", + whiteSpace: "nowrap" + }); + return btn; + }; + + const segTime = createSegBtn("Time"); + const segDivider = document.createElement("span"); + segDivider.style.cssText = "width:1px;height:12px;background:rgba(56,189,248,0.25);flex-shrink:0;"; + const segFrames = createSegBtn("Frames"); + + segmentedToggle.appendChild(segTime); + segmentedToggle.appendChild(segDivider); + segmentedToggle.appendChild(segFrames); + + const applySegmentState = (frames) => { + if (frames) { + segTime.style.background = "transparent"; + segTime.style.color = "rgba(255,255,255,0.45)"; + segFrames.style.background = "rgba(37,126,235,0.85)"; + segFrames.style.color = "#fff"; + } else { + segTime.style.background = "rgba(56,189,248,0.85)"; + segTime.style.color = "#fff"; + segFrames.style.background = "transparent"; + segFrames.style.color = "rgba(255,255,255,0.45)"; + } + }; + + // Keep a reference so the init block below can call it + let isFramesMode = false; + applySegmentState(false); // Default: Time is active + + const doToggle = () => { + isFramesMode = !isFramesMode; + applySegmentState(isFramesMode); + + if (displayModeWidget) displayModeWidget.value = isFramesMode ? "frames" : "seconds"; + + // Sync values perfectly on flip + if (isFramesMode) node.syncFramesFromTime(); + else node.syncTimeFromFrames(); + + node.toggleWidgetVisibility(); + updateRuler(); + updateUI(true); + }; + + segmentedToggle.onclick = doToggle; + + // Expose the switch activation so the init requestAnimationFrame below can call it + const switchBox = { onclick: doToggle }; + + // Allow onConfigure (workflow reload) to re-sync the visual highlight + node.syncToggleVisual = function () { + const savedIsFrames = displayModeWidget && displayModeWidget.value === "frames"; + isFramesMode = savedIsFrames; + applySegmentState(savedIsFrames); + }; + + toggleWrapper.appendChild(toggleTitle); + toggleWrapper.appendChild(segmentedToggle); + + const leftContainer = document.createElement("div"); + Object.assign(leftContainer.style, { + flex: "1 1 0%", + display: "flex", + justifyContent: "flex-start", + minWidth: "max-content" + }); + leftContainer.appendChild(toggleWrapper); + playerTop.appendChild(leftContainer); + + const trimLength = document.createElement("span"); + Object.assign(trimLength.style, { + display: "flex", + alignItems: "center", + fontSize: "12px", + color: "#38bdf8", // Always remains blue + fontWeight: "bold", + background: "rgba(56, 189, 248, 0.1)", // Always remains blue + padding: "0 6px", + borderRadius: "4px", + whiteSpace: "nowrap", + height: "22px", + boxSizing: "border-box", + cursor: "pointer" + }); + trimLength.textContent = "Trimmed: 0:00"; + + const cropBtn = document.createElement("button"); + cropBtn.textContent = "Crop"; + Object.assign(cropBtn.style, { + background: "rgba(255, 255, 255, 0.1)", + color: "white", + border: "none", + borderRadius: "4px", + padding: "0 8px", + height: "22px", + fontSize: "12px", + fontWeight: "bold", + cursor: "pointer" + }); + + let isCropVisible = false; + + const cropUIContainer = document.createElement("div"); + Object.assign(cropUIContainer.style, { + display: "flex", alignItems: "center", gap: "6px", zIndex: "11" + }); + + const cropDims = document.createElement("span"); + Object.assign(cropDims.style, { + fontSize: "12px", color: "#38bdf8", fontWeight: "bold", + display: "none", padding: "0 6px", pointerEvents: "none" + }); + + const cropEditContainer = document.createElement("div"); + Object.assign(cropEditContainer.style, { + display: "none", alignItems: "center", gap: "4px" + }); + + const arSelect = document.createElement("select"); + Object.assign(arSelect.style, { + background: "#222", color: "#fff", border: "1px solid #555", + borderRadius: "3px", fontSize: "12px", padding: "2px", outline: "none", + cursor: "pointer" + }); + const ratios = [ + { name: "Freeform", val: 0 }, + { name: "Original", val: -1 }, + { name: "1:1", val: 1 }, + { name: "4:5", val: 4 / 5 }, + { name: "5:4", val: 5 / 4 }, + { name: "16:9", val: 16 / 9 }, + { name: "9:16", val: 9 / 16 }, + { name: "4:3", val: 4 / 3 }, + { name: "3:4", val: 3 / 4 }, + { name: "3:2", val: 3 / 2 }, + { name: "2:3", val: 2 / 3 }, + { name: "2:1", val: 2 }, + { name: "1:2", val: 1 / 2 } + ]; + ratios.forEach(r => { + const opt = document.createElement("option"); + opt.textContent = r.name; + opt.value = r.val; + arSelect.appendChild(opt); + }); + + const wInput = document.createElement("input"); + const hInput = document.createElement("input"); + const inputStyle = { + width: "40px", background: "rgba(0,0,0,0.5)", color: "#38bdf8", + border: "1px solid #555", borderRadius: "3px", fontSize: "12px", + textAlign: "center", padding: "2px", outline: "none" + }; + Object.assign(wInput.style, inputStyle); + Object.assign(hInput.style, inputStyle); + wInput.type = "text"; + hInput.type = "text"; + + const xSpan = document.createElement("span"); + xSpan.textContent = "x"; + xSpan.style.color = "#888"; + xSpan.style.fontSize = "12px"; + + cropEditContainer.appendChild(arSelect); + cropEditContainer.appendChild(wInput); + cropEditContainer.appendChild(xSpan); + cropEditContainer.appendChild(hInput); + + cropUIContainer.appendChild(cropDims); + cropUIContainer.appendChild(cropEditContainer); + playerTop.appendChild(cropUIContainer); + + const rightContainer = document.createElement("div"); + Object.assign(rightContainer.style, { + flex: "1 1 0%", + display: "flex", + justifyContent: "flex-end", + gap: "6px", + minWidth: "max-content" + }); + rightContainer.appendChild(cropBtn); + rightContainer.appendChild(trimLength); + playerTop.appendChild(rightContainer); + + let currentAspectRatio = 0; + + const handleManualDimensionInput = (isWidth) => { + const vw = videoPreview.videoWidth; + const vh = videoPreview.videoHeight; + if (!vw || !vh) return; + + let newW = parseInt(wInput.value) || Math.round((cropWWidget ? parseFloat(cropWWidget.value) || 1 : 1) * vw); + let newH = parseInt(hInput.value) || Math.round((cropHWidget ? parseFloat(cropHWidget.value) || 1 : 1) * vh); + + if (currentAspectRatio > 0) { + if (isWidth) { + newH = Math.round(newW / currentAspectRatio); + } else { + newW = Math.round(newH * currentAspectRatio); + } + } + + newW = Math.max(1, Math.min(newW, vw)); + newH = Math.max(1, Math.min(newH, vh)); + + let cw_val = newW / vw; + let ch_val = newH / vh; + + let cx = cropXWidget ? parseFloat(cropXWidget.value) || 0 : 0; + let cy = cropYWidget ? parseFloat(cropYWidget.value) || 0 : 0; + + if (cx + cw_val > 1) cx = 1 - cw_val; + if (cy + ch_val > 1) cy = 1 - ch_val; + + if (cropXWidget) cropXWidget.value = parseFloat(cx.toFixed(3)); + if (cropYWidget) cropYWidget.value = parseFloat(cy.toFixed(3)); + if (cropWWidget) cropWWidget.value = parseFloat(cw_val.toFixed(3)); + if (cropHWidget) cropHWidget.value = parseFloat(ch_val.toFixed(3)); + + updateCropUI(); + app.graph.setDirtyCanvas(true, false); + }; + + wInput.addEventListener("change", () => handleManualDimensionInput(true)); + hInput.addEventListener("change", () => handleManualDimensionInput(false)); + wInput.addEventListener("keydown", (e) => { if (e.key === "Enter") handleManualDimensionInput(true); }); + hInput.addEventListener("keydown", (e) => { if (e.key === "Enter") handleManualDimensionInput(false); }); + + arSelect.onchange = () => { + currentAspectRatio = parseFloat(arSelect.value); + if (currentAspectRatio === -1 && videoPreview.videoWidth) { + currentAspectRatio = videoPreview.videoWidth / videoPreview.videoHeight; + } + if (currentAspectRatio > 0 && videoPreview.videoWidth) { + const vw = videoPreview.videoWidth; + const vh = videoPreview.videoHeight; + let cw_val = cropWWidget ? parseFloat(cropWWidget.value) || 1 : 1; + let cx = cropXWidget ? parseFloat(cropXWidget.value) || 0 : 0; + let cy = cropYWidget ? parseFloat(cropYWidget.value) || 0 : 0; + + const actualW = cw_val * vw; + let actualH = actualW / currentAspectRatio; + let ch_val = actualH / vh; + + if (ch_val > 1) { + ch_val = 1; + const newActualW = vh * currentAspectRatio; + cw_val = newActualW / vw; + } + if (cy + ch_val > 1) cy = 1 - ch_val; + if (cx + cw_val > 1) cx = 1 - cw_val; + + if (cropXWidget) cropXWidget.value = parseFloat(cx.toFixed(3)); + if (cropYWidget) cropYWidget.value = parseFloat(cy.toFixed(3)); + if (cropWWidget) cropWWidget.value = parseFloat(cw_val.toFixed(3)); + if (cropHWidget) cropHWidget.value = parseFloat(ch_val.toFixed(3)); + + updateCropUI(); + app.graph.setDirtyCanvas(true, false); + } + }; + + cropBtn.onclick = () => { + isCropVisible = !isCropVisible; + cropBtn.style.background = isCropVisible ? "#38bdf8" : "rgba(255, 255, 255, 0.1)"; + cropBtn.style.color = isCropVisible ? "black" : "white"; + if (isCropVisible) { + cropBox.style.display = "block"; + cropEditContainer.style.display = "flex"; + cropDims.style.display = "none"; + } else { + cropBox.style.display = "none"; + cropEditContainer.style.display = "none"; + // updateCropUI handles cropDims visibility when off + } + if (isCropVisible) { + videoPreview.pause(); + videoPreview.controls = false; + } else { + videoPreview.controls = true; + } + updateCropUI(); + }; + + container.appendChild(playerTop); + + // Video Preview Area (Native Controls) + const videoWrapper = document.createElement("div"); + Object.assign(videoWrapper.style, { + position: "relative", + width: "100%", + flexGrow: "1", + minHeight: "0px", + display: "flex", + alignItems: "center", + justifyContent: "center", + background: "#000", + borderRadius: "4px", + overflow: "hidden" + }); + + const videoPreview = document.createElement("video"); + Object.assign(videoPreview.style, { + width: "100%", + height: "100%", + objectFit: "contain", + outline: "none", + boxSizing: "border-box" + }); + videoPreview.controls = true; + videoPreview.controlsList = "nodownload nofullscreen noremoteplayback"; + videoPreview.muted = false; // Changed from true to false so the video starts unmuted + videoWrapper.appendChild(videoPreview); + + const cropBox = document.createElement("div"); + Object.assign(cropBox.style, { + position: "absolute", + border: "2px dashed #38bdf8", + display: "none", + pointerEvents: "auto", + cursor: "move", + boxSizing: "border-box", + boxShadow: "0 0 0 9999px rgba(0, 0, 0, 0.5)", + zIndex: "10", + overflow: "hidden" + }); + + // 3x3 Grid lines + for (let i = 1; i <= 2; i++) { + const vLine = document.createElement("div"); + Object.assign(vLine.style, { + position: "absolute", left: `${i * 33.33}%`, top: "0", bottom: "0", + borderLeft: "1px dashed rgba(255,255,255,0.3)", pointerEvents: "none" + }); + const hLine = document.createElement("div"); + Object.assign(hLine.style, { + position: "absolute", top: `${i * 33.33}%`, left: "0", right: "0", + borderTop: "1px dashed rgba(255,255,255,0.3)", pointerEvents: "none" + }); + cropBox.appendChild(vLine); + cropBox.appendChild(hLine); + } + + const createCropHandle = (cursor, pos, borders) => { + const h = document.createElement("div"); + Object.assign(h.style, { + position: "absolute", + width: "20px", + height: "20px", + background: "transparent", + cursor: cursor, + pointerEvents: "auto", + ...borders, + ...pos + }); + return h; + }; + + const tlHandle = createCropHandle("nwse-resize", { top: "-3px", left: "-3px" }, { borderTop: "6px solid #38bdf8", borderLeft: "6px solid #38bdf8" }); + const trHandle = createCropHandle("nesw-resize", { top: "-3px", right: "-3px" }, { borderTop: "6px solid #38bdf8", borderRight: "6px solid #38bdf8" }); + const blHandle = createCropHandle("nesw-resize", { bottom: "-3px", left: "-3px" }, { borderBottom: "6px solid #38bdf8", borderLeft: "6px solid #38bdf8" }); + const brHandle = createCropHandle("nwse-resize", { bottom: "-3px", right: "-3px" }, { borderBottom: "6px solid #38bdf8", borderRight: "6px solid #38bdf8" }); + + const tmHandle = createCropHandle("ns-resize", { top: "-3px", left: "50%", transform: "translateX(-50%)" }, { borderTop: "6px solid #38bdf8", width: "16px", height: "10px" }); + const bmHandle = createCropHandle("ns-resize", { bottom: "-3px", left: "50%", transform: "translateX(-50%)" }, { borderBottom: "6px solid #38bdf8", width: "16px", height: "10px" }); + const lmHandle = createCropHandle("ew-resize", { top: "50%", left: "-3px", transform: "translateY(-50%)" }, { borderLeft: "6px solid #38bdf8", width: "10px", height: "16px" }); + const rmHandle = createCropHandle("ew-resize", { top: "50%", right: "-3px", transform: "translateY(-50%)" }, { borderRight: "6px solid #38bdf8", width: "10px", height: "16px" }); + + const handles = [tlHandle, trHandle, blHandle, brHandle, tmHandle, bmHandle, lmHandle, rmHandle]; + handles.forEach(h => cropBox.appendChild(h)); + videoWrapper.appendChild(cropBox); + + container.appendChild(videoWrapper); + + // Trim Area (Time Ruler & Slider) + const trimArea = document.createElement("div"); + Object.assign(trimArea.style, { + display: "flex", + flexDirection: "column", + gap: "6px", + background: "rgba(0, 0, 0, 0.35)", + padding: "12px", + borderRadius: "6px", + border: "1px solid rgba(255, 255, 255, 0.05)", + flexShrink: "0", // Prevent timeline from squishing when shrinking node + boxSizing: "border-box" + }); + + const timeRuler = document.createElement("div"); + Object.assign(timeRuler.style, { + position: "relative", + width: "100%", + height: "22px", + fontSize: "11px", + color: "#aaa", + pointerEvents: "none", + userSelect: "none", + boxSizing: "border-box" + }); + trimArea.appendChild(timeRuler); + + const sliderBox = document.createElement("div"); + Object.assign(sliderBox.style, { + position: "relative", + width: "100%", + height: "24px", + background: "#111", + borderRadius: "4px", + cursor: "pointer", + userSelect: "none", + boxShadow: "inset 0 1px 3px rgba(0,0,0,0.5)", + boxSizing: "border-box" + }); + + const fill = document.createElement("div"); + Object.assign(fill.style, { + position: "absolute", + height: "100%", + background: "rgba(14, 165, 233, 0.35)", + pointerEvents: "none" + }); + sliderBox.appendChild(fill); + + const createHandle = (color) => { + const h = document.createElement("div"); + Object.assign(h.style, { + position: "absolute", + top: "0", + width: "8px", + height: "100%", + background: color, + transform: "translateX(-50%)", + pointerEvents: "none", + boxShadow: "0 0 4px rgba(0,0,0,0.8)", + borderRadius: "2px" + }); + return h; + }; + + const startHandle = createHandle("#38bdf8"); + const endHandle = createHandle("#38bdf8"); + sliderBox.appendChild(startHandle); + sliderBox.appendChild(endHandle); + trimArea.appendChild(sliderBox); + + container.appendChild(trimArea); + + // Delay DOM Widget creation to ensure it is added after all standard widgets + setTimeout(() => { + // Add HTML widget to LiteGraph + node.domWidget = node.addDOMWidget("VideoUI", "div", container); + + // Fixed: Return a solid minimum required bounding box. + // Bumped horizontal from 200px to 360px. This natively stops LiteGraph + // from letting the node be squished too thin, completely preventing overlap. + node.domWidget.computeSize = function () { + return [360, 250]; + }; + + // Applies the default creation bounds natively, increased default height + // to match the widgets required height out of the box. + requestAnimationFrame(() => { + if (node.size[0] < 690) { + node.size[0] = 690; + } + + // INCREASE DEFAULT HEIGHT HERE: + // Change the 620 below to adjust the starting height of the node + if (node.size[1] < 740) { + node.size[1] = 740; + } + + // Trigger manual resize call so the vertical math applies instantly + if (node.onResize) node.onResize(node.size); + + // Sync visual toggle to initial data + if (displayModeWidget && displayModeWidget.value === "frames") { + isFramesMode = false; // prime for click + switchBox.onclick(); + } + + app.graph.setDirtyCanvas(true, true); + }); + }, 100); + + // ==================================================================== + // LOGIC & SYNCING + // ==================================================================== + let duration = 0; + let dragging = null; + let dragOffset = 0; + let dragSelectionWidth = 0; + let isUpdatingDuration = false; + + // Crop logic + let cropDragging = null; + let dragStartX = 0; + let dragStartY = 0; + let dragStartCropX = 0; + let dragStartCropY = 0; + let dragStartCropW = 1; + let dragStartCropH = 1; + + const updateCropUI = () => { + const vw = videoPreview.videoWidth; + const vh = videoPreview.videoHeight; + + let cx = cropXWidget ? parseFloat(cropXWidget.value) || 0 : 0; + let cy = cropYWidget ? parseFloat(cropYWidget.value) || 0 : 0; + let cw_val = cropWWidget ? parseFloat(cropWWidget.value) || 1 : 1; + let ch_val = cropHWidget ? parseFloat(cropHWidget.value) || 1 : 1; + + const actualW = vw ? Math.round(cw_val * vw) : 0; + const actualH = vh ? Math.round(ch_val * vh) : 0; + + if (!isCropVisible || !vw) { + cropBox.style.display = "none"; + cropEditContainer.style.display = "none"; + if (cw_val < 0.999 || ch_val < 0.999 || cx > 0.001 || cy > 0.001) { + cropDims.textContent = `Crop: ${actualW}x${actualH}`; + cropDims.style.display = "inline-block"; + } else { + cropDims.style.display = "none"; + } + return; + } + + cropDims.style.display = "none"; + cropEditContainer.style.display = "flex"; + cropBox.style.display = "block"; + + if (document.activeElement !== wInput) wInput.value = actualW; + if (document.activeElement !== hInput) hInput.value = actualH; + + const cw = videoPreview.clientWidth; + const ch = videoPreview.clientHeight; + + const ratio = Math.min(cw / vw, ch / vh); + const renderedW = vw * ratio; + const renderedH = vh * ratio; + const xOffset = (cw - renderedW) / 2; + const yOffset = (ch - renderedH) / 2; + + cropBox.style.left = `${xOffset + cx * renderedW}px`; + cropBox.style.top = `${yOffset + cy * renderedH}px`; + cropBox.style.width = `${cw_val * renderedW}px`; + cropBox.style.height = `${ch_val * renderedH}px`; + }; + + const onCropPointerDown = (e, handle) => { + if (!isCropVisible) return; + e.preventDefault(); + e.stopPropagation(); + cropDragging = handle; + e.target.setPointerCapture(e.pointerId); + + dragStartX = e.clientX; + dragStartY = e.clientY; + dragStartCropX = cropXWidget ? parseFloat(cropXWidget.value) || 0 : 0; + dragStartCropY = cropYWidget ? parseFloat(cropYWidget.value) || 0 : 0; + dragStartCropW = cropWWidget ? parseFloat(cropWWidget.value) || 1 : 1; + dragStartCropH = cropHWidget ? parseFloat(cropHWidget.value) || 1 : 1; + + e.target.addEventListener("pointermove", onCropPointerMove); + e.target.addEventListener("pointerup", onCropPointerUp); + }; + + const onCropPointerMove = (e) => { + if (!cropDragging) return; + e.preventDefault(); + + const vw = videoPreview.videoWidth; + const vh = videoPreview.videoHeight; + const cw = videoPreview.clientWidth; + const ch = videoPreview.clientHeight; + + const ratio = Math.min(cw / vw, ch / vh); + const renderedW = vw * ratio; + const renderedH = vh * ratio; + + const dx = (e.clientX - dragStartX) / renderedW; + const dy = (e.clientY - dragStartY) / renderedH; + + let new_cw = dragStartCropW; + let new_ch = dragStartCropH; + let new_cx = dragStartCropX; + let new_cy = dragStartCropY; + + if (cropDragging === "tl") { + new_cw = dragStartCropW - dx; + new_ch = dragStartCropH - dy; + } else if (cropDragging === "tr") { + new_cw = dragStartCropW + dx; + new_ch = dragStartCropH - dy; + } else if (cropDragging === "bl") { + new_cw = dragStartCropW - dx; + new_ch = dragStartCropH + dy; + } else if (cropDragging === "br") { + new_cw = dragStartCropW + dx; + new_ch = dragStartCropH + dy; + } else if (cropDragging === "tm") { + new_ch = dragStartCropH - dy; + } else if (cropDragging === "bm") { + new_ch = dragStartCropH + dy; + } else if (cropDragging === "lm") { + new_cw = dragStartCropW - dx; + } else if (cropDragging === "rm") { + new_cw = dragStartCropW + dx; + } + + if (currentAspectRatio > 0 && cropDragging !== "center") { + const R = currentAspectRatio * (vh / vw); + if (["tm", "bm"].includes(cropDragging)) { + new_cw = new_ch * R; + new_cx = dragStartCropX + (dragStartCropW - new_cw) / 2; + } else if (["lm", "rm"].includes(cropDragging)) { + new_ch = new_cw / R; + new_cy = dragStartCropY + (dragStartCropH - new_ch) / 2; + } else { + new_ch = new_cw / R; + } + } + + if (cropDragging === "tl") { + new_cx = dragStartCropX + dragStartCropW - new_cw; + new_cy = dragStartCropY + dragStartCropH - new_ch; + } else if (cropDragging === "tr") { + new_cx = dragStartCropX; + new_cy = dragStartCropY + dragStartCropH - new_ch; + } else if (cropDragging === "bl") { + new_cx = dragStartCropX + dragStartCropW - new_cw; + new_cy = dragStartCropY; + } else if (cropDragging === "br") { + new_cx = dragStartCropX; + new_cy = dragStartCropY; + } else if (cropDragging === "tm") { + new_cy = dragStartCropY + dragStartCropH - new_ch; + if (!(currentAspectRatio > 0)) new_cx = dragStartCropX; + } else if (cropDragging === "bm") { + new_cy = dragStartCropY; + if (!(currentAspectRatio > 0)) new_cx = dragStartCropX; + } else if (cropDragging === "lm") { + new_cx = dragStartCropX + dragStartCropW - new_cw; + if (!(currentAspectRatio > 0)) new_cy = dragStartCropY; + } else if (cropDragging === "rm") { + new_cx = dragStartCropX; + if (!(currentAspectRatio > 0)) new_cy = dragStartCropY; + } else if (cropDragging === "center") { + new_cx = dragStartCropX + dx; + new_cy = dragStartCropY + dy; + } + + if (new_cw < 0.02) { + new_cw = 0.02; + if (currentAspectRatio > 0) new_ch = new_cw / (currentAspectRatio * (vh / vw)); + } + if (new_ch < 0.02) { + new_ch = 0.02; + if (currentAspectRatio > 0) new_cw = new_ch * (currentAspectRatio * (vh / vw)); + } + + if (cropDragging === "center") { + new_cx = Math.max(0, Math.min(new_cx, 1 - new_cw)); + new_cy = Math.max(0, Math.min(new_cy, 1 - new_ch)); + } else { + if (new_cx < 0) { + if (["tl", "bl", "lm"].includes(cropDragging)) { new_cw += new_cx; new_cx = 0; } + } + if (new_cy < 0) { + if (["tl", "tr", "tm"].includes(cropDragging)) { new_ch += new_cy; new_cy = 0; } + } + if (new_cx + new_cw > 1) { + if (["tr", "br", "rm"].includes(cropDragging)) new_cw = 1 - new_cx; + } + if (new_cy + new_ch > 1) { + if (["bl", "br", "bm"].includes(cropDragging)) new_ch = 1 - new_cy; + } + + if (currentAspectRatio > 0) { + const R = currentAspectRatio * (vh / vw); + if (new_cw / new_ch > R + 0.001) { + new_cw = new_ch * R; + if (["tl", "bl", "lm"].includes(cropDragging)) new_cx = dragStartCropX + dragStartCropW - new_cw; + } else if (new_cw / new_ch < R - 0.001) { + new_ch = new_cw / R; + if (["tl", "tr", "tm"].includes(cropDragging)) new_cy = dragStartCropY + dragStartCropH - new_ch; + } + } + } + + if (cropXWidget) cropXWidget.value = parseFloat(new_cx.toFixed(3)); + if (cropYWidget) cropYWidget.value = parseFloat(new_cy.toFixed(3)); + if (cropWWidget) cropWWidget.value = parseFloat(new_cw.toFixed(3)); + if (cropHWidget) cropHWidget.value = parseFloat(new_ch.toFixed(3)); + + updateCropUI(); + app.graph.setDirtyCanvas(true, false); + }; + + const onCropPointerUp = (e) => { + cropDragging = null; + e.target.releasePointerCapture(e.pointerId); + e.target.removeEventListener("pointermove", onCropPointerMove); + e.target.removeEventListener("pointerup", onCropPointerUp); + }; + + cropBox.onpointerdown = (e) => { + if (e.target === cropBox) onCropPointerDown(e, "center"); + }; + tlHandle.onpointerdown = (e) => onCropPointerDown(e, "tl"); + trHandle.onpointerdown = (e) => onCropPointerDown(e, "tr"); + blHandle.onpointerdown = (e) => onCropPointerDown(e, "bl"); + brHandle.onpointerdown = (e) => onCropPointerDown(e, "br"); + tmHandle.onpointerdown = (e) => onCropPointerDown(e, "tm"); + bmHandle.onpointerdown = (e) => onCropPointerDown(e, "bm"); + lmHandle.onpointerdown = (e) => onCropPointerDown(e, "lm"); + rmHandle.onpointerdown = (e) => onCropPointerDown(e, "rm"); + + // Add a resize observer to the video wrapper so crop handles stay pinned + const resizeObserver = new ResizeObserver(() => { + if (isCropVisible) updateCropUI(); + }); + resizeObserver.observe(videoWrapper); + + // Ensure we clean up observer + const oldOnRemoved = node.onRemoved; + node.onRemoved = function () { + resizeObserver.disconnect(); + if (oldOnRemoved) oldOnRemoved.apply(this, arguments); + } + + // Smart helper to ensure timeline displays correctly even with no video loaded + const getActiveDuration = () => { + if (duration > 0) return duration; + let e = endTimeWidget ? parseFloat(endTimeWidget.value) || 0 : 0; + let s = startTimeWidget ? parseFloat(startTimeWidget.value) || 0 : 0; + let maxVal = Math.max(e, s); + return maxVal > 0 ? Math.max(maxVal, 1.0) : 1.0; // Default to 1.0 if completely empty + }; + + // Time Duration Hook + if (durationWidget) { + const origCallback = durationWidget.callback; + durationWidget.callback = function (v) { + if (isUpdatingDuration) { + if (origCallback) origCallback.apply(this, arguments); + return; + } + + isUpdatingDuration = true; + const activeDur = getActiveDuration(); + let d = parseFloat(v) || 0; + if (d < 0) d = 0; + if (d > activeDur) d = activeDur; + + let s = startTimeWidget ? parseFloat(startTimeWidget.value) || 0 : 0; + let newStart = s; + let newEnd = s + d; + + if (newEnd > activeDur) { + newEnd = activeDur; + newStart = activeDur - d; + } + + if (startTimeWidget) startTimeWidget.value = parseFloat(newStart.toFixed(2)); + if (endTimeWidget) endTimeWidget.value = parseFloat(newEnd.toFixed(2)); + node.syncFramesFromTime(); + + if (duration === 0) updateRuler(); + updateUI(true); + app.graph.setDirtyCanvas(true, false); + + if (origCallback) origCallback.apply(this, arguments); + isUpdatingDuration = false; + }; + } + + // Frame Duration Hook + if (durationFramesWidget) { + const origCallback = durationFramesWidget.callback; + durationFramesWidget.callback = function (v) { + if (isUpdatingDuration || !frameRateWidget) { + if (origCallback) origCallback.apply(this, arguments); + return; + } + + isUpdatingDuration = true; + const fr = frameRateWidget.value || 24; + const activeDurFrames = Math.round(getActiveDuration() * fr); + + let d = parseInt(v) || 0; + if (d < 0) d = 0; + if (d > activeDurFrames) d = activeDurFrames; + + let s = startFrameWidget ? parseInt(startFrameWidget.value) || 0 : 0; + let newStart = s; + let newEnd = s + d; + + if (newEnd > activeDurFrames) { + newEnd = activeDurFrames; + newStart = activeDurFrames - d; + } + + if (startFrameWidget) startFrameWidget.value = newStart; + if (endFrameWidget) endFrameWidget.value = newEnd; + node.syncTimeFromFrames(); + + if (duration === 0) updateRuler(); + updateUI(true); + app.graph.setDirtyCanvas(true, false); + + if (origCallback) origCallback.apply(this, arguments); + isUpdatingDuration = false; + }; + } + + // Standard Video Player Format HH:MM:SS (only shows hours if it's over an hour long) + const formatTime = (secs) => { + const h = Math.floor(secs / 3600); + const m = Math.floor((secs % 3600) / 60); + const s = Math.floor(secs % 60); + const mStr = m.toString().padStart(2, '0'); + const sStr = s.toString().padStart(2, '0'); + + if (h > 0) { + return `${h}:${mStr}:${sStr}`; + } else { + return `${m}:${sStr}`; + } + }; + + const updateRuler = () => { + timeRuler.innerHTML = ''; + const activeDur = getActiveDuration(); + const numMajorTicks = 5; + const subTicks = 4; + const totalTicks = (numMajorTicks - 1) * subTicks; + + const isFrames = displayModeWidget && displayModeWidget.value === "frames"; + const fr = frameRateWidget ? frameRateWidget.value : 24; + + for (let i = 0; i <= totalTicks; i++) { + const pct = i / totalTicks; + const t = activeDur * pct; + const isMajor = i % subTicks === 0; + const tickWrapper = document.createElement("div"); + Object.assign(tickWrapper.style, { + position: "absolute", left: `${pct * 100}%`, top: "0", + display: "flex", flexDirection: "column", alignItems: "center", transform: "translateX(-50%)" + }); + if (i === 0) { tickWrapper.style.transform = "none"; tickWrapper.style.alignItems = "flex-start"; } + if (i === totalTicks) { tickWrapper.style.transform = "translateX(-100%)"; tickWrapper.style.alignItems = "flex-end"; } + const line = document.createElement("div"); + Object.assign(line.style, { + width: isMajor ? "2px" : "1px", height: isMajor ? "6px" : "4px", + background: isMajor ? "#aaa" : "#555", marginBottom: "2px", borderRadius: "1px" + }); + tickWrapper.appendChild(line); + + if (isMajor) { + const label = document.createElement("div"); + if (isFrames) { + label.textContent = Math.round(t * fr); + } else { + label.textContent = formatTime(t); + } + tickWrapper.appendChild(label); + } + timeRuler.appendChild(tickWrapper); + } + }; + + function updateUI(syncPlayer = false) { + const activeDur = getActiveDuration(); + + let s = startTimeWidget ? parseFloat(startTimeWidget.value) || 0 : 0; + let e = endTimeWidget ? parseFloat(endTimeWidget.value) || 0 : 0; + + let visualEnd = e; + if (visualEnd === 0 || visualEnd > activeDur) visualEnd = activeDur; + if (s > visualEnd) s = visualEnd; + + let pStart = (s / activeDur) * 100; + let pEnd = (visualEnd / activeDur) * 100; + + pStart = Math.max(0, Math.min(pStart, 100)); + pEnd = Math.max(0, Math.min(pEnd, 100)); + + startHandle.style.left = `${pStart}%`; + endHandle.style.left = `${pEnd}%`; + + fill.style.left = `${pStart}%`; + fill.style.width = `${pEnd - pStart}%`; + + const currentDur = parseFloat((visualEnd - s).toFixed(2)); + const isFrames = displayModeWidget && displayModeWidget.value === "frames"; + const fr = frameRateWidget ? frameRateWidget.value : 24; + + if (isFrames) { + trimLength.textContent = `Trimmed: ${Math.round(currentDur * fr)} frames`; + // Keeps its blue styling securely + } else { + trimLength.textContent = `Trimmed: ${formatTime(currentDur)}`; + // Keeps its blue styling securely + } + + // Only automatically push data directly to durationWidget if a real video is loaded + if (duration > 0 && !isUpdatingDuration) { + isUpdatingDuration = true; + if (durationWidget && durationWidget.value !== currentDur) { + durationWidget.value = currentDur; + } + if (durationFramesWidget && durationFramesWidget.value !== Math.round(currentDur * fr)) { + durationFramesWidget.value = Math.round(currentDur * fr); + } + isUpdatingDuration = false; + } + + if (syncPlayer && duration > 0) { + videoPreview.currentTime = s; + } + } + + // Force draw default empty state on creation + setTimeout(() => { + updateRuler(); + updateUI(); + }, 50); + + videoPreview.onloadedmetadata = () => { + duration = videoPreview.duration; + if (endTimeWidget && (endTimeWidget.value === 0 || endTimeWidget.value > duration)) { + endTimeWidget.value = duration; + node.syncFramesFromTime(); + } + updateRuler(); + updateUI(); + updateCropUI(); + }; + + // Loop Trim during Native Playback + videoPreview.ontimeupdate = () => { + if (!duration || dragging) return; + + let s = startTimeWidget ? parseFloat(startTimeWidget.value) || 0 : 0; + let e = endTimeWidget ? parseFloat(endTimeWidget.value) || duration : duration; + if (e === 0) e = duration; + + if (videoPreview.currentTime >= e && e > 0) { + videoPreview.currentTime = s; + } else if (videoPreview.currentTime < s) { + videoPreview.currentTime = s; + } + }; + + // --- Timeline Drag Logic (Primary state runs in Seconds format to lock playback natively) --- + sliderBox.onpointerdown = (e) => { + const activeDur = getActiveDuration(); + const rect = sliderBox.getBoundingClientRect(); + const x = Math.max(0, Math.min(e.clientX - rect.left, rect.width)); + const val = (x / rect.width) * activeDur; + + let s = startTimeWidget ? parseFloat(startTimeWidget.value) || 0 : 0; + let e_val = endTimeWidget ? parseFloat(endTimeWidget.value) || activeDur : activeDur; + if (e_val === 0) e_val = activeDur; + + const handleTolerance = (10 / rect.width) * activeDur; + + if (val > s + handleTolerance && val < e_val - handleTolerance) { + dragging = 'center'; + dragOffset = val - s; + dragSelectionWidth = e_val - s; + } else if (Math.abs(val - s) < Math.abs(val - e_val)) { + dragging = 'start'; + if (startTimeWidget) startTimeWidget.value = parseFloat(Math.min(val, e_val).toFixed(2)); + if (duration > 0) videoPreview.currentTime = startTimeWidget.value; + } else { + dragging = 'end'; + if (endTimeWidget) endTimeWidget.value = parseFloat(Math.max(val, s).toFixed(2)); + if (duration > 0) videoPreview.currentTime = endTimeWidget.value; + } + + node.syncFramesFromTime(); + updateUI(); + app.graph.setDirtyCanvas(true, false); + sliderBox.setPointerCapture(e.pointerId); + }; + + sliderBox.onpointermove = (e) => { + if (!dragging) return; + const activeDur = getActiveDuration(); + const rect = sliderBox.getBoundingClientRect(); + const x = Math.max(0, Math.min(e.clientX - rect.left, rect.width)); + const val = (x / rect.width) * activeDur; + + if (dragging === 'start') { + let e_val = endTimeWidget ? parseFloat(endTimeWidget.value) || activeDur : activeDur; + if (e_val === 0) e_val = activeDur; + if (startTimeWidget) startTimeWidget.value = parseFloat(Math.min(val, e_val).toFixed(2)); + if (duration > 0) videoPreview.currentTime = startTimeWidget.value; + } else if (dragging === 'end') { + const s = startTimeWidget ? parseFloat(startTimeWidget.value) || 0 : 0; + if (endTimeWidget) endTimeWidget.value = parseFloat(Math.max(val, s).toFixed(2)); + if (duration > 0) videoPreview.currentTime = endTimeWidget.value; + } else if (dragging === 'center') { + let newStart = val - dragOffset; + let newEnd = newStart + dragSelectionWidth; + + if (newStart < 0) { + newStart = 0; + newEnd = dragSelectionWidth; + } else if (newEnd > activeDur) { + newEnd = activeDur; + newStart = activeDur - dragSelectionWidth; + } + + if (startTimeWidget) startTimeWidget.value = parseFloat(newStart.toFixed(2)); + if (endTimeWidget) endTimeWidget.value = parseFloat(newEnd.toFixed(2)); + if (duration > 0) videoPreview.currentTime = startTimeWidget.value; + } + + node.syncFramesFromTime(); + updateUI(); + app.graph.setDirtyCanvas(true, false); + }; + + sliderBox.onpointerup = (e) => { + dragging = null; + sliderBox.releasePointerCapture(e.pointerId); + }; + + // --- Improved Global Drag & Drop for Node Inner Content --- + let dragCounter = 0; + container.addEventListener("dragenter", (e) => { + e.preventDefault(); + dragCounter++; + if (dragCounter === 1) { + container.style.outline = "2px dashed #38bdf8"; + container.style.outlineOffset = "-2px"; + container.style.background = "rgba(14, 165, 233, 0.1)"; + } + }); + + container.addEventListener("dragover", (e) => { + e.preventDefault(); + }); + + container.addEventListener("dragleave", (e) => { + e.preventDefault(); + dragCounter--; + if (dragCounter === 0) { + container.style.outline = "none"; + container.style.background = defaultBg; + } + }); + + container.addEventListener("drop", (e) => { + e.preventDefault(); + e.stopPropagation(); + dragCounter = 0; + container.style.outline = "none"; + container.style.background = defaultBg; + if (e.dataTransfer.files && e.dataTransfer.files.length > 0) { + const file = e.dataTransfer.files[0]; + if (file.type.startsWith('video/') || file.name.toLowerCase().match(/\.(mp4|webm|mkv|avi|mov|m4v|flv|wmv)$/)) { + uploadFile(file); + } + } + }); + + if (videoWidget && videoWidget.value) { + node.updatePreview(videoWidget.value); + } + + return r; + }; + } + }, }); \ No newline at end of file diff --git a/js/ltx_director.js b/js/ltx_director.js index 126ac73..0c7849f 100644 --- a/js/ltx_director.js +++ b/js/ltx_director.js @@ -1,3875 +1,4057 @@ -const { app } = window.comfyAPI.app; -const { api } = window.comfyAPI.api; - -// --- UI Constants & Configuration --- -const RULER_HEIGHT = 24; -const BLOCK_HEIGHT = 160; // Increased to make the image timeline area much taller -const AUDIO_TRACK_HEIGHT = 80; -const CANVAS_HEIGHT = RULER_HEIGHT + BLOCK_HEIGHT + AUDIO_TRACK_HEIGHT; -const HANDLE_HIT_PX = 14; -const MIN_SEGMENT_LENGTH = 6; -const MAX_THUMBNAIL_DIM = 512; // Increased to maintain quality for taller images - -const HIDDEN_WIDGET_NAMES = ["timeline_data", "local_prompts", "segment_lengths", "guide_strength", "audio_data", "use_custom_audio"]; - -function hideWidget(w) { - if (!w) return; - if (!w._origType && w.type !== "hidden") w._origType = w.type; - // We don't set w.type = "hidden" anymore because it causes rendering issues in Nodes 2.0. - // Instead we use the computeSize = () => [0,0] trick which works in both V1 and V2. - w.hidden = true; - if (!w.options) w.options = {}; - w.options.hidden = true; - w.computeSize = () => [0, 0]; - if (w.element) w.element.style.display = "none"; -} - -function clamp(v, min, max) { return Math.max(min, Math.min(max, v)); } - -// --- Modern Dark/Grey UI CSS (ComfyUI Match) --- -const STYLES = ` - .pr-wrapper { - font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; - display: flex; - flex-direction: column; - gap: 8px; - width: 100%; - height: 100%; - box-sizing: border-box; - padding-bottom: 4px; - } - .pr-wrapper.drag-active { - outline: 2px dashed #888; - background: rgba(255, 255, 255, 0.05); - border-radius: 6px; - } - .pr-toolbar { - display: flex; - justify-content: space-between; - align-items: center; - padding: 2px 0px; - flex-wrap: wrap; - gap: 6px; - } - .pr-actions { - display: flex; - gap: 6px; - flex-wrap: wrap; - } - .pr-btn { - background: #222; - color: #e0e0e0; - border: 1px solid #111; - border-radius: 4px; - padding: 6px 12px; - font-size: 11px; - font-weight: 500; - cursor: pointer; - display: flex; - align-items: center; - gap: 6px; - transition: all 0.2s ease; - } - .pr-btn:hover { - background: #333; - border-color: #555; - } - .pr-btn-danger:hover { - background: #4a1515; - border-color: #cc4444; - color: #ffaaaa; - } - .pr-canvas { - border-radius: 6px; - border: 1px solid #111; - background: #2a2a2a; - cursor: pointer; - width: 100%; - outline: none; - display: block; /* Ensure no inline baseline gaps */ - } - .pr-prop-container { - display: flex; - flex-direction: column; - width: 100%; - flex-grow: 1; /* Automatically scales to fill node height */ - min-height: 40px; - } - .pr-prompt-area { - width: 100%; - height: 100%; - background: #222; - color: #e0e0e0; - border: 1px solid #111; - border-radius: 6px; - padding: 8px; - resize: none; /* Removed the manual resize corner handle */ - font-size: 12px; - line-height: 1.4; - box-sizing: border-box; - outline: none; - transition: border-color 0.2s ease; - } - .pr-prompt-area:focus { - border-color: #888; - } - .pr-audio-info { - width: 100%; - height: 100%; - background: #181818; - color: #aaa; - border: 1px solid #111; - border-radius: 6px; - padding: 10px; - font-size: 12px; - line-height: 1.6; - box-sizing: border-box; - display: none; - } - .pr-audio-info span { color: #fff; font-weight: 500; } - .pr-controls-group { - background: #1e1e1e; - border: 1px solid #333; - border-radius: 6px; - padding: 6px 10px; - display: flex; - flex-direction: column; - gap: 4px; - margin-bottom: 4px; - box-sizing: border-box; - width: 100%; - } - .pr-strength-row { - display: flex; - align-items: center; - gap: 12px; - width: 100%; - box-sizing: border-box; - } - .pr-height-resizer { - height: 6px; - background: #2a2a2a; - cursor: ns-resize; - border-radius: 3px; - margin: 2px 0; - transition: background 0.15s; - border: 1px solid #1e1e1e; - } - .pr-height-resizer:hover { - background: #444; - border-color: #555; - } - .pr-strength-label { - font-size: 11px; - font-weight: 600; - color: #fff; - white-space: nowrap; - margin-left: auto; - } - .pr-strength-slider { - -webkit-appearance: none; - appearance: none; - width: 80px; - height: 4px; - background: #444; - border-radius: 2px; - outline: none; - cursor: pointer; - border: 1px solid #222; - } - .pr-strength-slider::-webkit-slider-thumb { - -webkit-appearance: none; - appearance: none; - width: 12px; - height: 12px; - border-radius: 50%; - background: #aaa; - cursor: pointer; - } - .pr-strength-slider:disabled { - opacity: 0.3; - cursor: not-allowed; - } - .pr-strength-input { - font-size: 12px; - color: #fff; - background: #222; - border: 1px solid #444; - border-radius: 4px; - width: 52px; - text-align: center; - padding: 3px; - } - .pr-strength-input::-webkit-outer-spin-button, - .pr-strength-input::-webkit-inner-spin-button { - -webkit-appearance: none; - margin: 0; - } - .pr-strength-input[type=number] { - -moz-appearance: textfield; - } - .pr-strength-input:disabled { - opacity: 0.35; - cursor: not-allowed; - } - .pr-gap-menu { - position: fixed; - background: #1e1e1e; - border: 1px solid #444; - border-radius: 6px; - padding: 4px; - display: flex; - flex-direction: column; - gap: 4px; - z-index: 9999; - box-shadow: 0 4px 16px rgba(0,0,0,0.6); - } - .pr-gap-menu-btn { - background: #2a2a2a; - color: #e0e0e0; - border: 1px solid #333; - border-radius: 4px; - padding: 6px 14px; - font-size: 11px; - font-family: inherit; - cursor: pointer; - text-align: left; - white-space: nowrap; - display: flex; - align-items: center; - gap: 6px; - transition: background 0.15s ease; - } - .pr-gap-menu-btn:hover { - background: #3a3a3a; - border-color: #666; - } - .pr-player-controls { - display: flex; - justify-content: center; - align-items: center; - gap: 12px; - padding: 2px 0; - flex-wrap: wrap; - width: 100%; - } - .pr-icon-btn { - background: #2a2a2a; - border: 1px solid #444; - color: #eee; - cursor: pointer; - padding: 6px 12px; - border-radius: 4px; - display: flex; - align-items: center; - justify-content: center; - transition: all 0.2s; - } - .pr-icon-btn * { - pointer-events: none; - } - .pr-icon-btn:hover { - color: #fff; - background: #3a3a3a; - border-color: #666; - } - .pr-icon-btn.active { - color: #4fff8f; - border-color: #4fff8f; - background: #1a3a2a; - } - .pr-seek-bar { - -webkit-appearance: none; - appearance: none; - height: 6px; - background: #444; - border-radius: 3px; - outline: none; - cursor: pointer; - border: 1px solid #222; - } - .pr-seek-bar::-webkit-slider-thumb { - -webkit-appearance: none; - appearance: none; - width: 14px; - height: 14px; - border-radius: 50%; - background: #ff4444; - cursor: pointer; - border: 2px solid #222; - } - .pr-timeline-viewport { - width: 100%; - overflow-x: auto; - overflow-y: hidden; - } - .pr-timeline-viewport::-webkit-scrollbar { - height: 10px; - } - .pr-timeline-viewport::-webkit-scrollbar-track { - background: #151515; - border-radius: 5px; - } - .pr-timeline-viewport::-webkit-scrollbar-thumb { - background: #444 - border-radius: 5px; - border: 1px solid #000; - } - .pr-timeline-viewport::-webkit-scrollbar-thumb:hover { - background: #666 - border-color: #000; - } - .pr-zoom-controls { - display: flex; - align-items: center; - gap: 4px; - margin-left: 12px; - } - .pr-zoom-slider { - width: 80px; - -webkit-appearance: none; - appearance: none; - height: 4px; - background: #444; - border-radius: 2px; - outline: none; - cursor: pointer; - } - .pr-zoom-slider::-webkit-slider-thumb { - -webkit-appearance: none; - appearance: none; - width: 12px; - height: 12px; - border-radius: 50%; - background: #aaa; - cursor: pointer; - } - .pr-right-group { - display: flex; - align-items: center; - gap: 12px; - } - .pr-segment-bounds { - font-size: 12px; - color: #aaa; - font-family: monospace; - } - .pr-timecode { - font-size: 14px; - font-weight: bold; - color: #e0e0e0; - font-family: monospace; - } - .pr-settings-menu { - position: fixed; - background: #1e1e1e; - border: 1px solid #444; - border-radius: 6px; - padding: 10px; - display: flex; - flex-direction: column; - gap: 8px; - z-index: 9999; - box-shadow: 0 4px 20px rgba(0,0,0,0.7); - min-width: 220px; - } - .pr-settings-title { - font-size: 11px; - font-weight: 600; - color: #888; - text-transform: uppercase; - letter-spacing: 0.06em; - padding-bottom: 4px; - border-bottom: 1px solid #333; - margin-bottom: 2px; - } - .pr-settings-row { - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; - } - .pr-settings-label { - font-size: 12px; - color: #bbb; - flex: 1; - white-space: nowrap; - } - .pr-number-control { - display: flex; - align-items: center; - border: 1px solid #444; - border-radius: 4px; - background: #2a2a2a; - overflow: hidden; - } - .pr-number-btn { - background: #333; - color: #aaa; - border: none; - width: 20px; - height: 22px; - cursor: pointer; - font-size: 12px; - display: flex; - align-items: center; - justify-content: center; - transition: background 0.15s; - user-select: none; - } - .pr-number-btn:hover { - background: #444; - color: #fff; - } - .pr-settings-input { - background: transparent; - color: #e0e0e0; - border: none; - padding: 0 4px; - font-size: 12px; - width: 50px; - height: 22px; - text-align: center; - font-family: monospace; - outline: none; - -moz-appearance: textfield; - } - .pr-settings-input::-webkit-outer-spin-button, - .pr-settings-input::-webkit-inner-spin-button { - -webkit-appearance: none; - margin: 0; - } - .pr-settings-select { - background: #2a2a2a; - color: #e0e0e0; - border: 1px solid #444; - border-radius: 4px; - padding: 3px 4px; - font-size: 12px; - width: 98px; - cursor: pointer; - } - .pr-settings-divider { - border: none; - border-top: 1px solid #2a2a2a; - margin: 2px 0; - } - .pr-settings-toggle-btn { - width: 100%; - background: #252525; - color: #aaa; - border: 1px solid #333; - border-radius: 4px; - padding: 5px 8px; - font-size: 11px; - cursor: pointer; - text-align: center; - transition: all 0.15s; - } - .pr-settings-toggle-btn:hover { - background: #2e2e2e; - color: #ccc; - border-color: #555; - } - .pr-settings-close-btn { - background: transparent; - color: #888; - border: none; - cursor: pointer; - padding: 2px; - display: flex; - align-items: center; - justify-content: center; - border-radius: 4px; - transition: all 0.15s; - } - .pr-settings-close-btn:hover { - color: #fff; - background: rgba(255,255,255,0.1); - } - .pr-segmented-control { - display: flex; - background: #1e1e1e; - border: 1px solid #333; - border-radius: 6px; - padding: 2px; - width: 110px; - height: 22px; - align-items: center; - box-sizing: border-box; - } - .pr-segment { - flex: 1; - text-align: center; - font-size: 10px; - font-weight: 500; - line-height: 18px; - cursor: pointer; - border-radius: 4px; - color: #888; - transition: all 0.15s ease; - } - .pr-segment.active { - background: #333; - color: #fff; - } - .pr-segment:hover:not(.active) { - color: #ccc; - } -`; - -if (!document.getElementById("prompt-relay-styles")) { - const styleEl = document.createElement("style"); - styleEl.id = "prompt-relay-styles"; - styleEl.textContent = STYLES; - document.head.appendChild(styleEl); -} - -// --- Icons --- -const ICONS = { - upload: ``, - audio: ``, - trash: ``, - text: ``, - play: ``, - pause: ``, - loop: ``, - minus: ``, - plus: ``, - fit: ``, - gear: ``, - close: `` -}; - -// --- Data Models --- -function parseInitial(jsonStr) { - let parsed = { segments: [], audioSegments: [] }; - try { - if (jsonStr) { - const p = JSON.parse(jsonStr); - if (Array.isArray(p.segments)) parsed.segments = p.segments; - if (Array.isArray(p.audioSegments)) parsed.audioSegments = p.audioSegments; - } - } catch (e) { } - - let currentStart = 0; - for (let seg of parsed.segments) { - if (seg.start === undefined) { - seg.start = currentStart; - currentStart += seg.length; - } - // Guarantee ID assignment to prevent node loading drag breaks - if (!seg.id) { - seg.id = Date.now().toString() + Math.random().toString(36).substr(2, 5); - } - } - - for (let seg of parsed.audioSegments) { - if (!seg.id) { - seg.id = Date.now().toString() + Math.random().toString(36).substr(2, 5); - } - if (seg.trimStart === undefined) seg.trimStart = 0; - } - - return parsed; -} - -class TimelineEditor { - constructor(node, container, domWidget) { - this.node = node; - this.container = container; - this.domWidget = domWidget; - - // Track heights (dynamic) - this.rulerHeight = RULER_HEIGHT; - this.blockHeight = BLOCK_HEIGHT; - this.audioTrackHeight = AUDIO_TRACK_HEIGHT; - this.canvasHeight = CANVAS_HEIGHT; - - // Core data - this.timeline = { segments: [], audioSegments: [] }; - this.selectionType = "image"; // "image" or "audio" - this.selectedIndex = -1; - - // Interactions - this._isDragging = false; - this._dragType = null; - this._dragStartX = 0; - this._dragInitialTimeline = null; - this.zoomLevel = 1.0; - this._lastZoom = 1.0; - this._lastScale = 1.0; - this._dragTargetId = null; - this._dragTargetIdRight = null; - this._previewSegments = null; - this._lastWidth = 0; - this._hoveredGapIdx = -1; - this._isHovering = false; - - // Playback state - this.currentFrame = 0; - this.isPlaying = false; - this.isLooping = false; - this.audioContext = null; - this.activeAudioNodes = []; - this.playbackStartTime = 0; - this.playbackStartFrame = 0; - this._playLoopId = null; - - // --- Ghost dragging state --- - this._ghostSegmentId = null; - this._ghostTrack = null; - this._ghostInitialTimeline = null; - - // Attach to Python widgets - this._gapMenu = null; // Active gap popup menu element - this._gapMenuDismisser = null; - - // Attach to Python widgets - this.durationFramesWidget = this.node.widgets.find(w => w.name === "duration_frames"); - this.durationSecondsWidget = this.node.widgets.find(w => w.name === "duration_seconds"); - this.frameRateWidget = this.node.widgets.find(w => w.name === "frame_rate"); - this.timelineDataWidget = this.node.widgets.find(w => w.name === "timeline_data"); - this.localPromptsWidget = this.node.widgets.find(w => w.name === "local_prompts"); - this.segmentLengthsWidget = this.node.widgets.find(w => w.name === "segment_lengths"); - this.guideStrengthWidget = this.node.widgets.find(w => w.name === "guide_strength"); - this.displayModeWidget = this.node.widgets.find(w => w.name === "display_mode"); - - this.timeline = parseInitial(this.timelineDataWidget?.value); - this.loadImages(); - - this.createDOM(); - if (this.timeline.segments.length > 0) { - this.selectedIndex = 0; - } - this.updateUIFromSelection(); - this.commitChanges(true); - // Hide settings widgets by default to reduce node clutter. - // Deferred so all widget types are finalized before we touch them. - setTimeout(() => this.hideSettingsWidgets(), 0); - - let isSyncing = false; - - const origDurationFramesCallback = this.durationFramesWidget?.callback; - if (this.durationFramesWidget) { - this.durationFramesWidget.callback = (...args) => { - if (origDurationFramesCallback) origDurationFramesCallback.apply(this.durationFramesWidget, args); - - if (!isSyncing && this.durationSecondsWidget) { - isSyncing = true; - this.durationSecondsWidget.value = parseFloat((this.getDurationFrames() / this.getFrameRate()).toFixed(3)); - isSyncing = false; - } - - this.commitChanges(); - }; - } - - const origDurationSecondsCallback = this.durationSecondsWidget?.callback; - if (this.durationSecondsWidget) { - this.durationSecondsWidget.callback = (...args) => { - if (origDurationSecondsCallback) origDurationSecondsCallback.apply(this.durationSecondsWidget, args); - - if (!isSyncing && this.durationFramesWidget) { - isSyncing = true; - const newFrames = Math.max(1, Math.round(this.durationSecondsWidget.value * this.getFrameRate())); - this.durationFramesWidget.value = newFrames; - if (this.durationFramesWidget.callback) this.durationFramesWidget.callback(newFrames); - isSyncing = false; - } - }; - } - - const origFrameRateCallback = this.frameRateWidget?.callback; - if (this.frameRateWidget) { - this.frameRateWidget.callback = (...args) => { - if (origFrameRateCallback) origFrameRateCallback.apply(this.frameRateWidget, args); - if (!isSyncing && this.durationSecondsWidget) { - isSyncing = true; - this.durationSecondsWidget.value = parseFloat((this.getDurationFrames() / this.getFrameRate()).toFixed(3)); - isSyncing = false; - } - }; - } - - const origDisplayModeCallback = this.displayModeWidget?.callback; - if (this.displayModeWidget) { - this.displayModeWidget.callback = (...args) => { - if (origDisplayModeCallback) origDisplayModeCallback.apply(this.displayModeWidget, args); - this.updateWidgetVisibility(); - this.updateUIFromSelection(); - this.render(); - }; - this.updateWidgetVisibility(); // Initial trigger - } - - // Polling is much more reliable in Comfy than ResizeObserver due to scale transforms - this._renderLoop = requestAnimationFrame(() => this.checkResize()); - } - - destroy() { - cancelAnimationFrame(this._renderLoop); - this.pauseAudio(); - window.removeEventListener("keydown", this.handleKeyDown, true); - window.removeEventListener("paste", this.handlePaste, true); - } - - getDurationFrames() { - return parseInt((this.durationFramesWidget && this.durationFramesWidget.value > 0) ? this.durationFramesWidget.value : 24, 10); - } - - getFrameRate() { - return parseInt((this.frameRateWidget && this.frameRateWidget.value > 0) ? this.frameRateWidget.value : 24, 10); - } - - // Grow the timeline duration to fit `requiredFrames` if it is currently shorter. - // The timeline only ever grows — never shrinks — through this method. - growTimelineIfNeeded(requiredFrames) { - const current = this.getDurationFrames(); - if (requiredFrames <= current) return; // already big enough - - const newFrames = Math.ceil(requiredFrames); - if (this.durationFramesWidget) { - this.durationFramesWidget.value = newFrames; - } - if (this.durationSecondsWidget) { - this.durationSecondsWidget.value = parseFloat((newFrames / this.getFrameRate()).toFixed(3)); - } - // Notify ComfyUI that the widget value changed so it serialises correctly. - if (window.app && window.app.graph) { - window.app.graph.setDirtyCanvas(true, true); - } - } - - // Returns the maximum allowed zoom level, computed so that at max zoom - // the viewport shows exactly 4 seconds of the visual timeline. - getMaxZoom() { - const visualDurationSecs = this.getVisualDurationFrames() / this.getFrameRate(); - const baseMaxZoom = Math.max(1, visualDurationSecs / 4); - - // Limit max zoom to prevent canvas width from exceeding browser limits (causing crash) - const viewportWidth = this.viewport ? this.viewport.clientWidth : 1000; - const MAX_CANVAS_WIDTH = 32768; // Extended limit for modern browsers - const limitMaxZoom = MAX_CANVAS_WIDTH / Math.max(1, viewportWidth); - - return Math.max(1, Math.min(baseMaxZoom, limitMaxZoom)); - } - - // Returns the visual timeline length in frames: - // the furthest segment end (across both tracks) × 1.30, with a floor of getDurationFrames(). - // This is used for all rendering/positioning — the actual output duration is getDurationFrames(). - getVisualDurationFrames() { - let furthest = 0; - for (const seg of this.timeline.segments) { - furthest = Math.max(furthest, seg.start + seg.length); - } - for (const seg of this.timeline.audioSegments) { - furthest = Math.max(furthest, seg.start + seg.length); - } - const outputDuration = this.getDurationFrames(); - if (furthest <= 0) return outputDuration; - return Math.max(outputDuration, Math.ceil(furthest * 1.30)); - } - - // Sync the zoom slider's max attribute to the current getMaxZoom() value, - // clamping zoomLevel if it now exceeds the new max. - updateZoomSliderMax() { - if (!this.zoomSlider) return; - const maxZoom = this.getMaxZoom(); - this.zoomSlider.max = maxZoom.toFixed(2); - if (this.zoomLevel > maxZoom) { - this.zoomLevel = maxZoom; - this.zoomSlider.value = maxZoom; - // Resize the canvas to match the clamped zoom - const viewportWidth = this.viewport ? this.viewport.clientWidth : 0; - if (viewportWidth > 0) { - const newCanvasWidth = Math.max(viewportWidth, viewportWidth * this.zoomLevel); - this.canvas.style.width = newCanvasWidth + "px"; - this.resizeCanvas(newCanvasWidth); - } - } - } - - loadImages() { - for (const seg of this.timeline.segments) { - if (seg.imageB64 && !seg.imgObj) { - seg.imgObj = new Image(); - seg.imgObj.onload = () => this.render(); - seg.imgObj.src = seg.imageB64; - } - } - } - - createDOM() { - this.wrapper = document.createElement("div"); - this.wrapper.className = "pr-wrapper"; - - this.wrapper.addEventListener("mouseenter", () => { this._isHovering = true; }); - this.wrapper.addEventListener("mouseleave", () => { this._isHovering = false; }); - - this.handleKeyDown = (e) => { - const activeTag = document.activeElement ? document.activeElement.tagName : ""; - if (activeTag === "INPUT" || activeTag === "TEXTAREA") return; - - if ((e.key === "Delete" || e.key === "Backspace") && this.selectedIndex !== -1 && this._isHovering) { - this.deleteSelectedSegment(); - e.stopPropagation(); - e.stopImmediatePropagation(); - e.preventDefault(); - } else if ((e.key === " " || e.code === "Space") && this._isHovering) { - this.togglePlay(); - e.stopPropagation(); - e.stopImmediatePropagation(); - e.preventDefault(); - } - }; - window.addEventListener("keydown", this.handleKeyDown, true); - - this.handlePaste = (e) => { - if (this._isHovering) { - const activeTag = document.activeElement ? document.activeElement.tagName : ""; - if (activeTag === "INPUT" || activeTag === "TEXTAREA") return; - - if (e.clipboardData && e.clipboardData.files && e.clipboardData.files.length > 0) { - const imageFiles = Array.from(e.clipboardData.files).filter(f => f.type.startsWith("image/")); - if (imageFiles.length > 0) { - this.handleImageUpload(imageFiles, this.currentFrame); - e.preventDefault(); - e.stopPropagation(); - } - } - } - }; - window.addEventListener("paste", this.handlePaste, true); - - // --- Toolbar --- - const toolbar = document.createElement("div"); - toolbar.className = "pr-toolbar"; - - const actionGroup = document.createElement("div"); - actionGroup.className = "pr-actions"; - - this.fileInput = document.createElement("input"); - this.fileInput.type = "file"; - this.fileInput.accept = "image/*"; - this.fileInput.multiple = true; - this.fileInput.style.display = "none"; - this.fileInput.addEventListener("change", (e) => this.handleImageUpload(e.target.files)); - - this.audioFileInput = document.createElement("input"); - this.audioFileInput.type = "file"; - this.audioFileInput.accept = "audio/*"; - this.audioFileInput.multiple = true; - this.audioFileInput.style.display = "none"; - this.audioFileInput.addEventListener("change", (e) => this.handleAudioUpload(e.target.files)); - - const uploadBtn = document.createElement("button"); - uploadBtn.className = "pr-btn"; - uploadBtn.innerHTML = `${ICONS.upload} Add Image`; - uploadBtn.addEventListener("click", () => this.fileInput.click()); - - const uploadAudioBtn = document.createElement("button"); - uploadAudioBtn.className = "pr-btn"; - uploadAudioBtn.innerHTML = `${ICONS.audio} Add Audio`; - uploadAudioBtn.addEventListener("click", () => this.audioFileInput.click()); - - const addTextBtn = document.createElement("button"); - addTextBtn.className = "pr-btn"; - addTextBtn.innerHTML = `${ICONS.text} Add Text`; - addTextBtn.addEventListener("click", () => this.addTextSegmentFreeSpace()); - - const deleteBtn = document.createElement("button"); - deleteBtn.className = "pr-btn pr-btn-danger"; - deleteBtn.innerHTML = `${ICONS.trash} Delete`; - deleteBtn.addEventListener("click", () => this.deleteSelectedSegment()); - - actionGroup.appendChild(this.fileInput); - actionGroup.appendChild(this.audioFileInput); - actionGroup.appendChild(uploadBtn); - actionGroup.appendChild(addTextBtn); - actionGroup.appendChild(uploadAudioBtn); - actionGroup.appendChild(deleteBtn); - toolbar.appendChild(actionGroup); - - const rightGroup = document.createElement("div"); - rightGroup.className = "pr-right-group"; - - this.segmentBoundsDisplay = document.createElement("div"); - this.segmentBoundsDisplay.className = "pr-segment-bounds"; - this.segmentBoundsDisplay.textContent = "Start: - | End: -"; - - this.timeCodeDisplay = document.createElement("div"); - this.timeCodeDisplay.className = "pr-timecode"; - this.timeCodeDisplay.textContent = this.formatTime(0); - - const settingsBtn = document.createElement("button"); - settingsBtn.className = "pr-btn"; - settingsBtn.style.padding = "6px"; - settingsBtn.style.justifyContent = "center"; - settingsBtn.style.width = "28px"; - settingsBtn.style.height = "28px"; - settingsBtn.style.boxSizing = "border-box"; - settingsBtn.innerHTML = ICONS.gear; - settingsBtn.title = "Settings"; - settingsBtn.addEventListener("click", (e) => { - e.stopPropagation(); - if (this._settingsMenu) { - this.dismissSettingsMenu(); - } else { - this.showSettingsMenu(settingsBtn); - } - }); - - const toggleBtn = document.createElement("button"); - toggleBtn.className = "pr-btn"; - toggleBtn.style.padding = "6px 8px"; - toggleBtn.style.fontSize = "11px"; - toggleBtn.style.marginRight = "0px"; - toggleBtn.textContent = "Custom Audio: OFF"; - toggleBtn.title = "Toggle Custom Audio Output"; - - const updateToggleStyle = (isOn) => { - toggleBtn.textContent = isOn ? "Custom Audio: ON" : "Custom Audio: OFF"; - if (isOn) { - toggleBtn.style.background = "#1c222d"; - toggleBtn.style.borderColor = "#283142"; - toggleBtn.style.color = "#e0e0e0"; - } else { - toggleBtn.style.background = "#222"; - toggleBtn.style.borderColor = "#111"; - toggleBtn.style.color = "#e0e0e0"; - } - }; - - toggleBtn.addEventListener("click", (e) => { - e.stopPropagation(); - const widget = this.node.widgets?.find(w => w.name === "use_custom_audio"); - if (widget) { - widget.value = !widget.value; - updateToggleStyle(widget.value); - this.node.setDirtyCanvas(true, true); - } - }); - - // Initial state check (widgets might not be ready immediately) - setTimeout(() => { - const widget = this.node.widgets?.find(w => w.name === "use_custom_audio"); - if (widget) { - updateToggleStyle(widget.value); - } - }, 100); - - const helpBtn = document.createElement("button"); - helpBtn.className = "pr-btn"; - helpBtn.style.padding = "6px"; - helpBtn.style.justifyContent = "center"; - helpBtn.style.width = "28px"; - helpBtn.style.height = "28px"; - helpBtn.style.boxSizing = "border-box"; - helpBtn.innerHTML = "?"; - helpBtn.title = "Help / Documentation"; - helpBtn.addEventListener("click", (e) => { - e.stopPropagation(); - window.open("https://github.com/WhatDreamsCost/WhatDreamsCost-ComfyUI", "_blank"); - }); - - const btnGroup = document.createElement("div"); - btnGroup.style.display = "flex"; - btnGroup.style.gap = "6px"; - btnGroup.style.alignItems = "center"; - btnGroup.appendChild(toggleBtn); - btnGroup.appendChild(helpBtn); - btnGroup.appendChild(settingsBtn); - rightGroup.appendChild(btnGroup); - - toolbar.appendChild(rightGroup); - - // --- Canvas & Viewport --- - this.viewport = document.createElement("div"); - this.viewport.className = "pr-timeline-viewport"; - - this.viewport.addEventListener("wheel", (e) => { - if (e.ctrlKey || e.metaKey) { - e.preventDefault(); - e.stopPropagation(); - - let zoomDelta = e.deltaY > 0 ? -0.5 : 0.5; - this.zoomLevel = Math.max(1, Math.min(this.getMaxZoom(), this.zoomLevel + zoomDelta)); - if (this.zoomSlider) this.zoomSlider.value = this.zoomLevel; - - const oldWidth = this.canvas.offsetWidth; - const newWidth = this.viewport.clientWidth * this.zoomLevel; - const mouseX = e.clientX - this.viewport.getBoundingClientRect().left; - const scrollRatio = (this.viewport.scrollLeft + mouseX) / oldWidth; - - this.canvas.style.width = newWidth + "px"; - this.viewport.scrollLeft = scrollRatio * newWidth - mouseX; - } - }, { passive: false, capture: true }); - - this.canvas = document.createElement("canvas"); - this.canvas.className = "pr-canvas"; - this.ctx = this.canvas.getContext("2d"); - this.canvas.style.width = "100%"; - - this.viewport.appendChild(this.canvas); - - this.canvas.addEventListener("mousedown", (e) => this.onMouseDown(e)); - this.canvas.addEventListener("contextmenu", (e) => this.onContextMenu(e)); - this.canvas.style.height = `${CANVAS_HEIGHT}px`; - - // --- Content Area Container --- - const propContainer = document.createElement("div"); - propContainer.className = "pr-prop-container"; - - // --- Text Area (Image/Text) --- - this.promptInput = document.createElement("textarea"); - this.promptInput.className = "pr-prompt-area"; - this.promptInput.placeholder = "Enter prompt for selected segment..."; - this.promptInput.addEventListener("input", () => { - if (this.selectionType === "image" && this.timeline.segments[this.selectedIndex]) { - this.timeline.segments[this.selectedIndex].prompt = this.promptInput.value; - this.commitChanges(); - } - }); - - // --- Audio Info Area --- - this.audioInfoArea = document.createElement("div"); - this.audioInfoArea.className = "pr-audio-info"; - - propContainer.appendChild(this.promptInput); - propContainer.appendChild(this.audioInfoArea); - - this.wrapper.addEventListener("dragover", (e) => { - e.preventDefault(); - this.wrapper.classList.add("drag-active"); - - const { x, y } = this.getMousePos(e); - const logicalWidth = this.canvas.offsetWidth; - const totalFrames = this.getVisualDurationFrames(); - if (!logicalWidth || totalFrames <= 0) return; - - const isAudioTrack = y > RULER_HEIGHT + this.blockHeight; - const trackType = isAudioTrack ? "audio" : "image"; - const arrToModify = isAudioTrack ? this.timeline.audioSegments : this.timeline.segments; - - if (!this._ghostSegmentId || this._ghostTrack !== trackType) { - this._ghostSegmentId = "GHOST_" + Date.now(); - this._ghostTrack = trackType; - this._ghostInitialTimeline = JSON.parse(JSON.stringify(arrToModify)); - - const frameRate = this.getFrameRate(); - const newLength = Math.max(1, frameRate * 1); - - let mouseFrameX = x * (totalFrames / logicalWidth); - let startFrame = clamp(Math.round(mouseFrameX - newLength / 2), 0, totalFrames - newLength); - - this._ghostInitialTimeline.push({ - id: this._ghostSegmentId, - start: startFrame, - length: newLength, - type: "ghost" - }); - } - - let mouseFrameX = x * (totalFrames / logicalWidth); - const ghost = this._ghostInitialTimeline.find(s => s.id === this._ghostSegmentId); - let D_mouse_start = mouseFrameX - ghost.length / 2; - - this._previewSegments = this._applyCenterDragPhysics( - this._ghostInitialTimeline, - this._ghostSegmentId, - D_mouse_start, - mouseFrameX, - totalFrames, - totalFrames, - logicalWidth - ); - this.render(); - }); - - this.wrapper.addEventListener("dragleave", (e) => { - const rect = this.wrapper.getBoundingClientRect(); - if (e.clientX < rect.left || e.clientX >= rect.right || - e.clientY < rect.top || e.clientY >= rect.bottom) { - this.wrapper.classList.remove("drag-active"); - this._ghostSegmentId = null; - this._ghostTrack = null; - this._ghostInitialTimeline = null; - this._previewSegments = null; - this.render(); - } - }); - - this.wrapper.addEventListener("drop", (e) => { - e.preventDefault(); - e.stopPropagation(); - this.wrapper.classList.remove("drag-active"); - - let targetFrameStart = null; - let targetTrack = this._ghostTrack || "image"; - - if (this._ghostSegmentId && this._previewSegments) { - const ghost = this._previewSegments.find(s => s.id === this._ghostSegmentId); - if (ghost) { - targetFrameStart = ghost.resolvedStart !== undefined ? ghost.resolvedStart : ghost.start; - } - } - this._ghostSegmentId = null; - this._ghostTrack = null; - this._ghostInitialTimeline = null; - this._previewSegments = null; - this.render(); - - if (e.dataTransfer.files && e.dataTransfer.files.length > 0) { - const imageFiles = []; - const audioFiles = []; - for (let file of e.dataTransfer.files) { - if (file.type.startsWith("audio/")) audioFiles.push(file); - if (file.type.startsWith("image/")) imageFiles.push(file); - } - - // Let implicit intent handle mixing drops: use the track we hovered over - // for the first type we process, or fallback. - if (audioFiles.length > 0 && (targetTrack === "audio" || imageFiles.length === 0)) { - this.handleAudioUpload(audioFiles, targetFrameStart); - } else if (imageFiles.length > 0) { - this.handleImageUpload(imageFiles, targetFrameStart); - } - } - }); - - window.addEventListener("mousemove", (e) => this.onMouseMove(e)); - window.addEventListener("mouseup", (e) => this.onMouseUp(e)); - - // --- Player Controls --- - const playerControls = document.createElement("div"); - playerControls.className = "pr-player-controls"; - - this.playBtn = document.createElement("button"); - this.playBtn.className = "pr-icon-btn"; - this.playBtn.style.padding = "4px"; - this.playBtn.innerHTML = ICONS.play; - this.playBtn.title = "Play/Pause Audio"; - this.playBtn.addEventListener("click", () => this.togglePlay()); - - this.loopBtn = document.createElement("button"); - this.loopBtn.className = "pr-icon-btn"; - this.loopBtn.style.padding = "4px"; - this.loopBtn.innerHTML = ICONS.loop; - this.loopBtn.title = "Toggle Loop"; - this.loopBtn.addEventListener("click", () => this.toggleLoop()); - - this.seekBar = document.createElement("input"); - this.seekBar.type = "range"; - this.seekBar.className = "pr-seek-bar"; - this.seekBar.min = "0"; - this.seekBar.value = "0"; - this.seekBar.style.flex = "1"; // take up remaining space - this.seekBar.addEventListener("input", (e) => { - this.currentFrame = parseInt(e.target.value, 10); - this.render(); - if (this.isPlaying) { - this.playAudio(); - } - }); - - // --- Zoom Controls --- - const zoomControls = document.createElement("div"); - zoomControls.className = "pr-zoom-controls"; - - const zoomOutBtn = document.createElement("button"); - zoomOutBtn.className = "pr-icon-btn"; - zoomOutBtn.style.padding = "4px"; - zoomOutBtn.innerHTML = ICONS.minus; - zoomOutBtn.title = "Zoom Out"; - zoomOutBtn.addEventListener("click", () => { - const currentZoom = parseFloat(this.zoomSlider.value); - this.zoomSlider.value = Math.max(1, currentZoom - 0.5); - this.zoomSlider.dispatchEvent(new Event("input")); - }); - - this.zoomSlider = document.createElement("input"); - this.zoomSlider.type = "range"; - this.zoomSlider.className = "pr-zoom-slider"; - this.zoomSlider.min = "1"; - this.zoomSlider.max = "1"; // Updated dynamically via updateZoomSliderMax() - this.zoomSlider.step = "0.1"; - this.zoomSlider.value = "1"; - this.zoomSlider.title = "Zoom Level"; - this.zoomSlider.addEventListener("input", (e) => { - this.zoomLevel = parseFloat(e.target.value); - - const viewportWidth = this.viewport.clientWidth; - const newCanvasWidth = Math.max(viewportWidth, viewportWidth * this.zoomLevel); - - this.canvas.style.width = newCanvasWidth + "px"; - this.resizeCanvas(newCanvasWidth); - this._lastWidth = viewportWidth; - this._lastZoom = this.zoomLevel; - - // Keep playhead centered - const totalFrames = this.getVisualDurationFrames(); - const playheadRatio = this.currentFrame / totalFrames; - const newPlayheadX = playheadRatio * newCanvasWidth; - this.viewport.scrollLeft = newPlayheadX - (viewportWidth / 2); - }); - - const zoomInBtn = document.createElement("button"); - zoomInBtn.className = "pr-icon-btn"; - zoomInBtn.style.padding = "4px"; - zoomInBtn.innerHTML = ICONS.plus; - zoomInBtn.title = "Zoom In"; - zoomInBtn.addEventListener("click", () => { - const currentZoom = parseFloat(this.zoomSlider.value); - this.zoomSlider.value = Math.min(this.getMaxZoom(), currentZoom + 0.5); - this.zoomSlider.dispatchEvent(new Event("input")); - }); - - const zoomFitBtn = document.createElement("button"); - zoomFitBtn.className = "pr-icon-btn"; - zoomFitBtn.style.padding = "4px"; - zoomFitBtn.style.marginLeft = "4px"; - zoomFitBtn.innerHTML = ICONS.fit; - zoomFitBtn.title = "Zoom to Fit (show full timeline)"; - zoomFitBtn.addEventListener("click", () => { - this.zoomLevel = 1; - this.zoomSlider.value = 1; - const viewportWidth = this.viewport.clientWidth; - this.canvas.style.width = viewportWidth + "px"; - this.resizeCanvas(viewportWidth); - this._lastWidth = viewportWidth; - this._lastZoom = 1; - this.viewport.scrollLeft = 0; - }); - - zoomControls.appendChild(zoomOutBtn); - zoomControls.appendChild(this.zoomSlider); - zoomControls.appendChild(zoomInBtn); - zoomControls.appendChild(zoomFitBtn); - - playerControls.appendChild(this.playBtn); - playerControls.appendChild(this.loopBtn); - playerControls.appendChild(this.seekBar); - playerControls.appendChild(zoomControls); - - - - // --- Guide Strength Slider --- - this.strengthRow = document.createElement("div"); - this.strengthRow.className = "pr-strength-row"; - - const strengthLabel = document.createElement("span"); - strengthLabel.className = "pr-strength-label"; - strengthLabel.textContent = "Guide Strength:"; - - this.strengthValue = document.createElement("input"); - this.strengthValue.type = "text"; - this.strengthValue.className = "pr-strength-input"; - this.strengthValue.value = "1.00"; - this.strengthValue.disabled = true; - this.strengthValue.style.cursor = "ew-resize"; - - // Dragging logic for guide strength - let isDragging = false; - let startX = 0; - let startVal = 0; - let hasMoved = false; - - this.strengthValue.addEventListener("mousedown", (e) => { - if (this.strengthValue.disabled) return; - startX = e.clientX; - startVal = parseFloat(this.strengthValue.value) || 1.0; - hasMoved = false; - - const onMouseMove = (moveEvent) => { - const deltaX = moveEvent.clientX - startX; - if (Math.abs(deltaX) > 3) { - hasMoved = true; - isDragging = true; - } - - if (isDragging) { - moveEvent.preventDefault(); - const sensitivity = 0.002; - let newVal = startVal + deltaX * sensitivity; - - if (newVal < 0) newVal = 0; - if (newVal > 1) newVal = 1; - - this.strengthValue.value = newVal.toFixed(2); - - if (this.selectionType === "image" && this.timeline.segments[this.selectedIndex]) { - const seg = this.timeline.segments[this.selectedIndex]; - if (seg.type !== "text") { - seg.guideStrength = newVal; - this.commitChanges(); - } - } - } - }; - - const onMouseUp = () => { - document.removeEventListener("mousemove", onMouseMove); - document.removeEventListener("mouseup", onMouseUp); - - if (!hasMoved) { - this.strengthValue.focus(); - this.strengthValue.select(); - } - isDragging = false; - }; - - document.addEventListener("mousemove", onMouseMove); - document.addEventListener("mouseup", onMouseUp); - }); - - this.strengthValue.addEventListener("change", (e) => { - let val = parseFloat(e.target.value); - if (isNaN(val)) val = 1; - val = Math.max(0, Math.min(1, val)); - this.strengthValue.value = val.toFixed(2); - if (this.selectionType === "image" && this.timeline.segments[this.selectedIndex]) { - const seg = this.timeline.segments[this.selectedIndex]; - if (seg.type !== "text") { - seg.guideStrength = val; - this.commitChanges(); - } - } - }); - - this.strengthRow.appendChild(this.timeCodeDisplay); - this.strengthRow.appendChild(this.segmentBoundsDisplay); - this.strengthRow.appendChild(strengthLabel); - this.strengthRow.appendChild(this.strengthValue); - - - this.wrapper.appendChild(toolbar); - this.wrapper.appendChild(this.viewport); - - const controlsGroup = document.createElement("div"); - controlsGroup.className = "pr-controls-group"; - controlsGroup.appendChild(this.strengthRow); - controlsGroup.appendChild(playerControls); - this.wrapper.appendChild(controlsGroup); - this.wrapper.appendChild(propContainer); - - this.container.appendChild(this.wrapper); - } - - checkResize() { - const viewportWidth = this.viewport.clientWidth; - const currentScale = this.getRenderScale(); - - if (viewportWidth > 0 && (this._lastWidth !== viewportWidth || this._lastZoom !== this.zoomLevel || this._lastScale !== currentScale)) { - this._lastWidth = viewportWidth; - this._lastZoom = this.zoomLevel; - this._lastScale = currentScale; - - const newCanvasWidth = Math.max(viewportWidth, viewportWidth * this.zoomLevel); - this.canvas.style.width = newCanvasWidth + "px"; - this.resizeCanvas(newCanvasWidth); - } - this._renderLoop = requestAnimationFrame(() => this.checkResize()); - } - - getRenderScale() { - const dpr = window.devicePixelRatio || 1; - let graphScale = 1; - try { - if (window.app && window.app.canvas && window.app.canvas.ds && window.app.canvas.ds.scale) { - graphScale = window.app.canvas.ds.scale; - } - } catch (e) { } - // Scale up if zoomed in, but don't drop below 1x DPR if zoomed out - return dpr * Math.max(1, graphScale); - } - - resizeCanvas(widthPx) { - const scale = this.getRenderScale(); - const targetWidth = Math.round(widthPx * scale); - const targetHeight = Math.round(this.canvasHeight * scale); - - this.canvas.width = targetWidth; - this.canvas.height = targetHeight; - this.ctx.setTransform(scale, 0, 0, scale, 0, 0); - this.render(); - } - - // Helper to map mouse events accurately regardless of canvas scaling - getMousePos(e) { - const rect = this.canvas.getBoundingClientRect(); - - const scaleX = this.canvas.offsetWidth / rect.width; - const scaleY = this.canvas.offsetHeight / rect.height; - - const x = (e.clientX - rect.left) * scaleX; - const y = (e.clientY - rect.top) * scaleY; - return { x, y }; - } - - // --- Async Image Upload Logic (Handles multiple images simultaneously) --- - async handleImageUpload(files, targetFrameStart = null, explicitLength = null) { - const frameRate = this.getFrameRate(); - const durationFrames = this.getDurationFrames(); - const newLength = explicitLength !== null ? explicitLength : frameRate * 1; // Default to 1 second long - - for (let file of files) { - if (!file.type.startsWith("image/")) continue; - - await new Promise(async (resolve) => { - try { - const body = new FormData(); - body.append("image", file); - const resp = await api.fetchApi("/upload/image", { method: "POST", body }); - if (resp.status !== 200) { resolve(); return; } - - const data = await resp.json(); - const filename = data.name; - const subfolder = data.subfolder || ""; - const imageFile = subfolder ? subfolder + "/" + filename : filename; - const imgUrl = api.apiURL(`/view?filename=${encodeURIComponent(filename)}&type=input&subfolder=${encodeURIComponent(subfolder)}`); - - const img = new Image(); - img.onload = () => { - - let newStart = targetFrameStart; - if (newStart === null) { - // Fallback: find the first free slot, or append past the end - newStart = 0; - this.timeline.segments.sort((a, b) => a.start - b.start); - for (let i = 0; i < this.timeline.segments.length; i++) { - let seg = this.timeline.segments[i]; - if (newStart + newLength <= seg.start) break; - newStart = Math.max(newStart, seg.start + seg.length); - } - } - - // Use the visual timeline as the physics bound so segments can - // land anywhere in the padded visual area without touching duration_frames. - const currentDuration = this.getVisualDurationFrames(); - - if (targetFrameStart !== null) { - // Resolve physics to push existing segments - let tempId = "TEMP_" + Date.now(); - this.timeline.segments.push({ id: tempId, start: newStart, length: newLength, type: "temp" }); - let result = this._applyCenterDragPhysics(this.timeline.segments, tempId, newStart, newStart + newLength / 2, currentDuration, currentDuration, 1); - - // Update original segments with resolved physics to preserve imgObj - for (let shiftedSeg of result) { - let original = this.timeline.segments.find(s => s.id === shiftedSeg.id); - if (original) { - original.start = shiftedSeg.resolvedStart !== undefined ? shiftedSeg.resolvedStart : shiftedSeg.start; - } - } - - let tempSeg = this.timeline.segments.find(s => s.id === tempId); - newStart = tempSeg.start; - this.timeline.segments = this.timeline.segments.filter(s => s.id !== tempId); - targetFrameStart = newStart + newLength; // For the next file in batch - } - - // Use the full intended length — the timeline has already been grown to fit. - let constrainedLength = newLength; - - const seg = { - id: Date.now().toString() + Math.random().toString(36).substr(2, 5), - start: newStart, - length: constrainedLength, - prompt: "", - type: "image", - imageFile: imageFile, - imageB64: imgUrl - }; - - const displayImg = new Image(); - displayImg.onload = () => { - seg.imgObj = displayImg; - this.render(); - resolve(); // Resolve promise letting next image process - }; - displayImg.src = imgUrl; - - this.timeline.segments.push(seg); - this.timeline.segments.sort((a, b) => a.start - b.start); - this.selectionType = "image"; - this.selectedIndex = this.timeline.segments.findIndex(s => s.id === seg.id); - - this.updateUIFromSelection(); - this.commitChanges(true); - }; - img.src = imgUrl; - } catch (err) { - console.error("[PromptRelay] Image upload failed", err); - resolve(); - } - }); - } - this.fileInput.value = ""; - } - - // --- Async Audio Upload Logic --- - async handleAudioUpload(files, targetFrameStart = null) { - const frameRate = this.getFrameRate(); - const durationFrames = this.getDurationFrames(); - - for (let file of files) { - if (!file.type.startsWith("audio/")) continue; - - await new Promise(async (resolve) => { - try { - const body = new FormData(); - body.append("image", file); - const resp = await api.fetchApi("/upload/image", { method: "POST", body }); - if (resp.status !== 200) { resolve(); return; } - - const data = await resp.json(); - const filename = data.name; - const subfolder = data.subfolder || ""; - const audioFile = subfolder ? subfolder + "/" + filename : filename; - - const arrayBuffer = await file.arrayBuffer(); - const audioCtx = new (window.AudioContext || window.webkitAudioContext)(); - const audioBuffer = await audioCtx.decodeAudioData(arrayBuffer); - const clipDurationSecs = audioBuffer.duration; - const clipFrames = Math.max(1, Math.ceil(clipDurationSecs * frameRate)); - - const channelData = audioBuffer.getChannelData(0); - const peaks = []; - const numPeaks = 200; - const step = Math.floor(channelData.length / numPeaks); - for (let i = 0; i < numPeaks; i++) { - let max = 0; - for (let j = 0; j < step; j++) { - const val = Math.abs(channelData[i * step + j]); - if (val > max) max = val; - } - peaks.push(max); - } - - let newLength = clipFrames; - let newStart = targetFrameStart; - - if (newStart === null) { - // Find the first free slot, or place past the end of all existing audio - newStart = 0; - this.timeline.audioSegments.sort((a, b) => a.start - b.start); - for (let i = 0; i < this.timeline.audioSegments.length; i++) { - let seg = this.timeline.audioSegments[i]; - if (newStart + newLength <= seg.start) break; - newStart = Math.max(newStart, seg.start + seg.length); - } - } - - // Use the visual timeline as the physics bound so segments can - // land anywhere in the padded visual area without touching duration_frames. - const currentDuration = this.getVisualDurationFrames(); - - if (targetFrameStart !== null) { - let tempId = "TEMP_" + Date.now(); - this.timeline.audioSegments.push({ id: tempId, start: newStart, length: newLength, type: "temp" }); - let result = this._applyCenterDragPhysics(this.timeline.audioSegments, tempId, newStart, newStart + newLength / 2, currentDuration, currentDuration, 1); - - for (let shiftedSeg of result) { - let original = this.timeline.audioSegments.find(s => s.id === shiftedSeg.id); - if (original) original.start = shiftedSeg.resolvedStart !== undefined ? shiftedSeg.resolvedStart : shiftedSeg.start; - } - - let tempSeg = this.timeline.audioSegments.find(s => s.id === tempId); - newStart = tempSeg.start; - this.timeline.audioSegments = this.timeline.audioSegments.filter(s => s.id !== tempId); - targetFrameStart = newStart + newLength; - } - - // Use the full clip length — timeline has already grown to fit. - let constrainedLength = newLength; - - const seg = { - id: Date.now().toString() + Math.random().toString(36).substr(2, 5), - type: "audio", - start: newStart, - length: constrainedLength, - trimStart: 0, - audioDurationFrames: clipFrames, - audioFile: audioFile, - fileName: file.name, - waveformPeaks: peaks - }; - - this.timeline.audioSegments.push(seg); - this.timeline.audioSegments.sort((a, b) => a.start - b.start); - this.selectionType = "audio"; - this.selectedIndex = this.timeline.audioSegments.findIndex(s => s.id === seg.id); - - this.updateUIFromSelection(); - this.commitChanges(true); - this.render(); - resolve(); - } catch (err) { - console.error("[PromptRelay] Audio processing failed", err); - resolve(); - } - }); - } - this.audioFileInput.value = ""; - } - - deleteSelectedSegment() { - if (this.selectionType === "audio") { - if (this.timeline.audioSegments.length === 0 || this.selectedIndex === -1) return; - this.timeline.audioSegments.splice(this.selectedIndex, 1); - this.selectedIndex = Math.max(-1, this.selectedIndex - 1); - } else { - if (this.timeline.segments.length === 0 || this.selectedIndex === -1) return; - this.timeline.segments.splice(this.selectedIndex, 1); - this.selectedIndex = Math.max(-1, this.selectedIndex - 1); - } - this.updateUIFromSelection(); - this.commitChanges(); - this.render(); - } - - formatTime(frames, dropSuffix = false) { - const mode = this.displayModeWidget ? this.displayModeWidget.value : "seconds"; - if (mode === "seconds") { - const secs = frames / this.getFrameRate(); - return dropSuffix ? secs.toFixed(2) : secs.toFixed(2) + "s"; - } - return dropSuffix ? Math.round(frames).toString() : Math.round(frames) + " frames"; - } - - updateWidgetVisibility() { - const mode = this.displayModeWidget ? this.displayModeWidget.value : "seconds"; - - if (this.durationFramesWidget) { - // Always visible regardless of display mode - this.durationFramesWidget.type = "INT"; - if (!this.durationFramesWidget.options) this.durationFramesWidget.options = {}; - this.durationFramesWidget.options.hidden = false; - this.durationFramesWidget.hidden = false; - delete this.durationFramesWidget.computeSize; - } - if (this.durationSecondsWidget) { - // Always visible regardless of display mode - this.durationSecondsWidget.type = "FLOAT"; - if (!this.durationSecondsWidget.options) this.durationSecondsWidget.options = {}; - this.durationSecondsWidget.options.hidden = false; - this.durationSecondsWidget.hidden = false; - delete this.durationSecondsWidget.computeSize; - } - - // Force node resize and redraw deferred to next tick - setTimeout(() => { - if (this.node && this.node.computeSize) { - const sz = this.node.computeSize(); - this.node.size[1] = sz[1]; - if (window.app && window.app.graph) { - window.app.graph.setDirtyCanvas(true, true); - } - } - }, 0); - } - - updateUIFromSelection() { - let seg = null; - if (this.selectedIndex >= 0) { - if (this.selectionType === "audio") { - const origSeg = this.timeline.audioSegments[this.selectedIndex]; - if (origSeg) { - const previewIsAudio = this._ghostTrack === 'audio' || (this._previewSegments && this._ghostTrack === null && this.selectionType === 'audio'); - const arr = (this._previewSegments && previewIsAudio) ? this._previewSegments : this.timeline.audioSegments; - seg = arr.find(s => s.id === origSeg.id) || origSeg; - } - } else { - const origSeg = this.timeline.segments[this.selectedIndex]; - if (origSeg) { - const previewIsImage = this._ghostTrack === 'image' || (this._previewSegments && this._ghostTrack === null && this.selectionType === 'image'); - const arr = (this._previewSegments && previewIsImage) ? this._previewSegments : this.timeline.segments; - seg = arr.find(s => s.id === origSeg.id) || origSeg; - } - } - } - - if (this.selectionType === "audio" && seg) { - this.promptInput.style.display = "none"; - this.strengthRow.style.display = "flex"; - this.audioInfoArea.style.display = "block"; - this.audioInfoArea.innerHTML = ` - File: ${seg.fileName || "Unknown"}
- Length: ${this.formatTime(seg.audioDurationFrames)} Output Length: ${this.formatTime(seg.length)}
- Trim-in: ${this.formatTime(Math.round(seg.trimStart))} Trim-Out: ${this.formatTime(Math.round(seg.audioDurationFrames - (seg.trimStart + seg.length)))} - `; - this.strengthValue.value = "1.00"; - this.strengthValue.disabled = true; - } else { - this.audioInfoArea.style.display = "none"; - this.promptInput.style.display = "block"; - this.strengthRow.style.display = "flex"; - - if (seg) { - this.promptInput.value = seg.prompt || ""; - this.promptInput.disabled = false; - - const isImage = seg.type !== "text"; - const strength = isImage ? (seg.guideStrength ?? 1.0) : 1.0; - this.strengthValue.value = strength.toFixed(2); - this.strengthValue.disabled = !isImage; - } else { - this.promptInput.value = ""; - this.promptInput.disabled = true; - this.strengthValue.value = "1.00"; - this.strengthValue.disabled = true; - } - } - - if (this.segmentBoundsDisplay) { - if (seg) { - const startStr = this.formatTime(seg.start, true); - const endStr = this.formatTime(seg.start + seg.length, true); - this.segmentBoundsDisplay.textContent = `Start: ${startStr} | End: ${endStr}`; - } else { - this.segmentBoundsDisplay.textContent = "Start: - | End: -"; - } - } - } - - // --- Rendering logic --- - render() { - const width = this.canvas.offsetWidth || this._lastWidth; - const height = this.canvasHeight; - const totalFrames = this.getVisualDurationFrames(); - - if (!width || width <= 0) return; - - this.ctx.clearRect(0, 0, width, height); - - - - // Render Track Backgrounds - this.ctx.fillStyle = "#111"; // Image track bg - this.ctx.fillRect(0, RULER_HEIGHT, width, this.blockHeight); - this.ctx.fillStyle = "#111"; // Audio track bg - this.ctx.fillRect(0, RULER_HEIGHT + this.blockHeight, width, this.audioTrackHeight); - - - - // Determine which track the preview belongs to. - // _ghostTrack is set during HTML file drag-and-drop. - // During canvas mouse drags, _ghostTrack is null, so fall back to selectionType. - const previewIsAudio = this._ghostTrack === 'audio' || - (this._previewSegments && this._ghostTrack === null && this.selectionType === 'audio'); - - let renderSegments = (this._previewSegments && !previewIsAudio) - ? this._previewSegments : this.timeline.segments; - - let renderAudioSegments = (this._previewSegments && previewIsAudio) - ? this._previewSegments : this.timeline.audioSegments; - - - - const activeSegId = this.timeline.segments[this.selectedIndex]?.id; - const activeAudioSegId = this.timeline.audioSegments[this.selectedIndex]?.id; - - // Sort segments so that the selected one is drawn last (on top) - const isImageSelection = this.selectionType === "image"; - const sortedSegments = [...renderSegments].sort((a, b) => { - const aSel = isImageSelection && a.id === activeSegId; - const bSel = isImageSelection && b.id === activeSegId; - return aSel - bSel; - }); - - const isAudioSelection = this.selectionType === "audio"; - const sortedAudioSegments = [...renderAudioSegments].sort((a, b) => { - const aSel = isAudioSelection && a.id === activeAudioSegId; - const bSel = isAudioSelection && b.id === activeAudioSegId; - return aSel - bSel; - }); - - // --- Draw Image/Text Segments --- - for (let i = 0; i < sortedSegments.length; i++) { - const seg = sortedSegments[i]; - const startX = (seg.start / totalFrames) * width; - const pxWidth = (seg.length / totalFrames) * width; - const isSelected = (this.selectionType === "image" && seg.id === activeSegId); - - const originalSeg = this.timeline.segments.find(s => s.id === seg.id); - const imgObj = originalSeg ? originalSeg.imgObj : seg.imgObj; - - if ((this._isDragging && this.selectionType === "image" && seg.id === this._dragTargetId) || (this._ghostSegmentId && seg.id === this._ghostSegmentId)) { - this.ctx.globalAlpha = 0.65; - } else { - this.ctx.globalAlpha = 1.0; - } - - if (seg.type === "ghost") { - this.ctx.fillStyle = "#2a2a2a"; - this.ctx.fillRect(startX, RULER_HEIGHT, pxWidth, this.blockHeight); - - this.ctx.strokeStyle = "#777"; - this.ctx.lineWidth = 2; - this.ctx.setLineDash([5, 5]); - this.ctx.strokeRect(startX, RULER_HEIGHT + 1, pxWidth, this.blockHeight - 2); - this.ctx.setLineDash([]); - - this.ctx.fillStyle = "#aaa"; - this.ctx.textAlign = "center"; - this.ctx.textBaseline = "middle"; - this.ctx.font = "bold 12px sans-serif"; - this.ctx.fillText("Drop to Place", startX + pxWidth / 2, RULER_HEIGHT + this.blockHeight / 2); - } else { - this.ctx.fillStyle = seg.type === "text" ? "#000b12" : "#000"; - this.ctx.fillRect(startX, RULER_HEIGHT + 1, pxWidth, this.blockHeight - 2); - } - - if (imgObj && imgObj.complete && imgObj.naturalWidth > 0 && seg.type !== "ghost") { - const imgRatio = imgObj.naturalWidth / imgObj.naturalHeight; - const boxRatio = pxWidth / this.blockHeight; - let drawW, drawH, drawX, drawY; - if (imgRatio > boxRatio) { - drawW = pxWidth; drawH = pxWidth / imgRatio; - drawX = startX; drawY = RULER_HEIGHT + (this.blockHeight - drawH) / 2; - } else { - drawH = this.blockHeight; drawW = this.blockHeight * imgRatio; - drawY = RULER_HEIGHT; drawX = startX + (pxWidth - drawW) / 2; - } - - // Clip to segment bounds so tiled images don't bleed into adjacent segments - this.ctx.save(); - this.ctx.beginPath(); - this.ctx.rect(startX, RULER_HEIGHT + 1, pxWidth, this.blockHeight - 2); - this.ctx.clip(); - - if (imgRatio > boxRatio) { - // Fits width, vertical letterboxing (black bars top/bottom) — keep as is - this.ctx.drawImage(imgObj, drawX, drawY, drawW, drawH); - } else { - // Fits height, horizontal letterboxing (black bars left/right) — tile horizontally - this.ctx.drawImage(imgObj, drawX, drawY, drawW, drawH); - - // Tile left - let leftX = drawX - drawW; - while (leftX + drawW > startX) { - this.ctx.drawImage(imgObj, leftX, drawY, drawW, drawH); - leftX -= drawW; - } - - // Tile right - let rightX = drawX + drawW; - while (rightX < startX + pxWidth) { - this.ctx.drawImage(imgObj, rightX, drawY, drawW, drawH); - rightX += drawW; - } - } - this.ctx.restore(); - - // --- Prompt subtitle overlay --- - if (seg.prompt && seg.type !== "ghost" && pxWidth > 24) { - const overlayH = Math.round(this.blockHeight * 0.20); - const overlayY = RULER_HEIGHT + this.blockHeight - overlayH; - - this.ctx.save(); - this.ctx.beginPath(); - this.ctx.rect(startX, overlayY, pxWidth, overlayH); - this.ctx.clip(); - - // Translucent background - this.ctx.fillStyle = "rgba(0, 0, 0, 0.60)"; - this.ctx.fillRect(startX, overlayY, pxWidth, overlayH); - - // Text - const fontSize = Math.min(11, overlayH * 0.58); - this.ctx.font = `${fontSize}px sans-serif`; - this.ctx.fillStyle = "#e0e3ed"; - this.ctx.textAlign = "center"; - this.ctx.textBaseline = "middle"; - - // Measure and truncate to single line - const maxTextW = pxWidth - 10; - let label = seg.prompt; - if (this.ctx.measureText(label).width > maxTextW) { - while (label.length > 0 && this.ctx.measureText(label + "…").width > maxTextW) { - label = label.slice(0, -1); - } - label += "…"; - } - - this.ctx.fillText(label, startX + pxWidth / 2, overlayY + overlayH / 2); - this.ctx.restore(); - } - } else if (seg.type === "text") { - const pad = 8; - const boxW = pxWidth - pad * 2; - if (boxW > 12) { - this.ctx.save(); - this.ctx.beginPath(); - this.ctx.rect(startX + pad, RULER_HEIGHT + pad, boxW, this.blockHeight - pad * 2); - this.ctx.clip(); - this.ctx.fillStyle = "#e0e3ed"; - this.ctx.font = "11px sans-serif"; - this.ctx.textAlign = "center"; - this.ctx.textBaseline = "top"; - const label = seg.prompt || "(no prompt)"; - const words = label.split(" "); - const lineH = 15; - let line = ""; - let lines = []; - for (const word of words) { - const test = line ? line + " " + word : word; - if (this.ctx.measureText(test).width > boxW && line) { - lines.push(line); - line = word; - } else { - line = test; - } - } - if (line) lines.push(line); - - const maxLines = Math.max(1, Math.floor((this.blockHeight - pad * 2) / lineH)); - if (lines.length > maxLines) { - lines = lines.slice(0, maxLines); - lines[lines.length - 1] += "…"; - } - - const totalTextHeight = lines.length * lineH; - let ty = RULER_HEIGHT + (this.blockHeight - totalTextHeight) / 2 + 2; - - for (const l of lines) { - this.ctx.fillText(l, startX + pxWidth / 2, ty); - ty += lineH; - } - this.ctx.restore(); - } - } - - if (isSelected) { - this.ctx.strokeStyle = "#fff"; - this.ctx.lineWidth = 2; - this.ctx.strokeRect(startX, RULER_HEIGHT + 1, pxWidth, this.blockHeight - 2); - this.ctx.fillStyle = "#fff"; - this.ctx.beginPath(); - this.ctx.roundRect(startX, RULER_HEIGHT + this.blockHeight / 2 - 12, 4, 24, 2); - this.ctx.fill(); - this.ctx.beginPath(); - this.ctx.roundRect(startX + pxWidth - 4, RULER_HEIGHT + this.blockHeight / 2 - 12, 4, 24, 2); - this.ctx.fill(); - } else { - this.ctx.strokeStyle = "#000"; - this.ctx.lineWidth = 1.5; - this.ctx.strokeRect(startX, RULER_HEIGHT + 1, pxWidth, this.blockHeight - 2); - } - this.ctx.globalAlpha = 1.0; - } - - // --- Draw Audio Segments --- - for (let i = 0; i < sortedAudioSegments.length; i++) { - const seg = sortedAudioSegments[i]; - const startX = (seg.start / totalFrames) * width; - const pxWidth = (seg.length / totalFrames) * width; - const isSelected = (this.selectionType === "audio" && seg.id === activeAudioSegId); - const trackY = RULER_HEIGHT + this.blockHeight; - - if ((this._isDragging && this.selectionType === "audio" && seg.id === this._dragTargetId) || (this._ghostSegmentId && seg.id === this._ghostSegmentId)) { - this.ctx.globalAlpha = 0.65; - } else { - this.ctx.globalAlpha = 1.0; - } - - if (seg.type === "ghost") { - this.ctx.fillStyle = "#1a1a1a"; - this.ctx.fillRect(startX, trackY, pxWidth, this.audioTrackHeight); - this.ctx.strokeStyle = "#555"; - this.ctx.lineWidth = 2; - this.ctx.setLineDash([5, 5]); - this.ctx.strokeRect(startX, trackY, pxWidth, this.audioTrackHeight); - this.ctx.setLineDash([]); - this.ctx.fillStyle = "#888"; - this.ctx.textAlign = "center"; - this.ctx.textBaseline = "middle"; - this.ctx.font = "bold 12px sans-serif"; - this.ctx.fillText("Drop Audio", startX + pxWidth / 2, trackY + this.audioTrackHeight / 2); - } else { - this.drawAudioSegmentVisuals(this.ctx, seg, isSelected, trackY, this.audioTrackHeight, startX, pxWidth); - } - this.ctx.globalAlpha = 1.0; - } - - // --- Draw Ruler & Divider AFTER segments to prevent overlap --- - // Ruler Background - this.ctx.fillStyle = "#1e1e1e"; - this.ctx.fillRect(0, 0, width, RULER_HEIGHT); - - // Crisp Ruler Text - this.ctx.fillStyle = "#aaa"; - this.ctx.textAlign = "center"; - this.ctx.textBaseline = "middle"; - this.ctx.font = "10px sans-serif"; - - const frameRate = this.getFrameRate(); - const mode = this.displayModeWidget ? this.displayModeWidget.value : "seconds"; - - // Define logical steps for both modes - let steps; - if (mode === "seconds") { - steps = [0.1, 0.2, 0.5, 1, 2, 5, 10, 15, 30, 60, 120, 300, 600]; - } else { - steps = [1, 2, 5, 10, 24, 48, 120, 240, 480, 960, 1920]; - } - - const minSpacingPx = 60; - let majorStep = steps[steps.length - 1]; - for (let i = 0; i < steps.length; i++) { - const stepFrames = mode === "seconds" ? steps[i] * frameRate : steps[i]; - const spacingPx = (stepFrames / totalFrames) * width; - if (spacingPx >= minSpacingPx) { - majorStep = steps[i]; - break; - } - } - - const majorStepFrames = mode === "seconds" ? majorStep * frameRate : majorStep; - - let minorStep; - if (mode === "seconds") { - if (majorStep <= 0.2) minorStep = majorStep / 2; - else if (majorStep <= 1) minorStep = majorStep / 5; - else if (majorStep <= 5) minorStep = 1; - else if (majorStep <= 15) minorStep = 5; - else if (majorStep <= 30) minorStep = 10; - else if (majorStep <= 60) minorStep = 10; - else minorStep = majorStep / 5; - } else { - if (majorStep <= 5) minorStep = 1; - else if (majorStep <= 10) minorStep = 2; - else if (majorStep <= 24) minorStep = 6; - else if (majorStep <= 48) minorStep = 12; - else minorStep = majorStep / 5; - } - const minorStepFrames = mode === "seconds" ? minorStep * frameRate : minorStep; - - this.ctx.fillStyle = "#444"; - const totalMinorTicks = Math.floor(totalFrames / minorStepFrames); - for (let i = 0; i <= totalMinorTicks; i++) { - const frameVal = i * minorStepFrames; - if (Math.abs(frameVal % majorStepFrames) < 0.1) continue; - - const x = (frameVal / totalFrames) * width; - this.ctx.fillRect(Math.floor(x), RULER_HEIGHT - 3, 1, 3); - } - - this.ctx.fillStyle = "#aaa"; - const totalMajorTicks = Math.floor(totalFrames / majorStepFrames); - for (let i = 0; i <= totalMajorTicks; i++) { - const frameVal = i * majorStepFrames; - const x = (frameVal / totalFrames) * width; - - this.ctx.fillStyle = "#aaa"; - this.ctx.fillRect(Math.floor(x), RULER_HEIGHT - 6, 1, 6); - - if (frameVal > 0 && frameVal < totalFrames) { - this.ctx.textAlign = "center"; - this.ctx.fillText(this.formatTime(frameVal, true), x, RULER_HEIGHT / 2); - } - } - - this.ctx.textAlign = "left"; - const zeroLabel = mode === "seconds" ? "0" : this.formatTime(0, true); - this.ctx.fillText(zeroLabel, 4, RULER_HEIGHT / 2); - - // Divider - this.ctx.fillStyle = "#333"; - this.ctx.fillRect(0, RULER_HEIGHT + this.blockHeight, width, 1); - - // Draw gap "+" buttons - if (!this._isDragging) { - const BTN_R = 12; - const gapRegions = this.getGapRegions(); - for (let i = 0; i < gapRegions.length; i++) { - const gap = gapRegions[i]; - if (gap.widthPx < BTN_R * 2 + 8) continue; - const hov = this._hoveredGapIdx === i; - const BTN_W = 18; - const BTN_H = 18; - this.ctx.beginPath(); - this.ctx.roundRect(gap.centerX - BTN_W / 2, gap.centerY - BTN_H / 2, BTN_W, BTN_H, 4); - this.ctx.fillStyle = hov ? "rgba(255,255,255,0.15)" : "rgba(255,255,255,0.05)"; - this.ctx.fill(); - this.ctx.fillStyle = hov ? "#fff" : "#888"; - this.ctx.font = "14px sans-serif"; - this.ctx.textAlign = "center"; - this.ctx.textBaseline = "middle"; - this.ctx.fillText("+", gap.centerX, gap.centerY + 1); - } - } - - // --- Out-of-duration shadow overlay --- - // Draw a translucent black mask over the region beyond the actual output duration - // so the user can clearly see which content will be included in the render. - const outputFrames = this.getDurationFrames(); - if (outputFrames < totalFrames) { - const cutoffX = (outputFrames / totalFrames) * width; - // Semi-transparent black overlay on both tracks - this.ctx.fillStyle = "rgba(0, 0, 0, 0.45)"; - this.ctx.fillRect(cutoffX, RULER_HEIGHT, width - cutoffX, this.blockHeight + this.audioTrackHeight); - // Subtle tinted ruler overlay - this.ctx.fillStyle = "rgba(0, 0, 0, 0.25)"; - this.ctx.fillRect(cutoffX, 0, width - cutoffX, RULER_HEIGHT); - /* - // Dashed boundary line at the output duration cutoff - this.ctx.save(); - this.ctx.strokeStyle = "rgba(255, 80, 80, 0.7)"; - this.ctx.lineWidth = 1.5; - this.ctx.setLineDash([5, 4]); - this.ctx.beginPath(); - this.ctx.moveTo(cutoffX, 0); - this.ctx.lineTo(cutoffX, CANVAS_HEIGHT); - this.ctx.stroke(); - this.ctx.setLineDash([]); - this.ctx.restore(); - */ - } - - // --- Draw Playhead --- - const playheadX = (this.currentFrame / totalFrames) * width; - - // Playhead Line - this.ctx.beginPath(); - this.ctx.moveTo(playheadX, 14); - this.ctx.lineTo(playheadX, this.canvasHeight); - this.ctx.strokeStyle = "#ff4444"; - this.ctx.lineWidth = 1.5; - this.ctx.stroke(); - - // Playhead Handle (Polygon above numbers) - this.ctx.fillStyle = "#ff4444"; - this.ctx.beginPath(); - this.ctx.moveTo(playheadX - 6, 0); - this.ctx.lineTo(playheadX + 6, 0); - this.ctx.lineTo(playheadX + 6, 8); - this.ctx.lineTo(playheadX, 14); - this.ctx.lineTo(playheadX - 6, 8); - this.ctx.fill(); - - // Draw vertical grab bar on the right edge of viewport for resizing width - const grabBarW = 4; - const grabBarH = 50; - const grabBarX = this.viewport.scrollLeft + this.viewport.clientWidth - grabBarW - 3; - const grabBarY = RULER_HEIGHT + (this.blockHeight + this.audioTrackHeight - grabBarH) / 2; - - this.ctx.fillStyle = "rgba(40, 40, 40, 0.6)"; - this.ctx.beginPath(); - this.ctx.roundRect(grabBarX, grabBarY, grabBarW, grabBarH, 2); - this.ctx.fill(); - - // Draw horizontal grab bar at the bottom of viewport for resizing height - const hBarW = 50; - const hBarH = 4; - const hBarX = this.viewport.scrollLeft + (this.viewport.clientWidth - hBarW) / 2; - const hBarY = this.canvasHeight - hBarH - 3; // 3px from the bottom edge - - this.ctx.fillStyle = "rgba(20, 20, 20, 0.8)"; - this.ctx.beginPath(); - this.ctx.roundRect(hBarX, hBarY, hBarW, hBarH, 2); - this.ctx.fill(); - - this.updatePlayerUI(); - } - - drawAudioSegmentVisuals(ctx, seg, isSelected, yOffset, trackHeight, startX, pxWidth) { - ctx.fillStyle = isSelected ? "#2a4a3a" : "#1a2a1a"; - ctx.fillRect(startX, yOffset + 2, pxWidth, trackHeight - 3); - - if (seg.waveformPeaks && pxWidth > 0) { - ctx.fillStyle = isSelected ? "rgba(100, 255, 100, 0.6)" : "rgba(100, 255, 100, 0.3)"; - const startRatio = seg.trimStart / seg.audioDurationFrames; - const endRatio = (seg.trimStart + seg.length) / seg.audioDurationFrames; - const peakCount = seg.waveformPeaks.length; - const centerY = yOffset + trackHeight / 2; - - ctx.beginPath(); - for (let i = 0; i < pxWidth; i++) { - const pixelRatio = i / pxWidth; - const globalRatio = startRatio + pixelRatio * (endRatio - startRatio); - const peakIdx = Math.floor(globalRatio * peakCount); - - if (peakIdx >= 0 && peakIdx < peakCount) { - const val = seg.waveformPeaks[peakIdx]; - const amp = (val * (trackHeight - 12) / 2) * 0.9; - ctx.fillRect(startX + i, centerY - amp, 1, amp * 2); - } - } - } - - ctx.strokeStyle = isSelected ? "#4fff8f" : "#000"; - ctx.lineWidth = 1.5; - ctx.strokeRect(startX, yOffset + 2, pxWidth, trackHeight - 3); - - if (isSelected) { - ctx.fillStyle = "#4fff8f"; - ctx.beginPath(); - ctx.roundRect(startX, yOffset + trackHeight / 2 - 12, 4, 24, 2); - ctx.fill(); - ctx.beginPath(); - ctx.roundRect(startX + pxWidth - 4, yOffset + trackHeight / 2 - 12, 4, 24, 2); - ctx.fill(); - } - - ctx.fillStyle = "#ccc"; - ctx.font = "11px sans-serif"; - ctx.textBaseline = "top"; - ctx.textAlign = "left"; - ctx.save(); - ctx.beginPath(); - ctx.rect(startX, yOffset + 2, pxWidth, trackHeight - 3); - ctx.clip(); - - let text = seg.fileName || "Audio Track"; - const maxWidth = pxWidth - 12; - if (ctx.measureText(text).width > maxWidth && maxWidth > 0) { - while (text.length > 0 && ctx.measureText(text + "...").width > maxWidth) { - text = text.slice(0, -1); - } - text = text + "..."; - } - - ctx.fillText(text, startX + 6, yOffset + 8); - ctx.restore(); - } - - - // --- Interaction Logic --- - getHitTest(mouseX, mouseY) { - const width = this.canvas.offsetWidth; - const totalFrames = this.getVisualDurationFrames(); - - // Check Playhead Handle first - const playheadX = (this.currentFrame / totalFrames) * width; - if (mouseY <= 24 && Math.abs(mouseX - playheadX) <= 12) { - return { type: "playhead" }; - } - - if (mouseY <= RULER_HEIGHT) { - return { type: "ruler" }; - } - - if (mouseY < RULER_HEIGHT || mouseY > this.canvasHeight) return null; - - const isAudioTrack = mouseY > RULER_HEIGHT + this.blockHeight; - const trackSegments = isAudioTrack ? this.timeline.audioSegments : this.timeline.segments; - const trackType = isAudioTrack ? "audio" : "image"; - - if (trackSegments.length === 0) return null; - - // The variables width and totalFrames are already declared above. - - let sortedSegments = [...trackSegments] - .map((s, i) => ({ ...s, originalIndex: i })) - .sort((a, b) => a.start - b.start); - - const HANDLE_CORE = 4; - - for (let i = 0; i < sortedSegments.length; i++) { - const seg = sortedSegments[i]; - const startX = (seg.start / totalFrames) * width; - const pxWidth = (seg.length / totalFrames) * width; - const endX = startX + pxWidth; - - const prevSeg = sortedSegments[i - 1]; - const nextSeg = sortedSegments[i + 1]; - - const isLeftJoint = prevSeg && prevSeg.start + prevSeg.length === seg.start; - if (!isLeftJoint) { - if (Math.abs(mouseX - startX) <= HANDLE_HIT_PX) { - return { type: "edge", index: seg.originalIndex, dir: "left", track: trackType }; - } - } - - const isRightJoint = nextSeg && nextSeg.start === seg.start + seg.length; - if (isRightJoint) { - const dx = mouseX - endX; - if (Math.abs(dx) <= HANDLE_HIT_PX) { - if (dx < -HANDLE_CORE) { - return { type: "edge", index: seg.originalIndex, dir: "right", track: trackType }; - } else if (dx > HANDLE_CORE) { - return { type: "edge", index: nextSeg.originalIndex, dir: "left", track: trackType }; - } else { - return { type: "joint", leftIndex: seg.originalIndex, rightIndex: nextSeg.originalIndex, track: trackType }; - } - } - } else { - if (Math.abs(mouseX - endX) <= HANDLE_HIT_PX) { - return { type: "edge", index: seg.originalIndex, dir: "right", track: trackType }; - } - } - } - - for (let i = 0; i < sortedSegments.length; i++) { - const seg = sortedSegments[i]; - const startX = (seg.start / totalFrames) * width; - const pxWidth = (seg.length / totalFrames) * width; - const endX = startX + pxWidth; - - if (mouseX >= startX && mouseX < endX) { - return { type: "center", index: seg.originalIndex, track: trackType }; - } - } - - return null; - } - - onMouseDown(e) { - if (e.button !== 0) return; - const { x, y } = this.getMousePos(e); - - const isOverDivider = Math.abs(y - (RULER_HEIGHT + this.blockHeight)) <= 4; - if (isOverDivider) { - this._isDragging = true; - this._dragType = "divider"; - this._startBlockHeight = this.blockHeight; - this._startAudioTrackHeight = this.audioTrackHeight; - this._startY = y; - return; - } - - const isAtBottom = Math.abs(y - this.canvasHeight) <= 15; - if (isAtBottom) { - this._isDragging = true; - this._dragType = "height_resize"; - this._startBlockHeight = this.blockHeight; - this._startY = y; - document.body.style.userSelect = "none"; - return; - } - - const viewRect = this.viewport.getBoundingClientRect(); - const isAtRightEdge = Math.abs(e.clientX - viewRect.right) <= 20; - if (isAtRightEdge) { - this._isDragging = true; - this._dragType = "width_resize"; - this._startNodeWidth = this.node.size[0]; - this._startX = e.clientX; - document.body.style.userSelect = "none"; - return; - } - - if (y >= RULER_HEIGHT && y <= this.canvasHeight) { - const BTN_R = 12; - const gapRegions = this.getGapRegions(); - for (let i = 0; i < gapRegions.length; i++) { - const gap = gapRegions[i]; - if (gap.widthPx < BTN_R * 2 + 8) continue; - const dx = x - gap.centerX, dy2 = y - gap.centerY; - if (dx * dx + dy2 * dy2 <= BTN_R * BTN_R) { - if (gap.track === "audio") { - // Direct to audio upload - this.promptAddAudioInGap(gap.frameStart, gap.frameEnd); - } else { - this.showGapMenu(e.clientX, e.clientY, gap); - } - return; - } - } - } - - const hit = this.getHitTest(x, y); - if (!hit) { - // Only deselect if they clicked the same track but hit empty space - const clickedTrack = y > RULER_HEIGHT + this.blockHeight ? "audio" : "image"; - if (this.selectionType === clickedTrack) { - this.selectedIndex = -1; - this.updateUIFromSelection(); - } - this.render(); - return; - } - - if (hit.type === "playhead" || hit.type === "ruler") { - this._isDragging = true; - this._dragType = "playhead"; - const logicalWidth = this.canvas.offsetWidth; - const totalFrames = this.getVisualDurationFrames(); - let mouseFrameX = x * (totalFrames / logicalWidth); - this.currentFrame = clamp(mouseFrameX, 0, totalFrames); - this.render(); - if (this.isPlaying) { - this.playAudio(); - } - return; - } - - this.selectionType = hit.track; - const targetArray = hit.track === "audio" ? this.timeline.audioSegments : this.timeline.segments; - - if (hit.type === "joint") { - this.selectedIndex = hit.leftIndex; - this.updateUIFromSelection(); - this._dragType = "joint"; - this._dragTargetId = targetArray[hit.leftIndex].id; - this._dragTargetIdRight = targetArray[hit.rightIndex].id; - } else if (hit.type === "center") { - this.selectedIndex = hit.index; - this.updateUIFromSelection(); - this._dragType = "center"; - } else { - if (this.selectedIndex !== hit.index) { - this.selectedIndex = hit.index; - this.updateUIFromSelection(); - } - this._dragType = hit.dir; - } - - this._isDragging = true; - this._previewSegments = null; - this._dragStartX = x; - this._dragInitialTimeline = JSON.parse(JSON.stringify(targetArray)); - - if (hit.type !== "joint") { - this._dragTargetId = targetArray[hit.index].id; - } - this.render(); - } - - onMouseMove(e) { - const { x: mouseX, y: mouseY } = this.getMousePos(e); - - if (!this._isDragging) { - let newHoveredGapIdx = -1; - const BTN_R = 12; - const gapRegions = this.getGapRegions(); - for (let i = 0; i < gapRegions.length; i++) { - const gap = gapRegions[i]; - if (gap.widthPx < BTN_R * 2 + 8) continue; - const dx = mouseX - gap.centerX, dy2 = mouseY - gap.centerY; - if (dx * dx + dy2 * dy2 <= BTN_R * BTN_R) { newHoveredGapIdx = i; break; } - } - if (this._hoveredGapIdx !== newHoveredGapIdx) { - this._hoveredGapIdx = newHoveredGapIdx; - this.render(); - } - - const isOverDivider = Math.abs(mouseY - (RULER_HEIGHT + this.blockHeight)) <= 4; - const isAtBottom = Math.abs(mouseY - this.canvasHeight) <= 15; - const viewRect = this.viewport.getBoundingClientRect(); - const isAtRightEdge = Math.abs(e.clientX - viewRect.right) <= 20; - const hit = this.getHitTest(mouseX, mouseY); - if (isOverDivider || isAtBottom) { - this.canvas.style.cursor = "ns-resize"; - } else if (isAtRightEdge) { - this.canvas.style.cursor = "ew-resize"; - } else if (newHoveredGapIdx >= 0) { - this.canvas.style.cursor = "pointer"; - } else if (hit?.type === "edge") { - this.canvas.style.cursor = "ew-resize"; - } else if (hit?.type === "joint") { - this.canvas.style.cursor = "col-resize"; - } else if (hit?.type === "center") { - this.canvas.style.cursor = "grab"; - } else if (hit?.type === "playhead") { - this.canvas.style.cursor = "ew-resize"; - } else { - this.canvas.style.cursor = "default"; - } - return; - } - - if (this._dragType === "divider") { - this.canvas.style.cursor = "ns-resize"; - const deltaY = mouseY - this._startY; - - const minBlockH = 50; - const minAudioH = 50; - - let newBlockHeight = this._startBlockHeight + deltaY; - let newAudioTrackHeight = this._startAudioTrackHeight - deltaY; - - if (newBlockHeight < minBlockH) { - newBlockHeight = minBlockH; - newAudioTrackHeight = this._startBlockHeight + this._startAudioTrackHeight - minBlockH; - } - if (newAudioTrackHeight < minAudioH) { - newAudioTrackHeight = minAudioH; - newBlockHeight = this._startBlockHeight + this._startAudioTrackHeight - minAudioH; - } - - this.blockHeight = newBlockHeight; - this.audioTrackHeight = newAudioTrackHeight; - - this.render(); - return; - } - - if (this._dragType === "height_resize") { - this.canvas.style.cursor = "ns-resize"; - const deltaY = mouseY - this._startY; - - this.blockHeight = Math.max(100, this._startBlockHeight + deltaY); - this.canvasHeight = this.rulerHeight + this.blockHeight + this.audioTrackHeight; - - this.canvas.style.height = `${this.canvasHeight}px`; - - this.resizeCanvas(this.canvas.offsetWidth); - this.render(); - - if (this.node && this.node.computeSize) { - const sz = this.node.computeSize(); - this.node.size[1] = sz[1]; - if (window.app && window.app.graph) { - window.app.graph.setDirtyCanvas(true, true); - } - } - return; - } - - if (this._dragType === "width_resize") { - this.canvas.style.cursor = "ew-resize"; - const deltaX = e.clientX - this._startX; - - this.node.size[0] = Math.max(300, this._startNodeWidth + deltaX); - - if (window.app && window.app.graph) { - window.app.graph.setDirtyCanvas(true, true); - } - return; - } - - if (this._dragType === "playhead") { - this.canvas.style.cursor = "ew-resize"; - const logicalWidth = this.canvas.offsetWidth; - const totalFrames = this.getVisualDurationFrames(); - let mouseFrameX = mouseX * (totalFrames / logicalWidth); - this.currentFrame = clamp(mouseFrameX, 0, totalFrames); - this.render(); - if (this.isPlaying) { - this.playAudio(); // Scrub (restart from new position) - } - return; - } - - this.canvas.style.cursor = this._dragType === "center" ? "grabbing" : - this._dragType === "joint" ? "col-resize" : "ew-resize"; - - const logicalWidth = this.canvas.offsetWidth; - const totalFrames = this.getVisualDurationFrames(); - const durationFrames = totalFrames; - const dragDelta = Math.round((mouseX - this._dragStartX) * (totalFrames / logicalWidth)); - - let t = JSON.parse(JSON.stringify(this._dragInitialTimeline)); - - // --- Rolling Edit (Slide Edit) --- - if (this._dragType === "joint") { - let leftIdx = t.findIndex(s => s.id === this._dragTargetId); - let rightIdx = t.findIndex(s => s.id === this._dragTargetIdRight); - - if (leftIdx >= 0 && rightIdx >= 0) { - let origLeft = this._dragInitialTimeline.find(s => s.id === this._dragTargetId); - let origRight = this._dragInitialTimeline.find(s => s.id === this._dragTargetIdRight); - - let maxDeltaRight = origRight.length - MIN_SEGMENT_LENGTH; - let maxDeltaLeft = origLeft.length - MIN_SEGMENT_LENGTH; - - if (this.selectionType === "audio") { - // Drag LEFT: right clip extends left by un-trimming its head. - // Can only un-trim as much as the right clip has been trimmed (trimStart >= 0). - maxDeltaLeft = Math.min(maxDeltaLeft, origRight.trimStart || 0); - // Drag RIGHT: left clip extends right by consuming its remaining tail audio. - // Can only extend as far as the left clip's unplayed tail allows. - let availLeftTail = (origLeft.audioDurationFrames || origLeft.length) - ((origLeft.trimStart || 0) + origLeft.length); - maxDeltaRight = Math.min(maxDeltaRight, availLeftTail); - } - - let safeDelta = clamp(dragDelta, -maxDeltaLeft, maxDeltaRight); - - t[leftIdx].length = origLeft.length + safeDelta; - t[rightIdx].start = origRight.start + safeDelta; - t[rightIdx].length = origRight.length - safeDelta; - - if (this.selectionType === "audio") { - t[rightIdx].trimStart = origRight.trimStart + safeDelta; - } - } - } - // --- Edge & Center Drags --- - else { - const targetIdx = t.findIndex((s) => s.id === this._dragTargetId); - if (targetIdx < 0) return; - - if (this._dragType === "right") { - let newLen = t[targetIdx].length + dragDelta; - let maxPossibleLength = totalFrames - t[targetIdx].start; - let nextSeg = t.find(s => s.start >= t[targetIdx].start + t[targetIdx].length && s.id !== t[targetIdx].id); - if (nextSeg) { - maxPossibleLength = nextSeg.start - t[targetIdx].start; - } - - if (this.selectionType === "audio") { - maxPossibleLength = Math.min(maxPossibleLength, (t[targetIdx].audioDurationFrames || t[targetIdx].length) - (t[targetIdx].trimStart || 0)); - } - - t[targetIdx].length = Math.max(MIN_SEGMENT_LENGTH, Math.min(newLen, maxPossibleLength)); - - } else if (this._dragType === "left") { - let newStart = t[targetIdx].start + dragDelta; - let minPossibleStart = 0; - let prevSeg = t.slice().reverse().find(s => s.start + s.length <= t[targetIdx].start && s.id !== t[targetIdx].id); - if (prevSeg) { - minPossibleStart = prevSeg.start + prevSeg.length; - } - - if (this.selectionType === "audio") { - minPossibleStart = Math.max(minPossibleStart, t[targetIdx].start - (t[targetIdx].trimStart || 0)); - } - - let maxStart = t[targetIdx].start + t[targetIdx].length - MIN_SEGMENT_LENGTH; - newStart = Math.max(minPossibleStart, Math.min(newStart, maxStart)); - - let diff = newStart - t[targetIdx].start; - t[targetIdx].start = newStart; - t[targetIdx].length -= diff; - if (this.selectionType === "audio") { - t[targetIdx].trimStart += diff; - } - - } else if (this._dragType === "center") { - let initT = this._dragInitialTimeline; - let dIdx = initT.findIndex(s => s.id === this._dragTargetId); - if (dIdx < 0) return; - let D = JSON.parse(JSON.stringify(initT[dIdx])); - - let D_mouse_start = D.start + dragDelta; - let mouseFrameX = mouseX * (totalFrames / logicalWidth); - - t = this._applyCenterDragPhysics(initT, D.id, D_mouse_start, mouseFrameX, durationFrames, totalFrames, logicalWidth); - } - } - - this._previewSegments = t; - this.updateUIFromSelection(); // Live update of trim values - this.render(); - } - - _applyCenterDragPhysics(initT, D_id, D_mouse_start, mouseFrameX, durationFrames, totalFrames, logicalWidth) { - let t_copy = JSON.parse(JSON.stringify(initT)); - let dIdx = t_copy.findIndex(s => s.id === D_id); - if (dIdx < 0) return t_copy; - - let D = t_copy[dIdx]; - let D_clamped_start = clamp(D_mouse_start, 0, durationFrames - D.length); - - let baseSegments = t_copy.filter(s => s.id !== D.id); - - let insertIdx = baseSegments.length; - for (let i = 0; i < baseSegments.length; i++) { - let centerBase = baseSegments[i].start + baseSegments[i].length / 2; - if (mouseFrameX < centerBase) { - insertIdx = i; - break; - } - } - - let leftBound = insertIdx > 0 ? baseSegments[insertIdx - 1].start + baseSegments[insertIdx - 1].length : 0; - let rightBound = insertIdx < baseSegments.length ? baseSegments[insertIdx].start : durationFrames; - - if (rightBound - leftBound >= D.length) { - D_clamped_start = clamp(D_clamped_start, leftBound, rightBound - D.length); - } else { - let gapCenter = (leftBound + rightBound) / 2; - D_clamped_start = gapCenter - D.length / 2; - } - - let t_test = []; - for (let i = 0; i < insertIdx; i++) { - t_test.push({ ...baseSegments[i], original_start: baseSegments[i].start }); - } - t_test.push({ ...D, start: D_clamped_start, original_start: D_clamped_start }); - let D_index = insertIdx; - - for (let i = insertIdx; i < baseSegments.length; i++) { - t_test.push({ ...baseSegments[i], original_start: baseSegments[i].start }); - } - - for (let i = D_index + 1; i < t_test.length; i++) { - let prev = t_test[i - 1]; - t_test[i].start = Math.max(t_test[i].original_start, prev.start + prev.length); - } - - for (let i = D_index - 1; i >= 0; i--) { - let next = t_test[i + 1]; - t_test[i].start = Math.min(t_test[i].original_start, next.start - t_test[i].length); - } - - let rightCursor = durationFrames; - for (let i = t_test.length - 1; i >= 0; i--) { - if (t_test[i].start + t_test[i].length > rightCursor) { - t_test[i].start = rightCursor - t_test[i].length; - } - rightCursor = t_test[i].start; - } - let leftCursor = 0; - for (let i = 0; i < t_test.length; i++) { - if (t_test[i].start < leftCursor) { - t_test[i].start = leftCursor; - } - leftCursor = t_test[i].start + t_test[i].length; - } - - let result = t_test.map(s => { - let clean = { ...s }; - delete clean.original_start; - return clean; - }); - - let draggedPreview = result.find(s => s.id === D.id); - if (draggedPreview) { - draggedPreview.resolvedStart = draggedPreview.start; - } - - return result; - } - - onMouseUp(e) { - document.body.style.userSelect = ""; - if (this._isDragging) { - if (this._previewSegments) { - const targetArray = this.selectionType === "audio" ? this.timeline.audioSegments : this.timeline.segments; - - const mappedArray = this._previewSegments.map(ps => { - const orig = targetArray.find(s => s.id === ps.id); - let finalStart = ps.resolvedStart !== undefined ? ps.resolvedStart : ps.start; - let newPs = { ...ps, start: finalStart }; - if (orig && orig.imgObj) newPs.imgObj = orig.imgObj; - delete newPs.resolvedStart; - return newPs; - }); - - if (this.selectionType === "audio") { - this.timeline.audioSegments = mappedArray; - if (this._dragTargetId) this.selectedIndex = this.timeline.audioSegments.findIndex(s => s.id === this._dragTargetId); - } else { - this.timeline.segments = mappedArray; - if (this._dragTargetId) this.selectedIndex = this.timeline.segments.findIndex(s => s.id === this._dragTargetId); - } - } - - this._isDragging = false; - this._previewSegments = null; - this._ghostTrack = null; - this.canvas.style.cursor = "default"; - this.commitChanges(); - } - } - - // --- Backend Data Sync --- - commitChanges(skipRender = false) { - let sortedSegments = [...this.timeline.segments].sort((a, b) => a.start - b.start); - let contiguousLengths = []; - let contiguousPrompts = []; - let currentCursor = 0; - const durationFrames = this.getDurationFrames(); - - // Build segment lengths clipped at the duration cutoff. - // - Gaps before the first segment, or between segments, are absorbed into the adjacent - // segment's length (same as before), but are also clipped at durationFrames. - // - Segments that start at or past the cutoff are excluded entirely. - // - Segments that cross the cutoff are trimmed so their end = durationFrames exactly. - let pendingGap = 0; - for (let seg of sortedSegments) { - // Skip segments entirely outside the duration. - if (seg.start >= durationFrames) break; - - if (seg.start > currentCursor) { - // Gap between the cursor and this segment — clip it at the cutoff too. - const gapLength = Math.min(seg.start, durationFrames) - currentCursor; - if (contiguousLengths.length > 0) { - contiguousLengths[contiguousLengths.length - 1] += gapLength; - } else { - pendingGap += gapLength; - } - } - - // Clip segment end at the duration cutoff. - const clippedEnd = Math.min(seg.start + seg.length, durationFrames); - const clippedLength = clippedEnd - seg.start; - - contiguousLengths.push(clippedLength + pendingGap); - contiguousPrompts.push(seg.prompt || ""); - pendingGap = 0; - currentCursor = seg.start + seg.length; // advance by the real (unclipped) end for gap detection - } - - // If segments don't fill to the end of the duration, pad the last segment to reach it. - const clampedCursor = Math.min(currentCursor, durationFrames); - if (contiguousLengths.length > 0 && clampedCursor < durationFrames) { - contiguousLengths[contiguousLengths.length - 1] += durationFrames - clampedCursor; - } - - const toSave = { - segments: sortedSegments.map(s => { - const { imgObj, ...rest } = s; - return rest; - }), - audioSegments: (this.timeline.audioSegments || []).map(s => ({ ...s })) - }; - - const jsonStr = JSON.stringify(toSave); - if (this.timelineDataWidget) this.timelineDataWidget.value = jsonStr; - - if (this.localPromptsWidget) { - this.localPromptsWidget.value = contiguousPrompts.join(" | "); - } - if (this.segmentLengthsWidget) { - this.segmentLengthsWidget.value = contiguousLengths.join(","); - } - - if (this.guideStrengthWidget) { - const imgStrengths = sortedSegments - .filter(s => s.type !== "text") - .map(s => (s.guideStrength !== undefined ? s.guideStrength : 1.0).toFixed(2)); - this.guideStrengthWidget.value = imgStrengths.join(","); - } - - // Keep zoom slider max in sync with the current timeline duration. - this.updateZoomSliderMax(); - - setTimeout(() => { - if (this.node && this.node.computeSize) { - const sz = this.node.computeSize(); - this.node.size[1] = sz[1]; - if (app.graph) app.graph.setDirtyCanvas(true, true); - } - }, 0); - - if (!skipRender) this.render(); - } - - // --- Gap Region Calculation --- - getGapRegions() { - const totalFrames = this.getVisualDurationFrames(); - const outputFrames = this.getDurationFrames(); - const width = this.canvas.offsetWidth || this._lastWidth || 0; - const gaps = []; - if (!width) return gaps; - - // Image gaps - let cursor = 0; - const sortedImg = [...this.timeline.segments].sort((a, b) => a.start - b.start); - for (const seg of sortedImg) { - if (seg.start > cursor) { - const x0 = (cursor / totalFrames) * width; - const x1 = (seg.start / totalFrames) * width; - gaps.push({ track: 'image', frameStart: cursor, frameEnd: seg.start, centerX: (x0 + x1) / 2, centerY: RULER_HEIGHT + this.blockHeight / 2, widthPx: x1 - x0 }); - } - cursor = seg.start + seg.length; - } - if (cursor < outputFrames) { - const x0 = (cursor / totalFrames) * width; - const x1 = (outputFrames / totalFrames) * width; - gaps.push({ track: 'image', frameStart: cursor, frameEnd: outputFrames, centerX: (x0 + x1) / 2, centerY: RULER_HEIGHT + this.blockHeight / 2, widthPx: x1 - x0 }); - } - - // Audio gaps - cursor = 0; - const sortedAud = [...this.timeline.audioSegments].sort((a, b) => a.start - b.start); - for (const seg of sortedAud) { - if (seg.start > cursor) { - const x0 = (cursor / totalFrames) * width; - const x1 = (seg.start / totalFrames) * width; - gaps.push({ track: 'audio', frameStart: cursor, frameEnd: seg.start, centerX: (x0 + x1) / 2, centerY: RULER_HEIGHT + this.blockHeight + this.audioTrackHeight / 2, widthPx: x1 - x0 }); - } - cursor = seg.start + seg.length; - } - if (cursor < outputFrames) { - const x0 = (cursor / totalFrames) * width; - const x1 = (outputFrames / totalFrames) * width; - gaps.push({ track: 'audio', frameStart: cursor, frameEnd: outputFrames, centerX: (x0 + x1) / 2, centerY: RULER_HEIGHT + this.blockHeight + this.audioTrackHeight / 2, widthPx: x1 - x0 }); - } - - return gaps; - } - - promptAddAudioInGap(frameStart, frameEnd) { - const fi = document.createElement("input"); - fi.type = "file"; - fi.accept = "audio/*"; - fi.addEventListener("change", (ev) => { - if (ev.target.files?.[0]) this.handleAudioUpload([ev.target.files[0]], frameStart); - }); - fi.click(); - } - - // --- Context Menu --- - onContextMenu(e) { - e.preventDefault(); - const { x: mouseX, y: mouseY } = this.getMousePos(e); - - const trackHeight = this.blockHeight; - const isAudioTrack = mouseY >= RULER_HEIGHT + trackHeight && mouseY <= RULER_HEIGHT + trackHeight + this.audioTrackHeight; - const isImageTrack = mouseY >= RULER_HEIGHT && mouseY <= RULER_HEIGHT + trackHeight; - - const logicalWidth = this.canvas.offsetWidth || 1; - const totalFrames = this.getVisualDurationFrames(); - const cursor = mouseX * (totalFrames / logicalWidth); - - let clickedSeg = null; - let trackType = ""; - - if (isAudioTrack) { - clickedSeg = this.timeline.audioSegments.find(s => cursor >= s.start && cursor <= s.start + s.length); - trackType = "audio"; - } else if (isImageTrack) { - clickedSeg = this.timeline.segments.find(s => cursor >= s.start && cursor <= s.start + s.length); - trackType = clickedSeg ? clickedSeg.type : ""; - } - - if (clickedSeg) { - this.showContextMenu(e.clientX, e.clientY, clickedSeg, trackType); - } else if (isAudioTrack || isImageTrack) { - const gapRegions = this.getGapRegions(); - const currentTrack = isAudioTrack ? "audio" : "image"; - let gap = gapRegions.find(g => cursor >= g.frameStart && cursor <= g.frameEnd && g.track === currentTrack); - - if (!gap) { - const startFrame = Math.round(cursor); - gap = { - track: currentTrack, - frameStart: startFrame, - frameEnd: startFrame + Math.max(1, this.getFrameRate()) - }; - } - gap.clickedFrame = cursor; - - this.showGapContextMenu(e.clientX, e.clientY, gap); - } - } - - showContextMenu(clientX, clientY, seg, trackType) { - this.dismissContextMenu(); - const menu = document.createElement("div"); - menu.className = "pr-gap-menu"; - menu.style.left = `${clientX + 6}px`; - menu.style.top = `${clientY - 10}px`; - - const isImage = trackType !== "audio" && trackType !== "text" && seg.imageB64; - - if (isImage) { - const copyBtn = document.createElement("button"); - copyBtn.className = "pr-gap-menu-btn"; - copyBtn.innerHTML = `Copy Image`; - copyBtn.onclick = async () => { - try { - const res = await fetch(seg.imageB64); - const blob = await res.blob(); - await navigator.clipboard.write([new ClipboardItem({ [blob.type]: blob })]); - } catch (err) { - console.error("Failed to copy image", err); - } - this.dismissContextMenu(); - }; - menu.appendChild(copyBtn); - - const saveBtn = document.createElement("button"); - saveBtn.className = "pr-gap-menu-btn"; - saveBtn.innerHTML = `Save Image`; - saveBtn.onclick = () => { - const a = document.createElement("a"); - a.href = seg.imageB64; - a.download = "timeline_image.jpg"; - a.click(); - this.dismissContextMenu(); - }; - menu.appendChild(saveBtn); - - const openBtn = document.createElement("button"); - openBtn.className = "pr-gap-menu-btn"; - openBtn.innerHTML = `Open Image in New Tab`; - openBtn.onclick = () => { - const win = window.open(); - if (win) { - win.document.write(``); - win.document.close(); - } - this.dismissContextMenu(); - }; - menu.appendChild(openBtn); - } - - if (trackType !== "audio") { - const copyPromptBtn = document.createElement("button"); - copyPromptBtn.className = "pr-gap-menu-btn"; - copyPromptBtn.innerHTML = `Copy Prompt`; - copyPromptBtn.onclick = async () => { - try { - await navigator.clipboard.writeText(seg.prompt || ""); - } catch (err) { - console.error("Failed to copy prompt", err); - } - this.dismissContextMenu(); - }; - menu.appendChild(copyPromptBtn); - } - - const copySegBtn = document.createElement("button"); - copySegBtn.className = "pr-gap-menu-btn"; - copySegBtn.innerHTML = `Copy Segment`; - copySegBtn.onclick = () => { - this._copiedSegment = { ...seg, id: Date.now().toString() + Math.random().toString(36).substr(2, 5) }; - this._copiedSegmentTrack = trackType === "audio" ? "audio" : "image"; - this.dismissContextMenu(); - }; - menu.appendChild(copySegBtn); - - const currentTrack = trackType === "audio" ? "audio" : "image"; - if (this._copiedSegment && this._copiedSegmentTrack === currentTrack) { - const pasteReplaceBtn = document.createElement("button"); - pasteReplaceBtn.className = "pr-gap-menu-btn"; - pasteReplaceBtn.innerHTML = `Paste & Replace`; - pasteReplaceBtn.onclick = () => { - const newSeg = { - ...this._copiedSegment, - id: Date.now().toString() + Math.random().toString(36).substr(2, 5), - start: seg.start, - length: this._copiedSegment.length - }; - const targetArray = currentTrack === "audio" ? this.timeline.audioSegments : this.timeline.segments; - const idx = targetArray.findIndex(s => s.id === seg.id); - if (idx >= 0) targetArray[idx] = newSeg; - this.commitChanges(); - this.dismissContextMenu(); - }; - menu.appendChild(pasteReplaceBtn); - } - - const delBtn = document.createElement("button"); - delBtn.className = "pr-gap-menu-btn"; - delBtn.innerHTML = `Delete`; - delBtn.style.color = "#ff4444"; - delBtn.onclick = () => { - this.selectionType = trackType === "audio" ? "audio" : "image"; - const list = trackType === "audio" ? this.timeline.audioSegments : this.timeline.segments; - this.selectedIndex = list.findIndex(s => s.id === seg.id); - this.deleteSelectedSegment(); - this.dismissContextMenu(); - }; - menu.appendChild(delBtn); - - document.body.appendChild(menu); - this._contextMenu = menu; - - setTimeout(() => { - this._contextMenuDismisser = (ev) => { if (!menu.contains(ev.target)) this.dismissContextMenu(); }; - document.addEventListener("pointerdown", this._contextMenuDismisser, true); - }, 0); - } - - showGapContextMenu(clientX, clientY, gap) { - this.dismissContextMenu(); - const menu = document.createElement("div"); - menu.className = "pr-gap-menu"; - menu.style.left = `${clientX + 6}px`; - menu.style.top = `${clientY - 10}px`; - - const currentTrack = gap.track === "audio" ? "audio" : "image"; - - if (this._copiedSegment && this._copiedSegmentTrack === currentTrack) { - const pasteBtn = document.createElement("button"); - pasteBtn.className = "pr-gap-menu-btn"; - pasteBtn.innerHTML = `Paste Segment`; - pasteBtn.onclick = () => { - const startFrame = Math.round(gap.clickedFrame !== undefined ? gap.clickedFrame : gap.frameStart); - const gapLength = gap.frameEnd - startFrame; - - const newSeg = { - ...this._copiedSegment, - id: Date.now().toString() + Math.random().toString(36).substr(2, 5), - start: startFrame, - length: Math.min(this._copiedSegment.length, gapLength) - }; - const targetArray = currentTrack === "audio" ? this.timeline.audioSegments : this.timeline.segments; - targetArray.push(newSeg); - targetArray.sort((a, b) => a.start - b.start); - this.commitChanges(); - this.dismissContextMenu(); - }; - menu.appendChild(pasteBtn); - } - - if (currentTrack === "image") { - const textBtn = document.createElement("button"); - textBtn.className = "pr-gap-menu-btn"; - textBtn.innerHTML = `${ICONS.text} Text Segment`; - textBtn.onclick = () => { - this.addSegmentInGap(gap.frameStart, gap.frameEnd, "text"); - this.dismissContextMenu(); - }; - menu.appendChild(textBtn); - - const imgBtn = document.createElement("button"); - imgBtn.className = "pr-gap-menu-btn"; - imgBtn.innerHTML = `${ICONS.upload} Image Segment`; - imgBtn.onclick = () => { - this.dismissContextMenu(); - const fi = document.createElement("input"); - fi.type = "file"; fi.accept = "image/*"; - fi.addEventListener("change", (ev) => { - if (ev.target.files?.[0]) { - const gapLength = gap.frameEnd - gap.frameStart; - this.handleImageUpload([ev.target.files[0]], gap.frameStart, gapLength); - } - }); - fi.click(); - }; - menu.appendChild(imgBtn); - } - - document.body.appendChild(menu); - this._contextMenu = menu; - setTimeout(() => { - this._contextMenuDismisser = (ev) => { if (!menu.contains(ev.target)) this.dismissContextMenu(); }; - document.addEventListener("pointerdown", this._contextMenuDismisser, true); - }, 0); - } - dismissContextMenu() { - if (this._contextMenu) { this._contextMenu.remove(); this._contextMenu = null; } - if (this._contextMenuDismisser) { document.removeEventListener("pointerdown", this._contextMenuDismisser, true); this._contextMenuDismisser = null; } - } - - // --- Gap Popup Menu --- - showGapMenu(clientX, clientY, gap) { - this.dismissGapMenu(); - const menu = document.createElement("div"); - menu.className = "pr-gap-menu"; - menu.style.left = `${clientX + 6}px`; - menu.style.top = `${clientY - 10}px`; - - const textBtn = document.createElement("button"); - textBtn.className = "pr-gap-menu-btn"; - textBtn.innerHTML = `${ICONS.text} Text Segment`; - textBtn.addEventListener("click", () => { - this.addSegmentInGap(gap.frameStart, gap.frameEnd, "text"); - this.dismissGapMenu(); - }); - - const imgBtn = document.createElement("button"); - imgBtn.className = "pr-gap-menu-btn"; - imgBtn.innerHTML = `${ICONS.upload} Image Segment`; - imgBtn.addEventListener("click", () => { - this.dismissGapMenu(); - const fi = document.createElement("input"); - fi.type = "file"; fi.accept = "image/*"; - fi.addEventListener("change", (ev) => { - if (ev.target.files?.[0]) { - const gapLength = gap.frameEnd - gap.frameStart; - this.handleImageUpload([ev.target.files[0]], gap.frameStart, gapLength); - } - }); - fi.click(); - }); - - menu.appendChild(textBtn); - menu.appendChild(imgBtn); - const currentTrack = gap.track === "audio" ? "audio" : "image"; - if (this._copiedSegment && this._copiedSegmentTrack === currentTrack) { - const pasteBtn = document.createElement("button"); - pasteBtn.className = "pr-gap-menu-btn"; - pasteBtn.innerHTML = `Paste Segment`; - pasteBtn.onclick = () => { - const gapLength = gap.frameEnd - gap.frameStart; - - let finalLength = Math.min(this._copiedSegment.length, gapLength); - if (currentTrack === "image") { - finalLength = gapLength; - } - - const newSeg = { - ...this._copiedSegment, - id: Date.now().toString() + Math.random().toString(36).substr(2, 5), - start: gap.frameStart, - length: finalLength - }; - const targetArray = currentTrack === "audio" ? this.timeline.audioSegments : this.timeline.segments; - targetArray.push(newSeg); - targetArray.sort((a, b) => a.start - b.start); - this.commitChanges(); - this.dismissGapMenu(); - }; - menu.appendChild(pasteBtn); - } - - document.body.appendChild(menu); - this._gapMenu = menu; - setTimeout(() => { - this._gapMenuDismisser = (ev) => { if (!menu.contains(ev.target)) this.dismissGapMenu(); }; - document.addEventListener("pointerdown", this._gapMenuDismisser, true); - }, 0); - } - - dismissGapMenu() { - if (this._gapMenu) { this._gapMenu.remove(); this._gapMenu = null; } - if (this._gapMenuDismisser) { document.removeEventListener("pointerdown", this._gapMenuDismisser, true); this._gapMenuDismisser = null; } - } - - // --- Settings Menu --- - // Widgets that are managed by the settings menu (hidden from node by default). - get _settingsWidgetNames() { - return ["display_mode", "epsilon", "divisible_by", "img_compression"]; - } - - // Hide all settings widgets on the node (called on init). - hideSettingsWidgets() { - for (const name of this._settingsWidgetNames) { - const w = this.node.widgets?.find(w => w.name === name); - if (w) hideWidget(w); - - // Also remove corresponding input slot if it exists and is NOT connected - // to prevent overlapping issues in classic ComfyUI (nodes v1) - if (this.node.inputs) { - const inputIdx = this.node.inputs.findIndex(i => i.name === name); - if (inputIdx !== -1) { - const input = this.node.inputs[inputIdx]; - if (input.link == null) { - this.node.removeInput(inputIdx); - } - } - } - } - this.updateWidgetVisibility(); - - // Workaround: toggle display mode to force ComfyUI to refresh the node - if (this.displayModeWidget) { - const origVal = this.displayModeWidget.value; - const otherVal = origVal === "frames" ? "seconds" : "frames"; - - this.displayModeWidget.value = otherVal; - if (this.displayModeWidget.callback) this.displayModeWidget.callback(otherVal); - - this.displayModeWidget.value = origVal; - if (this.displayModeWidget.callback) this.displayModeWidget.callback(origVal); - } - } - - // Restore all settings widgets on the node. - showSettingsWidgets() { - for (const name of this._settingsWidgetNames) { - const w = this.node.widgets?.find(w => w.name === name); - if (!w) continue; - - const typeMap = { - display_mode: "combo", epsilon: "FLOAT", divisible_by: "INT", - img_compression: "INT", - }; - w.type = typeMap[name] || w._origType || "number"; - w.hidden = false; - if (w.options) w.options.hidden = false; - delete w.computeSize; - if (w.element) w.element.style.display = ""; - } - this.updateWidgetVisibility(); - - // Workaround: toggle display mode to force ComfyUI to refresh the node - if (this.displayModeWidget) { - const origVal = this.displayModeWidget.value; - const otherVal = origVal === "frames" ? "seconds" : "frames"; - - this.displayModeWidget.value = otherVal; - if (this.displayModeWidget.callback) this.displayModeWidget.callback(otherVal); - - this.displayModeWidget.value = origVal; - if (this.displayModeWidget.callback) this.displayModeWidget.callback(origVal); - } - } - - _makeSettingRow(label, inputEl) { - const row = document.createElement("div"); - row.className = "pr-settings-row"; - const lbl = document.createElement("span"); - lbl.className = "pr-settings-label"; - lbl.textContent = label; - row.appendChild(lbl); - row.appendChild(inputEl); - return row; - } - - showSettingsMenu(anchorEl) { - this.dismissSettingsMenu(); - const menu = document.createElement("div"); - menu.className = "pr-settings-menu"; - - // Title & Close Button Container - const titleContainer = document.createElement("div"); - titleContainer.className = "pr-settings-title"; - titleContainer.style.display = "flex"; - titleContainer.style.justifyContent = "space-between"; - titleContainer.style.alignItems = "center"; - - const titleText = document.createElement("span"); - titleText.textContent = "Timeline Settings"; - titleContainer.appendChild(titleText); - - const closeBtn = document.createElement("button"); - closeBtn.className = "pr-settings-close-btn"; - closeBtn.innerHTML = ICONS.close; - closeBtn.title = "Close Settings"; - closeBtn.addEventListener("click", () => this.dismissSettingsMenu()); - titleContainer.appendChild(closeBtn); - - menu.appendChild(titleContainer); - - // Helper: fire a widget's callback safely - const fireCallback = (w, val) => { - w.value = val; - if (w.callback) { - try { w.callback(val, app.canvas, this.node, null, null); } catch (e) { } - } - if (window.app && window.app.graph) window.app.graph.setDirtyCanvas(true, true); - }; - - // --- Display Mode --- - const dmWidget = this.node.widgets?.find(w => w.name === "display_mode"); - if (dmWidget) { - const ctrl = document.createElement("div"); - ctrl.className = "pr-segmented-control"; - - const framesSeg = document.createElement("div"); - framesSeg.className = "pr-segment"; - framesSeg.textContent = "Frames"; - - const secondsSeg = document.createElement("div"); - secondsSeg.className = "pr-segment"; - secondsSeg.textContent = "Seconds"; - - const updateActive = (val) => { - if (val === "frames") { - framesSeg.classList.add("active"); - secondsSeg.classList.remove("active"); - } else { - secondsSeg.classList.add("active"); - framesSeg.classList.remove("active"); - } - }; - - updateActive(dmWidget.value); - - const onSegClick = (val) => { - fireCallback(dmWidget, val); - updateActive(val); - // Update ruler/timecode immediately - if (this.updateWidgetVisibility) this.updateWidgetVisibility(); - if (this.updateUIFromSelection) this.updateUIFromSelection(); - this.render(); - }; - - framesSeg.addEventListener("click", () => onSegClick("frames")); - secondsSeg.addEventListener("click", () => onSegClick("seconds")); - - ctrl.appendChild(secondsSeg); - ctrl.appendChild(framesSeg); - - menu.appendChild(this._makeSettingRow("Display Mode", ctrl)); - } - - const divider1 = document.createElement("hr"); - divider1.className = "pr-settings-divider"; - menu.appendChild(divider1); - - // Helper to create scrubbable number control with horizontal buttons - const createScrubbableNumberControl = (w, step, min, max, isFloat = false) => { - const container = document.createElement("div"); - container.className = "pr-number-control"; - - const decBtn = document.createElement("button"); - decBtn.className = "pr-number-btn"; - decBtn.textContent = "-"; - - const inp = document.createElement("input"); - inp.type = "number"; - inp.className = "pr-settings-input"; - inp.value = w.value; - inp.step = step.toString(); - inp.min = min.toString(); - inp.max = max.toString(); - - const incBtn = document.createElement("button"); - incBtn.className = "pr-number-btn"; - incBtn.textContent = "+"; - - decBtn.addEventListener("click", () => { - let val = parseFloat(inp.value) - step; - if (val < min) val = min; - inp.value = isFloat ? val.toFixed(4) : Math.round(val); - fireCallback(w, parseFloat(inp.value)); - }); - - incBtn.addEventListener("click", () => { - let val = parseFloat(inp.value) + step; - if (val > max) val = max; - inp.value = isFloat ? val.toFixed(4) : Math.round(val); - fireCallback(w, parseFloat(inp.value)); - }); - - inp.addEventListener("change", () => { - let val = parseFloat(inp.value); - if (isNaN(val)) val = w.value; - if (val < min) val = min; - if (val > max) val = max; - inp.value = isFloat ? val.toFixed(4) : Math.round(val); - fireCallback(w, parseFloat(inp.value)); - }); - - // Dragging logic - let isDragging = false; - let startX = 0; - let startVal = 0; - let hasMoved = false; - - inp.style.cursor = "ew-resize"; - - inp.addEventListener("mousedown", (e) => { - startX = e.clientX; - startVal = parseFloat(inp.value); - hasMoved = false; - - const onMouseMove = (moveEvent) => { - const deltaX = moveEvent.clientX - startX; - if (Math.abs(deltaX) > 3) { - hasMoved = true; - isDragging = true; - } - - if (isDragging) { - moveEvent.preventDefault(); - const sensitivity = isFloat ? 0.001 : 0.5; - let newVal = startVal + deltaX * sensitivity; - - if (newVal < min) newVal = min; - if (newVal > max) newVal = max; - - inp.value = isFloat ? newVal.toFixed(4) : Math.round(newVal); - fireCallback(w, parseFloat(inp.value)); - } - }; - - const onMouseUp = () => { - document.removeEventListener("mousemove", onMouseMove); - document.removeEventListener("mouseup", onMouseUp); - - if (!hasMoved) { - inp.focus(); - inp.select(); - } - isDragging = false; - }; - - document.addEventListener("mousemove", onMouseMove); - document.addEventListener("mouseup", onMouseUp); - }); - - container.appendChild(decBtn); - container.appendChild(inp); - container.appendChild(incBtn); - - return container; - }; - - // --- Epsilon --- - const epsWidget = this.node.widgets?.find(w => w.name === "epsilon"); - if (epsWidget) { - menu.appendChild(this._makeSettingRow("Epsilon", createScrubbableNumberControl(epsWidget, 0.0001, 0.0001, 0.99, true))); - } - - // --- Divisible By --- - const divByWidget = this.node.widgets?.find(w => w.name === "divisible_by"); - if (divByWidget) { - menu.appendChild(this._makeSettingRow("Divisible By", createScrubbableNumberControl(divByWidget, 1, 1, 256, false))); - } - - // --- Img Compression --- - const compWidget = this.node.widgets?.find(w => w.name === "img_compression"); - if (compWidget) { - menu.appendChild(this._makeSettingRow("Img Compression", createScrubbableNumberControl(compWidget, 1, 0, 100, false))); - } - - // --- Global Prompt Toggle --- - const globalPromptWidget = this.node.widgets?.find(w => w.name === "global_prompt"); - if (globalPromptWidget) { - const cb = document.createElement("input"); - cb.type = "checkbox"; - cb.checked = !(globalPromptWidget.options && globalPromptWidget.options.hidden); - cb.style.cursor = "pointer"; - cb.addEventListener("change", () => { - const isVisible = cb.checked; - if (!globalPromptWidget.options) globalPromptWidget.options = {}; - globalPromptWidget.options.hidden = !isVisible; - - if (isVisible) { - delete globalPromptWidget.computeSize; - globalPromptWidget.hidden = false; - if (globalPromptWidget.element) globalPromptWidget.element.style.display = ""; - } else { - globalPromptWidget.computeSize = () => [0, 0]; - globalPromptWidget.hidden = true; - if (globalPromptWidget.element) globalPromptWidget.element.style.display = "none"; - } - - // Force refresh via display mode double-toggle trick - if (this.displayModeWidget) { - const origVal = this.displayModeWidget.value; - const otherVal = origVal === "frames" ? "seconds" : "frames"; - this.displayModeWidget.value = otherVal; - if (this.displayModeWidget.callback) this.displayModeWidget.callback(otherVal); - this.displayModeWidget.value = origVal; - if (this.displayModeWidget.callback) this.displayModeWidget.callback(origVal); - } - }); - menu.appendChild(this._makeSettingRow("Use Global Prompt", cb)); - } - - - // --- Show/Hide on Node Toggle --- - const toggleBtn = document.createElement("button"); - toggleBtn.className = "pr-settings-toggle-btn"; - const widgetsVisible = !!(this.node.widgets?.find(w => w.name === "display_mode" && !(w.options && w.options.hidden))); - toggleBtn.textContent = widgetsVisible ? "Hide Widgets on Node" : "Show Widgets on Node"; - toggleBtn.addEventListener("click", () => { - const nowVisible = !!(this.node.widgets?.find(w => w.name === "display_mode" && !(w.options && w.options.hidden))); - if (nowVisible) { - this.hideSettingsWidgets(); - toggleBtn.textContent = "Show Widgets on Node"; - } else { - this.showSettingsWidgets(); - toggleBtn.textContent = "Hide Widgets on Node"; - } - }); - menu.appendChild(toggleBtn); - - // Position the menu below the anchor button (pop down) - document.body.appendChild(menu); - const rect = anchorEl.getBoundingClientRect(); - const menuW = menu.offsetWidth || 230; - const menuH = menu.offsetHeight || 350; - let left = rect.right - menuW; - let top = rect.bottom + 6; - if (left < 4) left = 4; - // Fallback to top if it overflows the bottom of the screen - if (top + menuH > window.innerHeight - 4) { - top = rect.top - menuH - 6; - } - menu.style.left = `${left}px`; - menu.style.top = `${top}px`; - - this._settingsMenu = menu; - setTimeout(() => { - this._settingsDismisser = (ev) => { - if (!menu.contains(ev.target) && !anchorEl.contains(ev.target)) this.dismissSettingsMenu(); - }; - document.addEventListener("mousedown", this._settingsDismisser); - }, 0); - } - - dismissSettingsMenu() { - if (this._settingsMenu) { this._settingsMenu.remove(); this._settingsMenu = null; } - if (this._settingsDismisser) { document.removeEventListener("mousedown", this._settingsDismisser); this._settingsDismisser = null; } - } - - - addSegmentInGap(frameStart, frameEnd, type = "text") { - const seg = { - id: Date.now().toString() + Math.random().toString(36).substr(2, 5), - start: frameStart, length: frameEnd - frameStart, - prompt: "", type, - }; - this.timeline.segments.push(seg); - this.timeline.segments.sort((a, b) => a.start - b.start); - this.selectionType = "image"; - this.selectedIndex = this.timeline.segments.findIndex(s => s.id === seg.id); - this.updateUIFromSelection(); - this.commitChanges(); - } - - addTextSegmentFreeSpace() { - const frameRate = this.getFrameRate(); - const newLength = Math.max(1, frameRate); // 1 second default - const sorted = [...this.timeline.segments].sort((a, b) => a.start - b.start); - let newStart = 0; - for (const seg of sorted) { - if (newStart + newLength <= seg.start) break; - newStart = Math.max(newStart, seg.start + seg.length); - } - // Place the segment at the first free slot in the visual timeline (no output duration change). - const durationFrames = this.getVisualDurationFrames(); - const seg = { - id: Date.now().toString() + Math.random().toString(36).substr(2, 5), - start: newStart, length: Math.min(newLength, Math.max(newLength, durationFrames - newStart)), - prompt: "", type: "text", - }; - this.timeline.segments.push(seg); - this.timeline.segments.sort((a, b) => a.start - b.start); - this.selectionType = "image"; - this.selectedIndex = this.timeline.segments.findIndex(s => s.id === seg.id); - this.updateUIFromSelection(); - this.commitChanges(); - } - - // --- Audio Player Engine --- - updatePlayerUI() { - if (!this.playBtn || !this.loopBtn) return; - this.playBtn.innerHTML = this.isPlaying ? ICONS.pause : ICONS.play; - if (this.isLooping) { - this.loopBtn.classList.add("active"); - } else { - this.loopBtn.classList.remove("active"); - } - if (this.seekBar) { - this.seekBar.max = this.getVisualDurationFrames(); - this.seekBar.value = this.currentFrame; - } - if (this.timeCodeDisplay) { - this.timeCodeDisplay.textContent = this.formatTime(this.currentFrame); - } - } - - togglePlay() { - if (this.isPlaying) { - this.pauseAudio(); - } else { - if (this.currentFrame >= this.getVisualDurationFrames()) { - this.currentFrame = 0; - } - this.playAudio(); - } - } - - toggleLoop() { - this.isLooping = !this.isLooping; - this.updatePlayerUI(); - } - - async playAudio() { - this.pauseAudio(true); // clear any existing playback, but don't suspend context if scrubbing - - this._playCounter = (this._playCounter || 0) + 1; - const playId = this._playCounter; - this._currentPlayId = playId; - this.isPlaying = true; - - if (!this.audioContext) { - this.audioContext = new (window.AudioContext || window.webkitAudioContext)(); - } - if (this.audioContext.state !== 'running') { - try { await this.audioContext.resume(); } catch (e) { } - } - if (this._currentPlayId !== playId || !this.isPlaying) return; - - this.updatePlayerUI(); - - const frameRate = this.getFrameRate(); - this.playbackStartFrame = this.currentFrame; - this.playbackStartTime = this.audioContext.currentTime; - - // Decode and schedule all audio segments that happen AT or AFTER currentFrame - for (let seg of this.timeline.audioSegments) { - const segStartFrame = seg.start; - const segEndFrame = seg.start + seg.length; - - if (segEndFrame <= this.currentFrame) continue; - - try { - // Build audio buffer: fetch from server URL if audioFile is set, otherwise fall back to audioB64 - let audioBuffer; - if (seg.audioFile) { - const audioUrl = api.apiURL(`/view?filename=${encodeURIComponent(seg.audioFile.split("/").pop())}&type=input&subfolder=${encodeURIComponent(seg.audioFile.includes("/") ? seg.audioFile.split("/").slice(0, -1).join("/") : "")}`); - const resp = await fetch(audioUrl); - const arrayBuffer = await resp.arrayBuffer(); - audioBuffer = await this.audioContext.decodeAudioData(arrayBuffer); - } else if (seg.audioB64) { - const binaryString = window.atob(seg.audioB64); - const len = binaryString.length; - const bytes = new Uint8Array(len); - for (let i = 0; i < len; i++) { - bytes[i] = binaryString.charCodeAt(i); - } - audioBuffer = await this.audioContext.decodeAudioData(bytes.buffer); - } else { - continue; - } - if (this._currentPlayId !== playId || !this.isPlaying) return; - - const framesToSkipInSegment = Math.max(0, this.currentFrame - segStartFrame); - const waitFrames = Math.max(0, segStartFrame - this.currentFrame); - const waitTimeSec = waitFrames / frameRate; - - const fileOffsetFrames = seg.trimStart + framesToSkipInSegment; - const fileOffsetSec = fileOffsetFrames / frameRate; - - const playDurationFrames = seg.length - framesToSkipInSegment; - const playDurationSec = playDurationFrames / frameRate; - - if (playDurationSec <= 0) continue; - - const bufferNode = this.audioContext.createBufferSource(); - bufferNode.buffer = audioBuffer; - bufferNode["connect"](this.audioContext.destination); - - const startTime = this.audioContext.currentTime + waitTimeSec; - bufferNode.start(startTime, fileOffsetSec, playDurationSec); - - this.activeAudioNodes.push(bufferNode); - } catch (err) { - console.error("Playback decode error for segment:", err); - } - } - - if (this._currentPlayId !== playId || !this.isPlaying) return; - - const loop = () => { - if (!this.isPlaying || this._currentPlayId !== playId) return; - - const elapsedSec = this.audioContext.currentTime - this.playbackStartTime; - const elapsedFrames = elapsedSec * frameRate; - - this.currentFrame = this.playbackStartFrame + elapsedFrames; - - const visualDurationFrames = this.getVisualDurationFrames(); - const durationFrames = this.getDurationFrames(); - - if (this.isLooping) { - const loopBound = (this.playbackStartFrame >= durationFrames) ? visualDurationFrames : durationFrames; - if (this.currentFrame >= loopBound) { - this.currentFrame = 0; - this.playAudio(); // Restart playback - return; - } - } else { - if (this.currentFrame >= visualDurationFrames) { - this.currentFrame = visualDurationFrames; - this.pauseAudio(); - this.render(); - return; - } - } - - this.render(); - this._playLoopId = requestAnimationFrame(loop); - }; - - this._playLoopId = requestAnimationFrame(loop); - } - - pauseAudio(isScrubbing = false) { - this.isPlaying = false; - this._currentPlayId = null; - - if (!isScrubbing && this.audioContext && this.audioContext.state === 'running') { - try { this.audioContext.suspend(); } catch (e) { } - } - - for (let node of this.activeAudioNodes) { - try { node.stop(); } catch (e) { } - try { node.disconnect(); } catch (e) { } - } - this.activeAudioNodes = []; - - if (this._playLoopId) { - cancelAnimationFrame(this._playLoopId); - this._playLoopId = null; - } - this.updatePlayerUI(); - } -} - -// --- Node Registration Hooks --- -const APPENDED_WIDGET_DEFAULTS = [ - ["timeline_data", "{}"], - ["local_prompts", ""], - ["segment_lengths", ""], -]; - -app.registerExtension({ - name: "LTXDirector", - async beforeRegisterNodeDef(nodeType, nodeData, app) { - if (nodeData.name === "LTXDirector") { - - const onNodeCreated = nodeType.prototype.onNodeCreated; - nodeType.prototype.onNodeCreated = function () { - if (onNodeCreated) onNodeCreated.apply(this, arguments); - - for (const [name, def] of APPENDED_WIDGET_DEFAULTS) { - if (!this.widgets?.find(w => w.name === name)) { - this.addWidget("string", name, def, () => { }); - } - } - for (const w of this.widgets) { - if (HIDDEN_WIDGET_NAMES.includes(w.name)) hideWidget(w); - } - - // Set default width to be wider on creation (approx 2.5x default ~220px) - this.size[0] = 1000; - - // Force default for img_compression if not set (ComfyUI sometimes skips optional defaults) - const compWidget = this.widgets?.find(w => w.name === "img_compression"); - if (compWidget && (compWidget.value === undefined || compWidget.value === null || compWidget.value === 0)) { - compWidget.value = 18; - } - - // Hide global prompt by default on creation without destroying its DOM element - const globalPromptWidget = this.widgets?.find(w => w.name === "global_prompt"); - if (globalPromptWidget) { - if (!globalPromptWidget.options) globalPromptWidget.options = {}; - globalPromptWidget.options.hidden = true; - globalPromptWidget.hidden = true; - globalPromptWidget.computeSize = () => [0, 0]; - setTimeout(() => { - if (globalPromptWidget.element) globalPromptWidget.element.style.display = "none"; - }, 0); - } - - const container = document.createElement("div"); - const widget = this.addDOMWidget("timeline_ui", "timeline_ui", container, { - getValue: () => "", - setValue: () => { }, - }); - - widget.computeSize = function (width) { - const canvasH = self._timelineEditor ? self._timelineEditor.canvasHeight : CANVAS_HEIGHT; - return [width, canvasH + 235]; - }; - - const self = this; - setTimeout(() => { - try { - self._timelineEditor = new TimelineEditor(self, container, widget); - } catch (err) { - console.error("[PromptRelay] timeline editor init failed:", err); - } - }, 0); - }; - - const onRemoved = nodeType.prototype.onRemoved; - nodeType.prototype.onRemoved = function () { - this._timelineEditor?.destroy(); - return onRemoved?.apply(this, arguments); - }; - - const onConfigure = nodeType.prototype.onConfigure; - nodeType.prototype.onConfigure = function (info) { - const out = onConfigure?.apply(this, arguments); - for (const [name, def] of APPENDED_WIDGET_DEFAULTS) { - const w = this.widgets.find(x => x.name === name); - if (w && (w.value == null || w.value === "")) w.value = def; - } - - setTimeout(() => { - if (this._timelineEditor) { - this._timelineEditor.timeline = parseInitial(this._timelineEditor.timelineDataWidget?.value); - this._timelineEditor.loadImages(); - this._timelineEditor.selectionType = "image"; - this._timelineEditor.selectedIndex = clamp( - this._timelineEditor.selectedIndex, -1, - Math.max(-1, this._timelineEditor.timeline.segments.length - 1) - ); - this._timelineEditor.updateUIFromSelection(); - this._timelineEditor.render(); - } - }, 0); - return out; - }; - } - }, -}); +// --- START OF FILE ltx_director.js --- + +const { app } = window.comfyAPI.app; +const { api } = window.comfyAPI.api; + +// --- UI Constants & Configuration --- +const RULER_HEIGHT = 24; +const BLOCK_HEIGHT = 160; // Increased to make the image timeline area much taller +const AUDIO_TRACK_HEIGHT = 80; +const CANVAS_HEIGHT = RULER_HEIGHT + BLOCK_HEIGHT + AUDIO_TRACK_HEIGHT; +const HANDLE_HIT_PX = 14; +const MIN_SEGMENT_LENGTH = 6; +const MAX_THUMBNAIL_DIM = 512; // Increased to maintain quality for taller images + +const HIDDEN_WIDGET_NAMES = ["timeline_data", "local_prompts", "segment_lengths", "guide_strength", "audio_data", "use_custom_audio"]; + +function hideWidget(w) { + if (!w) return; + if (!w._origType && w.type !== "hidden") w._origType = w.type; + // We don't set w.type = "hidden" anymore because it causes rendering issues in Nodes 2.0. + // Instead we use the computeSize = () => [0,0] trick which works in both V1 and V2. + w.hidden = true; + if (!w.options) w.options = {}; + w.options.hidden = true; + w.computeSize = () => [0, 0]; + if (w.element) w.element.style.display = "none"; +} + +function clamp(v, min, max) { return Math.max(min, Math.min(max, v)); } + +// --- Modern Dark/Grey UI CSS (ComfyUI Match) --- +const STYLES = ` + .pr-wrapper { + font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + display: flex; + flex-direction: column; + gap: 8px; + width: 100%; + height: 100%; + box-sizing: border-box; + padding-bottom: 4px; + } + .pr-wrapper.drag-active { + outline: 2px dashed #888; + background: rgba(255, 255, 255, 0.05); + border-radius: 6px; + } + .pr-toolbar { + display: flex; + justify-content: space-between; + align-items: center; + padding: 2px 0px; + flex-wrap: wrap; + gap: 6px; + } + .pr-actions { + display: flex; + gap: 6px; + flex-wrap: wrap; + } + .pr-btn { + background: #222; + color: #e0e0e0; + border: 1px solid #111; + border-radius: 4px; + padding: 6px 12px; + font-size: 11px; + font-weight: 500; + cursor: pointer; + display: flex; + align-items: center; + gap: 6px; + transition: all 0.2s ease; + } + .pr-btn:hover { + background: #333; + border-color: #555; + } + .pr-btn-danger:hover { + background: #4a1515; + border-color: #cc4444; + color: #ffaaaa; + } + .pr-canvas { + border-radius: 6px; + border: 1px solid #111; + background: #2a2a2a; + cursor: pointer; + width: 100%; + outline: none; + display: block; /* Ensure no inline baseline gaps */ + } + .pr-prop-container { + display: flex; + flex-direction: column; + width: 100%; + flex-grow: 1; /* Automatically scales to fill node height */ + min-height: 40px; + } + .pr-prompt-area { + width: 100%; + height: 100%; + background: #222; + color: #e0e0e0; + border: 1px solid #111; + border-radius: 6px; + padding: 8px; + resize: none; /* Removed the manual resize corner handle */ + font-size: 12px; + line-height: 1.4; + box-sizing: border-box; + outline: none; + transition: border-color 0.2s ease; + } + .pr-prompt-area:focus { + border-color: #888; + } + .pr-audio-info { + width: 100%; + height: 100%; + background: #181818; + color: #aaa; + border: 1px solid #111; + border-radius: 6px; + padding: 10px; + font-size: 12px; + line-height: 1.6; + box-sizing: border-box; + display: none; + } + .pr-audio-info span { color: #fff; font-weight: 500; } + .pr-controls-group { + background: #1e1e1e; + border: 1px solid #333; + border-radius: 6px; + padding: 6px 10px; + display: flex; + flex-direction: column; + gap: 4px; + margin-bottom: 4px; + box-sizing: border-box; + width: 100%; + } + .pr-strength-row { + display: flex; + align-items: center; + gap: 12px; + width: 100%; + box-sizing: border-box; + } + .pr-height-resizer { + height: 6px; + background: #2a2a2a; + cursor: ns-resize; + border-radius: 3px; + margin: 2px 0; + transition: background 0.15s; + border: 1px solid #1e1e1e; + } + .pr-height-resizer:hover { + background: #444; + border-color: #555; + } + .pr-strength-label { + font-size: 11px; + font-weight: 600; + color: #fff; + white-space: nowrap; + margin-left: auto; + } + .pr-strength-slider { + -webkit-appearance: none; + appearance: none; + width: 80px; + height: 4px; + background: #444; + border-radius: 2px; + outline: none; + cursor: pointer; + border: 1px solid #222; + } + .pr-strength-slider::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 12px; + height: 12px; + border-radius: 50%; + background: #aaa; + cursor: pointer; + } + .pr-strength-slider:disabled { + opacity: 0.3; + cursor: not-allowed; + } + .pr-strength-input { + font-size: 12px; + color: #fff; + background: #222; + border: 1px solid #444; + border-radius: 4px; + width: 52px; + text-align: center; + padding: 3px; + } + .pr-strength-input::-webkit-outer-spin-button, + .pr-strength-input::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; + } + .pr-strength-input[type=number] { + -moz-appearance: textfield; + } + .pr-strength-input:disabled { + opacity: 0.35; + cursor: not-allowed; + } + .pr-gap-menu { + position: fixed; + background: #1e1e1e; + border: 1px solid #444; + border-radius: 6px; + padding: 4px; + display: flex; + flex-direction: column; + gap: 4px; + z-index: 9999; + box-shadow: 0 4px 16px rgba(0,0,0,0.6); + } + .pr-gap-menu-btn { + background: #2a2a2a; + color: #e0e0e0; + border: 1px solid #333; + border-radius: 4px; + padding: 6px 14px; + font-size: 11px; + font-family: inherit; + cursor: pointer; + text-align: left; + white-space: nowrap; + display: flex; + align-items: center; + gap: 6px; + transition: background 0.15s ease; + } + .pr-gap-menu-btn:hover { + background: #3a3a3a; + border-color: #666; + } + .pr-player-controls { + display: flex; + justify-content: center; + align-items: center; + gap: 12px; + padding: 2px 0; + flex-wrap: wrap; + width: 100%; + } + .pr-icon-btn { + background: #2a2a2a; + border: 1px solid #444; + color: #eee; + cursor: pointer; + padding: 6px 12px; + border-radius: 4px; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.2s; + } + .pr-icon-btn * { + pointer-events: none; + } + .pr-icon-btn:hover { + color: #fff; + background: #3a3a3a; + border-color: #666; + } + .pr-icon-btn.active { + color: #4fff8f; + border-color: #4fff8f; + background: #1a3a2a; + } + .pr-seek-bar { + -webkit-appearance: none; + appearance: none; + height: 6px; + background: #444; + border-radius: 3px; + outline: none; + cursor: pointer; + border: 1px solid #222; + } + .pr-seek-bar::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 14px; + height: 14px; + border-radius: 50%; + background: #ff4444; + cursor: pointer; + border: 2px solid #222; + } + .pr-timeline-viewport { + width: 100%; + overflow-x: auto; + overflow-y: hidden; + } + .pr-timeline-viewport::-webkit-scrollbar { + height: 10px; + } + .pr-timeline-viewport::-webkit-scrollbar-track { + background: #151515; + border-radius: 5px; + } + .pr-timeline-viewport::-webkit-scrollbar-thumb { + background: #444 + border-radius: 5px; + border: 1px solid #000; + } + .pr-timeline-viewport::-webkit-scrollbar-thumb:hover { + background: #666 + border-color: #000; + } + .pr-zoom-controls { + display: flex; + align-items: center; + gap: 4px; + margin-left: 12px; + } + .pr-zoom-slider { + width: 80px; + -webkit-appearance: none; + appearance: none; + height: 4px; + background: #444; + border-radius: 2px; + outline: none; + cursor: pointer; + } + .pr-zoom-slider::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 12px; + height: 12px; + border-radius: 50%; + background: #aaa; + cursor: pointer; + } + .pr-right-group { + display: flex; + align-items: center; + gap: 12px; + } + .pr-segment-bounds { + font-size: 12px; + color: #aaa; + font-family: monospace; + } + .pr-timecode { + font-size: 14px; + font-weight: bold; + color: #e0e0e0; + font-family: monospace; + } + .pr-settings-menu { + position: fixed; + background: #1e1e1e; + border: 1px solid #444; + border-radius: 6px; + padding: 10px; + display: flex; + flex-direction: column; + gap: 8px; + z-index: 9999; + box-shadow: 0 4px 20px rgba(0,0,0,0.7); + min-width: 220px; + } + .pr-settings-title { + font-size: 11px; + font-weight: 600; + color: #888; + text-transform: uppercase; + letter-spacing: 0.06em; + padding-bottom: 4px; + border-bottom: 1px solid #333; + margin-bottom: 2px; + } + .pr-settings-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + } + .pr-settings-label { + font-size: 12px; + color: #bbb; + flex: 1; + white-space: nowrap; + } + .pr-number-control { + display: flex; + align-items: center; + border: 1px solid #444; + border-radius: 4px; + background: #2a2a2a; + overflow: hidden; + } + .pr-number-btn { + background: #333; + color: #aaa; + border: none; + width: 20px; + height: 22px; + cursor: pointer; + font-size: 12px; + display: flex; + align-items: center; + justify-content: center; + transition: background 0.15s; + user-select: none; + } + .pr-number-btn:hover { + background: #444; + color: #fff; + } + .pr-settings-input { + background: transparent; + color: #e0e0e0; + border: none; + padding: 0 4px; + font-size: 12px; + width: 50px; + height: 22px; + text-align: center; + font-family: monospace; + outline: none; + -moz-appearance: textfield; + } + .pr-settings-input::-webkit-outer-spin-button, + .pr-settings-input::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; + } + .pr-settings-select { + background: #2a2a2a; + color: #e0e0e0; + border: 1px solid #444; + border-radius: 4px; + padding: 3px 4px; + font-size: 12px; + width: 98px; + cursor: pointer; + } + .pr-settings-divider { + border: none; + border-top: 1px solid #2a2a2a; + margin: 2px 0; + } + .pr-settings-toggle-btn { + width: 100%; + background: #252525; + color: #aaa; + border: 1px solid #333; + border-radius: 4px; + padding: 5px 8px; + font-size: 11px; + cursor: pointer; + text-align: center; + transition: all 0.15s; + } + .pr-settings-toggle-btn:hover { + background: #2e2e2e; + color: #ccc; + border-color: #555; + } + .pr-settings-close-btn { + background: transparent; + color: #888; + border: none; + cursor: pointer; + padding: 2px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 4px; + transition: all 0.15s; + } + .pr-settings-close-btn:hover { + color: #fff; + background: rgba(255,255,255,0.1); + } + .pr-segmented-control { + display: flex; + background: #1e1e1e; + border: 1px solid #333; + border-radius: 6px; + padding: 2px; + width: 110px; + height: 22px; + align-items: center; + box-sizing: border-box; + } + .pr-segment { + flex: 1; + text-align: center; + font-size: 10px; + font-weight: 500; + line-height: 18px; + cursor: pointer; + border-radius: 4px; + color: #888; + transition: all 0.15s ease; + } + .pr-segment.active { + background: #333; + color: #fff; + } + .pr-segment:hover:not(.active) { + color: #ccc; + } + + /* Autocomplete suggestion styles */ + .pr-autocomplete-menu { + position: fixed; + background: #181818; + border: 1px solid #444; + border-radius: 6px; + padding: 4px; + display: flex; + flex-direction: column; + gap: 2px; + z-index: 10000; + box-shadow: 0 4px 16px rgba(0,0,0,0.6); + min-width: 180px; + max-height: 200px; + overflow-y: auto; + } + .pr-autocomplete-item { + background: #252525; + color: #aaa; + border: 1px solid #333; + border-radius: 4px; + padding: 6px 12px; + font-size: 11px; + font-family: monospace; + cursor: pointer; + text-align: left; + display: flex; + align-items: center; + justify-content: space-between; + transition: all 0.15s ease; + } + .pr-autocomplete-item:hover, .pr-autocomplete-item.active { + background: #1c222d; + color: #4fff8f; + border-color: #4fff8f; + } + .pr-autocomplete-item span { + font-weight: bold; + font-size: 12px; + } + .pr-autocomplete-item small { + color: #777; + font-size: 10px; + } + .pr-autocomplete-item.active small { + color: #4fff8f; + opacity: 0.8; + } +`; + +if (!document.getElementById("prompt-relay-styles")) { + const styleEl = document.createElement("style"); + styleEl.id = "prompt-relay-styles"; + styleEl.textContent = STYLES; + document.head.appendChild(styleEl); +} + +// --- Icons --- +const ICONS = { + upload: ``, + audio: ``, + trash: ``, + text: ``, + play: ``, + pause: ``, + loop: ``, + minus: ``, + plus: ``, + fit: ``, + gear: ``, + close: `` +}; + +// --- Data Models --- +function parseInitial(jsonStr) { + let parsed = { segments: [], audioSegments: [] }; + try { + if (jsonStr) { + const p = JSON.parse(jsonStr); + if (Array.isArray(p.segments)) parsed.segments = p.segments; + if (Array.isArray(p.audioSegments)) parsed.audioSegments = p.audioSegments; + } + } catch (e) { } + + let currentStart = 0; + for (let seg of parsed.segments) { + if (seg.start === undefined) { + seg.start = currentStart; + currentStart += seg.length; + } + // Guarantee ID assignment to prevent node loading drag breaks + if (!seg.id) { + seg.id = Date.now().toString() + Math.random().toString(36).substr(2, 5); + } + } + + for (let seg of parsed.audioSegments) { + if (!seg.id) { + seg.id = Date.now().toString() + Math.random().toString(36).substr(2, 5); + } + if (seg.trimStart === undefined) seg.trimStart = 0; + } + + return parsed; +} + +class TimelineEditor { + constructor(node, container, domWidget) { + this.node = node; + this.container = container; + this.domWidget = domWidget; + + // Track heights (dynamic) + this.rulerHeight = RULER_HEIGHT; + this.blockHeight = BLOCK_HEIGHT; + this.audioTrackHeight = AUDIO_TRACK_HEIGHT; + this.canvasHeight = CANVAS_HEIGHT; + + // Core data + this.timeline = { segments: [], audioSegments: [] }; + this.selectionType = "image"; // "image" or "audio" + this.selectedIndex = -1; + + // Interactions + this._isDragging = false; + this._dragType = null; + this._dragStartX = 0; + this._dragInitialTimeline = null; + this.zoomLevel = 1.0; + this._lastZoom = 1.0; + this._lastScale = 1.0; + this._dragTargetId = null; + this._dragTargetIdRight = null; + this._previewSegments = null; + this._lastWidth = 0; + this._hoveredGapIdx = -1; + this._isHovering = false; + + // Playback state + this.currentFrame = 0; + this.isPlaying = false; + this.isLooping = false; + this.audioContext = null; + this.activeAudioNodes = []; + this.playbackStartTime = 0; + this.playbackStartFrame = 0; + this._playLoopId = null; + + // --- Ghost dragging state --- + this._ghostSegmentId = null; + this._ghostTrack = null; + this._ghostInitialTimeline = null; + + // Attach to Python widgets + this._gapMenu = null; // Active gap popup menu element + this._gapMenuDismisser = null; + + // Attach to Python widgets + this.durationFramesWidget = this.node.widgets.find(w => w.name === "duration_frames"); + this.durationSecondsWidget = this.node.widgets.find(w => w.name === "duration_seconds"); + this.frameRateWidget = this.node.widgets.find(w => w.name === "frame_rate"); + this.timelineDataWidget = this.node.widgets.find(w => w.name === "timeline_data"); + this.localPromptsWidget = this.node.widgets.find(w => w.name === "local_prompts"); + this.segmentLengthsWidget = this.node.widgets.find(w => w.name === "segment_lengths"); + this.guideStrengthWidget = this.node.widgets.find(w => w.name === "guide_strength"); + this.displayModeWidget = this.node.widgets.find(w => w.name === "display_mode"); + + this.timeline = parseInitial(this.timelineDataWidget?.value); + this.loadImages(); + + this.createDOM(); + if (this.timeline.segments.length > 0) { + this.selectedIndex = 0; + } + this.updateUIFromSelection(); + this.commitChanges(true); + // Hide settings widgets by default to reduce node clutter. + // Deferred so all widget types are finalized before we touch them. + setTimeout(() => this.hideSettingsWidgets(), 0); + + let isSyncing = false; + + const origDurationFramesCallback = this.durationFramesWidget?.callback; + if (this.durationFramesWidget) { + this.durationFramesWidget.callback = (...args) => { + if (origDurationFramesCallback) origDurationFramesCallback.apply(this.durationFramesWidget, args); + + if (!isSyncing && this.durationSecondsWidget) { + isSyncing = true; + this.durationSecondsWidget.value = parseFloat((this.getDurationFrames() / this.getFrameRate()).toFixed(3)); + isSyncing = false; + } + + this.commitChanges(); + }; + } + + const origDurationSecondsCallback = this.durationSecondsWidget?.callback; + if (this.durationSecondsWidget) { + this.durationSecondsWidget.callback = (...args) => { + if (origDurationSecondsCallback) origDurationSecondsCallback.apply(this.durationSecondsWidget, args); + + if (!isSyncing && this.durationFramesWidget) { + isSyncing = true; + const newFrames = Math.max(1, Math.round(this.durationSecondsWidget.value * this.getFrameRate())); + this.durationFramesWidget.value = newFrames; + if (this.durationFramesWidget.callback) this.durationFramesWidget.callback(newFrames); + isSyncing = false; + } + }; + } + + const origFrameRateCallback = this.frameRateWidget?.callback; + if (this.frameRateWidget) { + this.frameRateWidget.callback = (...args) => { + if (origFrameRateCallback) origFrameRateCallback.apply(this.frameRateWidget, args); + if (!isSyncing && this.durationSecondsWidget) { + isSyncing = true; + this.durationSecondsWidget.value = parseFloat((this.getDurationFrames() / this.getFrameRate()).toFixed(3)); + isSyncing = false; + } + }; + } + + const origDisplayModeCallback = this.displayModeWidget?.callback; + if (this.displayModeWidget) { + this.displayModeWidget.callback = (...args) => { + if (origDisplayModeCallback) origDisplayModeCallback.apply(this.displayModeWidget, args); + this.updateWidgetVisibility(); + this.updateUIFromSelection(); + this.render(); + }; + this.updateWidgetVisibility(); // Initial trigger + } + + // Polling is much more reliable in Comfy than ResizeObserver due to scale transforms + this._renderLoop = requestAnimationFrame(() => this.checkResize()); + } + + destroy() { + cancelAnimationFrame(this._renderLoop); + this.pauseAudio(); + window.removeEventListener("keydown", this.handleKeyDown, true); + window.removeEventListener("paste", this.handlePaste, true); + if (this._autocompleteMenu) { this._autocompleteMenu.remove(); } + } + + getDurationFrames() { + return parseInt((this.durationFramesWidget && this.durationFramesWidget.value > 0) ? this.durationFramesWidget.value : 24, 10); + } + + getFrameRate() { + return parseInt((this.frameRateWidget && this.frameRateWidget.value > 0) ? this.frameRateWidget.value : 24, 10); + } + + // Grow the timeline duration to fit `requiredFrames` if it is currently shorter. + // The timeline only ever grows — never shrinks — through this method. + growTimelineIfNeeded(requiredFrames) { + const current = this.getDurationFrames(); + if (requiredFrames <= current) return; // already big enough + + const newFrames = Math.ceil(requiredFrames); + if (this.durationFramesWidget) { + this.durationFramesWidget.value = newFrames; + } + if (this.durationSecondsWidget) { + this.durationSecondsWidget.value = parseFloat((newFrames / this.getFrameRate()).toFixed(3)); + } + // Notify ComfyUI that the widget value changed so it serialises correctly. + if (window.app && window.app.graph) { + window.app.graph.setDirtyCanvas(true, true); + } + } + + // Returns the maximum allowed zoom level, computed so that at max zoom + // the viewport shows exactly 4 seconds of the visual timeline. + getMaxZoom() { + const visualDurationSecs = this.getVisualDurationFrames() / this.getFrameRate(); + const baseMaxZoom = Math.max(1, visualDurationSecs / 4); + + // Limit max zoom to prevent canvas width from exceeding browser limits (causing crash) + const viewportWidth = this.viewport ? this.viewport.clientWidth : 1000; + const MAX_CANVAS_WIDTH = 32768; // Extended limit for modern browsers + const limitMaxZoom = MAX_CANVAS_WIDTH / Math.max(1, viewportWidth); + + return Math.max(1, Math.min(baseMaxZoom, limitMaxZoom)); + } + + // Returns the visual timeline length in frames: + // the furthest segment end (across both tracks) × 1.30, with a floor of getDurationFrames(). + // This is used for all rendering/positioning — the actual output duration is getDurationFrames(). + getVisualDurationFrames() { + let furthest = 0; + for (const seg of this.timeline.segments) { + furthest = Math.max(furthest, seg.start + seg.length); + } + for (const seg of this.timeline.audioSegments) { + furthest = Math.max(furthest, seg.start + seg.length); + } + const outputDuration = this.getDurationFrames(); + if (furthest <= 0) return outputDuration; + return Math.max(outputDuration, Math.ceil(furthest * 1.30)); + } + + // Sync the zoom slider's max attribute to the current getMaxZoom() value, + // clamping zoomLevel if it now exceeds the new max. + updateZoomSliderMax() { + if (!this.zoomSlider) return; + const maxZoom = this.getMaxZoom(); + this.zoomSlider.max = maxZoom.toFixed(2); + if (this.zoomLevel > maxZoom) { + this.zoomLevel = maxZoom; + this.zoomSlider.value = maxZoom; + // Resize the canvas to match the clamped zoom + const viewportWidth = this.viewport ? this.viewport.clientWidth : 0; + if (viewportWidth > 0) { + const newCanvasWidth = Math.max(viewportWidth, viewportWidth * this.zoomLevel); + this.canvas.style.width = newCanvasWidth + "px"; + this.resizeCanvas(newCanvasWidth); + } + } + } + + loadImages() { + for (const seg of this.timeline.segments) { + if (seg.imageB64 && !seg.imgObj) { + seg.imgObj = new Image(); + seg.imgObj.onload = () => this.render(); + seg.imgObj.src = seg.imageB64; + } + } + } + + createDOM() { + this.wrapper = document.createElement("div"); + this.wrapper.className = "pr-wrapper"; + + this.wrapper.addEventListener("mouseenter", () => { this._isHovering = true; }); + this.wrapper.addEventListener("mouseleave", () => { this._isHovering = false; }); + + this.handleKeyDown = (e) => { + const activeTag = document.activeElement ? document.activeElement.tagName : ""; + if (activeTag === "INPUT" || activeTag === "TEXTAREA") return; + + if ((e.key === "Delete" || e.key === "Backspace") && this.selectedIndex !== -1 && this._isHovering) { + this.deleteSelectedSegment(); + e.stopPropagation(); + e.stopImmediatePropagation(); + e.preventDefault(); + } else if ((e.key === " " || e.code === "Space") && this._isHovering) { + this.togglePlay(); + e.stopPropagation(); + e.stopImmediatePropagation(); + e.preventDefault(); + } + }; + window.addEventListener("keydown", this.handleKeyDown, true); + + this.handlePaste = (e) => { + if (this._isHovering) { + const activeTag = document.activeElement ? document.activeElement.tagName : ""; + if (activeTag === "INPUT" || activeTag === "TEXTAREA") return; + + if (e.clipboardData && e.clipboardData.files && e.clipboardData.files.length > 0) { + const imageFiles = Array.from(e.clipboardData.files).filter(f => f.type.startsWith("image/")); + if (imageFiles.length > 0) { + this.handleImageUpload(imageFiles, this.currentFrame); + e.preventDefault(); + e.stopPropagation(); + } + } + } + }; + window.addEventListener("paste", this.handlePaste, true); + + // --- Toolbar --- + const toolbar = document.createElement("div"); + toolbar.className = "pr-toolbar"; + + const actionGroup = document.createElement("div"); + actionGroup.className = "pr-actions"; + + this.fileInput = document.createElement("input"); + this.fileInput.type = "file"; + this.fileInput.accept = "image/*"; + this.fileInput.multiple = true; + this.fileInput.style.display = "none"; + this.fileInput.addEventListener("change", (e) => this.handleImageUpload(e.target.files)); + + this.audioFileInput = document.createElement("input"); + this.audioFileInput.type = "file"; + this.audioFileInput.accept = "audio/*"; + this.audioFileInput.multiple = true; + this.audioFileInput.style.display = "none"; + this.audioFileInput.addEventListener("change", (e) => this.handleAudioUpload(e.target.files)); + + const uploadBtn = document.createElement("button"); + uploadBtn.className = "pr-btn"; + uploadBtn.innerHTML = `${ICONS.upload} Add Image`; + uploadBtn.addEventListener("click", () => this.fileInput.click()); + + const uploadAudioBtn = document.createElement("button"); + uploadAudioBtn.className = "pr-btn"; + uploadAudioBtn.innerHTML = `${ICONS.audio} Add Audio`; + uploadAudioBtn.addEventListener("click", () => this.audioFileInput.click()); + + const addTextBtn = document.createElement("button"); + addTextBtn.className = "pr-btn"; + addTextBtn.innerHTML = `${ICONS.text} Add Text`; + addTextBtn.addEventListener("click", () => this.addTextSegmentFreeSpace()); + + const deleteBtn = document.createElement("button"); + deleteBtn.className = "pr-btn pr-btn-danger"; + deleteBtn.innerHTML = `${ICONS.trash} Delete`; + deleteBtn.addEventListener("click", () => this.deleteSelectedSegment()); + + actionGroup.appendChild(this.fileInput); + actionGroup.appendChild(this.audioFileInput); + actionGroup.appendChild(uploadBtn); + actionGroup.appendChild(addTextBtn); + actionGroup.appendChild(uploadAudioBtn); + actionGroup.appendChild(deleteBtn); + toolbar.appendChild(actionGroup); + + const rightGroup = document.createElement("div"); + rightGroup.className = "pr-right-group"; + + this.segmentBoundsDisplay = document.createElement("div"); + this.segmentBoundsDisplay.className = "pr-segment-bounds"; + this.segmentBoundsDisplay.textContent = "Start: - | End: -"; + + this.timeCodeDisplay = document.createElement("div"); + this.timeCodeDisplay.className = "pr-timecode"; + this.timeCodeDisplay.textContent = this.formatTime(0); + + const settingsBtn = document.createElement("button"); + settingsBtn.className = "pr-btn"; + settingsBtn.style.padding = "6px"; + settingsBtn.style.justifyContent = "center"; + settingsBtn.style.width = "28px"; + settingsBtn.style.height = "28px"; + settingsBtn.style.boxSizing = "border-box"; + settingsBtn.innerHTML = ICONS.gear; + settingsBtn.title = "Settings"; + settingsBtn.addEventListener("click", (e) => { + e.stopPropagation(); + if (this._settingsMenu) { + this.dismissSettingsMenu(); + } else { + this.showSettingsMenu(settingsBtn); + } + }); + + const toggleBtn = document.createElement("button"); + toggleBtn.className = "pr-btn"; + toggleBtn.style.padding = "6px 8px"; + toggleBtn.style.fontSize = "11px"; + toggleBtn.style.marginRight = "0px"; + toggleBtn.textContent = "Custom Audio: OFF"; + toggleBtn.title = "Toggle Custom Audio Output"; + + const updateToggleStyle = (isOn) => { + toggleBtn.textContent = isOn ? "Custom Audio: ON" : "Custom Audio: OFF"; + if (isOn) { + toggleBtn.style.background = "#1c222d"; + toggleBtn.style.borderColor = "#283142"; + toggleBtn.style.color = "#e0e0e0"; + } else { + toggleBtn.style.background = "#222"; + toggleBtn.style.borderColor = "#111"; + toggleBtn.style.color = "#e0e0e0"; + } + }; + + toggleBtn.addEventListener("click", (e) => { + e.stopPropagation(); + const widget = this.node.widgets?.find(w => w.name === "use_custom_audio"); + if (widget) { + widget.value = !widget.value; + updateToggleStyle(widget.value); + this.node.setDirtyCanvas(true, true); + } + }); + + // Initial state check (widgets might not be ready immediately) + setTimeout(() => { + const widget = this.node.widgets?.find(w => w.name === "use_custom_audio"); + if (widget) { + updateToggleStyle(widget.value); + } + }, 100); + + const helpBtn = document.createElement("button"); + helpBtn.className = "pr-btn"; + helpBtn.style.padding = "6px"; + helpBtn.style.justifyContent = "center"; + helpBtn.style.width = "28px"; + helpBtn.style.height = "28px"; + helpBtn.style.boxSizing = "border-box"; + helpBtn.innerHTML = "?"; + helpBtn.title = "Help / Documentation"; + helpBtn.addEventListener("click", (e) => { + e.stopPropagation(); + window.open("https://github.com/WhatDreamsCost/WhatDreamsCost-ComfyUI", "_blank"); + }); + + const btnGroup = document.createElement("div"); + btnGroup.style.display = "flex"; + btnGroup.style.gap = "6px"; + btnGroup.style.alignItems = "center"; + btnGroup.appendChild(toggleBtn); + btnGroup.appendChild(helpBtn); + btnGroup.appendChild(settingsBtn); + rightGroup.appendChild(btnGroup); + + toolbar.appendChild(rightGroup); + + // --- Canvas & Viewport --- + this.viewport = document.createElement("div"); + this.viewport.className = "pr-timeline-viewport"; + + this.viewport.addEventListener("wheel", (e) => { + if (e.ctrlKey || e.metaKey) { + e.preventDefault(); + e.stopPropagation(); + + let zoomDelta = e.deltaY > 0 ? -0.5 : 0.5; + this.zoomLevel = Math.max(1, Math.min(this.getMaxZoom(), this.zoomLevel + zoomDelta)); + if (this.zoomSlider) this.zoomSlider.value = this.zoomLevel; + + const oldWidth = this.canvas.offsetWidth; + const newWidth = this.viewport.clientWidth * this.zoomLevel; + const mouseX = e.clientX - this.viewport.getBoundingClientRect().left; + const scrollRatio = (this.viewport.scrollLeft + mouseX) / oldWidth; + + this.canvas.style.width = newWidth + "px"; + this.viewport.scrollLeft = scrollRatio * newWidth - mouseX; + } + }, { passive: false, capture: true }); + + this.canvas = document.createElement("canvas"); + this.canvas.className = "pr-canvas"; + this.ctx = this.canvas.getContext("2d"); + this.canvas.style.width = "100%"; + + this.viewport.appendChild(this.canvas); + + this.canvas.addEventListener("mousedown", (e) => this.onMouseDown(e)); + this.canvas.addEventListener("contextmenu", (e) => this.onContextMenu(e)); + this.canvas.style.height = `${CANVAS_HEIGHT}px`; + + // --- Content Area Container --- + const propContainer = document.createElement("div"); + propContainer.className = "pr-prop-container"; + + // --- Text Area (Image/Text) --- + this.promptInput = document.createElement("textarea"); + this.promptInput.className = "pr-prompt-area"; + this.promptInput.placeholder = "Enter prompt for selected segment... Type '@' for character shortcuts."; + this.promptInput.addEventListener("input", () => { + if (this.selectionType === "image" && this.timeline.segments[this.selectedIndex]) { + this.timeline.segments[this.selectedIndex].prompt = this.promptInput.value; + this.commitChanges(); + } + }); + + // --- Audio Info Area --- + this.audioInfoArea = document.createElement("div"); + this.audioInfoArea.className = "pr-audio-info"; + + propContainer.appendChild(this.promptInput); + propContainer.appendChild(this.audioInfoArea); + + this.wrapper.addEventListener("dragover", (e) => { + e.preventDefault(); + this.wrapper.classList.add("drag-active"); + + const { x, y } = this.getMousePos(e); + const logicalWidth = this.canvas.offsetWidth; + const totalFrames = this.getVisualDurationFrames(); + if (!logicalWidth || totalFrames <= 0) return; + + const isAudioTrack = y > RULER_HEIGHT + this.blockHeight; + const trackType = isAudioTrack ? "audio" : "image"; + const arrToModify = isAudioTrack ? this.timeline.audioSegments : this.timeline.segments; + + if (!this._ghostSegmentId || this._ghostTrack !== trackType) { + this._ghostSegmentId = "GHOST_" + Date.now(); + this._ghostTrack = trackType; + this._ghostInitialTimeline = JSON.parse(JSON.stringify(arrToModify)); + + const frameRate = this.getFrameRate(); + const newLength = Math.max(1, frameRate * 1); + + let mouseFrameX = x * (totalFrames / logicalWidth); + let startFrame = clamp(Math.round(mouseFrameX - newLength / 2), 0, totalFrames - newLength); + + this._ghostInitialTimeline.push({ + id: this._ghostSegmentId, + start: startFrame, + length: newLength, + type: "ghost" + }); + } + + let mouseFrameX = x * (totalFrames / logicalWidth); + const ghost = this._ghostInitialTimeline.find(s => s.id === this._ghostSegmentId); + let D_mouse_start = mouseFrameX - ghost.length / 2; + + this._previewSegments = this._applyCenterDragPhysics( + this._ghostInitialTimeline, + this._ghostSegmentId, + D_mouse_start, + mouseFrameX, + totalFrames, + totalFrames, + logicalWidth + ); + this.render(); + }); + + this.wrapper.addEventListener("dragleave", (e) => { + const rect = this.wrapper.getBoundingClientRect(); + if (e.clientX < rect.left || e.clientX >= rect.right || + e.clientY < rect.top || e.clientY >= rect.bottom) { + this.wrapper.classList.remove("drag-active"); + this._ghostSegmentId = null; + this._ghostTrack = null; + this._ghostInitialTimeline = null; + this._previewSegments = null; + this.render(); + } + }); + + this.wrapper.addEventListener("drop", (e) => { + e.preventDefault(); + e.stopPropagation(); + this.wrapper.classList.remove("drag-active"); + + let targetFrameStart = null; + let targetTrack = this._ghostTrack || "image"; + + if (this._ghostSegmentId && this._previewSegments) { + const ghost = this._previewSegments.find(s => s.id === this._ghostSegmentId); + if (ghost) { + targetFrameStart = ghost.resolvedStart !== undefined ? ghost.resolvedStart : ghost.start; + } + } + this._ghostSegmentId = null; + this._ghostTrack = null; + this._ghostInitialTimeline = null; + this._previewSegments = null; + this.render(); + + if (e.dataTransfer.files && e.dataTransfer.files.length > 0) { + const imageFiles = []; + const audioFiles = []; + for (let file of e.dataTransfer.files) { + if (file.type.startsWith("audio/")) audioFiles.push(file); + if (file.type.startsWith("image/")) imageFiles.push(file); + } + + // Let implicit intent handle mixing drops: use the track we hovered over + // for the first type we process, or fallback. + if (audioFiles.length > 0 && (targetTrack === "audio" || imageFiles.length === 0)) { + this.handleAudioUpload(audioFiles, targetFrameStart); + } else if (imageFiles.length > 0) { + this.handleImageUpload(imageFiles, targetFrameStart); + } + } + }); + + window.addEventListener("mousemove", (e) => this.onMouseMove(e)); + window.addEventListener("mouseup", (e) => this.onMouseUp(e)); + + // --- Player Controls --- + const playerControls = document.createElement("div"); + playerControls.className = "pr-player-controls"; + + this.playBtn = document.createElement("button"); + this.playBtn.className = "pr-icon-btn"; + this.playBtn.style.padding = "4px"; + this.playBtn.innerHTML = ICONS.play; + this.playBtn.title = "Play/Pause Audio"; + this.playBtn.addEventListener("click", () => this.togglePlay()); + + this.loopBtn = document.createElement("button"); + this.loopBtn.className = "pr-icon-btn"; + this.loopBtn.style.padding = "4px"; + this.loopBtn.innerHTML = ICONS.loop; + this.loopBtn.title = "Toggle Loop"; + this.loopBtn.addEventListener("click", () => this.toggleLoop()); + + this.seekBar = document.createElement("input"); + this.seekBar.type = "range"; + this.seekBar.className = "pr-seek-bar"; + this.seekBar.min = "0"; + this.seekBar.value = "0"; + this.seekBar.style.flex = "1"; // take up remaining space + this.seekBar.addEventListener("input", (e) => { + this.currentFrame = parseInt(e.target.value, 10); + this.render(); + if (this.isPlaying) { + this.playAudio(); + } + }); + + // --- Zoom Controls --- + const zoomControls = document.createElement("div"); + zoomControls.className = "pr-zoom-controls"; + + const zoomOutBtn = document.createElement("button"); + zoomOutBtn.className = "pr-icon-btn"; + zoomOutBtn.style.padding = "4px"; + zoomOutBtn.innerHTML = ICONS.minus; + zoomOutBtn.title = "Zoom Out"; + zoomOutBtn.addEventListener("click", () => { + const currentZoom = parseFloat(this.zoomSlider.value); + this.zoomSlider.value = Math.max(1, currentZoom - 0.5); + this.zoomSlider.dispatchEvent(new Event("input")); + }); + + this.zoomSlider = document.createElement("input"); + this.zoomSlider.type = "range"; + this.zoomSlider.className = "pr-zoom-slider"; + this.zoomSlider.min = "1"; + this.zoomSlider.max = "1"; // Updated dynamically via updateZoomSliderMax() + this.zoomSlider.step = "0.1"; + this.zoomSlider.value = "1"; + this.zoomSlider.title = "Zoom Level"; + this.zoomSlider.addEventListener("input", (e) => { + this.zoomLevel = parseFloat(e.target.value); + + const viewportWidth = this.viewport.clientWidth; + const newCanvasWidth = Math.max(viewportWidth, viewportWidth * this.zoomLevel); + + this.canvas.style.width = newCanvasWidth + "px"; + this.resizeCanvas(newCanvasWidth); + this._lastWidth = viewportWidth; + this._lastZoom = this.zoomLevel; + + // Keep playhead centered + const totalFrames = this.getVisualDurationFrames(); + const playheadRatio = this.currentFrame / totalFrames; + const newPlayheadX = playheadRatio * newCanvasWidth; + this.viewport.scrollLeft = newPlayheadX - (viewportWidth / 2); + }); + + const zoomInBtn = document.createElement("button"); + zoomInBtn.className = "pr-icon-btn"; + zoomInBtn.style.padding = "4px"; + zoomInBtn.innerHTML = ICONS.plus; + zoomInBtn.title = "Zoom In"; + zoomInBtn.addEventListener("click", () => { + const currentZoom = parseFloat(this.zoomSlider.value); + this.zoomSlider.value = Math.min(this.getMaxZoom(), currentZoom + 0.5); + this.zoomSlider.dispatchEvent(new Event("input")); + }); + + const zoomFitBtn = document.createElement("button"); + zoomFitBtn.className = "pr-icon-btn"; + zoomFitBtn.style.padding = "4px"; + zoomFitBtn.style.marginLeft = "4px"; + zoomFitBtn.innerHTML = ICONS.fit; + zoomFitBtn.title = "Zoom to Fit (show full timeline)"; + zoomFitBtn.addEventListener("click", () => { + this.zoomLevel = 1; + this.zoomSlider.value = 1; + const viewportWidth = this.viewport.clientWidth; + this.canvas.style.width = viewportWidth + "px"; + this.resizeCanvas(viewportWidth); + this._lastWidth = viewportWidth; + this._lastZoom = 1; + this.viewport.scrollLeft = 0; + }); + + zoomControls.appendChild(zoomOutBtn); + zoomControls.appendChild(this.zoomSlider); + zoomControls.appendChild(zoomInBtn); + zoomControls.appendChild(zoomFitBtn); + + playerControls.appendChild(this.playBtn); + playerControls.appendChild(this.loopBtn); + playerControls.appendChild(this.seekBar); + playerControls.appendChild(zoomControls); + + + + // --- Guide Strength Slider --- + this.strengthRow = document.createElement("div"); + this.strengthRow.className = "pr-strength-row"; + + const strengthLabel = document.createElement("span"); + strengthLabel.className = "pr-strength-label"; + strengthLabel.textContent = "Guide Strength:"; + + this.strengthValue = document.createElement("input"); + this.strengthValue.type = "text"; + this.strengthValue.className = "pr-strength-input"; + this.strengthValue.value = "1.00"; + this.strengthValue.disabled = true; + this.strengthValue.style.cursor = "ew-resize"; + + // Dragging logic for guide strength + let isDragging = false; + let startX = 0; + let startVal = 0; + let hasMoved = false; + + this.strengthValue.addEventListener("mousedown", (e) => { + if (this.strengthValue.disabled) return; + startX = e.clientX; + startVal = parseFloat(this.strengthValue.value) || 1.0; + hasMoved = false; + + const onMouseMove = (moveEvent) => { + const deltaX = moveEvent.clientX - startX; + if (Math.abs(deltaX) > 3) { + hasMoved = true; + isDragging = true; + } + + if (isDragging) { + moveEvent.preventDefault(); + const sensitivity = 0.002; + let newVal = startVal + deltaX * sensitivity; + + if (newVal < 0) newVal = 0; + if (newVal > 1) newVal = 1; + + this.strengthValue.value = newVal.toFixed(2); + + if (this.selectionType === "image" && this.timeline.segments[this.selectedIndex]) { + const seg = this.timeline.segments[this.selectedIndex]; + if (seg.type !== "text") { + seg.guideStrength = newVal; + this.commitChanges(); + } + } + } + }; + + const onMouseUp = () => { + document.removeEventListener("mousemove", onMouseMove); + document.removeEventListener("mouseup", onMouseUp); + + if (!hasMoved) { + this.strengthValue.focus(); + this.strengthValue.select(); + } + isDragging = false; + }; + + document.addEventListener("mousemove", onMouseMove); + document.addEventListener("mouseup", onMouseUp); + }); + + this.strengthValue.addEventListener("change", (e) => { + let val = parseFloat(e.target.value); + if (isNaN(val)) val = 1; + val = Math.max(0, Math.min(1, val)); + this.strengthValue.value = val.toFixed(2); + if (this.selectionType === "image" && this.timeline.segments[this.selectedIndex]) { + const seg = this.timeline.segments[this.selectedIndex]; + if (seg.type !== "text") { + seg.guideStrength = val; + this.commitChanges(); + } + } + }); + + this.strengthRow.appendChild(this.timeCodeDisplay); + this.strengthRow.appendChild(this.segmentBoundsDisplay); + this.strengthRow.appendChild(strengthLabel); + this.strengthRow.appendChild(this.strengthValue); + + + this.wrapper.appendChild(toolbar); + this.wrapper.appendChild(this.viewport); + + const controlsGroup = document.createElement("div"); + controlsGroup.className = "pr-controls-group"; + controlsGroup.appendChild(this.strengthRow); + controlsGroup.appendChild(playerControls); + this.wrapper.appendChild(controlsGroup); + this.wrapper.appendChild(propContainer); + + this.container.appendChild(this.wrapper); + + // --- Initialize autocomplete popup support --- + this.setupAutocomplete(); + } + + // --- Auto-complete Popup Setup --- + setupAutocomplete() { + const input = this.promptInput; + if (!input) return; + + const menu = document.createElement("div"); + menu.className = "pr-autocomplete-menu"; + menu.style.display = "none"; + document.body.appendChild(menu); + this._autocompleteMenu = menu; + + const suggestions = [ + { tag: "@char1", label: "Character 1" }, + { tag: "@char2", label: "Character 2" }, + { tag: "@char3", label: "Character 3" }, + { tag: "@character1", label: "Character 1 (Full)" }, + { tag: "@character2", label: "Character 2 (Full)" }, + { tag: "@character3", label: "Character 3 (Full)" } + ]; + + let activeIndex = 0; + let showMenu = false; + let queryStart = -1; + + const hideMenu = () => { + menu.style.display = "none"; + showMenu = false; + }; + + const getCaretCoordinates = () => { + const rect = input.getBoundingClientRect(); + return { + left: rect.left, + top: rect.bottom + window.scrollY + 2 + }; + }; + + const updateMenu = () => { + if (!showMenu) return; + const text = input.value; + const cursor = input.selectionStart; + const query = text.slice(queryStart + 1, cursor).toLowerCase(); + + const filtered = suggestions.filter(s => s.tag.toLowerCase().includes("@" + query) || s.tag.toLowerCase().includes(query)); + if (filtered.length === 0) { + hideMenu(); + return; + } + + menu.innerHTML = ""; + + // Clamp activeIndex inside the filtered results boundaries + if (activeIndex >= filtered.length) { + activeIndex = 0; + } + + filtered.forEach((s, idx) => { + const item = document.createElement("div"); + item.className = "pr-autocomplete-item" + (idx === activeIndex ? " active" : ""); + item.innerHTML = `${s.tag}${s.label}`; + + item.addEventListener("mousedown", (e) => { + e.preventDefault(); // Prevent losing focus on textarea + insertSuggestion(s.tag); + }); + menu.appendChild(item); + }); + + const coords = getCaretCoordinates(); + menu.style.left = `${coords.left}px`; + menu.style.top = `${coords.top}px`; + menu.style.display = "flex"; + }; + + const insertSuggestion = (tag) => { + const text = input.value; + const cursor = input.selectionStart; + const before = text.slice(0, queryStart); + const after = text.slice(cursor); + + input.value = before + tag + " " + after; + input.selectionStart = input.selectionEnd = queryStart + tag.length + 1; + + // Trigger input event to save changes in the node data + input.dispatchEvent(new Event("input")); + hideMenu(); + input.focus(); + }; + + input.addEventListener("keydown", (e) => { + if (showMenu) { + const items = menu.querySelectorAll(".pr-autocomplete-item"); + if (items.length === 0) return; + + if (e.key === "ArrowDown") { + e.preventDefault(); + activeIndex = (activeIndex + 1) % items.length; + updateMenu(); + } else if (e.key === "ArrowUp") { + e.preventDefault(); + activeIndex = (activeIndex - 1 + items.length) % items.length; + updateMenu(); + } else if (e.key === "Enter" || e.key === "Tab") { + e.preventDefault(); + const activeItem = items[activeIndex]; + if (activeItem) { + const tag = activeItem.querySelector("span").textContent; + insertSuggestion(tag); + } + } else if (e.key === "Escape") { + e.preventDefault(); + hideMenu(); + } + } + }); + + input.addEventListener("input", () => { + const text = input.value; + const cursor = input.selectionStart; + const textBeforeCursor = text.slice(0, cursor); + const lastAt = textBeforeCursor.lastIndexOf("@"); + + if (lastAt !== -1 && lastAt >= textBeforeCursor.search(/\s[^\s]*$/)) { + showMenu = true; + queryStart = lastAt; + updateMenu(); + } else { + hideMenu(); + } + }); + + input.addEventListener("blur", () => { + // Small delay to let mousedown register on menu items before closing + setTimeout(hideMenu, 150); + }); + } + + checkResize() { + const viewportWidth = this.viewport.clientWidth; + const currentScale = this.getRenderScale(); + + if (viewportWidth > 0 && (this._lastWidth !== viewportWidth || this._lastZoom !== this.zoomLevel || this._lastScale !== currentScale)) { + this._lastWidth = viewportWidth; + this._lastZoom = this.zoomLevel; + this._lastScale = currentScale; + + const newCanvasWidth = Math.max(viewportWidth, viewportWidth * this.zoomLevel); + this.canvas.style.width = newCanvasWidth + "px"; + this.resizeCanvas(newCanvasWidth); + } + this._renderLoop = requestAnimationFrame(() => this.checkResize()); + } + + getRenderScale() { + const dpr = window.devicePixelRatio || 1; + let graphScale = 1; + try { + if (window.app && window.app.canvas && window.app.canvas.ds && window.app.canvas.ds.scale) { + graphScale = window.app.canvas.ds.scale; + } + } catch (e) { } + // Scale up if zoomed in, but don't drop below 1x DPR if zoomed out + return dpr * Math.max(1, graphScale); + } + + resizeCanvas(widthPx) { + const scale = this.getRenderScale(); + const targetWidth = Math.round(widthPx * scale); + const targetHeight = Math.round(this.canvasHeight * scale); + + this.canvas.width = targetWidth; + this.canvas.height = targetHeight; + this.ctx.setTransform(scale, 0, 0, scale, 0, 0); + this.render(); + } + + // Helper to map mouse events accurately regardless of canvas scaling + getMousePos(e) { + const rect = this.canvas.getBoundingClientRect(); + + const scaleX = this.canvas.offsetWidth / rect.width; + const scaleY = this.canvas.offsetHeight / rect.height; + + const x = (e.clientX - rect.left) * scaleX; + const y = (e.clientY - rect.top) * scaleY; + return { x, y }; + } + + // --- Async Image Upload Logic (Handles multiple images simultaneously) --- + async handleImageUpload(files, targetFrameStart = null, explicitLength = null) { + const frameRate = this.getFrameRate(); + const durationFrames = this.getDurationFrames(); + const newLength = explicitLength !== null ? explicitLength : frameRate * 1; // Default to 1 second long + + for (let file of files) { + if (!file.type.startsWith("image/")) continue; + + await new Promise(async (resolve) => { + try { + const body = new FormData(); + body.append("image", file); + const resp = await api.fetchApi("/upload/image", { method: "POST", body }); + if (resp.status !== 200) { resolve(); return; } + + const data = await resp.json(); + const filename = data.name; + const subfolder = data.subfolder || ""; + const imageFile = subfolder ? subfolder + "/" + filename : filename; + const imgUrl = api.apiURL(`/view?filename=${encodeURIComponent(filename)}&type=input&subfolder=${encodeURIComponent(subfolder)}`); + + const img = new Image(); + img.onload = () => { + + let newStart = targetFrameStart; + if (newStart === null) { + // Fallback: find the first free slot, or append past the end + newStart = 0; + this.timeline.segments.sort((a, b) => a.start - b.start); + for (let i = 0; i < this.timeline.segments.length; i++) { + let seg = this.timeline.segments[i]; + if (newStart + newLength <= seg.start) break; + newStart = Math.max(newStart, seg.start + seg.length); + } + } + + // Use the visual timeline as the physics bound so segments can + // land anywhere in the padded visual area without touching duration_frames. + const currentDuration = this.getVisualDurationFrames(); + + if (targetFrameStart !== null) { + // Resolve physics to push existing segments + let tempId = "TEMP_" + Date.now(); + this.timeline.segments.push({ id: tempId, start: newStart, length: newLength, type: "temp" }); + let result = this._applyCenterDragPhysics(this.timeline.segments, tempId, newStart, newStart + newLength / 2, currentDuration, currentDuration, 1); + + // Update original segments with resolved physics to preserve imgObj + for (let shiftedSeg of result) { + let original = this.timeline.segments.find(s => s.id === shiftedSeg.id); + if (original) { + original.start = shiftedSeg.resolvedStart !== undefined ? shiftedSeg.resolvedStart : shiftedSeg.start; + } + } + + let tempSeg = this.timeline.segments.find(s => s.id === tempId); + newStart = tempSeg.start; + this.timeline.segments = this.timeline.segments.filter(s => s.id !== tempId); + targetFrameStart = newStart + newLength; // For the next file in batch + } + + // Use the full intended length — the timeline has already been grown to fit. + let constrainedLength = newLength; + + const seg = { + id: Date.now().toString() + Math.random().toString(36).substr(2, 5), + start: newStart, + length: constrainedLength, + prompt: "", + type: "image", + imageFile: imageFile, + imageB64: imgUrl + }; + + const displayImg = new Image(); + displayImg.onload = () => { + seg.imgObj = displayImg; + this.render(); + resolve(); // Resolve promise letting next image process + }; + displayImg.src = imgUrl; + + this.timeline.segments.push(seg); + this.timeline.segments.sort((a, b) => a.start - b.start); + this.selectionType = "image"; + this.selectedIndex = this.timeline.segments.findIndex(s => s.id === seg.id); + + this.updateUIFromSelection(); + this.commitChanges(true); + }; + img.src = imgUrl; + } catch (err) { + console.error("[PromptRelay] Image upload failed", err); + resolve(); + } + }); + } + this.fileInput.value = ""; + } + + // --- Async Audio Upload Logic --- + async handleAudioUpload(files, targetFrameStart = null) { + const frameRate = this.getFrameRate(); + const durationFrames = this.getDurationFrames(); + + for (let file of files) { + if (!file.type.startsWith("audio/")) continue; + + await new Promise(async (resolve) => { + try { + const body = new FormData(); + body.append("image", file); + const resp = await api.fetchApi("/upload/image", { method: "POST", body }); + if (resp.status !== 200) { resolve(); return; } + + const data = await resp.json(); + const filename = data.name; + const subfolder = data.subfolder || ""; + const audioFile = subfolder ? subfolder + "/" + filename : filename; + + const arrayBuffer = await file.arrayBuffer(); + const audioCtx = new (window.AudioContext || window.webkitAudioContext)(); + const audioBuffer = await audioCtx.decodeAudioData(arrayBuffer); + const clipDurationSecs = audioBuffer.duration; + const clipFrames = Math.max(1, Math.ceil(clipDurationSecs * frameRate)); + + const channelData = audioBuffer.getChannelData(0); + const peaks = []; + const numPeaks = 200; + const step = Math.floor(channelData.length / numPeaks); + for (let i = 0; i < numPeaks; i++) { + let max = 0; + for (let j = 0; j < step; j++) { + const val = Math.abs(channelData[i * step + j]); + if (val > max) max = val; + } + peaks.push(max); + } + + let newLength = clipFrames; + let newStart = targetFrameStart; + + if (newStart === null) { + // Find the first free slot, or place past the end of all existing audio + newStart = 0; + this.timeline.audioSegments.sort((a, b) => a.start - b.start); + for (let i = 0; i < this.timeline.audioSegments.length; i++) { + let seg = this.timeline.audioSegments[i]; + if (newStart + newLength <= seg.start) break; + newStart = Math.max(newStart, seg.start + seg.length); + } + } + + // Use the visual timeline as the physics bound so segments can + // land anywhere in the padded visual area without touching duration_frames. + const currentDuration = this.getVisualDurationFrames(); + + if (targetFrameStart !== null) { + let tempId = "TEMP_" + Date.now(); + this.timeline.audioSegments.push({ id: tempId, start: newStart, length: newLength, type: "temp" }); + let result = this._applyCenterDragPhysics(this.timeline.audioSegments, tempId, newStart, newStart + newLength / 2, currentDuration, currentDuration, 1); + + for (let shiftedSeg of result) { + let original = this.timeline.audioSegments.find(s => s.id === shiftedSeg.id); + if (original) original.start = shiftedSeg.resolvedStart !== undefined ? shiftedSeg.resolvedStart : shiftedSeg.start; + } + + let tempSeg = this.timeline.audioSegments.find(s => s.id === tempId); + newStart = tempSeg.start; + this.timeline.audioSegments = this.timeline.audioSegments.filter(s => s.id !== tempId); + targetFrameStart = newStart + newLength; + } + + // Use the full clip length — timeline has already grown to fit. + let constrainedLength = newLength; + + const seg = { + id: Date.now().toString() + Math.random().toString(36).substr(2, 5), + type: "audio", + start: newStart, + length: constrainedLength, + trimStart: 0, + audioDurationFrames: clipFrames, + audioFile: audioFile, + fileName: file.name, + waveformPeaks: peaks + }; + + this.timeline.audioSegments.push(seg); + this.timeline.audioSegments.sort((a, b) => a.start - b.start); + this.selectionType = "audio"; + this.selectedIndex = this.timeline.audioSegments.findIndex(s => s.id === seg.id); + + this.updateUIFromSelection(); + this.commitChanges(true); + this.render(); + resolve(); + } catch (err) { + console.error("[PromptRelay] Audio processing failed", err); + resolve(); + } + }); + } + this.audioFileInput.value = ""; + } + + deleteSelectedSegment() { + if (this.selectionType === "audio") { + if (this.timeline.audioSegments.length === 0 || this.selectedIndex === -1) return; + this.timeline.audioSegments.splice(this.selectedIndex, 1); + this.selectedIndex = Math.max(-1, this.selectedIndex - 1); + } else { + if (this.timeline.segments.length === 0 || this.selectedIndex === -1) return; + this.timeline.segments.splice(this.selectedIndex, 1); + this.selectedIndex = Math.max(-1, this.selectedIndex - 1); + } + this.updateUIFromSelection(); + this.commitChanges(); + this.render(); + } + + formatTime(frames, dropSuffix = false) { + const mode = this.displayModeWidget ? this.displayModeWidget.value : "seconds"; + if (mode === "seconds") { + const secs = frames / this.getFrameRate(); + return dropSuffix ? secs.toFixed(2) : secs.toFixed(2) + "s"; + } + return dropSuffix ? Math.round(frames).toString() : Math.round(frames) + " frames"; + } + + updateWidgetVisibility() { + const mode = this.displayModeWidget ? this.displayModeWidget.value : "seconds"; + + if (this.durationFramesWidget) { + // Always visible regardless of display mode + this.durationFramesWidget.type = "INT"; + if (!this.durationFramesWidget.options) this.durationFramesWidget.options = {}; + this.durationFramesWidget.options.hidden = false; + this.durationFramesWidget.hidden = false; + delete this.durationFramesWidget.computeSize; + } + if (this.durationSecondsWidget) { + // Always visible regardless of display mode + this.durationSecondsWidget.type = "FLOAT"; + if (!this.durationSecondsWidget.options) this.durationSecondsWidget.options = {}; + this.durationSecondsWidget.options.hidden = false; + this.durationSecondsWidget.hidden = false; + delete this.durationSecondsWidget.computeSize; + } + + // Force node resize and redraw deferred to next tick + setTimeout(() => { + if (this.node && this.node.computeSize) { + const sz = this.node.computeSize(); + this.node.size[1] = sz[1]; + if (window.app && window.app.graph) { + window.app.graph.setDirtyCanvas(true, true); + } + } + }, 0); + } + + updateUIFromSelection() { + let seg = null; + if (this.selectedIndex >= 0) { + if (this.selectionType === "audio") { + const origSeg = this.timeline.audioSegments[this.selectedIndex]; + if (origSeg) { + const previewIsAudio = this._ghostTrack === 'audio' || (this._previewSegments && this._ghostTrack === null && this.selectionType === 'audio'); + const arr = (this._previewSegments && previewIsAudio) ? this._previewSegments : this.timeline.audioSegments; + seg = arr.find(s => s.id === origSeg.id) || origSeg; + } + } else { + const origSeg = this.timeline.segments[this.selectedIndex]; + if (origSeg) { + const previewIsImage = this._ghostTrack === 'image' || (this._previewSegments && this._ghostTrack === null && this.selectionType === 'image'); + const arr = (this._previewSegments && previewIsImage) ? this._previewSegments : this.timeline.segments; + seg = arr.find(s => s.id === origSeg.id) || origSeg; + } + } + } + + if (this.selectionType === "audio" && seg) { + this.promptInput.style.display = "none"; + this.strengthRow.style.display = "flex"; + this.audioInfoArea.style.display = "block"; + this.audioInfoArea.innerHTML = ` + File: ${seg.fileName || "Unknown"}
+ Length: ${this.formatTime(seg.audioDurationFrames)} Output Length: ${this.formatTime(seg.length)}
+ Trim-in: ${this.formatTime(Math.round(seg.trimStart))} Trim-Out: ${this.formatTime(Math.round(seg.audioDurationFrames - (seg.trimStart + seg.length)))} + `; + this.strengthValue.value = "1.00"; + this.strengthValue.disabled = true; + } else { + this.audioInfoArea.style.display = "none"; + this.promptInput.style.display = "block"; + this.strengthRow.style.display = "flex"; + + if (seg) { + this.promptInput.value = seg.prompt || ""; + this.promptInput.disabled = false; + + const isImage = seg.type !== "text"; + const strength = isImage ? (seg.guideStrength ?? 1.0) : 1.0; + this.strengthValue.value = strength.toFixed(2); + this.strengthValue.disabled = !isImage; + } else { + this.promptInput.value = ""; + this.promptInput.disabled = true; + this.strengthValue.value = "1.00"; + this.strengthValue.disabled = true; + } + } + + if (this.segmentBoundsDisplay) { + if (seg) { + const startStr = this.formatTime(seg.start, true); + const endStr = this.formatTime(seg.start + seg.length, true); + this.segmentBoundsDisplay.textContent = `Start: ${startStr} | End: ${endStr}`; + } else { + this.segmentBoundsDisplay.textContent = "Start: - | End: -"; + } + } + } + + // --- Rendering logic --- + render() { + const width = this.canvas.offsetWidth || this._lastWidth; + const height = this.canvasHeight; + const totalFrames = this.getVisualDurationFrames(); + + if (!width || width <= 0) return; + + this.ctx.clearRect(0, 0, width, height); + + + + // Render Track Backgrounds + this.ctx.fillStyle = "#111"; // Image track bg + this.ctx.fillRect(0, RULER_HEIGHT, width, this.blockHeight); + this.ctx.fillStyle = "#111"; // Audio track bg + this.ctx.fillRect(0, RULER_HEIGHT + this.blockHeight, width, this.audioTrackHeight); + + + + // Determine which track the preview belongs to. + // _ghostTrack is set during HTML file drag-and-drop. + // During canvas mouse drags, _ghostTrack is null, so fall back to selectionType. + const previewIsAudio = this._ghostTrack === 'audio' || + (this._previewSegments && this._ghostTrack === null && this.selectionType === 'audio'); + + let renderSegments = (this._previewSegments && !previewIsAudio) + ? this._previewSegments : this.timeline.segments; + + let renderAudioSegments = (this._previewSegments && previewIsAudio) + ? this._previewSegments : this.timeline.audioSegments; + + + + const activeSegId = this.timeline.segments[this.selectedIndex]?.id; + const activeAudioSegId = this.timeline.audioSegments[this.selectedIndex]?.id; + + // Sort segments so that the selected one is drawn last (on top) + const isImageSelection = this.selectionType === "image"; + const sortedSegments = [...renderSegments].sort((a, b) => { + const aSel = isImageSelection && a.id === activeSegId; + const bSel = isImageSelection && b.id === activeSegId; + return aSel - bSel; + }); + + const isAudioSelection = this.selectionType === "audio"; + const sortedAudioSegments = [...renderAudioSegments].sort((a, b) => { + const aSel = isAudioSelection && a.id === activeAudioSegId; + const bSel = isAudioSelection && b.id === activeAudioSegId; + return aSel - bSel; + }); + + // --- Draw Image/Text Segments --- + for (let i = 0; i < sortedSegments.length; i++) { + const seg = sortedSegments[i]; + const startX = (seg.start / totalFrames) * width; + const pxWidth = (seg.length / totalFrames) * width; + const isSelected = (this.selectionType === "image" && seg.id === activeSegId); + + const originalSeg = this.timeline.segments.find(s => s.id === seg.id); + const imgObj = originalSeg ? originalSeg.imgObj : seg.imgObj; + + if ((this._isDragging && this.selectionType === "image" && seg.id === this._dragTargetId) || (this._ghostSegmentId && seg.id === this._ghostSegmentId)) { + this.ctx.globalAlpha = 0.65; + } else { + this.ctx.globalAlpha = 1.0; + } + + if (seg.type === "ghost") { + this.ctx.fillStyle = "#2a2a2a"; + this.ctx.fillRect(startX, RULER_HEIGHT, pxWidth, this.blockHeight); + + this.ctx.strokeStyle = "#777"; + this.ctx.lineWidth = 2; + this.ctx.setLineDash([5, 5]); + this.ctx.strokeRect(startX, RULER_HEIGHT + 1, pxWidth, this.blockHeight - 2); + this.ctx.setLineDash([]); + + this.ctx.fillStyle = "#aaa"; + this.ctx.textAlign = "center"; + this.ctx.textBaseline = "middle"; + this.ctx.font = "bold 12px sans-serif"; + this.ctx.fillText("Drop to Place", startX + pxWidth / 2, RULER_HEIGHT + this.blockHeight / 2); + } else { + this.ctx.fillStyle = seg.type === "text" ? "#000b12" : "#000"; + this.ctx.fillRect(startX, RULER_HEIGHT + 1, pxWidth, this.blockHeight - 2); + } + + if (imgObj && imgObj.complete && imgObj.naturalWidth > 0 && seg.type !== "ghost") { + const imgRatio = imgObj.naturalWidth / imgObj.naturalHeight; + const boxRatio = pxWidth / this.blockHeight; + let drawW, drawH, drawX, drawY; + if (imgRatio > boxRatio) { + drawW = pxWidth; drawH = pxWidth / imgRatio; + drawX = startX; drawY = RULER_HEIGHT + (this.blockHeight - drawH) / 2; + } else { + drawH = this.blockHeight; drawW = this.blockHeight * imgRatio; + drawY = RULER_HEIGHT; drawX = startX + (pxWidth - drawW) / 2; + } + + // Clip to segment bounds so tiled images don't bleed into adjacent segments + this.ctx.save(); + this.ctx.beginPath(); + this.ctx.rect(startX, RULER_HEIGHT + 1, pxWidth, this.blockHeight - 2); + this.ctx.clip(); + + if (imgRatio > boxRatio) { + // Fits width, vertical letterboxing (black bars top/bottom) — keep as is + this.ctx.drawImage(imgObj, drawX, drawY, drawW, drawH); + } else { + // Fits height, horizontal letterboxing (black bars left/right) — tile horizontally + this.ctx.drawImage(imgObj, drawX, drawY, drawW, drawH); + + // Tile left + let leftX = drawX - drawW; + while (leftX + drawW > startX) { + this.ctx.drawImage(imgObj, leftX, drawY, drawW, drawH); + leftX -= drawW; + } + + // Tile right + let rightX = drawX + drawW; + while (rightX < startX + pxWidth) { + this.ctx.drawImage(imgObj, rightX, drawY, drawW, drawH); + rightX += drawW; + } + } + this.ctx.restore(); + + // --- Prompt subtitle overlay --- + if (seg.prompt && seg.type !== "ghost" && pxWidth > 24) { + const overlayH = Math.round(this.blockHeight * 0.20); + const overlayY = RULER_HEIGHT + this.blockHeight - overlayH; + + this.ctx.save(); + this.ctx.beginPath(); + this.ctx.rect(startX, overlayY, pxWidth, overlayH); + this.ctx.clip(); + + // Translucent background + this.ctx.fillStyle = "rgba(0, 0, 0, 0.60)"; + this.ctx.fillRect(startX, overlayY, pxWidth, overlayH); + + // Text + const fontSize = Math.min(11, overlayH * 0.58); + this.ctx.font = `${fontSize}px sans-serif`; + this.ctx.fillStyle = "#e0e3ed"; + this.ctx.textAlign = "center"; + this.ctx.textBaseline = "middle"; + + // Measure and truncate to single line + const maxTextW = pxWidth - 10; + let label = seg.prompt; + if (this.ctx.measureText(label).width > maxTextW) { + while (label.length > 0 && this.ctx.measureText(label + "…").width > maxTextW) { + label = label.slice(0, -1); + } + label += "…"; + } + + this.ctx.fillText(label, startX + pxWidth / 2, overlayY + overlayH / 2); + this.ctx.restore(); + } + } else if (seg.type === "text") { + const pad = 8; + const boxW = pxWidth - pad * 2; + if (boxW > 12) { + this.ctx.save(); + this.ctx.beginPath(); + this.ctx.rect(startX + pad, RULER_HEIGHT + pad, boxW, this.blockHeight - pad * 2); + this.ctx.clip(); + this.ctx.fillStyle = "#e0e3ed"; + this.ctx.font = "11px sans-serif"; + this.ctx.textAlign = "center"; + this.ctx.textBaseline = "top"; + const label = seg.prompt || "(no prompt)"; + const words = label.split(" "); + const lineH = 15; + let line = ""; + let lines = []; + for (const word of words) { + const test = line ? line + " " + word : word; + if (this.ctx.measureText(test).width > boxW && line) { + lines.push(line); + line = word; + } else { + line = test; + } + } + if (line) lines.push(line); + + const maxLines = Math.max(1, Math.floor((this.blockHeight - pad * 2) / lineH)); + if (lines.length > maxLines) { + lines = lines.slice(0, maxLines); + lines[lines.length - 1] += "…"; + } + + const totalTextHeight = lines.length * lineH; + let ty = RULER_HEIGHT + (this.blockHeight - totalTextHeight) / 2 + 2; + + for (const l of lines) { + this.ctx.fillText(l, startX + pxWidth / 2, ty); + ty += lineH; + } + this.ctx.restore(); + } + } + + if (isSelected) { + this.ctx.strokeStyle = "#fff"; + this.ctx.lineWidth = 2; + this.ctx.strokeRect(startX, RULER_HEIGHT + 1, pxWidth, this.blockHeight - 2); + this.ctx.fillStyle = "#fff"; + this.ctx.beginPath(); + this.ctx.roundRect(startX, RULER_HEIGHT + this.blockHeight / 2 - 12, 4, 24, 2); + this.ctx.fill(); + this.ctx.beginPath(); + this.ctx.roundRect(startX + pxWidth - 4, RULER_HEIGHT + this.blockHeight / 2 - 12, 4, 24, 2); + this.ctx.fill(); + } else { + this.ctx.strokeStyle = "#000"; + this.ctx.lineWidth = 1.5; + this.ctx.strokeRect(startX, RULER_HEIGHT + 1, pxWidth, this.blockHeight - 2); + } + this.ctx.globalAlpha = 1.0; + } + + // --- Draw Audio Segments --- + for (let i = 0; i < sortedAudioSegments.length; i++) { + const seg = sortedAudioSegments[i]; + const startX = (seg.start / totalFrames) * width; + const pxWidth = (seg.length / totalFrames) * width; + const isSelected = (this.selectionType === "audio" && seg.id === activeAudioSegId); + const trackY = RULER_HEIGHT + this.blockHeight; + + if ((this._isDragging && this.selectionType === "audio" && seg.id === this._dragTargetId) || (this._ghostSegmentId && seg.id === this._ghostSegmentId)) { + this.ctx.globalAlpha = 0.65; + } else { + this.ctx.globalAlpha = 1.0; + } + + if (seg.type === "ghost") { + this.ctx.fillStyle = "#1a1a1a"; + this.ctx.fillRect(startX, trackY, pxWidth, this.audioTrackHeight); + this.ctx.strokeStyle = "#555"; + this.ctx.lineWidth = 2; + this.ctx.setLineDash([5, 5]); + this.ctx.strokeRect(startX, trackY, pxWidth, this.audioTrackHeight); + this.ctx.setLineDash([]); + this.ctx.fillStyle = "#888"; + this.ctx.textAlign = "center"; + this.ctx.textBaseline = "middle"; + this.ctx.font = "bold 12px sans-serif"; + this.ctx.fillText("Drop Audio", startX + pxWidth / 2, trackY + this.audioTrackHeight / 2); + } else { + this.drawAudioSegmentVisuals(this.ctx, seg, isSelected, trackY, this.audioTrackHeight, startX, pxWidth); + } + this.ctx.globalAlpha = 1.0; + } + + // --- Draw Ruler & Divider AFTER segments to prevent overlap --- + // Ruler Background + this.ctx.fillStyle = "#1e1e1e"; + this.ctx.fillRect(0, 0, width, RULER_HEIGHT); + + // Crisp Ruler Text + this.ctx.fillStyle = "#aaa"; + this.ctx.textAlign = "center"; + this.ctx.textBaseline = "middle"; + this.ctx.font = "10px sans-serif"; + + const frameRate = this.getFrameRate(); + const mode = this.displayModeWidget ? this.displayModeWidget.value : "seconds"; + + // Define logical steps for both modes + let steps; + if (mode === "seconds") { + steps = [0.1, 0.2, 0.5, 1, 2, 5, 10, 15, 30, 60, 120, 300, 600]; + } else { + steps = [1, 2, 5, 10, 24, 48, 120, 240, 480, 960, 1920]; + } + + const minSpacingPx = 60; + let majorStep = steps[steps.length - 1]; + for (let i = 0; i < steps.length; i++) { + const stepFrames = mode === "seconds" ? steps[i] * frameRate : steps[i]; + const spacingPx = (stepFrames / totalFrames) * width; + if (spacingPx >= minSpacingPx) { + majorStep = steps[i]; + break; + } + } + + const majorStepFrames = mode === "seconds" ? majorStep * frameRate : majorStep; + + let minorStep; + if (mode === "seconds") { + if (majorStep <= 0.2) minorStep = majorStep / 2; + else if (majorStep <= 1) minorStep = majorStep / 5; + else if (majorStep <= 5) minorStep = 1; + else if (majorStep <= 15) minorStep = 5; + else if (majorStep <= 30) minorStep = 10; + else if (majorStep <= 60) minorStep = 10; + else minorStep = majorStep / 5; + } else { + if (majorStep <= 5) minorStep = 1; + else if (majorStep <= 10) minorStep = 2; + else if (majorStep <= 24) minorStep = 6; + else if (majorStep <= 48) minorStep = 12; + else minorStep = majorStep / 5; + } + const minorStepFrames = mode === "seconds" ? minorStep * frameRate : minorStep; + + this.ctx.fillStyle = "#444"; + const totalMinorTicks = Math.floor(totalFrames / minorStepFrames); + for (let i = 0; i <= totalMinorTicks; i++) { + const frameVal = i * minorStepFrames; + if (Math.abs(frameVal % majorStepFrames) < 0.1) continue; + + const x = (frameVal / totalFrames) * width; + this.ctx.fillRect(Math.floor(x), RULER_HEIGHT - 3, 1, 3); + } + + this.ctx.fillStyle = "#aaa"; + const totalMajorTicks = Math.floor(totalFrames / majorStepFrames); + for (let i = 0; i <= totalMajorTicks; i++) { + const frameVal = i * majorStepFrames; + const x = (frameVal / totalFrames) * width; + + this.ctx.fillStyle = "#aaa"; + this.ctx.fillRect(Math.floor(x), RULER_HEIGHT - 6, 1, 6); + + if (frameVal > 0 && frameVal < totalFrames) { + this.ctx.textAlign = "center"; + this.ctx.fillText(this.formatTime(frameVal, true), x, RULER_HEIGHT / 2); + } + } + + this.ctx.textAlign = "left"; + const zeroLabel = mode === "seconds" ? "0" : this.formatTime(0, true); + this.ctx.fillText(zeroLabel, 4, RULER_HEIGHT / 2); + + // Divider + this.ctx.fillStyle = "#333"; + this.ctx.fillRect(0, RULER_HEIGHT + this.blockHeight, width, 1); + + // Draw gap "+" buttons + if (!this._isDragging) { + const BTN_R = 12; + const gapRegions = this.getGapRegions(); + for (let i = 0; i < gapRegions.length; i++) { + const gap = gapRegions[i]; + if (gap.widthPx < BTN_R * 2 + 8) continue; + const hov = this._hoveredGapIdx === i; + const BTN_W = 18; + const BTN_H = 18; + this.ctx.beginPath(); + this.ctx.roundRect(gap.centerX - BTN_W / 2, gap.centerY - BTN_H / 2, BTN_W, BTN_H, 4); + this.ctx.fillStyle = hov ? "rgba(255,255,255,0.15)" : "rgba(255,255,255,0.05)"; + this.ctx.fill(); + this.ctx.fillStyle = hov ? "#fff" : "#888"; + this.ctx.font = "14px sans-serif"; + this.ctx.textAlign = "center"; + this.ctx.textBaseline = "middle"; + this.ctx.fillText("+", gap.centerX, gap.centerY + 1); + } + } + + // --- Out-of-duration shadow overlay --- + // Draw a translucent black mask over the region beyond the actual output duration + // so the user can clearly see which content will be included in the render. + const outputFrames = this.getDurationFrames(); + if (outputFrames < totalFrames) { + const cutoffX = (outputFrames / totalFrames) * width; + // Semi-transparent black overlay on both tracks + this.ctx.fillStyle = "rgba(0, 0, 0, 0.45)"; + this.ctx.fillRect(cutoffX, RULER_HEIGHT, width - cutoffX, this.blockHeight + this.audioTrackHeight); + // Subtle tinted ruler overlay + this.ctx.fillStyle = "rgba(0, 0, 0, 0.25)"; + this.ctx.fillRect(cutoffX, 0, width - cutoffX, RULER_HEIGHT); + /* + // Dashed boundary line at the output duration cutoff + this.ctx.save(); + this.ctx.strokeStyle = "rgba(255, 80, 80, 0.7)"; + this.ctx.lineWidth = 1.5; + this.ctx.setLineDash([5, 4]); + this.ctx.beginPath(); + this.ctx.moveTo(cutoffX, 0); + this.ctx.lineTo(cutoffX, CANVAS_HEIGHT); + this.ctx.stroke(); + this.ctx.setLineDash([]); + this.ctx.restore(); + */ + } + + // --- Draw Playhead --- + const playheadX = (this.currentFrame / totalFrames) * width; + + // Playhead Line + this.ctx.beginPath(); + this.ctx.moveTo(playheadX, 14); + this.ctx.lineTo(playheadX, this.canvasHeight); + this.ctx.strokeStyle = "#ff4444"; + this.ctx.lineWidth = 1.5; + this.ctx.stroke(); + + // Playhead Handle (Polygon above numbers) + this.ctx.fillStyle = "#ff4444"; + this.ctx.beginPath(); + this.ctx.moveTo(playheadX - 6, 0); + this.ctx.lineTo(playheadX + 6, 0); + this.ctx.lineTo(playheadX + 6, 8); + this.ctx.lineTo(playheadX, 14); + this.ctx.lineTo(playheadX - 6, 8); + this.ctx.fill(); + + // Draw vertical grab bar on the right edge of viewport for resizing width + const grabBarW = 4; + const grabBarH = 50; + const grabBarX = this.viewport.scrollLeft + this.viewport.clientWidth - grabBarW - 3; + const grabBarY = RULER_HEIGHT + (this.blockHeight + this.audioTrackHeight - grabBarH) / 2; + + this.ctx.fillStyle = "rgba(40, 40, 40, 0.6)"; + this.ctx.beginPath(); + this.ctx.roundRect(grabBarX, grabBarY, grabBarW, grabBarH, 2); + this.ctx.fill(); + + // Draw horizontal grab bar at the bottom of viewport for resizing height + const hBarW = 50; + const hBarH = 4; + const hBarX = this.viewport.scrollLeft + (this.viewport.clientWidth - hBarW) / 2; + const hBarY = this.canvasHeight - hBarH - 3; // 3px from the bottom edge + + this.ctx.fillStyle = "rgba(20, 20, 20, 0.8)"; + this.ctx.beginPath(); + this.ctx.roundRect(hBarX, hBarY, hBarW, hBarH, 2); + this.ctx.fill(); + + this.updatePlayerUI(); + } + + drawAudioSegmentVisuals(ctx, seg, isSelected, yOffset, trackHeight, startX, pxWidth) { + ctx.fillStyle = isSelected ? "#2a4a3a" : "#1a2a1a"; + ctx.fillRect(startX, yOffset + 2, pxWidth, trackHeight - 3); + + if (seg.waveformPeaks && pxWidth > 0) { + ctx.fillStyle = isSelected ? "rgba(100, 255, 100, 0.6)" : "rgba(100, 255, 100, 0.3)"; + const startRatio = seg.trimStart / seg.audioDurationFrames; + const endRatio = (seg.trimStart + seg.length) / seg.audioDurationFrames; + const peakCount = seg.waveformPeaks.length; + const centerY = yOffset + trackHeight / 2; + + ctx.beginPath(); + for (let i = 0; i < pxWidth; i++) { + const pixelRatio = i / pxWidth; + const globalRatio = startRatio + pixelRatio * (endRatio - startRatio); + const peakIdx = Math.floor(globalRatio * peakCount); + + if (peakIdx >= 0 && peakIdx < peakCount) { + const val = seg.waveformPeaks[peakIdx]; + const amp = (val * (trackHeight - 12) / 2) * 0.9; + ctx.fillRect(startX + i, centerY - amp, 1, amp * 2); + } + } + } + + ctx.strokeStyle = isSelected ? "#4fff8f" : "#000"; + ctx.lineWidth = 1.5; + ctx.strokeRect(startX, yOffset + 2, pxWidth, trackHeight - 3); + + if (isSelected) { + ctx.fillStyle = "#4fff8f"; + ctx.beginPath(); + ctx.roundRect(startX, yOffset + trackHeight / 2 - 12, 4, 24, 2); + ctx.fill(); + ctx.beginPath(); + ctx.roundRect(startX + pxWidth - 4, yOffset + trackHeight / 2 - 12, 4, 24, 2); + ctx.fill(); + } + + ctx.fillStyle = "#ccc"; + ctx.font = "11px sans-serif"; + ctx.textBaseline = "top"; + ctx.textAlign = "left"; + ctx.save(); + ctx.beginPath(); + ctx.rect(startX, yOffset + 2, pxWidth, trackHeight - 3); + ctx.clip(); + + let text = seg.fileName || "Audio Track"; + const maxWidth = pxWidth - 12; + if (ctx.measureText(text).width > maxWidth && maxWidth > 0) { + while (text.length > 0 && ctx.measureText(text + "...").width > maxWidth) { + text = text.slice(0, -1); + } + text = text + "..."; + } + + ctx.fillText(text, startX + 6, yOffset + 8); + ctx.restore(); + } + + + // --- Interaction Logic --- + getHitTest(mouseX, mouseY) { + const width = this.canvas.offsetWidth; + const totalFrames = this.getVisualDurationFrames(); + + // Check Playhead Handle first + const playheadX = (this.currentFrame / totalFrames) * width; + if (mouseY <= 24 && Math.abs(mouseX - playheadX) <= 12) { + return { type: "playhead" }; + } + + if (mouseY <= RULER_HEIGHT) { + return { type: "ruler" }; + } + + if (mouseY < RULER_HEIGHT || mouseY > this.canvasHeight) return null; + + const isAudioTrack = mouseY > RULER_HEIGHT + this.blockHeight; + const trackSegments = isAudioTrack ? this.timeline.audioSegments : this.timeline.segments; + const trackType = isAudioTrack ? "audio" : "image"; + + if (trackSegments.length === 0) return null; + + // The variables width and totalFrames are already declared above. + + let sortedSegments = [...trackSegments] + .map((s, i) => ({ ...s, originalIndex: i })) + .sort((a, b) => a.start - b.start); + + const HANDLE_CORE = 4; + + for (let i = 0; i < sortedSegments.length; i++) { + const seg = sortedSegments[i]; + const startX = (seg.start / totalFrames) * width; + const pxWidth = (seg.length / totalFrames) * width; + const endX = startX + pxWidth; + + const prevSeg = sortedSegments[i - 1]; + const nextSeg = sortedSegments[i + 1]; + + const isLeftJoint = prevSeg && prevSeg.start + prevSeg.length === seg.start; + if (!isLeftJoint) { + if (Math.abs(mouseX - startX) <= HANDLE_HIT_PX) { + return { type: "edge", index: seg.originalIndex, dir: "left", track: trackType }; + } + } + + const isRightJoint = nextSeg && nextSeg.start === seg.start + seg.length; + if (isRightJoint) { + const dx = mouseX - endX; + if (Math.abs(dx) <= HANDLE_HIT_PX) { + if (dx < -HANDLE_CORE) { + return { type: "edge", index: seg.originalIndex, dir: "right", track: trackType }; + } else if (dx > HANDLE_CORE) { + return { type: "edge", index: nextSeg.originalIndex, dir: "left", track: trackType }; + } else { + return { type: "joint", leftIndex: seg.originalIndex, rightIndex: nextSeg.originalIndex, track: trackType }; + } + } + } else { + if (Math.abs(mouseX - endX) <= HANDLE_HIT_PX) { + return { type: "edge", index: seg.originalIndex, dir: "right", track: trackType }; + } + } + } + + for (let i = 0; i < sortedSegments.length; i++) { + const seg = sortedSegments[i]; + const startX = (seg.start / totalFrames) * width; + const pxWidth = (seg.length / totalFrames) * width; + const endX = startX + pxWidth; + + if (mouseX >= startX && mouseX < endX) { + return { type: "center", index: seg.originalIndex, track: trackType }; + } + } + + return null; + } + + onMouseDown(e) { + if (e.button !== 0) return; + const { x, y } = this.getMousePos(e); + + const isOverDivider = Math.abs(y - (RULER_HEIGHT + this.blockHeight)) <= 4; + if (isOverDivider) { + this._isDragging = true; + this._dragType = "divider"; + this._startBlockHeight = this.blockHeight; + this._startAudioTrackHeight = this.audioTrackHeight; + this._startY = y; + return; + } + + const isAtBottom = Math.abs(y - this.canvasHeight) <= 15; + if (isAtBottom) { + this._isDragging = true; + this._dragType = "height_resize"; + this._startBlockHeight = this.blockHeight; + this._startY = y; + document.body.style.userSelect = "none"; + return; + } + + const viewRect = this.viewport.getBoundingClientRect(); + const isAtRightEdge = Math.abs(e.clientX - viewRect.right) <= 20; + if (isAtRightEdge) { + this._isDragging = true; + this._dragType = "width_resize"; + this._startNodeWidth = this.node.size[0]; + this._startX = e.clientX; + document.body.style.userSelect = "none"; + return; + } + + if (y >= RULER_HEIGHT && y <= this.canvasHeight) { + const BTN_R = 12; + const gapRegions = this.getGapRegions(); + for (let i = 0; i < gapRegions.length; i++) { + const gap = gapRegions[i]; + if (gap.widthPx < BTN_R * 2 + 8) continue; + const dx = x - gap.centerX, dy2 = y - gap.centerY; + if (dx * dx + dy2 * dy2 <= BTN_R * BTN_R) { + if (gap.track === "audio") { + // Direct to audio upload + this.promptAddAudioInGap(gap.frameStart, gap.frameEnd); + } else { + this.showGapMenu(e.clientX, e.clientY, gap); + } + return; + } + } + } + + const hit = this.getHitTest(x, y); + if (!hit) { + // Only deselect if they clicked the same track but hit empty space + const clickedTrack = y > RULER_HEIGHT + this.blockHeight ? "audio" : "image"; + if (this.selectionType === clickedTrack) { + this.selectedIndex = -1; + this.updateUIFromSelection(); + } + this.render(); + return; + } + + if (hit.type === "playhead" || hit.type === "ruler") { + this._isDragging = true; + this._dragType = "playhead"; + const logicalWidth = this.canvas.offsetWidth; + const totalFrames = this.getVisualDurationFrames(); + let mouseFrameX = x * (totalFrames / logicalWidth); + this.currentFrame = clamp(mouseFrameX, 0, totalFrames); + this.render(); + if (this.isPlaying) { + this.playAudio(); + } + return; + } + + this.selectionType = hit.track; + const targetArray = hit.track === "audio" ? this.timeline.audioSegments : this.timeline.segments; + + if (hit.type === "joint") { + this.selectedIndex = hit.leftIndex; + this.updateUIFromSelection(); + this._dragType = "joint"; + this._dragTargetId = targetArray[hit.leftIndex].id; + this._dragTargetIdRight = targetArray[hit.rightIndex].id; + } else if (hit.type === "center") { + this.selectedIndex = hit.index; + this.updateUIFromSelection(); + this._dragType = "center"; + } else { + if (this.selectedIndex !== hit.index) { + this.selectedIndex = hit.index; + this.updateUIFromSelection(); + } + this._dragType = hit.dir; + } + + this._isDragging = true; + this._previewSegments = null; + this._dragStartX = x; + this._dragInitialTimeline = JSON.parse(JSON.stringify(targetArray)); + + if (hit.type !== "joint") { + this._dragTargetId = targetArray[hit.index].id; + } + this.render(); + } + + onMouseMove(e) { + const { x: mouseX, y: mouseY } = this.getMousePos(e); + + if (!this._isDragging) { + let newHoveredGapIdx = -1; + const BTN_R = 12; + const gapRegions = this.getGapRegions(); + for (let i = 0; i < gapRegions.length; i++) { + const gap = gapRegions[i]; + if (gap.widthPx < BTN_R * 2 + 8) continue; + const dx = mouseX - gap.centerX, dy2 = mouseY - gap.centerY; + if (dx * dx + dy2 * dy2 <= BTN_R * BTN_R) { newHoveredGapIdx = i; break; } + } + if (this._hoveredGapIdx !== newHoveredGapIdx) { + this._hoveredGapIdx = newHoveredGapIdx; + this.render(); + } + + const isOverDivider = Math.abs(mouseY - (RULER_HEIGHT + this.blockHeight)) <= 4; + const isAtBottom = Math.abs(mouseY - this.canvasHeight) <= 15; + const viewRect = this.viewport.getBoundingClientRect(); + const isAtRightEdge = Math.abs(e.clientX - viewRect.right) <= 20; + const hit = this.getHitTest(mouseX, mouseY); + if (isOverDivider || isAtBottom) { + this.canvas.style.cursor = "ns-resize"; + } else if (isAtRightEdge) { + this.canvas.style.cursor = "ew-resize"; + } else if (newHoveredGapIdx >= 0) { + this.canvas.style.cursor = "pointer"; + } else if (hit?.type === "edge") { + this.canvas.style.cursor = "ew-resize"; + } else if (hit?.type === "joint") { + this.canvas.style.cursor = "col-resize"; + } else if (hit?.type === "center") { + this.canvas.style.cursor = "grab"; + } else if (hit?.type === "playhead") { + this.canvas.style.cursor = "ew-resize"; + } else { + this.canvas.style.cursor = "default"; + } + return; + } + + if (this._dragType === "divider") { + this.canvas.style.cursor = "ns-resize"; + const deltaY = mouseY - this._startY; + + const minBlockH = 50; + const minAudioH = 50; + + let newBlockHeight = this._startBlockHeight + deltaY; + let newAudioTrackHeight = this._startAudioTrackHeight - deltaY; + + if (newBlockHeight < minBlockH) { + newBlockHeight = minBlockH; + newAudioTrackHeight = this._startBlockHeight + this._startAudioTrackHeight - minBlockH; + } + if (newAudioTrackHeight < minAudioH) { + newAudioTrackHeight = minAudioH; + newBlockHeight = this._startBlockHeight + this._startAudioTrackHeight - minAudioH; + } + + this.blockHeight = newBlockHeight; + this.audioTrackHeight = newAudioTrackHeight; + + this.render(); + return; + } + + if (this._dragType === "height_resize") { + this.canvas.style.cursor = "ns-resize"; + const deltaY = mouseY - this._startY; + + this.blockHeight = Math.max(100, this._startBlockHeight + deltaY); + this.canvasHeight = this.rulerHeight + this.blockHeight + this.audioTrackHeight; + + this.canvas.style.height = `${this.canvasHeight}px`; + + this.resizeCanvas(this.canvas.offsetWidth); + this.render(); + + if (this.node && this.node.computeSize) { + const sz = this.node.computeSize(); + this.node.size[1] = sz[1]; + if (window.app && window.app.graph) { + window.app.graph.setDirtyCanvas(true, true); + } + } + return; + } + + if (this._dragType === "width_resize") { + this.canvas.style.cursor = "ew-resize"; + const deltaX = e.clientX - this._startX; + + this.node.size[0] = Math.max(300, this._startNodeWidth + deltaX); + + if (window.app && window.app.graph) { + window.app.graph.setDirtyCanvas(true, true); + } + return; + } + + if (this._dragType === "playhead") { + this.canvas.style.cursor = "ew-resize"; + const logicalWidth = this.canvas.offsetWidth; + const totalFrames = this.getVisualDurationFrames(); + let mouseFrameX = mouseX * (totalFrames / logicalWidth); + this.currentFrame = clamp(mouseFrameX, 0, totalFrames); + this.render(); + if (this.isPlaying) { + this.playAudio(); // Scrub (restart from new position) + } + return; + } + + this.canvas.style.cursor = this._dragType === "center" ? "grabbing" : + this._dragType === "joint" ? "col-resize" : "ew-resize"; + + const logicalWidth = this.canvas.offsetWidth; + const totalFrames = this.getVisualDurationFrames(); + const durationFrames = totalFrames; + const dragDelta = Math.round((mouseX - this._dragStartX) * (totalFrames / logicalWidth)); + + let t = JSON.parse(JSON.stringify(this._dragInitialTimeline)); + + // --- Rolling Edit (Slide Edit) --- + if (this._dragType === "joint") { + let leftIdx = t.findIndex(s => s.id === this._dragTargetId); + let rightIdx = t.findIndex(s => s.id === this._dragTargetIdRight); + + if (leftIdx >= 0 && rightIdx >= 0) { + let origLeft = this._dragInitialTimeline.find(s => s.id === this._dragTargetId); + let origRight = this._dragInitialTimeline.find(s => s.id === this._dragTargetIdRight); + + let maxDeltaRight = origRight.length - MIN_SEGMENT_LENGTH; + let maxDeltaLeft = origLeft.length - MIN_SEGMENT_LENGTH; + + if (this.selectionType === "audio") { + // Drag LEFT: right clip extends left by un-trimming its head. + // Can only un-trim as much as the right clip has been trimmed (trimStart >= 0). + maxDeltaLeft = Math.min(maxDeltaLeft, origRight.trimStart || 0); + // Drag RIGHT: left clip extends right by consuming its remaining tail audio. + // Can only extend as far as the left clip's unplayed tail allows. + let availLeftTail = (origLeft.audioDurationFrames || origLeft.length) - ((origLeft.trimStart || 0) + origLeft.length); + maxDeltaRight = Math.min(maxDeltaRight, availLeftTail); + } + + let safeDelta = clamp(dragDelta, -maxDeltaLeft, maxDeltaRight); + + t[leftIdx].length = origLeft.length + safeDelta; + t[rightIdx].start = origRight.start + safeDelta; + t[rightIdx].length = origRight.length - safeDelta; + + if (this.selectionType === "audio") { + t[rightIdx].trimStart = origRight.trimStart + safeDelta; + } + } + } + // --- Edge & Center Drags --- + else { + const targetIdx = t.findIndex((s) => s.id === this._dragTargetId); + if (targetIdx < 0) return; + + if (this._dragType === "right") { + let newLen = t[targetIdx].length + dragDelta; + let maxPossibleLength = totalFrames - t[targetIdx].start; + let nextSeg = t.find(s => s.start >= t[targetIdx].start + t[targetIdx].length && s.id !== t[targetIdx].id); + if (nextSeg) { + maxPossibleLength = nextSeg.start - t[targetIdx].start; + } + + if (this.selectionType === "audio") { + maxPossibleLength = Math.min(maxPossibleLength, (t[targetIdx].audioDurationFrames || t[targetIdx].length) - (t[targetIdx].trimStart || 0)); + } + + t[targetIdx].length = Math.max(MIN_SEGMENT_LENGTH, Math.min(newLen, maxPossibleLength)); + + } else if (this._dragType === "left") { + let newStart = t[targetIdx].start + dragDelta; + let minPossibleStart = 0; + let prevSeg = t.slice().reverse().find(s => s.start + s.length <= t[targetIdx].start && s.id !== t[targetIdx].id); + if (prevSeg) { + minPossibleStart = prevSeg.start + prevSeg.length; + } + + if (this.selectionType === "audio") { + minPossibleStart = Math.max(minPossibleStart, t[targetIdx].start - (t[targetIdx].trimStart || 0)); + } + + let maxStart = t[targetIdx].start + t[targetIdx].length - MIN_SEGMENT_LENGTH; + newStart = Math.max(minPossibleStart, Math.min(newStart, maxStart)); + + let diff = newStart - t[targetIdx].start; + t[targetIdx].start = newStart; + t[targetIdx].length -= diff; + if (this.selectionType === "audio") { + t[targetIdx].trimStart += diff; + } + + } else if (this._dragType === "center") { + let initT = this._dragInitialTimeline; + let dIdx = initT.findIndex(s => s.id === this._dragTargetId); + if (dIdx < 0) return; + let D = JSON.parse(JSON.stringify(initT[dIdx])); + + let D_mouse_start = D.start + dragDelta; + let mouseFrameX = mouseX * (totalFrames / logicalWidth); + + t = this._applyCenterDragPhysics(initT, D.id, D_mouse_start, mouseFrameX, durationFrames, totalFrames, logicalWidth); + } + } + + this._previewSegments = t; + this.updateUIFromSelection(); // Live update of trim values + this.render(); + } + + _applyCenterDragPhysics(initT, D_id, D_mouse_start, mouseFrameX, durationFrames, totalFrames, logicalWidth) { + let t_copy = JSON.parse(JSON.stringify(initT)); + let dIdx = t_copy.findIndex(s => s.id === D_id); + if (dIdx < 0) return t_copy; + + let D = t_copy[dIdx]; + let D_clamped_start = clamp(D_mouse_start, 0, durationFrames - D.length); + + let baseSegments = t_copy.filter(s => s.id !== D.id); + + let insertIdx = baseSegments.length; + for (let i = 0; i < baseSegments.length; i++) { + let centerBase = baseSegments[i].start + baseSegments[i].length / 2; + if (mouseFrameX < centerBase) { + insertIdx = i; + break; + } + } + + let leftBound = insertIdx > 0 ? baseSegments[insertIdx - 1].start + baseSegments[insertIdx - 1].length : 0; + let rightBound = insertIdx < baseSegments.length ? baseSegments[insertIdx].start : durationFrames; + + if (rightBound - leftBound >= D.length) { + D_clamped_start = clamp(D_clamped_start, leftBound, rightBound - D.length); + } else { + let gapCenter = (leftBound + rightBound) / 2; + D_clamped_start = gapCenter - D.length / 2; + } + + let t_test = []; + for (let i = 0; i < insertIdx; i++) { + t_test.push({ ...baseSegments[i], original_start: baseSegments[i].start }); + } + t_test.push({ ...D, start: D_clamped_start, original_start: D_clamped_start }); + let D_index = insertIdx; + + for (let i = insertIdx; i < baseSegments.length; i++) { + t_test.push({ ...baseSegments[i], original_start: baseSegments[i].start }); + } + + for (let i = D_index + 1; i < t_test.length; i++) { + let prev = t_test[i - 1]; + t_test[i].start = Math.max(t_test[i].original_start, prev.start + prev.length); + } + + for (let i = D_index - 1; i >= 0; i--) { + let next = t_test[i + 1]; + t_test[i].start = Math.min(t_test[i].original_start, next.start - t_test[i].length); + } + + let rightCursor = durationFrames; + for (let i = t_test.length - 1; i >= 0; i--) { + if (t_test[i].start + t_test[i].length > rightCursor) { + t_test[i].start = rightCursor - t_test[i].length; + } + rightCursor = t_test[i].start; + } + let leftCursor = 0; + for (let i = 0; i < t_test.length; i++) { + if (t_test[i].start < leftCursor) { + t_test[i].start = leftCursor; + } + leftCursor = t_test[i].start + t_test[i].length; + } + + let result = t_test.map(s => { + let clean = { ...s }; + delete clean.original_start; + return clean; + }); + + let draggedPreview = result.find(s => s.id === D.id); + if (draggedPreview) { + draggedPreview.resolvedStart = draggedPreview.start; + } + + return result; + } + + onMouseUp(e) { + document.body.style.userSelect = ""; + if (this._isDragging) { + if (this._previewSegments) { + const targetArray = this.selectionType === "audio" ? this.timeline.audioSegments : this.timeline.segments; + + const mappedArray = this._previewSegments.map(ps => { + const orig = targetArray.find(s => s.id === ps.id); + let finalStart = ps.resolvedStart !== undefined ? ps.resolvedStart : ps.start; + let newPs = { ...ps, start: finalStart }; + if (orig && orig.imgObj) newPs.imgObj = orig.imgObj; + delete newPs.resolvedStart; + return newPs; + }); + + if (this.selectionType === "audio") { + this.timeline.audioSegments = mappedArray; + if (this._dragTargetId) this.selectedIndex = this.timeline.audioSegments.findIndex(s => s.id === this._dragTargetId); + } else { + this.timeline.segments = mappedArray; + if (this._dragTargetId) this.selectedIndex = this.timeline.segments.findIndex(s => s.id === this._dragTargetId); + } + } + + this._isDragging = false; + this._previewSegments = null; + this._ghostTrack = null; + this.canvas.style.cursor = "default"; + this.commitChanges(); + } + } + + // --- Backend Data Sync --- + commitChanges(skipRender = false) { + let sortedSegments = [...this.timeline.segments].sort((a, b) => a.start - b.start); + let contiguousLengths = []; + let contiguousPrompts = []; + let currentCursor = 0; + const durationFrames = this.getDurationFrames(); + + // Build segment lengths clipped at the duration cutoff. + // - Gaps before the first segment, or between segments, are absorbed into the adjacent + // segment's length (same as before), but are also clipped at durationFrames. + // - Segments that start at or past the cutoff are excluded entirely. + // - Segments that cross the cutoff are trimmed so their end = durationFrames exactly. + let pendingGap = 0; + for (let seg of sortedSegments) { + // Skip segments entirely outside the duration. + if (seg.start >= durationFrames) break; + + if (seg.start > currentCursor) { + // Gap between the cursor and this segment — clip it at the cutoff too. + const gapLength = Math.min(seg.start, durationFrames) - currentCursor; + if (contiguousLengths.length > 0) { + contiguousLengths[contiguousLengths.length - 1] += gapLength; + } else { + pendingGap += gapLength; + } + } + + // Clip segment end at the duration cutoff. + const clippedEnd = Math.min(seg.start + seg.length, durationFrames); + const clippedLength = clippedEnd - seg.start; + + contiguousLengths.push(clippedLength + pendingGap); + contiguousPrompts.push(seg.prompt || ""); + pendingGap = 0; + currentCursor = seg.start + seg.length; // advance by the real (unclipped) end for gap detection + } + + // If segments don't fill to the end of the duration, pad the last segment to reach it. + const clampedCursor = Math.min(currentCursor, durationFrames); + if (contiguousLengths.length > 0 && clampedCursor < durationFrames) { + contiguousLengths[contiguousLengths.length - 1] += durationFrames - clampedCursor; + } + + const toSave = { + segments: sortedSegments.map(s => { + const { imgObj, ...rest } = s; + return rest; + }), + audioSegments: (this.timeline.audioSegments || []).map(s => ({ ...s })) + }; + + const jsonStr = JSON.stringify(toSave); + if (this.timelineDataWidget) this.timelineDataWidget.value = jsonStr; + + if (this.localPromptsWidget) { + this.localPromptsWidget.value = contiguousPrompts.join(" | "); + } + if (this.segmentLengthsWidget) { + this.segmentLengthsWidget.value = contiguousLengths.join(","); + } + + if (this.guideStrengthWidget) { + const imgStrengths = sortedSegments + .filter(s => s.type !== "text") + .map(s => (s.guideStrength !== undefined ? s.guideStrength : 1.0).toFixed(2)); + this.guideStrengthWidget.value = imgStrengths.join(","); + } + + // Keep zoom slider max in sync with the current timeline duration. + this.updateZoomSliderMax(); + + setTimeout(() => { + if (this.node && this.node.computeSize) { + const sz = this.node.computeSize(); + this.node.size[1] = sz[1]; + if (app.graph) app.graph.setDirtyCanvas(true, true); + } + }, 0); + + if (!skipRender) this.render(); + } + + // --- Gap Region Calculation --- + getGapRegions() { + const totalFrames = this.getVisualDurationFrames(); + const outputFrames = this.getDurationFrames(); + const width = this.canvas.offsetWidth || this._lastWidth || 0; + const gaps = []; + if (!width) return gaps; + + // Image gaps + let cursor = 0; + const sortedImg = [...this.timeline.segments].sort((a, b) => a.start - b.start); + for (const seg of sortedImg) { + if (seg.start > cursor) { + const x0 = (cursor / totalFrames) * width; + const x1 = (seg.start / totalFrames) * width; + gaps.push({ track: 'image', frameStart: cursor, frameEnd: seg.start, centerX: (x0 + x1) / 2, centerY: RULER_HEIGHT + this.blockHeight / 2, widthPx: x1 - x0 }); + } + cursor = seg.start + seg.length; + } + if (cursor < outputFrames) { + const x0 = (cursor / totalFrames) * width; + const x1 = (outputFrames / totalFrames) * width; + gaps.push({ track: 'image', frameStart: cursor, frameEnd: outputFrames, centerX: (x0 + x1) / 2, centerY: RULER_HEIGHT + this.blockHeight / 2, widthPx: x1 - x0 }); + } + + // Audio gaps + cursor = 0; + const sortedAud = [...this.timeline.audioSegments].sort((a, b) => a.start - b.start); + for (const seg of sortedAud) { + if (seg.start > cursor) { + const x0 = (cursor / totalFrames) * width; + const x1 = (seg.start / totalFrames) * width; + gaps.push({ track: 'audio', frameStart: cursor, frameEnd: seg.start, centerX: (x0 + x1) / 2, centerY: RULER_HEIGHT + this.blockHeight + this.audioTrackHeight / 2, widthPx: x1 - x0 }); + } + cursor = seg.start + seg.length; + } + if (cursor < outputFrames) { + const x0 = (cursor / totalFrames) * width; + const x1 = (outputFrames / totalFrames) * width; + gaps.push({ track: 'audio', frameStart: cursor, frameEnd: outputFrames, centerX: (x0 + x1) / 2, centerY: RULER_HEIGHT + this.blockHeight + this.audioTrackHeight / 2, widthPx: x1 - x0 }); + } + + return gaps; + } + + promptAddAudioInGap(frameStart, frameEnd) { + const fi = document.createElement("input"); + fi.type = "file"; + fi.accept = "audio/*"; + fi.addEventListener("change", (ev) => { + if (ev.target.files?.[0]) this.handleAudioUpload([ev.target.files[0]], frameStart); + }); + fi.click(); + } + + // --- Context Menu --- + onContextMenu(e) { + e.preventDefault(); + const { x: mouseX, y: mouseY } = this.getMousePos(e); + + const trackHeight = this.blockHeight; + const isAudioTrack = mouseY >= RULER_HEIGHT + trackHeight && mouseY <= RULER_HEIGHT + trackHeight + this.audioTrackHeight; + const isImageTrack = mouseY >= RULER_HEIGHT && mouseY <= RULER_HEIGHT + trackHeight; + + const logicalWidth = this.canvas.offsetWidth || 1; + const totalFrames = this.getVisualDurationFrames(); + const cursor = mouseX * (totalFrames / logicalWidth); + + let clickedSeg = null; + let trackType = ""; + + if (isAudioTrack) { + clickedSeg = this.timeline.audioSegments.find(s => cursor >= s.start && cursor <= s.start + s.length); + trackType = "audio"; + } else if (isImageTrack) { + clickedSeg = this.timeline.segments.find(s => cursor >= s.start && cursor <= s.start + s.length); + trackType = clickedSeg ? clickedSeg.type : ""; + } + + if (clickedSeg) { + this.showContextMenu(e.clientX, e.clientY, clickedSeg, trackType); + } else if (isAudioTrack || isImageTrack) { + const gapRegions = this.getGapRegions(); + const currentTrack = isAudioTrack ? "audio" : "image"; + let gap = gapRegions.find(g => cursor >= g.frameStart && cursor <= g.frameEnd && g.track === currentTrack); + + if (!gap) { + const startFrame = Math.round(cursor); + gap = { + track: currentTrack, + frameStart: startFrame, + frameEnd: startFrame + Math.max(1, this.getFrameRate()) + }; + } + gap.clickedFrame = cursor; + + this.showGapContextMenu(e.clientX, e.clientY, gap); + } + } + + showContextMenu(clientX, clientY, seg, trackType) { + this.dismissContextMenu(); + const menu = document.createElement("div"); + menu.className = "pr-gap-menu"; + menu.style.left = `${clientX + 6}px`; + menu.style.top = `${clientY - 10}px`; + + const isImage = trackType !== "audio" && trackType !== "text" && seg.imageB64; + + if (isImage) { + const copyBtn = document.createElement("button"); + copyBtn.className = "pr-gap-menu-btn"; + copyBtn.innerHTML = `Copy Image`; + copyBtn.onclick = async () => { + try { + const res = await fetch(seg.imageB64); + const blob = await res.blob(); + await navigator.clipboard.write([new ClipboardItem({ [blob.type]: blob })]); + } catch (err) { + console.error("Failed to copy image", err); + } + this.dismissContextMenu(); + }; + menu.appendChild(copyBtn); + + const saveBtn = document.createElement("button"); + saveBtn.className = "pr-gap-menu-btn"; + saveBtn.innerHTML = `Save Image`; + saveBtn.onclick = () => { + const a = document.createElement("a"); + a.href = seg.imageB64; + a.download = "timeline_image.jpg"; + a.click(); + this.dismissContextMenu(); + }; + menu.appendChild(saveBtn); + + const openBtn = document.createElement("button"); + openBtn.className = "pr-gap-menu-btn"; + openBtn.innerHTML = `Open Image in New Tab`; + openBtn.onclick = () => { + const win = window.open(); + if (win) { + win.document.write(``); + win.document.close(); + } + this.dismissContextMenu(); + }; + menu.appendChild(openBtn); + } + + if (trackType !== "audio") { + const copyPromptBtn = document.createElement("button"); + copyPromptBtn.className = "pr-gap-menu-btn"; + copyPromptBtn.innerHTML = `Copy Prompt`; + copyPromptBtn.onclick = async () => { + try { + await navigator.clipboard.writeText(seg.prompt || ""); + } catch (err) { + console.error("Failed to copy prompt", err); + } + this.dismissContextMenu(); + }; + menu.appendChild(copyPromptBtn); + } + + const copySegBtn = document.createElement("button"); + copySegBtn.className = "pr-gap-menu-btn"; + copySegBtn.innerHTML = `Copy Segment`; + copySegBtn.onclick = () => { + this._copiedSegment = { ...seg, id: Date.now().toString() + Math.random().toString(36).substr(2, 5) }; + this._copiedSegmentTrack = trackType === "audio" ? "audio" : "image"; + this.dismissContextMenu(); + }; + menu.appendChild(copySegBtn); + + const currentTrack = trackType === "audio" ? "audio" : "image"; + if (this._copiedSegment && this._copiedSegmentTrack === currentTrack) { + const pasteReplaceBtn = document.createElement("button"); + pasteReplaceBtn.className = "pr-gap-menu-btn"; + pasteReplaceBtn.innerHTML = `Paste & Replace`; + pasteReplaceBtn.onclick = () => { + const newSeg = { + ...this._copiedSegment, + id: Date.now().toString() + Math.random().toString(36).substr(2, 5), + start: seg.start, + length: this._copiedSegment.length + }; + const targetArray = currentTrack === "audio" ? this.timeline.audioSegments : this.timeline.segments; + const idx = targetArray.findIndex(s => s.id === seg.id); + if (idx >= 0) targetArray[idx] = newSeg; + this.commitChanges(); + this.dismissContextMenu(); + }; + menu.appendChild(pasteReplaceBtn); + } + + const delBtn = document.createElement("button"); + delBtn.className = "pr-gap-menu-btn"; + delBtn.innerHTML = `Delete`; + delBtn.style.color = "#ff4444"; + delBtn.onclick = () => { + this.selectionType = trackType === "audio" ? "audio" : "image"; + const list = trackType === "audio" ? this.timeline.audioSegments : this.timeline.segments; + this.selectedIndex = list.findIndex(s => s.id === seg.id); + this.deleteSelectedSegment(); + this.dismissContextMenu(); + }; + menu.appendChild(delBtn); + + document.body.appendChild(menu); + this._contextMenu = menu; + + setTimeout(() => { + this._contextMenuDismisser = (ev) => { if (!menu.contains(ev.target)) this.dismissContextMenu(); }; + document.addEventListener("pointerdown", this._contextMenuDismisser, true); + }, 0); + } + + showGapContextMenu(clientX, clientY, gap) { + this.dismissContextMenu(); + const menu = document.createElement("div"); + menu.className = "pr-gap-menu"; + menu.style.left = `${clientX + 6}px`; + menu.style.top = `${clientY - 10}px`; + + const currentTrack = gap.track === "audio" ? "audio" : "image"; + + if (this._copiedSegment && this._copiedSegmentTrack === currentTrack) { + const pasteBtn = document.createElement("button"); + pasteBtn.className = "pr-gap-menu-btn"; + pasteBtn.innerHTML = `Paste Segment`; + pasteBtn.onclick = () => { + const startFrame = Math.round(gap.clickedFrame !== undefined ? gap.clickedFrame : gap.frameStart); + const gapLength = gap.frameEnd - startFrame; + + const newSeg = { + ...this._copiedSegment, + id: Date.now().toString() + Math.random().toString(36).substr(2, 5), + start: startFrame, + length: Math.min(this._copiedSegment.length, gapLength) + }; + const targetArray = currentTrack === "audio" ? this.timeline.audioSegments : this.timeline.segments; + targetArray.push(newSeg); + targetArray.sort((a, b) => a.start - b.start); + this.commitChanges(); + this.dismissContextMenu(); + }; + menu.appendChild(pasteBtn); + } + + if (currentTrack === "image") { + const textBtn = document.createElement("button"); + textBtn.className = "pr-gap-menu-btn"; + textBtn.innerHTML = `${ICONS.text} Text Segment`; + textBtn.onclick = () => { + this.addSegmentInGap(gap.frameStart, gap.frameEnd, "text"); + this.dismissContextMenu(); + }; + menu.appendChild(textBtn); + + const imgBtn = document.createElement("button"); + imgBtn.className = "pr-gap-menu-btn"; + imgBtn.innerHTML = `${ICONS.upload} Image Segment`; + imgBtn.onclick = () => { + this.dismissContextMenu(); + const fi = document.createElement("input"); + fi.type = "file"; fi.accept = "image/*"; + fi.addEventListener("change", (ev) => { + if (ev.target.files?.[0]) { + const gapLength = gap.frameEnd - gap.frameStart; + this.handleImageUpload([ev.target.files[0]], gap.frameStart, gapLength); + } + }); + fi.click(); + }; + menu.appendChild(imgBtn); + } + + document.body.appendChild(menu); + this._contextMenu = menu; + setTimeout(() => { + this._contextMenuDismisser = (ev) => { if (!menu.contains(ev.target)) this.dismissContextMenu(); }; + document.addEventListener("pointerdown", this._contextMenuDismisser, true); + }, 0); + } + dismissContextMenu() { + if (this._contextMenu) { this._contextMenu.remove(); this._contextMenu = null; } + if (this._contextMenuDismisser) { document.removeEventListener("pointerdown", this._contextMenuDismisser, true); this._contextMenuDismisser = null; } + } + + // --- Gap Popup Menu --- + showGapMenu(clientX, clientY, gap) { + this.dismissGapMenu(); + const menu = document.createElement("div"); + menu.className = "pr-gap-menu"; + menu.style.left = `${clientX + 6}px`; + menu.style.top = `${clientY - 10}px`; + + const textBtn = document.createElement("button"); + textBtn.className = "pr-gap-menu-btn"; + textBtn.innerHTML = `${ICONS.text} Text Segment`; + textBtn.addEventListener("click", () => { + this.addSegmentInGap(gap.frameStart, gap.frameEnd, "text"); + this.dismissGapMenu(); + }); + + const imgBtn = document.createElement("button"); + imgBtn.className = "pr-gap-menu-btn"; + imgBtn.innerHTML = `${ICONS.upload} Image Segment`; + imgBtn.addEventListener("click", () => { + this.dismissGapMenu(); + const fi = document.createElement("input"); + fi.type = "file"; fi.accept = "image/*"; + fi.addEventListener("change", (ev) => { + if (ev.target.files?.[0]) { + const gapLength = gap.frameEnd - gap.frameStart; + this.handleImageUpload([ev.target.files[0]], gap.frameStart, gapLength); + } + }); + fi.click(); + }); + + menu.appendChild(textBtn); + menu.appendChild(imgBtn); + const currentTrack = gap.track === "audio" ? "audio" : "image"; + if (this._copiedSegment && this._copiedSegmentTrack === currentTrack) { + const pasteBtn = document.createElement("button"); + pasteBtn.className = "pr-gap-menu-btn"; + pasteBtn.innerHTML = `Paste Segment`; + pasteBtn.onclick = () => { + const gapLength = gap.frameEnd - gap.frameStart; + + let finalLength = Math.min(this._copiedSegment.length, gapLength); + if (currentTrack === "image") { + finalLength = gapLength; + } + + const newSeg = { + ...this._copiedSegment, + id: Date.now().toString() + Math.random().toString(36).substr(2, 5), + start: gap.frameStart, + length: finalLength + }; + const targetArray = currentTrack === "audio" ? this.timeline.audioSegments : this.timeline.segments; + targetArray.push(newSeg); + targetArray.sort((a, b) => a.start - b.start); + this.commitChanges(); + this.dismissGapMenu(); + }; + menu.appendChild(pasteBtn); + } + + document.body.appendChild(menu); + this._gapMenu = menu; + setTimeout(() => { + this._gapMenuDismisser = (ev) => { if (!menu.contains(ev.target)) this.dismissGapMenu(); }; + document.addEventListener("pointerdown", this._gapMenuDismisser, true); + }, 0); + } + + dismissGapMenu() { + if (this._gapMenu) { this._gapMenu.remove(); this._gapMenu = null; } + if (this._gapMenuDismisser) { document.removeEventListener("pointerdown", this._gapMenuDismisser, true); this._gapMenuDismisser = null; } + } + + // --- Settings Menu --- + // Widgets that are managed by the settings menu (hidden from node by default). + get _settingsWidgetNames() { + return ["display_mode", "epsilon", "divisible_by", "img_compression"]; + } + + // Hide all settings widgets on the node (called on init). + hideSettingsWidgets() { + for (const name of this._settingsWidgetNames) { + const w = this.node.widgets?.find(w => w.name === name); + if (w) hideWidget(w); + + // Also remove corresponding input slot if it exists and is NOT connected + // to prevent overlapping issues in classic ComfyUI (nodes v1) + if (this.node.inputs) { + const inputIdx = this.node.inputs.findIndex(i => i.name === name); + if (inputIdx !== -1) { + const input = this.node.inputs[inputIdx]; + if (input.link == null) { + this.node.removeInput(inputIdx); + } + } + } + } + this.updateWidgetVisibility(); + + // Workaround: toggle display mode to force ComfyUI to refresh the node + if (this.displayModeWidget) { + const origVal = this.displayModeWidget.value; + const otherVal = origVal === "frames" ? "seconds" : "frames"; + + this.displayModeWidget.value = otherVal; + if (this.displayModeWidget.callback) this.displayModeWidget.callback(otherVal); + + this.displayModeWidget.value = origVal; + if (this.displayModeWidget.callback) this.displayModeWidget.callback(origVal); + } + } + + // Restore all settings widgets on the node. + showSettingsWidgets() { + for (const name of this._settingsWidgetNames) { + const w = this.node.widgets?.find(w => w.name === name); + if (!w) continue; + + const typeMap = { + display_mode: "combo", epsilon: "FLOAT", divisible_by: "INT", + img_compression: "INT", + }; + w.type = typeMap[name] || w._origType || "number"; + w.hidden = false; + if (w.options) w.options.hidden = false; + delete w.computeSize; + if (w.element) w.element.style.display = ""; + } + this.updateWidgetVisibility(); + + // Workaround: toggle display mode to force ComfyUI to refresh the node + if (this.displayModeWidget) { + const origVal = this.displayModeWidget.value; + const otherVal = origVal === "frames" ? "seconds" : "frames"; + + this.displayModeWidget.value = otherVal; + if (this.displayModeWidget.callback) this.displayModeWidget.callback(otherVal); + + this.displayModeWidget.value = origVal; + if (this.displayModeWidget.callback) this.displayModeWidget.callback(origVal); + } + } + + _makeSettingRow(label, inputEl) { + const row = document.createElement("div"); + row.className = "pr-settings-row"; + const lbl = document.createElement("span"); + lbl.className = "pr-settings-label"; + lbl.textContent = label; + row.appendChild(lbl); + row.appendChild(inputEl); + return row; + } + + showSettingsMenu(anchorEl) { + this.dismissSettingsMenu(); + const menu = document.createElement("div"); + menu.className = "pr-settings-menu"; + + // Title & Close Button Container + const titleContainer = document.createElement("div"); + titleContainer.className = "pr-settings-title"; + titleContainer.style.display = "flex"; + titleContainer.style.justifyContent = "space-between"; + titleContainer.style.alignItems = "center"; + + const titleText = document.createElement("span"); + titleText.textContent = "Timeline Settings"; + titleContainer.appendChild(titleText); + + const closeBtn = document.createElement("button"); + closeBtn.className = "pr-settings-close-btn"; + closeBtn.innerHTML = ICONS.close; + closeBtn.title = "Close Settings"; + closeBtn.addEventListener("click", () => this.dismissSettingsMenu()); + titleContainer.appendChild(closeBtn); + + menu.appendChild(titleContainer); + + // Helper: fire a widget's callback safely + const fireCallback = (w, val) => { + w.value = val; + if (w.callback) { + try { w.callback(val, app.canvas, this.node, null, null); } catch (e) { } + } + if (window.app && window.app.graph) window.app.graph.setDirtyCanvas(true, true); + }; + + // --- Display Mode --- + const dmWidget = this.node.widgets?.find(w => w.name === "display_mode"); + if (dmWidget) { + const ctrl = document.createElement("div"); + ctrl.className = "pr-segmented-control"; + + const framesSeg = document.createElement("div"); + framesSeg.className = "pr-segment"; + framesSeg.textContent = "Frames"; + + const secondsSeg = document.createElement("div"); + secondsSeg.className = "pr-segment"; + secondsSeg.textContent = "Seconds"; + + const updateActive = (val) => { + if (val === "frames") { + framesSeg.classList.add("active"); + secondsSeg.classList.remove("active"); + } else { + secondsSeg.classList.add("active"); + framesSeg.classList.remove("active"); + } + }; + + updateActive(dmWidget.value); + + const onSegClick = (val) => { + fireCallback(dmWidget, val); + updateActive(val); + // Update ruler/timecode immediately + if (this.updateWidgetVisibility) this.updateWidgetVisibility(); + if (this.updateUIFromSelection) this.updateUIFromSelection(); + this.render(); + }; + + framesSeg.addEventListener("click", () => onSegClick("frames")); + secondsSeg.addEventListener("click", () => onSegClick("seconds")); + + ctrl.appendChild(secondsSeg); + ctrl.appendChild(framesSeg); + + menu.appendChild(this._makeSettingRow("Display Mode", ctrl)); + } + + const divider1 = document.createElement("hr"); + divider1.className = "pr-settings-divider"; + menu.appendChild(divider1); + + // Helper to create scrubbable number control with horizontal buttons + const createScrubbableNumberControl = (w, step, min, max, isFloat = false) => { + const container = document.createElement("div"); + container.className = "pr-number-control"; + + const decBtn = document.createElement("button"); + decBtn.className = "pr-number-btn"; + decBtn.textContent = "-"; + + const inp = document.createElement("input"); + inp.type = "number"; + inp.className = "pr-settings-input"; + inp.value = w.value; + inp.step = step.toString(); + inp.min = min.toString(); + inp.max = max.toString(); + + const incBtn = document.createElement("button"); + incBtn.className = "pr-number-btn"; + incBtn.textContent = "+"; + + decBtn.addEventListener("click", () => { + let val = parseFloat(inp.value) - step; + if (val < min) val = min; + inp.value = isFloat ? val.toFixed(4) : Math.round(val); + fireCallback(w, parseFloat(inp.value)); + }); + + incBtn.addEventListener("click", () => { + let val = parseFloat(inp.value) + step; + if (val > max) val = max; + inp.value = isFloat ? val.toFixed(4) : Math.round(val); + fireCallback(w, parseFloat(inp.value)); + }); + + inp.addEventListener("change", () => { + let val = parseFloat(inp.value); + if (isNaN(val)) val = w.value; + if (val < min) val = min; + if (val > max) val = max; + inp.value = isFloat ? val.toFixed(4) : Math.round(val); + fireCallback(w, parseFloat(inp.value)); + }); + + // Dragging logic + let isDragging = false; + let startX = 0; + let startVal = 0; + let hasMoved = false; + + inp.style.cursor = "ew-resize"; + + inp.addEventListener("mousedown", (e) => { + startX = e.clientX; + startVal = parseFloat(inp.value); + hasMoved = false; + + const onMouseMove = (moveEvent) => { + const deltaX = moveEvent.clientX - startX; + if (Math.abs(deltaX) > 3) { + hasMoved = true; + isDragging = true; + } + + if (isDragging) { + moveEvent.preventDefault(); + const sensitivity = isFloat ? 0.001 : 0.5; + let newVal = startVal + deltaX * sensitivity; + + if (newVal < min) newVal = min; + if (newVal > max) newVal = max; + + inp.value = isFloat ? newVal.toFixed(4) : Math.round(newVal); + fireCallback(w, parseFloat(inp.value)); + } + }; + + const onMouseUp = () => { + document.removeEventListener("mousemove", onMouseMove); + document.removeEventListener("mouseup", onMouseUp); + + if (!hasMoved) { + inp.focus(); + inp.select(); + } + isDragging = false; + }; + + document.addEventListener("mousemove", onMouseMove); + document.addEventListener("mouseup", onMouseUp); + }); + + container.appendChild(decBtn); + container.appendChild(inp); + container.appendChild(incBtn); + + return container; + }; + + // --- Epsilon --- + const epsWidget = this.node.widgets?.find(w => w.name === "epsilon"); + if (epsWidget) { + menu.appendChild(this._makeSettingRow("Epsilon", createScrubbableNumberControl(epsWidget, 0.0001, 0.0001, 0.99, true))); + } + + // --- Divisible By --- + const divByWidget = this.node.widgets?.find(w => w.name === "divisible_by"); + if (divByWidget) { + menu.appendChild(this._makeSettingRow("Divisible By", createScrubbableNumberControl(divByWidget, 1, 1, 256, false))); + } + + // --- Img Compression --- + const compWidget = this.node.widgets?.find(w => w.name === "img_compression"); + if (compWidget) { + menu.appendChild(this._makeSettingRow("Img Compression", createScrubbableNumberControl(compWidget, 1, 0, 100, false))); + } + + // --- Global Prompt Toggle --- + const globalPromptWidget = this.node.widgets?.find(w => w.name === "global_prompt"); + if (globalPromptWidget) { + const cb = document.createElement("input"); + cb.type = "checkbox"; + cb.checked = !(globalPromptWidget.options && globalPromptWidget.options.hidden); + cb.style.cursor = "pointer"; + cb.addEventListener("change", () => { + const isVisible = cb.checked; + if (!globalPromptWidget.options) globalPromptWidget.options = {}; + globalPromptWidget.options.hidden = !isVisible; + + if (isVisible) { + delete globalPromptWidget.computeSize; + globalPromptWidget.hidden = false; + if (globalPromptWidget.element) globalPromptWidget.element.style.display = ""; + } else { + globalPromptWidget.computeSize = () => [0, 0]; + globalPromptWidget.hidden = true; + if (globalPromptWidget.element) globalPromptWidget.element.style.display = "none"; + } + + // Force refresh via display mode double-toggle trick + if (this.displayModeWidget) { + const origVal = this.displayModeWidget.value; + const otherVal = origVal === "frames" ? "seconds" : "frames"; + this.displayModeWidget.value = otherVal; + if (this.displayModeWidget.callback) this.displayModeWidget.callback(otherVal); + this.displayModeWidget.value = origVal; + if (this.displayModeWidget.callback) this.displayModeWidget.callback(origVal); + } + }); + menu.appendChild(this._makeSettingRow("Use Global Prompt", cb)); + } + + + // --- Show/Hide on Node Toggle --- + const toggleBtn = document.createElement("button"); + toggleBtn.className = "pr-settings-toggle-btn"; + const widgetsVisible = !!(this.node.widgets?.find(w => w.name === "display_mode" && !(w.options && w.options.hidden))); + toggleBtn.textContent = widgetsVisible ? "Hide Widgets on Node" : "Show Widgets on Node"; + toggleBtn.addEventListener("click", () => { + const nowVisible = !!(this.node.widgets?.find(w => w.name === "display_mode" && !(w.options && w.options.hidden))); + if (nowVisible) { + this.hideSettingsWidgets(); + toggleBtn.textContent = "Show Widgets on Node"; + } else { + this.showSettingsWidgets(); + toggleBtn.textContent = "Hide Widgets on Node"; + } + }); + menu.appendChild(toggleBtn); + + // Position the menu below the anchor button (pop down) + document.body.appendChild(menu); + const rect = anchorEl.getBoundingClientRect(); + const menuW = menu.offsetWidth || 230; + const menuH = menu.offsetHeight || 350; + let left = rect.right - menuW; + let top = rect.bottom + 6; + if (left < 4) left = 4; + // Fallback to top if it overflows the bottom of the screen + if (top + menuH > window.innerHeight - 4) { + top = rect.top - menuH - 6; + } + menu.style.left = `${left}px`; + menu.style.top = `${top}px`; + + this._settingsMenu = menu; + setTimeout(() => { + this._settingsDismisser = (ev) => { + if (!menu.contains(ev.target) && !anchorEl.contains(ev.target)) this.dismissSettingsMenu(); + }; + document.addEventListener("mousedown", this._settingsDismisser); + }, 0); + } + + dismissSettingsMenu() { + if (this._settingsMenu) { this._settingsMenu.remove(); this._settingsMenu = null; } + if (this._settingsDismisser) { document.removeEventListener("mousedown", this._settingsDismisser); this._settingsDismisser = null; } + } + + + addSegmentInGap(frameStart, frameEnd, type = "text") { + const seg = { + id: Date.now().toString() + Math.random().toString(36).substr(2, 5), + start: frameStart, length: frameEnd - frameStart, + prompt: "", type, + }; + this.timeline.segments.push(seg); + this.timeline.segments.sort((a, b) => a.start - b.start); + this.selectionType = "image"; + this.selectedIndex = this.timeline.segments.findIndex(s => s.id === seg.id); + this.updateUIFromSelection(); + this.commitChanges(); + } + + addTextSegmentFreeSpace() { + const frameRate = this.getFrameRate(); + const newLength = Math.max(1, frameRate); // 1 second default + const sorted = [...this.timeline.segments].sort((a, b) => a.start - b.start); + let newStart = 0; + for (const seg of sorted) { + if (newStart + newLength <= seg.start) break; + newStart = Math.max(newStart, seg.start + seg.length); + } + // Place the segment at the first free slot in the visual timeline (no output duration change). + const durationFrames = this.getVisualDurationFrames(); + const seg = { + id: Date.now().toString() + Math.random().toString(36).substr(2, 5), + start: newStart, length: Math.min(newLength, Math.max(newLength, durationFrames - newStart)), + prompt: "", type: "text", + }; + this.timeline.segments.push(seg); + this.timeline.segments.sort((a, b) => a.start - b.start); + this.selectionType = "image"; + this.selectedIndex = this.timeline.segments.findIndex(s => s.id === seg.id); + this.updateUIFromSelection(); + this.commitChanges(); + } + + // --- Audio Player Engine --- + updatePlayerUI() { + if (!this.playBtn || !this.loopBtn) return; + this.playBtn.innerHTML = this.isPlaying ? ICONS.pause : ICONS.play; + if (this.isLooping) { + this.loopBtn.classList.add("active"); + } else { + this.loopBtn.classList.remove("active"); + } + if (this.seekBar) { + this.seekBar.max = this.getVisualDurationFrames(); + this.seekBar.value = this.currentFrame; + } + if (this.timeCodeDisplay) { + this.timeCodeDisplay.textContent = this.formatTime(this.currentFrame); + } + } + + togglePlay() { + if (this.isPlaying) { + this.pauseAudio(); + } else { + if (this.currentFrame >= this.getVisualDurationFrames()) { + this.currentFrame = 0; + } + this.playAudio(); + } + } + + toggleLoop() { + this.isLooping = !this.isLooping; + this.updatePlayerUI(); + } + + async playAudio() { + this.pauseAudio(true); // clear any existing playback, but don't suspend context if scrubbing + + this._playCounter = (this._playCounter || 0) + 1; + const playId = this._playCounter; + this._currentPlayId = playId; + this.isPlaying = true; + + if (!this.audioContext) { + this.audioContext = new (window.AudioContext || window.webkitAudioContext)(); + } + if (this.audioContext.state !== 'running') { + try { await this.audioContext.resume(); } catch (e) { } + } + if (this._currentPlayId !== playId || !this.isPlaying) return; + + this.updatePlayerUI(); + + const frameRate = this.getFrameRate(); + this.playbackStartFrame = this.currentFrame; + this.playbackStartTime = this.audioContext.currentTime; + + // Decode and schedule all audio segments that happen AT or AFTER currentFrame + for (let seg of this.timeline.audioSegments) { + const segStartFrame = seg.start; + const segEndFrame = seg.start + seg.length; + + if (segEndFrame <= this.currentFrame) continue; + + try { + // Build audio buffer: fetch from server URL if audioFile is set, otherwise fall back to audioB64 + let audioBuffer; + if (seg.audioFile) { + const audioUrl = api.apiURL(`/view?filename=${encodeURIComponent(seg.audioFile.split("/").pop())}&type=input&subfolder=${encodeURIComponent(seg.audioFile.includes("/") ? seg.audioFile.split("/").slice(0, -1).join("/") : "")}`); + const resp = await fetch(audioUrl); + const arrayBuffer = await resp.arrayBuffer(); + audioBuffer = await this.audioContext.decodeAudioData(arrayBuffer); + } else if (seg.audioB64) { + const binaryString = window.atob(seg.audioB64); + const len = binaryString.length; + const bytes = new Uint8Array(len); + for (let i = 0; i < len; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + audioBuffer = await this.audioContext.decodeAudioData(bytes.buffer); + } else { + continue; + } + if (this._currentPlayId !== playId || !this.isPlaying) return; + + const framesToSkipInSegment = Math.max(0, this.currentFrame - segStartFrame); + const waitFrames = Math.max(0, segStartFrame - this.currentFrame); + const waitTimeSec = waitFrames / frameRate; + + const fileOffsetFrames = seg.trimStart + framesToSkipInSegment; + const fileOffsetSec = fileOffsetFrames / frameRate; + + const playDurationFrames = seg.length - framesToSkipInSegment; + const playDurationSec = playDurationFrames / frameRate; + + if (playDurationSec <= 0) continue; + + const bufferNode = this.audioContext.createBufferSource(); + bufferNode.buffer = audioBuffer; + bufferNode["connect"](this.audioContext.destination); + + const startTime = this.audioContext.currentTime + waitTimeSec; + bufferNode.start(startTime, fileOffsetSec, playDurationSec); + + this.activeAudioNodes.push(bufferNode); + } catch (err) { + console.error("Playback decode error for segment:", err); + } + } + + if (this._currentPlayId !== playId || !this.isPlaying) return; + + const loop = () => { + if (!this.isPlaying || this._currentPlayId !== playId) return; + + const elapsedSec = this.audioContext.currentTime - this.playbackStartTime; + const elapsedFrames = elapsedSec * frameRate; + + this.currentFrame = this.playbackStartFrame + elapsedFrames; + + const visualDurationFrames = this.getVisualDurationFrames(); + const durationFrames = this.getDurationFrames(); + + if (this.isLooping) { + const loopBound = (this.playbackStartFrame >= durationFrames) ? visualDurationFrames : durationFrames; + if (this.currentFrame >= loopBound) { + this.currentFrame = 0; + this.playAudio(); // Restart playback + return; + } + } else { + if (this.currentFrame >= visualDurationFrames) { + this.currentFrame = visualDurationFrames; + this.pauseAudio(); + this.render(); + return; + } + } + + this.render(); + this._playLoopId = requestAnimationFrame(loop); + }; + + this._playLoopId = requestAnimationFrame(loop); + } + + pauseAudio(isScrubbing = false) { + this.isPlaying = false; + this._currentPlayId = null; + + if (!isScrubbing && this.audioContext && this.audioContext.state === 'running') { + try { this.audioContext.suspend(); } catch (e) { } + } + + for (let node of this.activeAudioNodes) { + try { node.stop(); } catch (e) { } + try { node.disconnect(); } catch (e) { } + } + this.activeAudioNodes = []; + + if (this._playLoopId) { + cancelAnimationFrame(this._playLoopId); + this._playLoopId = null; + } + this.updatePlayerUI(); + } +} + +// --- Node Registration Hooks --- +const APPENDED_WIDGET_DEFAULTS = [ + ["timeline_data", "{}"], + ["local_prompts", ""], + ["segment_lengths", ""], +]; + +app.registerExtension({ + name: "LTXDirector", + async beforeRegisterNodeDef(nodeType, nodeData, app) { + if (nodeData.name === "LTXDirector") { + + const onNodeCreated = nodeType.prototype.onNodeCreated; + nodeType.prototype.onNodeCreated = function () { + if (onNodeCreated) onNodeCreated.apply(this, arguments); + + for (const [name, def] of APPENDED_WIDGET_DEFAULTS) { + if (!this.widgets?.find(w => w.name === name)) { + this.addWidget("string", name, def, () => { }); + } + } + for (const w of this.widgets) { + if (HIDDEN_WIDGET_NAMES.includes(w.name)) hideWidget(w); + } + + // Set default width to be wider on creation (approx 2.5x default ~220px) + this.size[0] = 1000; + + // Force default for img_compression if not set (ComfyUI sometimes skips optional defaults) + const compWidget = this.widgets?.find(w => w.name === "img_compression"); + if (compWidget && (compWidget.value === undefined || compWidget.value === null || compWidget.value === 0)) { + compWidget.value = 18; + } + + // Global Prompt is now left completely visible on creation! + + const container = document.createElement("div"); + const widget = this.addDOMWidget("timeline_ui", "timeline_ui", container, { + getValue: () => "", + setValue: () => { }, + }); + + widget.computeSize = function (width) { + const canvasH = self._timelineEditor ? self._timelineEditor.canvasHeight : CANVAS_HEIGHT; + return [width, canvasH + 235]; + }; + + const self = this; + setTimeout(() => { + try { + self._timelineEditor = new TimelineEditor(self, container, widget); + } catch (err) { + console.error("[PromptRelay] timeline editor init failed:", err); + } + }, 0); + }; + + const onRemoved = nodeType.prototype.onRemoved; + nodeType.prototype.onRemoved = function () { + this._timelineEditor?.destroy(); + return onRemoved?.apply(this, arguments); + }; + + const onConfigure = nodeType.prototype.onConfigure; + nodeType.prototype.onConfigure = function (info) { + const out = onConfigure?.apply(this, arguments); + for (const [name, def] of APPENDED_WIDGET_DEFAULTS) { + const w = this.widgets.find(x => x.name === name); + if (w && (w.value == null || w.value === "")) w.value = def; + } + + setTimeout(() => { + if (this._timelineEditor) { + this._timelineEditor.timeline = parseInitial(this._timelineEditor.timelineDataWidget?.value); + this._timelineEditor.loadImages(); + this._timelineEditor.selectionType = "image"; + this._timelineEditor.selectedIndex = clamp( + this._timelineEditor.selectedIndex, -1, + Math.max(-1, this._timelineEditor.timeline.segments.length - 1) + ); + this._timelineEditor.updateUIFromSelection(); + this._timelineEditor.render(); + } + }, 0); + return out; + }; + } + }, +}); diff --git a/js/ltx_director_guide.js b/js/ltx_director_guide.js index 9edc2a1..27484db 100644 --- a/js/ltx_director_guide.js +++ b/js/ltx_director_guide.js @@ -1,17 +1,17 @@ -import { app } from "../../scripts/app.js"; - -// LTX Director Guide is a pure pass-through processor node. -// All configuration (images, insert frames, strengths) comes from -// the guide_data output of Prompt Relay Encode (Timeline). -// No dynamic widgets or sync logic needed. -app.registerExtension({ - name: "Comfy.LTXDirectorGuide", - async nodeCreated(node) { - if (node.comfyClass !== "LTXDirectorGuide") return; - // Nothing to initialize — the node has no configurable widgets. - }, -}); - - // Nothing to initialize — the node has no configurable widgets. - }, +import { app } from "../../scripts/app.js"; + +// LTX Director Guide is a pure pass-through processor node. +// All configuration (images, insert frames, strengths) comes from +// the guide_data output of Prompt Relay Encode (Timeline). +// No dynamic widgets or sync logic needed. +app.registerExtension({ + name: "Comfy.LTXDirectorGuide", + async nodeCreated(node) { + if (node.comfyClass !== "LTXDirectorGuide") return; + // Nothing to initialize — the node has no configurable widgets. + }, +}); + + // Nothing to initialize — the node has no configurable widgets. + }, }); \ No newline at end of file diff --git a/js/ltx_keyframer.js b/js/ltx_keyframer.js index d5d0bc4..6f573c4 100644 --- a/js/ltx_keyframer.js +++ b/js/ltx_keyframer.js @@ -1,419 +1,419 @@ -import { app } from "../../scripts/app.js"; - -// Global registry to track all LTXKeyframer nodes across all subgraphs -window._LTXKeyframerGlobalNodes = window._LTXKeyframerGlobalNodes || new Set(); - -// ComfyUI native trick to cleanly hide/show widgets without deleting them -function toggleWidget(widget, visible) { - if (visible) { - if (widget.origType !== undefined) { - widget.type = widget.origType; - widget.computeSize = widget.origComputeSize; - delete widget.origType; - delete widget.origComputeSize; - } - } else { - if (widget.type !== "hidden") { - widget.origType = widget.type; - widget.origComputeSize = widget.computeSize; - widget.type = "hidden"; - widget.computeSize = () => [0, -4]; - } - } -} - -// --- NEW SYNC HELPER FUNCTION --- -// Finds all other LTXKeyframer nodes globally and mirrors the value to them -function syncWidgetAcrossNodes(sourceNode, widgetName, value) { - if (!window._LTXKeyframerGlobalNodes) return; - - for (const targetNode of window._LTXKeyframerGlobalNodes) { - // Target all OTHER LTXKeyframer nodes by direct object reference - if (targetNode !== sourceNode) { - - // 1. Always update the hidden properties cache so it remembers the sync - // even if the widget isn't currently visible (e.g. fewer images loaded right now) - targetNode.properties[widgetName] = value; - - // 2. If the widget is currently visible on the UI, update it visually - if (targetNode.widgets) { - const targetWidget = targetNode.widgets.find(w => w.name === widgetName); - if (targetWidget && targetWidget.value !== value) { - targetWidget.value = value; - targetNode.setDirtyCanvas(true, false); - } - } - } - } -} - -app.registerExtension({ - name: "Comfy.LTXKeyframer.DynamicInputs", - async nodeCreated(node) { - if (node.comfyClass !== "LTXKeyframer") return; - - // Register this node instance globally - window._LTXKeyframerGlobalNodes.add(node); - - node._currentImageCount = -1; // Force first update - - // Initialize persistent properties cache - node.properties = node.properties || {}; - - // Add subtle separator line above images_loaded - node.addCustomWidget({ - name: "num_images_separator", - type: "text", - draw(ctx, node, widget_width, y, widget_height) { - ctx.save(); - ctx.strokeStyle = "#444"; - ctx.lineWidth = 1; - ctx.beginPath(); - ctx.moveTo(10, y + 5); - ctx.lineTo(widget_width - 10, y + 5); - ctx.stroke(); - ctx.restore(); - }, - computeSize(width) { - return [width, 10]; - } - }); - - // Move separator before num_images - const moveSeparator = () => { - const idx = node.widgets.findIndex(w => w.name === "num_images"); - const sepIdx = node.widgets.findIndex(w => w.name === "num_images_separator"); - if (idx !== -1 && sepIdx !== -1) { - const separator = node.widgets.splice(sepIdx, 1)[0]; - node.widgets.splice(idx, 0, separator); - } - }; - setTimeout(moveSeparator, 50); // Small delay to ensure num_images is present - - // Core update: synchronize widget visibility to match imageCount - node._applyWidgetCount = function(count) { - const isInitialLoad = this._currentImageCount === -1; - - if (this._currentImageCount === count && !isInitialLoad) return; - this._currentImageCount = count; - - const initialWidth = this.size[0]; - const numWidget = this.widgets?.find(w => w.name === "num_images"); - if (numWidget) { - numWidget.label = "images_loaded"; - numWidget.value = Math.max(0, Math.min(count || 0, 50)); - } - - // 1. Store current widget values in properties BEFORE removing them - // We skip reading from `this.widgets` on the initial load because it might be scrambling. - if (!isInitialLoad && this.widgets) { - this.widgets.forEach(w => { - if (w.name.startsWith("insert_frame_") || w.name.startsWith("strength_")) { - this.properties[w.name] = w.value; - } - }); - } - - // 2. Remove all existing dynamic insert_frame/strength/header widgets - if (this.widgets) { - this.widgets = this.widgets.filter(w => - !w.name.startsWith("insert_frame_") && - !w.name.startsWith("strength_") && - !w.name.startsWith("header_") - ); - } else { - this.widgets = []; - } - - // 3. Add back exactly the right amount of widgets using the cached values - for (let i = 1; i <= count; i++) { - // Add header/separator widget for grouping - const headerName = `header_${i}`; - this.addCustomWidget({ - name: headerName, - type: "text", - value: `Image #${i}`, - draw(ctx, node, widget_width, y, widget_height) { - ctx.save(); - const margin = 10; - const topPadding = 15; - - // Subtle separator line - ctx.strokeStyle = "#333"; - ctx.lineWidth = 1; - ctx.beginPath(); - ctx.moveTo(margin, y + 5); - ctx.lineTo(widget_width - margin, y + 5); - ctx.stroke(); - - // Text label - ctx.fillStyle = "#dddddd"; // Light gray - ctx.font = "bold 12px Arial"; - ctx.textAlign = "left"; - ctx.fillText(`Image #${i}`, margin, y + topPadding + 10); - ctx.restore(); - }, - computeSize(width) { - return [width, 35]; // Vertical gap + label height - } - }); - - const insertFrameWidgetName = `insert_frame_${i}`; - const strengthWidgetName = `strength_${i}`; - - // Add insert_frame widget with Sync Callback - const savedInsertFrameValue = this.properties[insertFrameWidgetName]; - this.addWidget("number", insertFrameWidgetName, - savedInsertFrameValue !== undefined ? savedInsertFrameValue : 0, - (value) => { - const rounded = Math.round(value); - this.properties[insertFrameWidgetName] = rounded; - syncWidgetAcrossNodes(this, insertFrameWidgetName, rounded); // Sync out - }, { min: -9999, max: 9999, step: 10, precision: 0 } - ); - - // Add strength widget with Sync Callback - const savedStrengthValue = this.properties[strengthWidgetName]; - this.addWidget("number", strengthWidgetName, - savedStrengthValue !== undefined ? savedStrengthValue : 1.0, - (value) => { - this.properties[strengthWidgetName] = value; - syncWidgetAcrossNodes(this, strengthWidgetName, value); // Sync out - }, { min: 0.0, max: 1.0, step: 0.01 } - ); - } - - this.setDirtyCanvas(true, true); - requestAnimationFrame(() => { - if (this.computeSize) { - this.setSize(this.computeSize()); - this.size[0] = initialWidth; // keep width fixed when restructuring - } - }); - }; - - // --- STRICT ARRAY MAPPER: FIXES ALL SHIFTING FOREVER --- - // This runs the exact instant the node is loaded, before any UI widgets shift indices. - // It locks the perfectly mapped array values directly into our properties dictionary. - const origConfigure = node.configure; - node.configure = function(info) { - if (origConfigure) { - origConfigure.apply(this, arguments); - } - if (this.widgets) { - this.widgets.forEach(w => { - if (w.name === "num_images" || w.name.startsWith("insert_frame_") || w.name.startsWith("strength_")) { - this.properties[w.name] = w.value; - } - }); - } - }; - - // Handle deserialization to load properties properly from JSON - const originalOnConfigure = node.onConfigure; - node.onConfigure = function(info) { - if (originalOnConfigure) { - originalOnConfigure.apply(this, arguments); - } - if (info.properties) { - this.properties = { ...this.properties, ...info.properties }; - } - setTimeout(() => { - const count = readSourceImageCount(this); - // Fallback to properties.num_images if source node disconnected - let targetCount = count !== null ? count : (this.properties.num_images || 0); - this._applyWidgetCount(targetCount); - }, 100); - }; - - // --- STRICT ARRAY GENERATOR --- - // Completely detach from ComfyUI's blind visual array saving. - // We construct an exact 101-element strict array that Python expects. - // This makes your node 100% immune to UI/Header index shifting. - const originalOnSerialize = node.onSerialize; - node.onSerialize = function(info) { - // Ensure properties are strictly synced with current widget values before building - if (this.widgets) { - this.widgets.forEach(w => { - if (w.name === "num_images" || w.name.startsWith("insert_frame_") || w.name.startsWith("strength_")) { - this.properties[w.name] = w.value; - } - }); - } - - if (originalOnSerialize) { - originalOnSerialize.apply(this, arguments); - } - - info.properties = { ...this.properties }; - - // Build the exact strict array that maps 1-to-1 to the Python backend - const strictArray = []; - const numWidgetVal = this.properties["num_images"]; - strictArray.push(numWidgetVal !== undefined ? numWidgetVal : 1); - - for (let i = 1; i <= 50; i++) { - const fVal = this.properties[`insert_frame_${i}`]; - const sVal = this.properties[`strength_${i}`]; - strictArray.push(fVal !== undefined ? fVal : 0); - strictArray.push(sVal !== undefined ? sVal : 1.0); - } - - info.widgets_values = strictArray; - }; - - // Set up manual num_images widget callback - setTimeout(() => { - const numWidget = node.widgets?.find(w => w.name === "num_images"); - if (numWidget) { - numWidget.callback = (val) => { - node.properties["num_images"] = val; - node._applyWidgetCount(val); - }; - } - }, 100); - - // Exposed receiver for push-based notifications - node._syncImageCount = function(count) { - this._applyWidgetCount(count); - }; - - // Helper: read image count from a connected MultiImageLoader node - function readSourceImageCount(self) { - const multiInput = self.inputs?.find(inp => inp.name === "multi_input"); - if (!multiInput || !multiInput.link) return null; - - const nodeGraph = self.graph || app.graph; - - // Helper to safely trace back through Reroutes and ComfyUI Group Nodes/Subgraphs - function traceUpstream(graph, linkId, visited = new Set()) { - if (!linkId || visited.has(linkId)) return null; - visited.add(linkId); - - const link = graph.links[linkId]; - if (!link) return null; - - const originNode = graph.getNodeById(link.origin_id); - if (!originNode) return null; - - if (originNode.comfyClass === "MultiImageLoader") { - return originNode; - } - - // Traverse Reroute nodes - if (originNode.type === "Reroute" || originNode.comfyClass === "Reroute") { - if (originNode.inputs && originNode.inputs.length > 0 && originNode.inputs[0].link) { - return traceUpstream(graph, originNode.inputs[0].link, visited); - } - } - - // Traverse standard ComfyUI Group Nodes (subgraphs) - if (typeof originNode.getInnerNode === "function") { - try { - const innerNode = originNode.getInnerNode(link.origin_slot); - if (innerNode && innerNode.comfyClass === "MultiImageLoader") { - return innerNode; - } - } catch (e) { - console.warn("Could not trace inner node", e); - } - } - - return null; - } - - let sourceNode = traceUpstream(nodeGraph, multiInput.link); - - // Helper to extract the count once we find a node - function getCountFromNode(n) { - if (typeof n._imageCount === "number") return n._imageCount; - const pathsWidget = n.widgets?.find(w => w.name === "image_paths"); - if (pathsWidget) { - return (pathsWidget.value || "").split('\n').map(p => p.trim()).filter(p => p.length > 0).length; - } - return null; - } - - if (sourceNode) { - return getCountFromNode(sourceNode); - } - - // Fallback Strategy: If it is connected to something, but we couldn't resolve it - // directly (e.g. complex nested 3rd party subgraphs), scan the entire UI. - // If there is EXACTLY ONE MultiImageLoader in the workspace, safely assume that's the one. - let multiImageLoaders = []; - function findAllLoaders(nodes) { - if (!nodes) return; - for (let n of nodes) { - if (n.comfyClass === "MultiImageLoader") { - multiImageLoaders.push(n); - } - if (n.subgraph && n.subgraph._nodes) { - findAllLoaders(n.subgraph._nodes); - } - } - } - if (app.graph && app.graph._nodes) { - findAllLoaders(app.graph._nodes); - } - - if (multiImageLoaders.length === 1) { - return getCountFromNode(multiImageLoaders[0]); - } - - return null; - } - - // --- Backup polling via setInterval (4 Hz) --- - const pollInterval = setInterval(() => { - if (!node.graph) { - clearInterval(pollInterval); - return; - } - const count = readSourceImageCount(node); - if (count !== null) { - node._applyWidgetCount(count); - } - }, 250); - - // Clean up the interval when the node is deleted - const origOnRemoved = node.onRemoved; - node.onRemoved = function() { - // Unregister this node instance - window._LTXKeyframerGlobalNodes.delete(node); - - clearInterval(pollInterval); - if (origOnRemoved) origOnRemoved.apply(this, arguments); - }; - - // --- Connection change handler --- - const onConnectionsChange = node.onConnectionsChange; - node.onConnectionsChange = function(type, index, connected, link_info) { - if (onConnectionsChange) onConnectionsChange.apply(this, arguments); - - if (type === 1) { // 1 = Input - const input = this.inputs[index]; - if (input && input.name === "multi_input") { - if (connected) { - setTimeout(() => { - const count = readSourceImageCount(this); - this._applyWidgetCount(count !== null ? count : (this.properties.num_images || 0)); - }, 100); - } else { - this._applyWidgetCount(0); - } - } - } - }; - - // --- Initial sync when first placed on canvas --- - const origOnAdded = node.onAdded; - node.onAdded = function() { - if (origOnAdded) origOnAdded.apply(this, arguments); - setTimeout(() => { - const count = readSourceImageCount(this); - this._applyWidgetCount(count !== null ? count : (this.properties.num_images || 0)); - }, 100); - }; - } +import { app } from "../../scripts/app.js"; + +// Global registry to track all LTXKeyframer nodes across all subgraphs +window._LTXKeyframerGlobalNodes = window._LTXKeyframerGlobalNodes || new Set(); + +// ComfyUI native trick to cleanly hide/show widgets without deleting them +function toggleWidget(widget, visible) { + if (visible) { + if (widget.origType !== undefined) { + widget.type = widget.origType; + widget.computeSize = widget.origComputeSize; + delete widget.origType; + delete widget.origComputeSize; + } + } else { + if (widget.type !== "hidden") { + widget.origType = widget.type; + widget.origComputeSize = widget.computeSize; + widget.type = "hidden"; + widget.computeSize = () => [0, -4]; + } + } +} + +// --- NEW SYNC HELPER FUNCTION --- +// Finds all other LTXKeyframer nodes globally and mirrors the value to them +function syncWidgetAcrossNodes(sourceNode, widgetName, value) { + if (!window._LTXKeyframerGlobalNodes) return; + + for (const targetNode of window._LTXKeyframerGlobalNodes) { + // Target all OTHER LTXKeyframer nodes by direct object reference + if (targetNode !== sourceNode) { + + // 1. Always update the hidden properties cache so it remembers the sync + // even if the widget isn't currently visible (e.g. fewer images loaded right now) + targetNode.properties[widgetName] = value; + + // 2. If the widget is currently visible on the UI, update it visually + if (targetNode.widgets) { + const targetWidget = targetNode.widgets.find(w => w.name === widgetName); + if (targetWidget && targetWidget.value !== value) { + targetWidget.value = value; + targetNode.setDirtyCanvas(true, false); + } + } + } + } +} + +app.registerExtension({ + name: "Comfy.LTXKeyframer.DynamicInputs", + async nodeCreated(node) { + if (node.comfyClass !== "LTXKeyframer") return; + + // Register this node instance globally + window._LTXKeyframerGlobalNodes.add(node); + + node._currentImageCount = -1; // Force first update + + // Initialize persistent properties cache + node.properties = node.properties || {}; + + // Add subtle separator line above images_loaded + node.addCustomWidget({ + name: "num_images_separator", + type: "text", + draw(ctx, node, widget_width, y, widget_height) { + ctx.save(); + ctx.strokeStyle = "#444"; + ctx.lineWidth = 1; + ctx.beginPath(); + ctx.moveTo(10, y + 5); + ctx.lineTo(widget_width - 10, y + 5); + ctx.stroke(); + ctx.restore(); + }, + computeSize(width) { + return [width, 10]; + } + }); + + // Move separator before num_images + const moveSeparator = () => { + const idx = node.widgets.findIndex(w => w.name === "num_images"); + const sepIdx = node.widgets.findIndex(w => w.name === "num_images_separator"); + if (idx !== -1 && sepIdx !== -1) { + const separator = node.widgets.splice(sepIdx, 1)[0]; + node.widgets.splice(idx, 0, separator); + } + }; + setTimeout(moveSeparator, 50); // Small delay to ensure num_images is present + + // Core update: synchronize widget visibility to match imageCount + node._applyWidgetCount = function(count) { + const isInitialLoad = this._currentImageCount === -1; + + if (this._currentImageCount === count && !isInitialLoad) return; + this._currentImageCount = count; + + const initialWidth = this.size[0]; + const numWidget = this.widgets?.find(w => w.name === "num_images"); + if (numWidget) { + numWidget.label = "images_loaded"; + numWidget.value = Math.max(0, Math.min(count || 0, 50)); + } + + // 1. Store current widget values in properties BEFORE removing them + // We skip reading from `this.widgets` on the initial load because it might be scrambling. + if (!isInitialLoad && this.widgets) { + this.widgets.forEach(w => { + if (w.name.startsWith("insert_frame_") || w.name.startsWith("strength_")) { + this.properties[w.name] = w.value; + } + }); + } + + // 2. Remove all existing dynamic insert_frame/strength/header widgets + if (this.widgets) { + this.widgets = this.widgets.filter(w => + !w.name.startsWith("insert_frame_") && + !w.name.startsWith("strength_") && + !w.name.startsWith("header_") + ); + } else { + this.widgets = []; + } + + // 3. Add back exactly the right amount of widgets using the cached values + for (let i = 1; i <= count; i++) { + // Add header/separator widget for grouping + const headerName = `header_${i}`; + this.addCustomWidget({ + name: headerName, + type: "text", + value: `Image #${i}`, + draw(ctx, node, widget_width, y, widget_height) { + ctx.save(); + const margin = 10; + const topPadding = 15; + + // Subtle separator line + ctx.strokeStyle = "#333"; + ctx.lineWidth = 1; + ctx.beginPath(); + ctx.moveTo(margin, y + 5); + ctx.lineTo(widget_width - margin, y + 5); + ctx.stroke(); + + // Text label + ctx.fillStyle = "#dddddd"; // Light gray + ctx.font = "bold 12px Arial"; + ctx.textAlign = "left"; + ctx.fillText(`Image #${i}`, margin, y + topPadding + 10); + ctx.restore(); + }, + computeSize(width) { + return [width, 35]; // Vertical gap + label height + } + }); + + const insertFrameWidgetName = `insert_frame_${i}`; + const strengthWidgetName = `strength_${i}`; + + // Add insert_frame widget with Sync Callback + const savedInsertFrameValue = this.properties[insertFrameWidgetName]; + this.addWidget("number", insertFrameWidgetName, + savedInsertFrameValue !== undefined ? savedInsertFrameValue : 0, + (value) => { + const rounded = Math.round(value); + this.properties[insertFrameWidgetName] = rounded; + syncWidgetAcrossNodes(this, insertFrameWidgetName, rounded); // Sync out + }, { min: -9999, max: 9999, step: 10, precision: 0 } + ); + + // Add strength widget with Sync Callback + const savedStrengthValue = this.properties[strengthWidgetName]; + this.addWidget("number", strengthWidgetName, + savedStrengthValue !== undefined ? savedStrengthValue : 1.0, + (value) => { + this.properties[strengthWidgetName] = value; + syncWidgetAcrossNodes(this, strengthWidgetName, value); // Sync out + }, { min: 0.0, max: 1.0, step: 0.01 } + ); + } + + this.setDirtyCanvas(true, true); + requestAnimationFrame(() => { + if (this.computeSize) { + this.setSize(this.computeSize()); + this.size[0] = initialWidth; // keep width fixed when restructuring + } + }); + }; + + // --- STRICT ARRAY MAPPER: FIXES ALL SHIFTING FOREVER --- + // This runs the exact instant the node is loaded, before any UI widgets shift indices. + // It locks the perfectly mapped array values directly into our properties dictionary. + const origConfigure = node.configure; + node.configure = function(info) { + if (origConfigure) { + origConfigure.apply(this, arguments); + } + if (this.widgets) { + this.widgets.forEach(w => { + if (w.name === "num_images" || w.name.startsWith("insert_frame_") || w.name.startsWith("strength_")) { + this.properties[w.name] = w.value; + } + }); + } + }; + + // Handle deserialization to load properties properly from JSON + const originalOnConfigure = node.onConfigure; + node.onConfigure = function(info) { + if (originalOnConfigure) { + originalOnConfigure.apply(this, arguments); + } + if (info.properties) { + this.properties = { ...this.properties, ...info.properties }; + } + setTimeout(() => { + const count = readSourceImageCount(this); + // Fallback to properties.num_images if source node disconnected + let targetCount = count !== null ? count : (this.properties.num_images || 0); + this._applyWidgetCount(targetCount); + }, 100); + }; + + // --- STRICT ARRAY GENERATOR --- + // Completely detach from ComfyUI's blind visual array saving. + // We construct an exact 101-element strict array that Python expects. + // This makes your node 100% immune to UI/Header index shifting. + const originalOnSerialize = node.onSerialize; + node.onSerialize = function(info) { + // Ensure properties are strictly synced with current widget values before building + if (this.widgets) { + this.widgets.forEach(w => { + if (w.name === "num_images" || w.name.startsWith("insert_frame_") || w.name.startsWith("strength_")) { + this.properties[w.name] = w.value; + } + }); + } + + if (originalOnSerialize) { + originalOnSerialize.apply(this, arguments); + } + + info.properties = { ...this.properties }; + + // Build the exact strict array that maps 1-to-1 to the Python backend + const strictArray = []; + const numWidgetVal = this.properties["num_images"]; + strictArray.push(numWidgetVal !== undefined ? numWidgetVal : 1); + + for (let i = 1; i <= 50; i++) { + const fVal = this.properties[`insert_frame_${i}`]; + const sVal = this.properties[`strength_${i}`]; + strictArray.push(fVal !== undefined ? fVal : 0); + strictArray.push(sVal !== undefined ? sVal : 1.0); + } + + info.widgets_values = strictArray; + }; + + // Set up manual num_images widget callback + setTimeout(() => { + const numWidget = node.widgets?.find(w => w.name === "num_images"); + if (numWidget) { + numWidget.callback = (val) => { + node.properties["num_images"] = val; + node._applyWidgetCount(val); + }; + } + }, 100); + + // Exposed receiver for push-based notifications + node._syncImageCount = function(count) { + this._applyWidgetCount(count); + }; + + // Helper: read image count from a connected MultiImageLoader node + function readSourceImageCount(self) { + const multiInput = self.inputs?.find(inp => inp.name === "multi_input"); + if (!multiInput || !multiInput.link) return null; + + const nodeGraph = self.graph || app.graph; + + // Helper to safely trace back through Reroutes and ComfyUI Group Nodes/Subgraphs + function traceUpstream(graph, linkId, visited = new Set()) { + if (!linkId || visited.has(linkId)) return null; + visited.add(linkId); + + const link = graph.links[linkId]; + if (!link) return null; + + const originNode = graph.getNodeById(link.origin_id); + if (!originNode) return null; + + if (originNode.comfyClass === "MultiImageLoader") { + return originNode; + } + + // Traverse Reroute nodes + if (originNode.type === "Reroute" || originNode.comfyClass === "Reroute") { + if (originNode.inputs && originNode.inputs.length > 0 && originNode.inputs[0].link) { + return traceUpstream(graph, originNode.inputs[0].link, visited); + } + } + + // Traverse standard ComfyUI Group Nodes (subgraphs) + if (typeof originNode.getInnerNode === "function") { + try { + const innerNode = originNode.getInnerNode(link.origin_slot); + if (innerNode && innerNode.comfyClass === "MultiImageLoader") { + return innerNode; + } + } catch (e) { + console.warn("Could not trace inner node", e); + } + } + + return null; + } + + let sourceNode = traceUpstream(nodeGraph, multiInput.link); + + // Helper to extract the count once we find a node + function getCountFromNode(n) { + if (typeof n._imageCount === "number") return n._imageCount; + const pathsWidget = n.widgets?.find(w => w.name === "image_paths"); + if (pathsWidget) { + return (pathsWidget.value || "").split('\n').map(p => p.trim()).filter(p => p.length > 0).length; + } + return null; + } + + if (sourceNode) { + return getCountFromNode(sourceNode); + } + + // Fallback Strategy: If it is connected to something, but we couldn't resolve it + // directly (e.g. complex nested 3rd party subgraphs), scan the entire UI. + // If there is EXACTLY ONE MultiImageLoader in the workspace, safely assume that's the one. + let multiImageLoaders = []; + function findAllLoaders(nodes) { + if (!nodes) return; + for (let n of nodes) { + if (n.comfyClass === "MultiImageLoader") { + multiImageLoaders.push(n); + } + if (n.subgraph && n.subgraph._nodes) { + findAllLoaders(n.subgraph._nodes); + } + } + } + if (app.graph && app.graph._nodes) { + findAllLoaders(app.graph._nodes); + } + + if (multiImageLoaders.length === 1) { + return getCountFromNode(multiImageLoaders[0]); + } + + return null; + } + + // --- Backup polling via setInterval (4 Hz) --- + const pollInterval = setInterval(() => { + if (!node.graph) { + clearInterval(pollInterval); + return; + } + const count = readSourceImageCount(node); + if (count !== null) { + node._applyWidgetCount(count); + } + }, 250); + + // Clean up the interval when the node is deleted + const origOnRemoved = node.onRemoved; + node.onRemoved = function() { + // Unregister this node instance + window._LTXKeyframerGlobalNodes.delete(node); + + clearInterval(pollInterval); + if (origOnRemoved) origOnRemoved.apply(this, arguments); + }; + + // --- Connection change handler --- + const onConnectionsChange = node.onConnectionsChange; + node.onConnectionsChange = function(type, index, connected, link_info) { + if (onConnectionsChange) onConnectionsChange.apply(this, arguments); + + if (type === 1) { // 1 = Input + const input = this.inputs[index]; + if (input && input.name === "multi_input") { + if (connected) { + setTimeout(() => { + const count = readSourceImageCount(this); + this._applyWidgetCount(count !== null ? count : (this.properties.num_images || 0)); + }, 100); + } else { + this._applyWidgetCount(0); + } + } + } + }; + + // --- Initial sync when first placed on canvas --- + const origOnAdded = node.onAdded; + node.onAdded = function() { + if (origOnAdded) origOnAdded.apply(this, arguments); + setTimeout(() => { + const count = readSourceImageCount(this); + this._applyWidgetCount(count !== null ? count : (this.properties.num_images || 0)); + }, 100); + }; + } }); \ No newline at end of file diff --git a/js/ltx_sequencer.js b/js/ltx_sequencer.js index 2df62a0..000587a 100644 --- a/js/ltx_sequencer.js +++ b/js/ltx_sequencer.js @@ -1,398 +1,398 @@ -import { app } from "../../scripts/app.js"; - -// Global registry to track all LTXSequencer nodes across all subgraphs -window._LTXSequencerGlobalNodes = window._LTXSequencerGlobalNodes || new Set(); - -// ComfyUI native trick to cleanly hide/show widgets without deleting them -function toggleWidget(widget, visible) { - if (visible) { - if (widget.origType !== undefined) { - widget.type = widget.origType; - widget.computeSize = widget.origComputeSize; - delete widget.origType; - delete widget.origComputeSize; - } - } else { - if (widget.type !== "hidden") { - widget.origType = widget.type; - widget.origComputeSize = widget.computeSize; - widget.type = "hidden"; - widget.computeSize = () => [0, -4]; - } - } -} - -/** - * FULL STATE SYNC: - * Instead of syncing one widget, we push the entire properties object - * to ensure no values are ever lost during subgraph transitions or deletions. - */ -function syncFullStateAcrossNodes(sourceNode) { - if (!window._LTXSequencerGlobalNodes) return; - - for (const targetNode of window._LTXSequencerGlobalNodes) { - if (targetNode === sourceNode) continue; - - // 1. Mirror the properties object completely - // We use a shallow copy to ensure we don't accidentally share object references - const newState = { ...sourceNode.properties }; - targetNode.properties = { ...targetNode.properties, ...newState }; - - // 2. Check if we need to rebuild the widget list (if num_images changed) - const targetImageCount = targetNode.properties["num_images"] || 0; - const currentVisibleCount = targetNode._currentImageCount; - - if (targetImageCount !== currentVisibleCount) { - targetNode._applyWidgetCount(targetImageCount); - } - - // 3. Update all existing widget values visually - if (targetNode.widgets) { - let modeChanged = false; - targetNode.widgets.forEach(w => { - const newValue = targetNode.properties[w.name]; - if (newValue !== undefined && w.value !== newValue) { - w.value = newValue; - if (w.name === "insert_mode") modeChanged = true; - } - }); - - if (modeChanged && targetNode._updateVisibility) { - targetNode._updateVisibility(); - } - } - - targetNode.setDirtyCanvas(true, false); - } -} - -app.registerExtension({ - name: "Comfy.LTXSequencer.DynamicInputs", - async nodeCreated(node) { - if (node.comfyClass !== "LTXSequencer") return; - - // Register this node instance globally - window._LTXSequencerGlobalNodes.add(node); - - node._currentImageCount = -1; // Force first update - - // Initialize persistent properties cache - node.properties = node.properties || {}; - - // Add subtle separator line above images_loaded - node.addCustomWidget({ - name: "num_images_separator", - type: "text", - draw(ctx, node, widget_width, y, widget_height) { - ctx.save(); - ctx.strokeStyle = "#444"; - ctx.lineWidth = 1; - ctx.beginPath(); - ctx.moveTo(10, y + 5); - ctx.lineTo(widget_width - 10, y + 5); - ctx.stroke(); - ctx.restore(); - }, - computeSize(width) { - return [width, 10]; - } - }); - - // Move separator before num_images - const moveSeparator = () => { - const idx = node.widgets.findIndex(w => w.name === "num_images"); - const sepIdx = node.widgets.findIndex(w => w.name === "num_images_separator"); - if (idx !== -1 && sepIdx !== -1) { - const separator = node.widgets.splice(sepIdx, 1)[0]; - node.widgets.splice(idx, 0, separator); - } - }; - setTimeout(moveSeparator, 50); - - // Binds custom callbacks to python-schema generated widgets - node._hookStaticWidgets = function() { - if (!this.widgets) return; - const staticNames = ["num_images", "insert_mode", "frame_rate"]; - staticNames.forEach(name => { - const w = this.widgets.find(w => w.name === name); - if (w && !w._has_custom_callback) { - const orig = w.callback; - w.callback = (val) => { - this.properties[name] = val; - - if (name === "num_images") { - this._applyWidgetCount(val); - } - - // Push full state to siblings - syncFullStateAcrossNodes(this); - - if (name === "insert_mode") { - this._updateVisibility(); - } - if (orig) orig.apply(w, [val]); - }; - w._has_custom_callback = true; - } - }); - }; - - // Handles show/hiding widgets based on insertion method - node._updateVisibility = function() { - const mode = this.properties["insert_mode"] || "frames"; - if (!this.widgets) return; - - let changed = false; - for (const w of this.widgets) { - let shouldBeVisible = true; - - if (w.name.startsWith("insert_frame_")) { - shouldBeVisible = (mode === "frames"); - } else if (w.name.startsWith("insert_second_")) { - shouldBeVisible = (mode === "seconds"); - } - - const isHidden = (w.type === "hidden"); - if (shouldBeVisible && isHidden) { - toggleWidget(w, true); - changed = true; - } else if (!shouldBeVisible && !isHidden) { - toggleWidget(w, false); - changed = true; - } - } - - if (changed) { - this.setDirtyCanvas(true, true); - requestAnimationFrame(() => { - if (this.computeSize) { - this.setSize(this.computeSize()); - } - }); - } - }; - - // Core update: synchronize widget visibility to match imageCount - node._applyWidgetCount = function(count) { - this._hookStaticWidgets(); - - const isInitialLoad = this._currentImageCount === -1; - if (this._currentImageCount === count && !isInitialLoad) return; - this._currentImageCount = count; - - const initialWidth = this.size[0]; - const numWidget = this.widgets?.find(w => w.name === "num_images"); - if (numWidget) { - numWidget.label = "images_loaded"; - numWidget.value = Math.max(0, Math.min(count || 0, 50)); - } - - // 1. Update properties from current widget values before restructuring - if (this.widgets) { - this.widgets.forEach(w => { - if (w.name.startsWith("insert_") || w.name.startsWith("strength_") || ["num_images", "insert_mode", "frame_rate"].includes(w.name)) { - if (w.type !== "hidden" && w.type !== "button") this.properties[w.name] = w.value; - } - }); - } - - // 2. Clear dynamic widgets - if (this.widgets) { - this.widgets = this.widgets.filter(w => - !w.name.startsWith("insert_frame_") && - !w.name.startsWith("insert_second_") && - !w.name.startsWith("strength_") && - !w.name.startsWith("header_") - ); - } else { - this.widgets = []; - } - - // 3. Rebuild precisely - for (let i = 1; i <= count; i++) { - // Header - const headerName = `header_${i}`; - this.addCustomWidget({ - name: headerName, - type: "text", - value: `Image #${i}`, - draw(ctx, node, widget_width, y, widget_height) { - ctx.save(); - const margin = 10; - const topPadding = 15; - ctx.strokeStyle = "#333"; - ctx.lineWidth = 1; - ctx.beginPath(); - ctx.moveTo(margin, y + 5); - ctx.lineTo(widget_width - margin, y + 5); - ctx.stroke(); - ctx.fillStyle = "#dddddd"; - ctx.font = "bold 12px Arial"; - ctx.textAlign = "left"; - ctx.fillText(`Image #${i}`, margin, y + topPadding + 10); - ctx.restore(); - }, - computeSize(width) { return [width, 35]; } - }); - - // Helper to add widget with full sync - const addSyncedWidget = (type, name, def, options) => { - const saved = this.properties[name]; - return this.addWidget(type, name, saved !== undefined ? saved : def, (val) => { - this.properties[name] = val; - syncFullStateAcrossNodes(this); - }, options); - }; - - addSyncedWidget("number", `insert_frame_${i}`, 0, { min: -9999, max: 9999, step: 10, precision: 0 }); - addSyncedWidget("number", `insert_second_${i}`, 0.0, { min: 0.0, max: 9999.0, step: 0.1, precision: 2 }); - addSyncedWidget("number", `strength_${i}`, 1.0, { min: 0.0, max: 1.0, step: 0.01 }); - } - - this._updateVisibility(); - this.setDirtyCanvas(true, true); - requestAnimationFrame(() => { - if (this.computeSize) { - this.setSize(this.computeSize()); - this.size[0] = initialWidth; - } - }); - }; - - const origConfigure = node.configure; - node.configure = function(info) { - if (origConfigure) origConfigure.apply(this, arguments); - if (this.widgets) { - this.widgets.forEach(w => { - if (w.name.startsWith("insert_") || w.name.startsWith("strength_") || ["num_images", "insert_mode", "frame_rate"].includes(w.name)) { - if (w.type !== "button") this.properties[w.name] = w.value; - } - }); - } - }; - - node.onConfigure = function(info) { - if (info.properties) { - this.properties = { ...this.properties, ...info.properties }; - } - this._hookStaticWidgets(); - setTimeout(() => { - const count = readSourceImageCount(this); - let targetCount = count !== null ? count : (this.properties.num_images || 0); - this._applyWidgetCount(targetCount); - this._updateVisibility(); - }, 100); - }; - - const originalOnSerialize = node.onSerialize; - node.onSerialize = function(info) { - if (this.widgets) { - this.widgets.forEach(w => { - if (w.name.startsWith("insert_") || w.name.startsWith("strength_") || ["num_images", "insert_mode", "frame_rate"].includes(w.name)) { - if (w.type !== "hidden" && w.type !== "button") this.properties[w.name] = w.value; - } - }); - } - if (originalOnSerialize) originalOnSerialize.apply(this, arguments); - info.properties = { ...this.properties }; - - const strictArray = []; - strictArray.push(this.properties["num_images"] !== undefined ? this.properties["num_images"] : 1); - strictArray.push(this.properties["insert_mode"] !== undefined ? this.properties["insert_mode"] : "frames"); - strictArray.push(this.properties["frame_rate"] !== undefined ? this.properties["frame_rate"] : 24); - - for (let i = 1; i <= 50; i++) { - strictArray.push(this.properties[`insert_frame_${i}`] !== undefined ? this.properties[`insert_frame_${i}`] : 0); - strictArray.push(this.properties[`insert_second_${i}`] !== undefined ? this.properties[`insert_second_${i}`] : 0.0); - strictArray.push(this.properties[`strength_${i}`] !== undefined ? this.properties[`strength_${i}`] : 1.0); - } - info.widgets_values = strictArray; - }; - - function readSourceImageCount(self) { - const multiInput = self.inputs?.find(inp => inp.name === "multi_input"); - if (!multiInput || !multiInput.link) return null; - const nodeGraph = self.graph || app.graph; - - function traceUpstream(graph, linkId, visited = new Set()) { - if (!linkId || visited.has(linkId)) return null; - visited.add(linkId); - const link = graph.links[linkId]; - if (!link) return null; - const originNode = graph.getNodeById(link.origin_id); - if (!originNode) return null; - if (originNode.comfyClass === "MultiImageLoader") return originNode; - if (originNode.type === "Reroute" || originNode.comfyClass === "Reroute") { - if (originNode.inputs?.[0]?.link) return traceUpstream(graph, originNode.inputs[0].link, visited); - } - if (typeof originNode.getInnerNode === "function") { - try { - const innerNode = originNode.getInnerNode(link.origin_slot); - if (innerNode?.comfyClass === "MultiImageLoader") return innerNode; - } catch (e) {} - } - return null; - } - - let sourceNode = traceUpstream(nodeGraph, multiInput.link); - function getCountFromNode(n) { - if (typeof n._imageCount === "number") return n._imageCount; - const pathsWidget = n.widgets?.find(w => w.name === "image_paths"); - return pathsWidget ? (pathsWidget.value || "").split('\n').filter(p => p.trim()).length : null; - } - - if (sourceNode) return getCountFromNode(sourceNode); - - let multiImageLoaders = []; - function findAllLoaders(nodes) { - if (!nodes) return; - for (let n of nodes) { - if (n.comfyClass === "MultiImageLoader") multiImageLoaders.push(n); - if (n.subgraph?._nodes) findAllLoaders(n.subgraph._nodes); - } - } - if (app.graph?._nodes) findAllLoaders(app.graph._nodes); - if (multiImageLoaders.length === 1) return getCountFromNode(multiImageLoaders[0]); - return null; - } - - const pollInterval = setInterval(() => { - if (!node.graph) { - clearInterval(pollInterval); - return; - } - const count = readSourceImageCount(node); - if (count !== null && count !== node._currentImageCount) { - node._applyWidgetCount(count); - syncFullStateAcrossNodes(node); // Sync the new count to others - } - }, 500); - - const origOnRemoved = node.onRemoved; - node.onRemoved = function() { - window._LTXSequencerGlobalNodes.delete(node); - clearInterval(pollInterval); - if (origOnRemoved) origOnRemoved.apply(this, arguments); - }; - - node.onConnectionsChange = function(type, index, connected) { - if (type === 1 && this.inputs[index]?.name === "multi_input") { - if (connected) { - setTimeout(() => { - const count = readSourceImageCount(this); - this._applyWidgetCount(count !== null ? count : (this.properties.num_images || 0)); - }, 100); - } else { - this._applyWidgetCount(0); - } - } - }; - - node.onAdded = function() { - setTimeout(() => { - const count = readSourceImageCount(this); - this._applyWidgetCount(count !== null ? count : (this.properties.num_images || 0)); - }, 100); - }; - } +import { app } from "../../scripts/app.js"; + +// Global registry to track all LTXSequencer nodes across all subgraphs +window._LTXSequencerGlobalNodes = window._LTXSequencerGlobalNodes || new Set(); + +// ComfyUI native trick to cleanly hide/show widgets without deleting them +function toggleWidget(widget, visible) { + if (visible) { + if (widget.origType !== undefined) { + widget.type = widget.origType; + widget.computeSize = widget.origComputeSize; + delete widget.origType; + delete widget.origComputeSize; + } + } else { + if (widget.type !== "hidden") { + widget.origType = widget.type; + widget.origComputeSize = widget.computeSize; + widget.type = "hidden"; + widget.computeSize = () => [0, -4]; + } + } +} + +/** + * FULL STATE SYNC: + * Instead of syncing one widget, we push the entire properties object + * to ensure no values are ever lost during subgraph transitions or deletions. + */ +function syncFullStateAcrossNodes(sourceNode) { + if (!window._LTXSequencerGlobalNodes) return; + + for (const targetNode of window._LTXSequencerGlobalNodes) { + if (targetNode === sourceNode) continue; + + // 1. Mirror the properties object completely + // We use a shallow copy to ensure we don't accidentally share object references + const newState = { ...sourceNode.properties }; + targetNode.properties = { ...targetNode.properties, ...newState }; + + // 2. Check if we need to rebuild the widget list (if num_images changed) + const targetImageCount = targetNode.properties["num_images"] || 0; + const currentVisibleCount = targetNode._currentImageCount; + + if (targetImageCount !== currentVisibleCount) { + targetNode._applyWidgetCount(targetImageCount); + } + + // 3. Update all existing widget values visually + if (targetNode.widgets) { + let modeChanged = false; + targetNode.widgets.forEach(w => { + const newValue = targetNode.properties[w.name]; + if (newValue !== undefined && w.value !== newValue) { + w.value = newValue; + if (w.name === "insert_mode") modeChanged = true; + } + }); + + if (modeChanged && targetNode._updateVisibility) { + targetNode._updateVisibility(); + } + } + + targetNode.setDirtyCanvas(true, false); + } +} + +app.registerExtension({ + name: "Comfy.LTXSequencer.DynamicInputs", + async nodeCreated(node) { + if (node.comfyClass !== "LTXSequencer") return; + + // Register this node instance globally + window._LTXSequencerGlobalNodes.add(node); + + node._currentImageCount = -1; // Force first update + + // Initialize persistent properties cache + node.properties = node.properties || {}; + + // Add subtle separator line above images_loaded + node.addCustomWidget({ + name: "num_images_separator", + type: "text", + draw(ctx, node, widget_width, y, widget_height) { + ctx.save(); + ctx.strokeStyle = "#444"; + ctx.lineWidth = 1; + ctx.beginPath(); + ctx.moveTo(10, y + 5); + ctx.lineTo(widget_width - 10, y + 5); + ctx.stroke(); + ctx.restore(); + }, + computeSize(width) { + return [width, 10]; + } + }); + + // Move separator before num_images + const moveSeparator = () => { + const idx = node.widgets.findIndex(w => w.name === "num_images"); + const sepIdx = node.widgets.findIndex(w => w.name === "num_images_separator"); + if (idx !== -1 && sepIdx !== -1) { + const separator = node.widgets.splice(sepIdx, 1)[0]; + node.widgets.splice(idx, 0, separator); + } + }; + setTimeout(moveSeparator, 50); + + // Binds custom callbacks to python-schema generated widgets + node._hookStaticWidgets = function() { + if (!this.widgets) return; + const staticNames = ["num_images", "insert_mode", "frame_rate"]; + staticNames.forEach(name => { + const w = this.widgets.find(w => w.name === name); + if (w && !w._has_custom_callback) { + const orig = w.callback; + w.callback = (val) => { + this.properties[name] = val; + + if (name === "num_images") { + this._applyWidgetCount(val); + } + + // Push full state to siblings + syncFullStateAcrossNodes(this); + + if (name === "insert_mode") { + this._updateVisibility(); + } + if (orig) orig.apply(w, [val]); + }; + w._has_custom_callback = true; + } + }); + }; + + // Handles show/hiding widgets based on insertion method + node._updateVisibility = function() { + const mode = this.properties["insert_mode"] || "frames"; + if (!this.widgets) return; + + let changed = false; + for (const w of this.widgets) { + let shouldBeVisible = true; + + if (w.name.startsWith("insert_frame_")) { + shouldBeVisible = (mode === "frames"); + } else if (w.name.startsWith("insert_second_")) { + shouldBeVisible = (mode === "seconds"); + } + + const isHidden = (w.type === "hidden"); + if (shouldBeVisible && isHidden) { + toggleWidget(w, true); + changed = true; + } else if (!shouldBeVisible && !isHidden) { + toggleWidget(w, false); + changed = true; + } + } + + if (changed) { + this.setDirtyCanvas(true, true); + requestAnimationFrame(() => { + if (this.computeSize) { + this.setSize(this.computeSize()); + } + }); + } + }; + + // Core update: synchronize widget visibility to match imageCount + node._applyWidgetCount = function(count) { + this._hookStaticWidgets(); + + const isInitialLoad = this._currentImageCount === -1; + if (this._currentImageCount === count && !isInitialLoad) return; + this._currentImageCount = count; + + const initialWidth = this.size[0]; + const numWidget = this.widgets?.find(w => w.name === "num_images"); + if (numWidget) { + numWidget.label = "images_loaded"; + numWidget.value = Math.max(0, Math.min(count || 0, 50)); + } + + // 1. Update properties from current widget values before restructuring + if (this.widgets) { + this.widgets.forEach(w => { + if (w.name.startsWith("insert_") || w.name.startsWith("strength_") || ["num_images", "insert_mode", "frame_rate"].includes(w.name)) { + if (w.type !== "hidden" && w.type !== "button") this.properties[w.name] = w.value; + } + }); + } + + // 2. Clear dynamic widgets + if (this.widgets) { + this.widgets = this.widgets.filter(w => + !w.name.startsWith("insert_frame_") && + !w.name.startsWith("insert_second_") && + !w.name.startsWith("strength_") && + !w.name.startsWith("header_") + ); + } else { + this.widgets = []; + } + + // 3. Rebuild precisely + for (let i = 1; i <= count; i++) { + // Header + const headerName = `header_${i}`; + this.addCustomWidget({ + name: headerName, + type: "text", + value: `Image #${i}`, + draw(ctx, node, widget_width, y, widget_height) { + ctx.save(); + const margin = 10; + const topPadding = 15; + ctx.strokeStyle = "#333"; + ctx.lineWidth = 1; + ctx.beginPath(); + ctx.moveTo(margin, y + 5); + ctx.lineTo(widget_width - margin, y + 5); + ctx.stroke(); + ctx.fillStyle = "#dddddd"; + ctx.font = "bold 12px Arial"; + ctx.textAlign = "left"; + ctx.fillText(`Image #${i}`, margin, y + topPadding + 10); + ctx.restore(); + }, + computeSize(width) { return [width, 35]; } + }); + + // Helper to add widget with full sync + const addSyncedWidget = (type, name, def, options) => { + const saved = this.properties[name]; + return this.addWidget(type, name, saved !== undefined ? saved : def, (val) => { + this.properties[name] = val; + syncFullStateAcrossNodes(this); + }, options); + }; + + addSyncedWidget("number", `insert_frame_${i}`, 0, { min: -9999, max: 9999, step: 10, precision: 0 }); + addSyncedWidget("number", `insert_second_${i}`, 0.0, { min: 0.0, max: 9999.0, step: 0.1, precision: 2 }); + addSyncedWidget("number", `strength_${i}`, 1.0, { min: 0.0, max: 1.0, step: 0.01 }); + } + + this._updateVisibility(); + this.setDirtyCanvas(true, true); + requestAnimationFrame(() => { + if (this.computeSize) { + this.setSize(this.computeSize()); + this.size[0] = initialWidth; + } + }); + }; + + const origConfigure = node.configure; + node.configure = function(info) { + if (origConfigure) origConfigure.apply(this, arguments); + if (this.widgets) { + this.widgets.forEach(w => { + if (w.name.startsWith("insert_") || w.name.startsWith("strength_") || ["num_images", "insert_mode", "frame_rate"].includes(w.name)) { + if (w.type !== "button") this.properties[w.name] = w.value; + } + }); + } + }; + + node.onConfigure = function(info) { + if (info.properties) { + this.properties = { ...this.properties, ...info.properties }; + } + this._hookStaticWidgets(); + setTimeout(() => { + const count = readSourceImageCount(this); + let targetCount = count !== null ? count : (this.properties.num_images || 0); + this._applyWidgetCount(targetCount); + this._updateVisibility(); + }, 100); + }; + + const originalOnSerialize = node.onSerialize; + node.onSerialize = function(info) { + if (this.widgets) { + this.widgets.forEach(w => { + if (w.name.startsWith("insert_") || w.name.startsWith("strength_") || ["num_images", "insert_mode", "frame_rate"].includes(w.name)) { + if (w.type !== "hidden" && w.type !== "button") this.properties[w.name] = w.value; + } + }); + } + if (originalOnSerialize) originalOnSerialize.apply(this, arguments); + info.properties = { ...this.properties }; + + const strictArray = []; + strictArray.push(this.properties["num_images"] !== undefined ? this.properties["num_images"] : 1); + strictArray.push(this.properties["insert_mode"] !== undefined ? this.properties["insert_mode"] : "frames"); + strictArray.push(this.properties["frame_rate"] !== undefined ? this.properties["frame_rate"] : 24); + + for (let i = 1; i <= 50; i++) { + strictArray.push(this.properties[`insert_frame_${i}`] !== undefined ? this.properties[`insert_frame_${i}`] : 0); + strictArray.push(this.properties[`insert_second_${i}`] !== undefined ? this.properties[`insert_second_${i}`] : 0.0); + strictArray.push(this.properties[`strength_${i}`] !== undefined ? this.properties[`strength_${i}`] : 1.0); + } + info.widgets_values = strictArray; + }; + + function readSourceImageCount(self) { + const multiInput = self.inputs?.find(inp => inp.name === "multi_input"); + if (!multiInput || !multiInput.link) return null; + const nodeGraph = self.graph || app.graph; + + function traceUpstream(graph, linkId, visited = new Set()) { + if (!linkId || visited.has(linkId)) return null; + visited.add(linkId); + const link = graph.links[linkId]; + if (!link) return null; + const originNode = graph.getNodeById(link.origin_id); + if (!originNode) return null; + if (originNode.comfyClass === "MultiImageLoader") return originNode; + if (originNode.type === "Reroute" || originNode.comfyClass === "Reroute") { + if (originNode.inputs?.[0]?.link) return traceUpstream(graph, originNode.inputs[0].link, visited); + } + if (typeof originNode.getInnerNode === "function") { + try { + const innerNode = originNode.getInnerNode(link.origin_slot); + if (innerNode?.comfyClass === "MultiImageLoader") return innerNode; + } catch (e) {} + } + return null; + } + + let sourceNode = traceUpstream(nodeGraph, multiInput.link); + function getCountFromNode(n) { + if (typeof n._imageCount === "number") return n._imageCount; + const pathsWidget = n.widgets?.find(w => w.name === "image_paths"); + return pathsWidget ? (pathsWidget.value || "").split('\n').filter(p => p.trim()).length : null; + } + + if (sourceNode) return getCountFromNode(sourceNode); + + let multiImageLoaders = []; + function findAllLoaders(nodes) { + if (!nodes) return; + for (let n of nodes) { + if (n.comfyClass === "MultiImageLoader") multiImageLoaders.push(n); + if (n.subgraph?._nodes) findAllLoaders(n.subgraph._nodes); + } + } + if (app.graph?._nodes) findAllLoaders(app.graph._nodes); + if (multiImageLoaders.length === 1) return getCountFromNode(multiImageLoaders[0]); + return null; + } + + const pollInterval = setInterval(() => { + if (!node.graph) { + clearInterval(pollInterval); + return; + } + const count = readSourceImageCount(node); + if (count !== null && count !== node._currentImageCount) { + node._applyWidgetCount(count); + syncFullStateAcrossNodes(node); // Sync the new count to others + } + }, 500); + + const origOnRemoved = node.onRemoved; + node.onRemoved = function() { + window._LTXSequencerGlobalNodes.delete(node); + clearInterval(pollInterval); + if (origOnRemoved) origOnRemoved.apply(this, arguments); + }; + + node.onConnectionsChange = function(type, index, connected) { + if (type === 1 && this.inputs[index]?.name === "multi_input") { + if (connected) { + setTimeout(() => { + const count = readSourceImageCount(this); + this._applyWidgetCount(count !== null ? count : (this.properties.num_images || 0)); + }, 100); + } else { + this._applyWidgetCount(0); + } + } + }; + + node.onAdded = function() { + setTimeout(() => { + const count = readSourceImageCount(this); + this._applyWidgetCount(count !== null ? count : (this.properties.num_images || 0)); + }, 100); + }; + } }); \ No newline at end of file diff --git a/js/multi_image_loader.js b/js/multi_image_loader.js index d1a05cb..b67d927 100644 --- a/js/multi_image_loader.js +++ b/js/multi_image_loader.js @@ -1,688 +1,688 @@ -import { app } from "../../scripts/app.js"; -import { api } from "../../scripts/api.js"; - -app.registerExtension({ - name: "Comfy.MultiImageLoader", - async nodeCreated(node) { - if (node.comfyClass !== "MultiImageLoader") return; - - // Helper to detect if we are in the new Nodes 2.0 / V3 Web Component frontend - let v3NodeElement = null; - function checkIsV3() { - if (v3NodeElement) return true; - let el = container.parentElement; - while (el) { - if ((el.tagName && el.tagName.toLowerCase().includes('comfy-node')) || - (el.classList && el.classList.contains('comfy-node'))) { - v3NodeElement = el; - return true; - } - el = el.parentElement || (el.getRootNode ? el.getRootNode().host : null); - } - return false; - } - - // --- 1. UI Setup: Main Container --- - const container = document.createElement("div"); - container.style.cssText = ` - width: 100%; - min-height: 250px; - min-width: 100px; /* Reduced from 400px to allow thin resizing in V3 */ - background: #222222; - border: 1px solid #353545; - border-radius: 4px; - margin-top: 5px; - padding: 10px; - box-sizing: border-box; - display: flex; - flex-direction: column; - gap: 10px; - pointer-events: auto; - overflow: hidden; - `; - - // Top Bar for Actions - const topBar = document.createElement("div"); - // Added flex-wrap: wrap so buttons stack if the node gets extremely thin - topBar.style.cssText = "display: flex; flex-wrap: wrap; justify-content: flex-start; align-items: center; width: 100%; gap: 8px;"; - - const uploadBtn = document.createElement("button"); - uploadBtn.innerText = "Upload Images"; - uploadBtn.style.cssText = ` - background: #3a3f4b; color: white; border: 1px solid #5a5f6b; - padding: 3px 8px; border-radius: 3px; cursor: pointer; font-size: 10px; - `; - - const removeAllBtn = document.createElement("button"); - removeAllBtn.innerText = "Remove All"; - removeAllBtn.style.cssText = ` - background: #cc2222; color: white; border: 1px solid #aa1111; - padding: 3px 8px; border-radius: 3px; cursor: pointer; font-size: 10px; - transition: background 0.2s; - `; - removeAllBtn.onmouseenter = () => { removeAllBtn.style.background = "#ff3333"; }; - removeAllBtn.onmouseleave = () => { removeAllBtn.style.background = "#cc2222"; }; - removeAllBtn.onclick = () => { - setWidgetValue([], false); - }; - - topBar.appendChild(uploadBtn); - topBar.appendChild(removeAllBtn); - container.appendChild(topBar); - - const gridWrapper = document.createElement("div"); - gridWrapper.style.cssText = ` - position: relative; - flex-grow: 1; - width: 100%; - min-height: 0; - `; - - const grid = document.createElement("div"); - grid.style.cssText = ` - position: absolute; - top: 0; left: 0; right: 0; bottom: 0; - display: grid; - gap: 8px; - justify-content: center; - align-content: center; - `; - - gridWrapper.appendChild(grid); - container.appendChild(gridWrapper); - - const fileInput = document.createElement("input"); - fileInput.type = "file"; - fileInput.multiple = true; - fileInput.accept = "image/*"; - fileInput.style.display = "none"; - container.appendChild(fileInput); - - // Add the Widget to the Node - const galleryWidget = node.addDOMWidget("Gallery", "html_gallery", container, { serialize: false }); - - galleryWidget.computeSize = function() { - const galleryY = this.last_y || 40; - const minOutputsHeight = (node.outputs ? node.outputs.length : 1) * 20; - const requiredGalleryHeight = Math.max(250, minOutputsHeight + 40 - galleryY); - return [150, requiredGalleryHeight]; // Changed minimum theoretical widget width - }; - - // --- SAFELY HIDE THE IMAGE_PATHS WIDGET --- - const pathsWidget = node.widgets.find(w => w.name === "image_paths"); - if (pathsWidget) { - // Forcefully lock the hidden state against V3's reactive redraws - Object.defineProperty(pathsWidget, 'hidden', { - get: () => true, - set: () => {} // Ignore attempts by V3 to unhide it - }); - Object.defineProperty(pathsWidget, 'type', { - get: () => "hidden", - set: () => {} // Ignore attempts by V3 to reset the type - }); - - pathsWidget.computeSize = function() { - return [0, 0]; - }; - - // Catch for V3 delayed DOM rendering to ensure no stubborn inputs appear - const hideInterval = setInterval(() => { - if (pathsWidget.element) { - pathsWidget.element.style.display = "none"; - } - }, 50); - setTimeout(() => clearInterval(hideInterval), 1000); - } - - const oldCallback = pathsWidget?.callback; - - function setWidgetValue(newPathsArray, isRearranging = false) { - if (!pathsWidget) return; - const val = newPathsArray.join("\n"); - - const tempCallback = pathsWidget.callback; - pathsWidget.callback = null; - - pathsWidget.value = val; - if (oldCallback) oldCallback.apply(pathsWidget, [val]); - - pathsWidget.callback = tempCallback; - refreshGallery(isRearranging); - } - - // --- 2. Logic: Output Syncing & Dynamic Packing --- - function syncOutputs(count) { - if (!node.outputs) return; - - let changed = false; - const targetTotal = count + 1; - - const wasFresh = node.outputs.length >= 50; - - while (node.outputs.length > targetTotal && node.outputs.length > 1) { - node.removeOutput(node.outputs.length - 1); - changed = true; - } - - for (let i = node.outputs.length; i < targetTotal; i++) { - node.addOutput(`image_${i}`, "IMAGE"); - changed = true; - } - - if (changed || wasFresh) { - updateLayout(wasFresh); - } - } - - function notifyConnectedNodes(imageCount) { - if (!node.outputs) return; - for (const output of node.outputs) { - if (!output.links) continue; - for (const linkId of output.links) { - const link = app.graph.links[linkId]; - if (!link) continue; - const targetNode = app.graph.getNodeById(link.target_id); - if (targetNode && typeof targetNode._syncImageCount === "function") { - targetNode._syncImageCount(imageCount); - } - } - } - } - - function optimizeGrid(gridW, gridH) { - const paths = (pathsWidget?.value || "").split(/\n|,/).map(s => s.trim()).filter(s => s); - const N = paths.length; - - if (N === 0) { - grid.style.gridTemplateColumns = 'repeat(auto-fit, minmax(75px, 1fr))'; - grid.style.gridAutoRows = 'max-content'; - return; - } - - if (gridW <= 0 || gridH <= 0) return; - - let bestS = 0; - let bestCols = 1; - - for (let c = 1; c <= N; c++) { - const r = Math.ceil(N / c); - // Math.max guarantees we don't end up with negative max width in ultra-thin layouts - const maxW = Math.max(5, (gridW - (c - 1) * 8) / c); - const maxH = Math.max(5, (gridH - (r - 1) * 8) / r); - const size = Math.min(maxW, maxH); - - // By using >= instead of strict > (with a tiny 0.1 buffer for float precision), - // if multiple column counts yield the exact same optimal cell size - // (which happens when height is the bottleneck), we aggressively pack - // more items horizontally onto the row to fill empty space. - if (size >= bestS - 0.1) { - bestS = size; - bestCols = c; - } - } - - // Allow grid cells to shrink down to 10px instead of 15 to prevent horizontal overflow in V3 - bestS = Math.max(10, Math.floor(bestS)); - - grid.style.gridTemplateColumns = `repeat(${bestCols}, ${bestS}px)`; - grid.style.gridAutoRows = `${bestS}px`; - } - - let v3EventsAttached = false; - - function enforceV3CSS() { - const isV3 = checkIsV3(); - if (isV3 && v3NodeElement) { - const paddingBottom = 15; - const galleryY = galleryWidget.last_y || 40; - const minOutputsHeight = (node.outputs ? node.outputs.length : 1) * 20; - const absoluteMinHeight = Math.max(galleryY + 250 + paddingBottom, minOutputsHeight + 40); - - // For Nodes 2.0 (V3), we remove the min-width constraint completely. - // We leave min-height so outputs don't bleed out vertically. - v3NodeElement.style.removeProperty('min-width'); - v3NodeElement.style.setProperty('min-height', absoluteMinHeight + 'px', 'important'); - - // Attach drag & drop to the entire V3 Web Component - if (!v3EventsAttached) { - v3EventsAttached = true; - v3NodeElement.addEventListener("dragover", (e) => { - e.preventDefault(); - }); - v3NodeElement.addEventListener("drop", (e) => { - if (e.dataTransfer && e.dataTransfer.files) { - const files = Array.from(e.dataTransfer.files).filter(f => f.type.startsWith('image/')); - if (files.length > 0) { - e.preventDefault(); - e.stopPropagation(); - handleFiles(files); - } - } - }); - } - } - } - - let isLayouting = false; - - function updateLayout(forceShrink = false) { - if (isLayouting) return; - isLayouting = true; - - const isV3 = checkIsV3(); - const minW = isV3 ? 100 : 200; // 100 in V3, 440 in V1 - const paddingBottom = isV3 ? 15 : 25; // Apply extra 20px pad on V1 - - const galleryY = galleryWidget.last_y || 40; - const minOutputsHeight = (node.outputs ? node.outputs.length : 1) * 20; - const absoluteMinHeight = Math.max(galleryY + 250 + paddingBottom, minOutputsHeight + 40); - - node.min_size = [minW, absoluteMinHeight]; - enforceV3CSS(); - - let targetW = Math.max(node.size[0], minW); - let targetH = forceShrink ? absoluteMinHeight : node.size[1]; - - targetH = Math.max(targetH, absoluteMinHeight); - - if (node.size[0] !== targetW || node.size[1] !== targetH) { - node.setSize([targetW, targetH]); - app.graph.setDirtyCanvas(true, true); - } - - const availableGalleryHeight = targetH - galleryY - paddingBottom; - container.style.height = availableGalleryHeight + "px"; - - isLayouting = false; - } - - // --- OVERRIDE LOGIC FOR RESIZING --- - const origOnResize = node.onResize; - node.onResize = function(size) { - const isV3 = checkIsV3(); - const minW = isV3 ? 100 : 220; // Adjust limits based on frontend - const paddingBottom = isV3 ? 15 : 25; // Apply extra 20px pad on V1 - - const galleryY = galleryWidget.last_y || 40; - const minOutputsHeight = (this.outputs ? this.outputs.length : 1) * 20; - const absoluteMinHeight = Math.max(galleryY + 250 + paddingBottom, minOutputsHeight + 40); - - size[0] = Math.max(size[0], minW); - size[1] = Math.max(size[1], absoluteMinHeight); - - if (origOnResize) origOnResize.call(this, size); - if (isLayouting) return; - - node.min_size = [minW, absoluteMinHeight]; - enforceV3CSS(); - - const availableGalleryHeight = size[1] - galleryY - paddingBottom; - container.style.height = availableGalleryHeight + "px"; - }; - - const origComputeSize = node.computeSize; - node.computeSize = function(out) { - const isV3 = checkIsV3(); - const minW = isV3 ? 100 : 220; - const paddingBottom = isV3 ? 15 : 25; - - let res = origComputeSize ? origComputeSize.apply(this, arguments) : [minW, 250]; - const galleryY = galleryWidget.last_y || 40; - const minOutputsHeight = (this.outputs ? this.outputs.length : 1) * 20; - const absoluteMinHeight = Math.max(galleryY + 250 + paddingBottom, minOutputsHeight + 40); - - this.min_size = [minW, absoluteMinHeight]; - res[0] = Math.max(res[0], minW); - res[1] = Math.max(res[1], absoluteMinHeight); - - enforceV3CSS(); - return res; - }; - - const origSetSize = node.setSize; - node.setSize = function(size) { - const isV3 = checkIsV3(); - const minW = isV3 ? 100 : 220; - const paddingBottom = isV3 ? 15 : 25; - - const galleryY = galleryWidget.last_y || 40; - const minOutputsHeight = (this.outputs ? this.outputs.length : 1) * 20; - const absoluteMinHeight = Math.max(galleryY + 250 + paddingBottom, minOutputsHeight + 40); - - size[0] = Math.max(size[0], minW); - size[1] = Math.max(size[1], absoluteMinHeight); - - if (origSetSize) { - origSetSize.call(this, size); - } else { - this.size = size; - } - enforceV3CSS(); - }; - - let lastObservedWidth = 0; - let lastObservedHeight = 0; - - const resizeObserver = new ResizeObserver((entries) => { - enforceV3CSS(); - for (const entry of entries) { - const w = Math.round(entry.contentRect.width); - const h = Math.round(entry.contentRect.height); - - if (Math.abs(w - lastObservedWidth) > 1 || Math.abs(h - lastObservedHeight) > 1) { - lastObservedWidth = w; - lastObservedHeight = h; - if (h > 0) { - optimizeGrid(w, h); - } - } - } - }); - resizeObserver.observe(gridWrapper); - - // --- 3. Logic: Gallery Rendering --- - let draggedNode = null; - let lastSwapX = 0; - let lastSwapY = 0; - let lastSwapTime = 0; - - function refreshGallery(isRearranging = false) { - grid.innerHTML = ""; - const paths = (pathsWidget?.value || "").split(/\n|,/).map(s => s.trim()).filter(s => s); - - if (!isRearranging) { - syncOutputs(paths.length); - } - node._imageCount = paths.length; - notifyConnectedNodes(paths.length); - - paths.forEach((path, index) => { - const item = document.createElement("div"); - item.dataset.path = path; - item.draggable = true; - item.style.cssText = ` - position: relative; - width: 100%; - height: 100%; - aspect-ratio: 1 / 1; - background: #000000; - border-radius: 4px; - border: 1px solid #444; - overflow: hidden; - cursor: grab; - display: flex; - align-items: center; - justify-content: center; - `; - - const img = document.createElement("img"); - img.src = `/api/view?filename=${encodeURIComponent(path)}&type=input`; - // Allow pointer-events so context menu interacts directly with the image - img.style.cssText = "max-width: 100%; max-height: 100%; object-fit: contain; pointer-events: auto; display: block;"; - img.draggable = false; // Prevent native browser ghost dragging on the image itself - - const del = document.createElement("div"); - del.style.cssText = ` - position: absolute; top: 0; right: 0; - background: #cc2222; color: white; - width: 18px; height: 18px; - display: flex; align-items: center; justify-content: center; - font-size: 14px; cursor: pointer; z-index: 10; - font-family: Arial, sans-serif; font-weight: bold; - line-height: 1; border-bottom-left-radius: 4px; - transition: background 0.2s; - `; - del.innerHTML = ` - - `; - - del.onmouseenter = () => { del.style.background = "#ff3333"; }; - del.onmouseleave = () => { del.style.background = "#cc2222"; }; - - del.onclick = (e) => { - e.stopPropagation(); - const newPaths = paths.filter((_, i) => i !== index); - setWidgetValue(newPaths, false); - }; - - const numBadge = document.createElement("div"); - numBadge.style.cssText = ` - position: absolute; bottom: 0; left: 0; - background: rgba(0, 0, 0, 0.75); color: #fff; - padding: 2px 6px; font-size: 11px; font-family: sans-serif; - font-weight: bold; border-top-right-radius: 4px; pointer-events: none; - z-index: 5; - `; - numBadge.innerText = (index + 1).toString(); - - // Prevent LiteGraph context menu and instead show standard browser context menu (Copy, Save, Open) - item.addEventListener("contextmenu", (e) => { - e.stopPropagation(); - }); - - item.ondragstart = (e) => { - draggedNode = item; - - e.dataTransfer.setData('text/plain', path); - e.dataTransfer.effectAllowed = "move"; - - setTimeout(() => { - if (draggedNode === item) { - // Style as an empty dashed placeholder - item.style.background = "transparent"; - item.style.border = "2px dashed #666"; - // Hide the visual children (image, delete button, badge) - Array.from(item.children).forEach(c => c.style.opacity = "0"); - } - }, 0); - }; - - item.ondragend = () => { - if (draggedNode) { - // Restore original appearance - draggedNode.style.background = "#000000"; - draggedNode.style.border = "1px solid #444"; - Array.from(draggedNode.children).forEach(c => c.style.opacity = "1"); - } - draggedNode = null; - - const newPaths = Array.from(grid.children).map(n => n.dataset.path); - const currentVal = (pathsWidget?.value || "").trim(); - if (newPaths.join("\n") !== currentVal) { - setWidgetValue(newPaths, true); - } - }; - - item.ondragover = (e) => { - e.preventDefault(); - e.stopPropagation(); - if (!draggedNode || draggedNode === item) return; - - const distMoved = Math.hypot(e.clientX - lastSwapX, e.clientY - lastSwapY); - if (Date.now() - lastSwapTime < 50 && distMoved < 5) { - return; - } - - const itemRect = item.getBoundingClientRect(); - const bufferX = itemRect.width * 0.25; - const bufferY = itemRect.height * 0.25; - - if (e.clientX < itemRect.left + bufferX || e.clientX > itemRect.right - bufferX || - e.clientY < itemRect.top + bufferY || e.clientY > itemRect.bottom - bufferY) { - return; - } - - const items = Array.from(grid.children); - const draggedIdx = items.indexOf(draggedNode); - const targetIdx = items.indexOf(item); - - // Instantly snap the placeholder to its new position, moving items aside - if (draggedIdx < targetIdx) { - grid.insertBefore(draggedNode, item.nextSibling); - } else { - grid.insertBefore(draggedNode, item); - } - - lastSwapX = e.clientX; - lastSwapY = e.clientY; - lastSwapTime = Date.now(); - }; - - item.ondrop = (e) => { - e.preventDefault(); - e.stopPropagation(); - }; - - item.appendChild(img); - item.appendChild(del); - item.appendChild(numBadge); - grid.appendChild(item); - }); - - if (!isRearranging) { - requestAnimationFrame(() => { - updateLayout(); - if (gridWrapper.offsetWidth > 0) optimizeGrid(gridWrapper.offsetWidth, gridWrapper.offsetHeight); - }); - } - } - - // --- 4. Logic: File Handling --- - async function handleFiles(files) { - const uploaded = []; - for (const file of files) { - const body = new FormData(); - body.append("image", file); - try { - const resp = await api.fetchApi("/upload/image", { method: "POST", body }); - if (resp.status === 200) { - const data = await resp.json(); - let name = data.name; - if (data.subfolder) name = data.subfolder + "/" + name; - uploaded.push(name); - } - } catch (e) { console.error("Upload error", e); } - } - if (uploaded.length > 0) { - const current = (pathsWidget?.value || "").trim(); - const allPaths = current ? current.split('\n').concat(uploaded) : uploaded; - setWidgetValue(allPaths, false); - } - } - - // Apply drag & drop to LiteGraph node container bounds (V1) - const origOnDragDrop = node.onDragDrop; - node.onDragDrop = function(e) { - let handled = false; - if (e.dataTransfer && e.dataTransfer.files) { - const files = Array.from(e.dataTransfer.files).filter(f => f.type.startsWith('image/')); - if (files.length > 0) { - e.preventDefault(); - handleFiles(files); - handled = true; - } - } - if (!handled && origOnDragDrop) { - return origOnDragDrop.apply(this, arguments); - } - return handled; - }; - - const origOnDragOver = node.onDragOver; - node.onDragOver = function(e) { - if (e.dataTransfer && e.dataTransfer.items) { - const hasImage = Array.from(e.dataTransfer.items).some(f => f.kind === 'file' && f.type.startsWith('image/')); - if (hasImage) { - e.preventDefault(); - return true; - } - } - if (origOnDragOver) { - return origOnDragOver.apply(this, arguments); - } - return false; - }; - - uploadBtn.onclick = () => fileInput.click(); - fileInput.onchange = (e) => handleFiles(e.target.files); - - container.ondragover = (e) => { - e.preventDefault(); - e.stopPropagation(); - container.style.borderColor = "#4CAF50"; - }; - container.ondragleave = (e) => { - e.preventDefault(); - e.stopPropagation(); - container.style.borderColor = "#353545"; - }; - container.ondrop = (e) => { - e.preventDefault(); - e.stopPropagation(); - container.style.borderColor = "#353545"; - if (e.dataTransfer.files.length > 0) handleFiles(e.dataTransfer.files); - }; - - // --- 5. Logic: Paste Handling --- - const pasteHandler = (e) => { - if (app.canvas.selected_nodes && app.canvas.selected_nodes[node.id]) { - const items = e.clipboardData?.items; - if (!items) return; - - const files = []; - for (let i = 0; i < items.length; i++) { - if (items[i].kind === 'file' && items[i].type.startsWith('image/')) { - files.push(items[i].getAsFile()); - } - } - - if (files.length > 0) { - e.preventDefault(); - e.stopImmediatePropagation(); - handleFiles(files); - } - } - }; - - document.addEventListener("paste", pasteHandler, { capture: true }); - - const origOnRemoved = node.onRemoved; - node.onRemoved = function() { - document.removeEventListener("paste", pasteHandler, { capture: true }); - resizeObserver.disconnect(); - if (origOnRemoved) origOnRemoved.apply(this, arguments); - }; - - if (pathsWidget) { - pathsWidget.callback = (v) => { - if (oldCallback) oldCallback.apply(pathsWidget, [v]); - refreshGallery(); - }; - } - - // Run immediately to trim blank outputs before LiteGraph does its first layout calculations - refreshGallery(); - - // Enforce the tightest possible packing explicitly upon being dropped into the graph (V1 specifically) - const origOnAdded = node.onAdded; - node.onAdded = function() { - if (origOnAdded) origOnAdded.apply(this, arguments); - const isV3 = checkIsV3(); - if (!isV3) { - requestAnimationFrame(() => { - const galleryY = galleryWidget.last_y || 40; - const minOutputsHeight = (this.outputs ? this.outputs.length : 1) * 20; - const paddingBottom = 25; // Apply extra 20px pad on V1 - const absoluteMinHeight = Math.max(galleryY + 250 + paddingBottom, minOutputsHeight + 40); - // Force the node to snap to its absolute minimum size on initial drop - if (this.size && this.size[1] > absoluteMinHeight + 5) { - this.setSize([this.size[0], absoluteMinHeight]); - if (app.graph) app.graph.setDirtyCanvas(true, true); - } - }); - } - }; - - setTimeout(() => refreshGallery(), 100); - } +import { app } from "../../scripts/app.js"; +import { api } from "../../scripts/api.js"; + +app.registerExtension({ + name: "Comfy.MultiImageLoader", + async nodeCreated(node) { + if (node.comfyClass !== "MultiImageLoader") return; + + // Helper to detect if we are in the new Nodes 2.0 / V3 Web Component frontend + let v3NodeElement = null; + function checkIsV3() { + if (v3NodeElement) return true; + let el = container.parentElement; + while (el) { + if ((el.tagName && el.tagName.toLowerCase().includes('comfy-node')) || + (el.classList && el.classList.contains('comfy-node'))) { + v3NodeElement = el; + return true; + } + el = el.parentElement || (el.getRootNode ? el.getRootNode().host : null); + } + return false; + } + + // --- 1. UI Setup: Main Container --- + const container = document.createElement("div"); + container.style.cssText = ` + width: 100%; + min-height: 250px; + min-width: 100px; /* Reduced from 400px to allow thin resizing in V3 */ + background: #222222; + border: 1px solid #353545; + border-radius: 4px; + margin-top: 5px; + padding: 10px; + box-sizing: border-box; + display: flex; + flex-direction: column; + gap: 10px; + pointer-events: auto; + overflow: hidden; + `; + + // Top Bar for Actions + const topBar = document.createElement("div"); + // Added flex-wrap: wrap so buttons stack if the node gets extremely thin + topBar.style.cssText = "display: flex; flex-wrap: wrap; justify-content: flex-start; align-items: center; width: 100%; gap: 8px;"; + + const uploadBtn = document.createElement("button"); + uploadBtn.innerText = "Upload Images"; + uploadBtn.style.cssText = ` + background: #3a3f4b; color: white; border: 1px solid #5a5f6b; + padding: 3px 8px; border-radius: 3px; cursor: pointer; font-size: 10px; + `; + + const removeAllBtn = document.createElement("button"); + removeAllBtn.innerText = "Remove All"; + removeAllBtn.style.cssText = ` + background: #cc2222; color: white; border: 1px solid #aa1111; + padding: 3px 8px; border-radius: 3px; cursor: pointer; font-size: 10px; + transition: background 0.2s; + `; + removeAllBtn.onmouseenter = () => { removeAllBtn.style.background = "#ff3333"; }; + removeAllBtn.onmouseleave = () => { removeAllBtn.style.background = "#cc2222"; }; + removeAllBtn.onclick = () => { + setWidgetValue([], false); + }; + + topBar.appendChild(uploadBtn); + topBar.appendChild(removeAllBtn); + container.appendChild(topBar); + + const gridWrapper = document.createElement("div"); + gridWrapper.style.cssText = ` + position: relative; + flex-grow: 1; + width: 100%; + min-height: 0; + `; + + const grid = document.createElement("div"); + grid.style.cssText = ` + position: absolute; + top: 0; left: 0; right: 0; bottom: 0; + display: grid; + gap: 8px; + justify-content: center; + align-content: center; + `; + + gridWrapper.appendChild(grid); + container.appendChild(gridWrapper); + + const fileInput = document.createElement("input"); + fileInput.type = "file"; + fileInput.multiple = true; + fileInput.accept = "image/*"; + fileInput.style.display = "none"; + container.appendChild(fileInput); + + // Add the Widget to the Node + const galleryWidget = node.addDOMWidget("Gallery", "html_gallery", container, { serialize: false }); + + galleryWidget.computeSize = function() { + const galleryY = this.last_y || 40; + const minOutputsHeight = (node.outputs ? node.outputs.length : 1) * 20; + const requiredGalleryHeight = Math.max(250, minOutputsHeight + 40 - galleryY); + return [150, requiredGalleryHeight]; // Changed minimum theoretical widget width + }; + + // --- SAFELY HIDE THE IMAGE_PATHS WIDGET --- + const pathsWidget = node.widgets.find(w => w.name === "image_paths"); + if (pathsWidget) { + // Forcefully lock the hidden state against V3's reactive redraws + Object.defineProperty(pathsWidget, 'hidden', { + get: () => true, + set: () => {} // Ignore attempts by V3 to unhide it + }); + Object.defineProperty(pathsWidget, 'type', { + get: () => "hidden", + set: () => {} // Ignore attempts by V3 to reset the type + }); + + pathsWidget.computeSize = function() { + return [0, 0]; + }; + + // Catch for V3 delayed DOM rendering to ensure no stubborn inputs appear + const hideInterval = setInterval(() => { + if (pathsWidget.element) { + pathsWidget.element.style.display = "none"; + } + }, 50); + setTimeout(() => clearInterval(hideInterval), 1000); + } + + const oldCallback = pathsWidget?.callback; + + function setWidgetValue(newPathsArray, isRearranging = false) { + if (!pathsWidget) return; + const val = newPathsArray.join("\n"); + + const tempCallback = pathsWidget.callback; + pathsWidget.callback = null; + + pathsWidget.value = val; + if (oldCallback) oldCallback.apply(pathsWidget, [val]); + + pathsWidget.callback = tempCallback; + refreshGallery(isRearranging); + } + + // --- 2. Logic: Output Syncing & Dynamic Packing --- + function syncOutputs(count) { + if (!node.outputs) return; + + let changed = false; + const targetTotal = count + 1; + + const wasFresh = node.outputs.length >= 50; + + while (node.outputs.length > targetTotal && node.outputs.length > 1) { + node.removeOutput(node.outputs.length - 1); + changed = true; + } + + for (let i = node.outputs.length; i < targetTotal; i++) { + node.addOutput(`image_${i}`, "IMAGE"); + changed = true; + } + + if (changed || wasFresh) { + updateLayout(wasFresh); + } + } + + function notifyConnectedNodes(imageCount) { + if (!node.outputs) return; + for (const output of node.outputs) { + if (!output.links) continue; + for (const linkId of output.links) { + const link = app.graph.links[linkId]; + if (!link) continue; + const targetNode = app.graph.getNodeById(link.target_id); + if (targetNode && typeof targetNode._syncImageCount === "function") { + targetNode._syncImageCount(imageCount); + } + } + } + } + + function optimizeGrid(gridW, gridH) { + const paths = (pathsWidget?.value || "").split(/\n|,/).map(s => s.trim()).filter(s => s); + const N = paths.length; + + if (N === 0) { + grid.style.gridTemplateColumns = 'repeat(auto-fit, minmax(75px, 1fr))'; + grid.style.gridAutoRows = 'max-content'; + return; + } + + if (gridW <= 0 || gridH <= 0) return; + + let bestS = 0; + let bestCols = 1; + + for (let c = 1; c <= N; c++) { + const r = Math.ceil(N / c); + // Math.max guarantees we don't end up with negative max width in ultra-thin layouts + const maxW = Math.max(5, (gridW - (c - 1) * 8) / c); + const maxH = Math.max(5, (gridH - (r - 1) * 8) / r); + const size = Math.min(maxW, maxH); + + // By using >= instead of strict > (with a tiny 0.1 buffer for float precision), + // if multiple column counts yield the exact same optimal cell size + // (which happens when height is the bottleneck), we aggressively pack + // more items horizontally onto the row to fill empty space. + if (size >= bestS - 0.1) { + bestS = size; + bestCols = c; + } + } + + // Allow grid cells to shrink down to 10px instead of 15 to prevent horizontal overflow in V3 + bestS = Math.max(10, Math.floor(bestS)); + + grid.style.gridTemplateColumns = `repeat(${bestCols}, ${bestS}px)`; + grid.style.gridAutoRows = `${bestS}px`; + } + + let v3EventsAttached = false; + + function enforceV3CSS() { + const isV3 = checkIsV3(); + if (isV3 && v3NodeElement) { + const paddingBottom = 15; + const galleryY = galleryWidget.last_y || 40; + const minOutputsHeight = (node.outputs ? node.outputs.length : 1) * 20; + const absoluteMinHeight = Math.max(galleryY + 250 + paddingBottom, minOutputsHeight + 40); + + // For Nodes 2.0 (V3), we remove the min-width constraint completely. + // We leave min-height so outputs don't bleed out vertically. + v3NodeElement.style.removeProperty('min-width'); + v3NodeElement.style.setProperty('min-height', absoluteMinHeight + 'px', 'important'); + + // Attach drag & drop to the entire V3 Web Component + if (!v3EventsAttached) { + v3EventsAttached = true; + v3NodeElement.addEventListener("dragover", (e) => { + e.preventDefault(); + }); + v3NodeElement.addEventListener("drop", (e) => { + if (e.dataTransfer && e.dataTransfer.files) { + const files = Array.from(e.dataTransfer.files).filter(f => f.type.startsWith('image/')); + if (files.length > 0) { + e.preventDefault(); + e.stopPropagation(); + handleFiles(files); + } + } + }); + } + } + } + + let isLayouting = false; + + function updateLayout(forceShrink = false) { + if (isLayouting) return; + isLayouting = true; + + const isV3 = checkIsV3(); + const minW = isV3 ? 100 : 200; // 100 in V3, 440 in V1 + const paddingBottom = isV3 ? 15 : 25; // Apply extra 20px pad on V1 + + const galleryY = galleryWidget.last_y || 40; + const minOutputsHeight = (node.outputs ? node.outputs.length : 1) * 20; + const absoluteMinHeight = Math.max(galleryY + 250 + paddingBottom, minOutputsHeight + 40); + + node.min_size = [minW, absoluteMinHeight]; + enforceV3CSS(); + + let targetW = Math.max(node.size[0], minW); + let targetH = forceShrink ? absoluteMinHeight : node.size[1]; + + targetH = Math.max(targetH, absoluteMinHeight); + + if (node.size[0] !== targetW || node.size[1] !== targetH) { + node.setSize([targetW, targetH]); + app.graph.setDirtyCanvas(true, true); + } + + const availableGalleryHeight = targetH - galleryY - paddingBottom; + container.style.height = availableGalleryHeight + "px"; + + isLayouting = false; + } + + // --- OVERRIDE LOGIC FOR RESIZING --- + const origOnResize = node.onResize; + node.onResize = function(size) { + const isV3 = checkIsV3(); + const minW = isV3 ? 100 : 220; // Adjust limits based on frontend + const paddingBottom = isV3 ? 15 : 25; // Apply extra 20px pad on V1 + + const galleryY = galleryWidget.last_y || 40; + const minOutputsHeight = (this.outputs ? this.outputs.length : 1) * 20; + const absoluteMinHeight = Math.max(galleryY + 250 + paddingBottom, minOutputsHeight + 40); + + size[0] = Math.max(size[0], minW); + size[1] = Math.max(size[1], absoluteMinHeight); + + if (origOnResize) origOnResize.call(this, size); + if (isLayouting) return; + + node.min_size = [minW, absoluteMinHeight]; + enforceV3CSS(); + + const availableGalleryHeight = size[1] - galleryY - paddingBottom; + container.style.height = availableGalleryHeight + "px"; + }; + + const origComputeSize = node.computeSize; + node.computeSize = function(out) { + const isV3 = checkIsV3(); + const minW = isV3 ? 100 : 220; + const paddingBottom = isV3 ? 15 : 25; + + let res = origComputeSize ? origComputeSize.apply(this, arguments) : [minW, 250]; + const galleryY = galleryWidget.last_y || 40; + const minOutputsHeight = (this.outputs ? this.outputs.length : 1) * 20; + const absoluteMinHeight = Math.max(galleryY + 250 + paddingBottom, minOutputsHeight + 40); + + this.min_size = [minW, absoluteMinHeight]; + res[0] = Math.max(res[0], minW); + res[1] = Math.max(res[1], absoluteMinHeight); + + enforceV3CSS(); + return res; + }; + + const origSetSize = node.setSize; + node.setSize = function(size) { + const isV3 = checkIsV3(); + const minW = isV3 ? 100 : 220; + const paddingBottom = isV3 ? 15 : 25; + + const galleryY = galleryWidget.last_y || 40; + const minOutputsHeight = (this.outputs ? this.outputs.length : 1) * 20; + const absoluteMinHeight = Math.max(galleryY + 250 + paddingBottom, minOutputsHeight + 40); + + size[0] = Math.max(size[0], minW); + size[1] = Math.max(size[1], absoluteMinHeight); + + if (origSetSize) { + origSetSize.call(this, size); + } else { + this.size = size; + } + enforceV3CSS(); + }; + + let lastObservedWidth = 0; + let lastObservedHeight = 0; + + const resizeObserver = new ResizeObserver((entries) => { + enforceV3CSS(); + for (const entry of entries) { + const w = Math.round(entry.contentRect.width); + const h = Math.round(entry.contentRect.height); + + if (Math.abs(w - lastObservedWidth) > 1 || Math.abs(h - lastObservedHeight) > 1) { + lastObservedWidth = w; + lastObservedHeight = h; + if (h > 0) { + optimizeGrid(w, h); + } + } + } + }); + resizeObserver.observe(gridWrapper); + + // --- 3. Logic: Gallery Rendering --- + let draggedNode = null; + let lastSwapX = 0; + let lastSwapY = 0; + let lastSwapTime = 0; + + function refreshGallery(isRearranging = false) { + grid.innerHTML = ""; + const paths = (pathsWidget?.value || "").split(/\n|,/).map(s => s.trim()).filter(s => s); + + if (!isRearranging) { + syncOutputs(paths.length); + } + node._imageCount = paths.length; + notifyConnectedNodes(paths.length); + + paths.forEach((path, index) => { + const item = document.createElement("div"); + item.dataset.path = path; + item.draggable = true; + item.style.cssText = ` + position: relative; + width: 100%; + height: 100%; + aspect-ratio: 1 / 1; + background: #000000; + border-radius: 4px; + border: 1px solid #444; + overflow: hidden; + cursor: grab; + display: flex; + align-items: center; + justify-content: center; + `; + + const img = document.createElement("img"); + img.src = `/api/view?filename=${encodeURIComponent(path)}&type=input`; + // Allow pointer-events so context menu interacts directly with the image + img.style.cssText = "max-width: 100%; max-height: 100%; object-fit: contain; pointer-events: auto; display: block;"; + img.draggable = false; // Prevent native browser ghost dragging on the image itself + + const del = document.createElement("div"); + del.style.cssText = ` + position: absolute; top: 0; right: 0; + background: #cc2222; color: white; + width: 18px; height: 18px; + display: flex; align-items: center; justify-content: center; + font-size: 14px; cursor: pointer; z-index: 10; + font-family: Arial, sans-serif; font-weight: bold; + line-height: 1; border-bottom-left-radius: 4px; + transition: background 0.2s; + `; + del.innerHTML = ` + + `; + + del.onmouseenter = () => { del.style.background = "#ff3333"; }; + del.onmouseleave = () => { del.style.background = "#cc2222"; }; + + del.onclick = (e) => { + e.stopPropagation(); + const newPaths = paths.filter((_, i) => i !== index); + setWidgetValue(newPaths, false); + }; + + const numBadge = document.createElement("div"); + numBadge.style.cssText = ` + position: absolute; bottom: 0; left: 0; + background: rgba(0, 0, 0, 0.75); color: #fff; + padding: 2px 6px; font-size: 11px; font-family: sans-serif; + font-weight: bold; border-top-right-radius: 4px; pointer-events: none; + z-index: 5; + `; + numBadge.innerText = (index + 1).toString(); + + // Prevent LiteGraph context menu and instead show standard browser context menu (Copy, Save, Open) + item.addEventListener("contextmenu", (e) => { + e.stopPropagation(); + }); + + item.ondragstart = (e) => { + draggedNode = item; + + e.dataTransfer.setData('text/plain', path); + e.dataTransfer.effectAllowed = "move"; + + setTimeout(() => { + if (draggedNode === item) { + // Style as an empty dashed placeholder + item.style.background = "transparent"; + item.style.border = "2px dashed #666"; + // Hide the visual children (image, delete button, badge) + Array.from(item.children).forEach(c => c.style.opacity = "0"); + } + }, 0); + }; + + item.ondragend = () => { + if (draggedNode) { + // Restore original appearance + draggedNode.style.background = "#000000"; + draggedNode.style.border = "1px solid #444"; + Array.from(draggedNode.children).forEach(c => c.style.opacity = "1"); + } + draggedNode = null; + + const newPaths = Array.from(grid.children).map(n => n.dataset.path); + const currentVal = (pathsWidget?.value || "").trim(); + if (newPaths.join("\n") !== currentVal) { + setWidgetValue(newPaths, true); + } + }; + + item.ondragover = (e) => { + e.preventDefault(); + e.stopPropagation(); + if (!draggedNode || draggedNode === item) return; + + const distMoved = Math.hypot(e.clientX - lastSwapX, e.clientY - lastSwapY); + if (Date.now() - lastSwapTime < 50 && distMoved < 5) { + return; + } + + const itemRect = item.getBoundingClientRect(); + const bufferX = itemRect.width * 0.25; + const bufferY = itemRect.height * 0.25; + + if (e.clientX < itemRect.left + bufferX || e.clientX > itemRect.right - bufferX || + e.clientY < itemRect.top + bufferY || e.clientY > itemRect.bottom - bufferY) { + return; + } + + const items = Array.from(grid.children); + const draggedIdx = items.indexOf(draggedNode); + const targetIdx = items.indexOf(item); + + // Instantly snap the placeholder to its new position, moving items aside + if (draggedIdx < targetIdx) { + grid.insertBefore(draggedNode, item.nextSibling); + } else { + grid.insertBefore(draggedNode, item); + } + + lastSwapX = e.clientX; + lastSwapY = e.clientY; + lastSwapTime = Date.now(); + }; + + item.ondrop = (e) => { + e.preventDefault(); + e.stopPropagation(); + }; + + item.appendChild(img); + item.appendChild(del); + item.appendChild(numBadge); + grid.appendChild(item); + }); + + if (!isRearranging) { + requestAnimationFrame(() => { + updateLayout(); + if (gridWrapper.offsetWidth > 0) optimizeGrid(gridWrapper.offsetWidth, gridWrapper.offsetHeight); + }); + } + } + + // --- 4. Logic: File Handling --- + async function handleFiles(files) { + const uploaded = []; + for (const file of files) { + const body = new FormData(); + body.append("image", file); + try { + const resp = await api.fetchApi("/upload/image", { method: "POST", body }); + if (resp.status === 200) { + const data = await resp.json(); + let name = data.name; + if (data.subfolder) name = data.subfolder + "/" + name; + uploaded.push(name); + } + } catch (e) { console.error("Upload error", e); } + } + if (uploaded.length > 0) { + const current = (pathsWidget?.value || "").trim(); + const allPaths = current ? current.split('\n').concat(uploaded) : uploaded; + setWidgetValue(allPaths, false); + } + } + + // Apply drag & drop to LiteGraph node container bounds (V1) + const origOnDragDrop = node.onDragDrop; + node.onDragDrop = function(e) { + let handled = false; + if (e.dataTransfer && e.dataTransfer.files) { + const files = Array.from(e.dataTransfer.files).filter(f => f.type.startsWith('image/')); + if (files.length > 0) { + e.preventDefault(); + handleFiles(files); + handled = true; + } + } + if (!handled && origOnDragDrop) { + return origOnDragDrop.apply(this, arguments); + } + return handled; + }; + + const origOnDragOver = node.onDragOver; + node.onDragOver = function(e) { + if (e.dataTransfer && e.dataTransfer.items) { + const hasImage = Array.from(e.dataTransfer.items).some(f => f.kind === 'file' && f.type.startsWith('image/')); + if (hasImage) { + e.preventDefault(); + return true; + } + } + if (origOnDragOver) { + return origOnDragOver.apply(this, arguments); + } + return false; + }; + + uploadBtn.onclick = () => fileInput.click(); + fileInput.onchange = (e) => handleFiles(e.target.files); + + container.ondragover = (e) => { + e.preventDefault(); + e.stopPropagation(); + container.style.borderColor = "#4CAF50"; + }; + container.ondragleave = (e) => { + e.preventDefault(); + e.stopPropagation(); + container.style.borderColor = "#353545"; + }; + container.ondrop = (e) => { + e.preventDefault(); + e.stopPropagation(); + container.style.borderColor = "#353545"; + if (e.dataTransfer.files.length > 0) handleFiles(e.dataTransfer.files); + }; + + // --- 5. Logic: Paste Handling --- + const pasteHandler = (e) => { + if (app.canvas.selected_nodes && app.canvas.selected_nodes[node.id]) { + const items = e.clipboardData?.items; + if (!items) return; + + const files = []; + for (let i = 0; i < items.length; i++) { + if (items[i].kind === 'file' && items[i].type.startsWith('image/')) { + files.push(items[i].getAsFile()); + } + } + + if (files.length > 0) { + e.preventDefault(); + e.stopImmediatePropagation(); + handleFiles(files); + } + } + }; + + document.addEventListener("paste", pasteHandler, { capture: true }); + + const origOnRemoved = node.onRemoved; + node.onRemoved = function() { + document.removeEventListener("paste", pasteHandler, { capture: true }); + resizeObserver.disconnect(); + if (origOnRemoved) origOnRemoved.apply(this, arguments); + }; + + if (pathsWidget) { + pathsWidget.callback = (v) => { + if (oldCallback) oldCallback.apply(pathsWidget, [v]); + refreshGallery(); + }; + } + + // Run immediately to trim blank outputs before LiteGraph does its first layout calculations + refreshGallery(); + + // Enforce the tightest possible packing explicitly upon being dropped into the graph (V1 specifically) + const origOnAdded = node.onAdded; + node.onAdded = function() { + if (origOnAdded) origOnAdded.apply(this, arguments); + const isV3 = checkIsV3(); + if (!isV3) { + requestAnimationFrame(() => { + const galleryY = galleryWidget.last_y || 40; + const minOutputsHeight = (this.outputs ? this.outputs.length : 1) * 20; + const paddingBottom = 25; // Apply extra 20px pad on V1 + const absoluteMinHeight = Math.max(galleryY + 250 + paddingBottom, minOutputsHeight + 40); + // Force the node to snap to its absolute minimum size on initial drop + if (this.size && this.size[1] > absoluteMinHeight + 5) { + this.setSize([this.size[0], absoluteMinHeight]); + if (app.graph) app.graph.setDirtyCanvas(true, true); + } + }); + } + }; + + setTimeout(() => refreshGallery(), 100); + } }); \ No newline at end of file diff --git a/js/speech_length_calculator.js b/js/speech_length_calculator.js index 93a51de..dfcf565 100644 --- a/js/speech_length_calculator.js +++ b/js/speech_length_calculator.js @@ -1,336 +1,336 @@ -import { app } from "../../scripts/app.js"; -import { ComfyWidgets } from "../../scripts/widgets.js"; - -// 1. DEFINE CSS STYLES GLOBALLY ONCE -// We use DOM elements instead of Canvas drawing so it works flawlessly in ComfyUI V3 -const cssStyles = ` - .slc-ui-container { - width: 100%; - height: 100%; - min-height: 260px; /* Increased from 220px to prevent V1 cropping */ - background-color: rgba(15, 15, 19, 0.7); - border: 1px solid rgba(255, 255, 255, 0.1); - border-radius: 8px; - padding: 12px; - box-sizing: border-box; - display: flex; - flex-direction: column; - font-family: sans-serif; - color: #ffffff; - overflow: hidden; - pointer-events: auto; /* allows text selection */ - } - .slc-empty { - flex: 1; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - } - .slc-empty-title { color: #aaaaaa; font-size: 14px; font-weight: 500; margin-bottom: 4px; } - .slc-empty-sub { color: #777777; font-size: 11px; } - .slc-headers { - display: flex; - gap: 8px; - margin-bottom: 12px; - } - .slc-header-block { - flex: 1; - background-color: rgba(0, 0, 0, 0.4); - border-radius: 6px; - padding: 8px 0; - display: flex; - flex-direction: column; - align-items: center; - } - .slc-header-title { color: #888888; font-size: 9px; font-weight: bold; margin-bottom: 4px; } - .slc-header-value { color: #ffffff; font-size: 16px; font-weight: bold; } - .slc-cards { - display: flex; - flex-direction: column; - gap: 8px; - } - .slc-card { - display: flex; - justify-content: space-between; - align-items: center; - background-color: rgba(0, 0, 0, 0.25); - border: 1px solid rgba(255, 255, 255, 0.05); - border-radius: 4px; - padding: 8px 12px 8px 16px; - position: relative; - overflow: hidden; - } - .slc-card::before { - content: ''; - position: absolute; - left: 0; - top: 0; - bottom: 0; - width: 4px; - } - .slc-card.slow::before { background-color: #93c5fd; } - .slc-card.avg::before { background-color: #86efac; } - .slc-card.fast::before { background-color: #fca5a5; } - - .slc-card-left { display: flex; flex-direction: column; } - .slc-card-speed { font-size: 12px; font-weight: bold; margin-bottom: 2px; } - .slc-card.slow .slc-card-speed { color: #93c5fd; } - .slc-card.avg .slc-card-speed { color: #86efac; } - .slc-card.fast .slc-card-speed { color: #fca5a5; } - .slc-card-wpm { font-size: 10px; color: #aaaaaa; } - - .slc-card-right { display: flex; flex-direction: column; align-items: flex-end; } - .slc-card-time { font-size: 13px; font-weight: bold; color: #ffffff; margin-bottom: 2px; } - .slc-card-frames { font-size: 11px; font-family: monospace; color: #888888; } - - .slc-legend { - margin-top: auto; - padding-top: 8px; - color: #999999; - font-size: 10px; - font-weight: 500; - } -`; - -// Inject styles to document head safely -if (!document.getElementById("speech-length-calculator-styles")) { - const styleEl = document.createElement("style"); - styleEl.id = "speech-length-calculator-styles"; - styleEl.textContent = cssStyles; - document.head.appendChild(styleEl); -} - -// 2. HELPER TO GENERATE THE HTML CONTENT -function buildUIHTML(statsData) { - if (!statsData || statsData.empty) { - return ` -
-
Awaiting Script
-
Wrap spoken text inside "quotes"
-
- `; - } - - return ` -
-
-
SPOKEN WORDS
-
${statsData.wordCount}
-
-
-
ADDED TIME
-
${statsData.additionalTime}s
-
-
-
-
-
-
SLOW
-
100 WPM
-
-
-
${statsData.slow.time}
-
${statsData.slow.frames} frames
-
-
-
-
-
AVG
-
130 WPM
-
-
-
${statsData.avg.time}
-
${statsData.avg.frames} frames
-
-
-
-
-
FAST
-
160 WPM
-
-
-
${statsData.fast.time}
-
${statsData.fast.frames} frames
-
-
-
-
WPM = Words Per Minute
- `; -} - -app.registerExtension({ - name: "Comfy.SpeechLengthCalculator", - async beforeRegisterNodeDef(nodeType, nodeData, app) { - if (nodeData.name === "SpeechLengthCalculator") { - - const onNodeCreated = nodeType.prototype.onNodeCreated; - nodeType.prototype.onNodeCreated = function () { - const r = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined; - - // 3. CREATE THE DOM WIDGET - const uiContainer = document.createElement("div"); - uiContainer.className = "slc-ui-container"; - uiContainer.innerHTML = buildUIHTML({ empty: true }); - - // Attach to node: standard addDOMWidget works natively in both V1 and V3 Frontends - let statsWidget; - if (typeof this.addDOMWidget === "function") { - statsWidget = this.addDOMWidget("Stats", "HTML", uiContainer, { - serialize: false, - hideOnZoom: false - }); - } else { - // Fallback for very old unpatched V1 installations - statsWidget = ComfyWidgets["STRING"](this, "Stats", ["STRING", { multiline: true }], app).widget; - if (statsWidget.inputEl) { - statsWidget.inputEl.style.display = "none"; - if (statsWidget.inputEl.parentNode) { - statsWidget.inputEl.parentNode.appendChild(uiContainer); - } - } - } - - // Tell LiteGraph exactly how much vertical space to hold for our UI - statsWidget.computeSize = function() { - return [0, 280]; // Increased from 240 to prevent bottom cropping in V1 - }; - - // Ensure the node is wide enough on initial creation - requestAnimationFrame(() => { - const minWidth = 340; - if (this.size[0] < minWidth) this.size[0] = minWidth; - - // Fine tune initial height to account for the larger computeSize and make text box taller - if (this.size[1] < 500) this.size[1] = 500; - - this.setDirtyCanvas(true, true); - }); - - // Fetch text from input link OR widget - this._getCurrentText = () => { - const inputSlot = this.inputs && this.inputs.find(i => i.name === "text_input" || (i.name === "text" && i.link)); - if (inputSlot && inputSlot.link) { - const link = app.graph.links[inputSlot.link]; - if (link) { - const sourceNode = app.graph.getNodeById(link.origin_id); - if (sourceNode && sourceNode.widgets) { - const w = sourceNode.widgets.find(w => w.name === "value" || w.name === "text" || w.name === "Text" || w.type === "customtext" || w.type === "STRING"); - if (w && typeof w.value === "string") return w.value; - } - } - } - const textWidget = this.widgets && this.widgets.find(w => w.name === "text"); - return textWidget ? (textWidget.value || "") : ""; - }; - - this._lastState = { text: null, fps: null, addTime: null }; - - const updateStats = () => { - const fpsWidget = this.widgets && this.widgets.find(w => w.name === "fps"); - const additionalTimeWidget = this.widgets && this.widgets.find(w => w.name === "additional_time"); - - if (!fpsWidget) return; - - const text = this._getCurrentText(); - const fps = fpsWidget.value || 24; - const additionalTime = additionalTimeWidget ? parseFloat(additionalTimeWidget.value) || 0 : 0; - - // Skip expensive calculations if nothing changed - if (this._lastState.text === text && - this._lastState.fps === fps && - this._lastState.addTime === additionalTime) { - return; - } - - this._lastState.text = text; - this._lastState.fps = fps; - this._lastState.addTime = additionalTime; - - const regex = /"([^"]*)"|'([^']*)'|“([^”]*)”|‘([^’]*)’/g; - let match; - let quotedText = ""; - while ((match = regex.exec(text)) !== null) { - quotedText += (match[1] || match[2] || match[3] || match[4] || "") + " "; - } - - const words = quotedText.trim().split(/\s+/).filter(w => w.length > 0); - const wordCount = words.length; - - const formatTime = (wpm) => { - const baseMins = wordCount / wpm; - const totalSecs = (baseMins * 60) + additionalTime; - - const mins = Math.floor(totalSecs / 60); - let secs = totalSecs % 60; - secs = Math.ceil(secs * 10) / 10; - const frames = Math.ceil(totalSecs * fps); - - const secsStr = secs.toFixed(1); - const timeStr = mins > 0 ? `${mins}m ${secsStr}s` : `${secsStr}s`; - - return { - time: timeStr, - frames: frames.toString() - }; - }; - - const statsData = { - empty: (wordCount === 0 && additionalTime === 0), - wordCount: wordCount, - additionalTime: additionalTime, - slow: formatTime(100), - avg: formatTime(130), - fast: formatTime(160) - }; - - // 4. INJECT HTML TO THE DOM - // Replaces the heavy canvas redrawing! - uiContainer.innerHTML = buildUIHTML(statsData); - - this.setDirtyCanvas(true, false); - }; - - // We still use onDrawBackground as an efficient silent trigger - // to auto-update in case upstream nodes silently change their text - const onDrawBackground = this.onDrawBackground; - this.onDrawBackground = function(ctx) { - if (onDrawBackground) onDrawBackground.apply(this, arguments); - updateStats(); - }; - - // Bind events to update in real time based on node interactions - setTimeout(() => { - const textWidget = this.widgets && this.widgets.find(w => w.name === "text"); - const fpsWidget = this.widgets && this.widgets.find(w => w.name === "fps"); - const additionalTimeWidget = this.widgets && this.widgets.find(w => w.name === "additional_time"); - - if (textWidget) { - const origCallback = textWidget.callback; - textWidget.callback = function() { - if (origCallback) origCallback.apply(this, arguments); - updateStats(); - } - } - if (fpsWidget) { - const origFpsCallback = fpsWidget.callback; - fpsWidget.callback = function() { - if (origFpsCallback) origFpsCallback.apply(this, arguments); - updateStats(); - } - } - if (additionalTimeWidget) { - const origAddCallback = additionalTimeWidget.callback; - additionalTimeWidget.callback = function() { - if (origAddCallback) origAddCallback.apply(this, arguments); - updateStats(); - } - } - updateStats(); - }, 100); - - return r; - }; - } - } +import { app } from "../../scripts/app.js"; +import { ComfyWidgets } from "../../scripts/widgets.js"; + +// 1. DEFINE CSS STYLES GLOBALLY ONCE +// We use DOM elements instead of Canvas drawing so it works flawlessly in ComfyUI V3 +const cssStyles = ` + .slc-ui-container { + width: 100%; + height: 100%; + min-height: 260px; /* Increased from 220px to prevent V1 cropping */ + background-color: rgba(15, 15, 19, 0.7); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 8px; + padding: 12px; + box-sizing: border-box; + display: flex; + flex-direction: column; + font-family: sans-serif; + color: #ffffff; + overflow: hidden; + pointer-events: auto; /* allows text selection */ + } + .slc-empty { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + } + .slc-empty-title { color: #aaaaaa; font-size: 14px; font-weight: 500; margin-bottom: 4px; } + .slc-empty-sub { color: #777777; font-size: 11px; } + .slc-headers { + display: flex; + gap: 8px; + margin-bottom: 12px; + } + .slc-header-block { + flex: 1; + background-color: rgba(0, 0, 0, 0.4); + border-radius: 6px; + padding: 8px 0; + display: flex; + flex-direction: column; + align-items: center; + } + .slc-header-title { color: #888888; font-size: 9px; font-weight: bold; margin-bottom: 4px; } + .slc-header-value { color: #ffffff; font-size: 16px; font-weight: bold; } + .slc-cards { + display: flex; + flex-direction: column; + gap: 8px; + } + .slc-card { + display: flex; + justify-content: space-between; + align-items: center; + background-color: rgba(0, 0, 0, 0.25); + border: 1px solid rgba(255, 255, 255, 0.05); + border-radius: 4px; + padding: 8px 12px 8px 16px; + position: relative; + overflow: hidden; + } + .slc-card::before { + content: ''; + position: absolute; + left: 0; + top: 0; + bottom: 0; + width: 4px; + } + .slc-card.slow::before { background-color: #93c5fd; } + .slc-card.avg::before { background-color: #86efac; } + .slc-card.fast::before { background-color: #fca5a5; } + + .slc-card-left { display: flex; flex-direction: column; } + .slc-card-speed { font-size: 12px; font-weight: bold; margin-bottom: 2px; } + .slc-card.slow .slc-card-speed { color: #93c5fd; } + .slc-card.avg .slc-card-speed { color: #86efac; } + .slc-card.fast .slc-card-speed { color: #fca5a5; } + .slc-card-wpm { font-size: 10px; color: #aaaaaa; } + + .slc-card-right { display: flex; flex-direction: column; align-items: flex-end; } + .slc-card-time { font-size: 13px; font-weight: bold; color: #ffffff; margin-bottom: 2px; } + .slc-card-frames { font-size: 11px; font-family: monospace; color: #888888; } + + .slc-legend { + margin-top: auto; + padding-top: 8px; + color: #999999; + font-size: 10px; + font-weight: 500; + } +`; + +// Inject styles to document head safely +if (!document.getElementById("speech-length-calculator-styles")) { + const styleEl = document.createElement("style"); + styleEl.id = "speech-length-calculator-styles"; + styleEl.textContent = cssStyles; + document.head.appendChild(styleEl); +} + +// 2. HELPER TO GENERATE THE HTML CONTENT +function buildUIHTML(statsData) { + if (!statsData || statsData.empty) { + return ` +
+
Awaiting Script
+
Wrap spoken text inside "quotes"
+
+ `; + } + + return ` +
+
+
SPOKEN WORDS
+
${statsData.wordCount}
+
+
+
ADDED TIME
+
${statsData.additionalTime}s
+
+
+
+
+
+
SLOW
+
100 WPM
+
+
+
${statsData.slow.time}
+
${statsData.slow.frames} frames
+
+
+
+
+
AVG
+
130 WPM
+
+
+
${statsData.avg.time}
+
${statsData.avg.frames} frames
+
+
+
+
+
FAST
+
160 WPM
+
+
+
${statsData.fast.time}
+
${statsData.fast.frames} frames
+
+
+
+
WPM = Words Per Minute
+ `; +} + +app.registerExtension({ + name: "Comfy.SpeechLengthCalculator", + async beforeRegisterNodeDef(nodeType, nodeData, app) { + if (nodeData.name === "SpeechLengthCalculator") { + + const onNodeCreated = nodeType.prototype.onNodeCreated; + nodeType.prototype.onNodeCreated = function () { + const r = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined; + + // 3. CREATE THE DOM WIDGET + const uiContainer = document.createElement("div"); + uiContainer.className = "slc-ui-container"; + uiContainer.innerHTML = buildUIHTML({ empty: true }); + + // Attach to node: standard addDOMWidget works natively in both V1 and V3 Frontends + let statsWidget; + if (typeof this.addDOMWidget === "function") { + statsWidget = this.addDOMWidget("Stats", "HTML", uiContainer, { + serialize: false, + hideOnZoom: false + }); + } else { + // Fallback for very old unpatched V1 installations + statsWidget = ComfyWidgets["STRING"](this, "Stats", ["STRING", { multiline: true }], app).widget; + if (statsWidget.inputEl) { + statsWidget.inputEl.style.display = "none"; + if (statsWidget.inputEl.parentNode) { + statsWidget.inputEl.parentNode.appendChild(uiContainer); + } + } + } + + // Tell LiteGraph exactly how much vertical space to hold for our UI + statsWidget.computeSize = function() { + return [0, 280]; // Increased from 240 to prevent bottom cropping in V1 + }; + + // Ensure the node is wide enough on initial creation + requestAnimationFrame(() => { + const minWidth = 340; + if (this.size[0] < minWidth) this.size[0] = minWidth; + + // Fine tune initial height to account for the larger computeSize and make text box taller + if (this.size[1] < 500) this.size[1] = 500; + + this.setDirtyCanvas(true, true); + }); + + // Fetch text from input link OR widget + this._getCurrentText = () => { + const inputSlot = this.inputs && this.inputs.find(i => i.name === "text_input" || (i.name === "text" && i.link)); + if (inputSlot && inputSlot.link) { + const link = app.graph.links[inputSlot.link]; + if (link) { + const sourceNode = app.graph.getNodeById(link.origin_id); + if (sourceNode && sourceNode.widgets) { + const w = sourceNode.widgets.find(w => w.name === "value" || w.name === "text" || w.name === "Text" || w.type === "customtext" || w.type === "STRING"); + if (w && typeof w.value === "string") return w.value; + } + } + } + const textWidget = this.widgets && this.widgets.find(w => w.name === "text"); + return textWidget ? (textWidget.value || "") : ""; + }; + + this._lastState = { text: null, fps: null, addTime: null }; + + const updateStats = () => { + const fpsWidget = this.widgets && this.widgets.find(w => w.name === "fps"); + const additionalTimeWidget = this.widgets && this.widgets.find(w => w.name === "additional_time"); + + if (!fpsWidget) return; + + const text = this._getCurrentText(); + const fps = fpsWidget.value || 24; + const additionalTime = additionalTimeWidget ? parseFloat(additionalTimeWidget.value) || 0 : 0; + + // Skip expensive calculations if nothing changed + if (this._lastState.text === text && + this._lastState.fps === fps && + this._lastState.addTime === additionalTime) { + return; + } + + this._lastState.text = text; + this._lastState.fps = fps; + this._lastState.addTime = additionalTime; + + const regex = /"([^"]*)"|'([^']*)'|“([^”]*)”|‘([^’]*)’/g; + let match; + let quotedText = ""; + while ((match = regex.exec(text)) !== null) { + quotedText += (match[1] || match[2] || match[3] || match[4] || "") + " "; + } + + const words = quotedText.trim().split(/\s+/).filter(w => w.length > 0); + const wordCount = words.length; + + const formatTime = (wpm) => { + const baseMins = wordCount / wpm; + const totalSecs = (baseMins * 60) + additionalTime; + + const mins = Math.floor(totalSecs / 60); + let secs = totalSecs % 60; + secs = Math.ceil(secs * 10) / 10; + const frames = Math.ceil(totalSecs * fps); + + const secsStr = secs.toFixed(1); + const timeStr = mins > 0 ? `${mins}m ${secsStr}s` : `${secsStr}s`; + + return { + time: timeStr, + frames: frames.toString() + }; + }; + + const statsData = { + empty: (wordCount === 0 && additionalTime === 0), + wordCount: wordCount, + additionalTime: additionalTime, + slow: formatTime(100), + avg: formatTime(130), + fast: formatTime(160) + }; + + // 4. INJECT HTML TO THE DOM + // Replaces the heavy canvas redrawing! + uiContainer.innerHTML = buildUIHTML(statsData); + + this.setDirtyCanvas(true, false); + }; + + // We still use onDrawBackground as an efficient silent trigger + // to auto-update in case upstream nodes silently change their text + const onDrawBackground = this.onDrawBackground; + this.onDrawBackground = function(ctx) { + if (onDrawBackground) onDrawBackground.apply(this, arguments); + updateStats(); + }; + + // Bind events to update in real time based on node interactions + setTimeout(() => { + const textWidget = this.widgets && this.widgets.find(w => w.name === "text"); + const fpsWidget = this.widgets && this.widgets.find(w => w.name === "fps"); + const additionalTimeWidget = this.widgets && this.widgets.find(w => w.name === "additional_time"); + + if (textWidget) { + const origCallback = textWidget.callback; + textWidget.callback = function() { + if (origCallback) origCallback.apply(this, arguments); + updateStats(); + } + } + if (fpsWidget) { + const origFpsCallback = fpsWidget.callback; + fpsWidget.callback = function() { + if (origFpsCallback) origFpsCallback.apply(this, arguments); + updateStats(); + } + } + if (additionalTimeWidget) { + const origAddCallback = additionalTimeWidget.callback; + additionalTimeWidget.callback = function() { + if (origAddCallback) origAddCallback.apply(this, arguments); + updateStats(); + } + } + updateStats(); + }, 100); + + return r; + }; + } + } }); \ No newline at end of file diff --git a/latent_slice.py b/latent_slice.py index 6663c76..5bb10f9 100644 --- a/latent_slice.py +++ b/latent_slice.py @@ -8,7 +8,8 @@ class CleanLatentSlice: return { "required": { "latent": ("LATENT",), - "length": ("INT", {"default": 1, "min": 1, "max": 100000, "step": 1, "tooltip": "The target length in latent frames."}), + "start": ("INT", {"default": 0, "min": 0, "max": 100000, "step": 1, "tooltip": "The starting frame index to slice from (plug 'latent_start_index' here)."}), + "length": ("INT", {"default": 1, "min": 1, "max": 100000, "step": 1, "tooltip": "The number of frames to keep (plug 'clean_latent_frames' here)."}), } } @@ -16,37 +17,59 @@ class CleanLatentSlice: RETURN_NAMES = ("latent",) FUNCTION = "slice_latent" CATEGORY = "WhatDreamsCost" - DESCRIPTION = "Safely slices a video latent to a specific length. Uses torch.narrow to bypass PyTorch NestedTensor slicing bugs." + DESCRIPTION = "Safely slices a video latent starting from an offset index for a specific length. Uses torch.narrow to bypass PyTorch NestedTensor slicing bugs." - def slice_latent(self, latent, length): + def slice_latent(self, latent, start, length): new_latent = latent.copy() - def safe_slice(tensor, target_len): + def safe_slice(tensor, target_start, target_len): dims = tensor.ndim if hasattr(tensor, "ndim") else len(tensor.shape) + # Constrain starting index and length to the actual size of the tensor + max_size = tensor.size(2) if dims == 5 else tensor.size(0) + actual_start = min(target_start, max_size - 1) if max_size > 0 else 0 + actual_len = min(target_len, max_size - actual_start) + try: - # Bypasses NestedTensor slicing bugs + # torch.narrow is the safest low-level C++ slice method to bypass NestedTensor bugs if dims == 5: - return torch.narrow(tensor, 2, 0, target_len) + # [Batch, Channels, Frames, Height, Width] -> Slice dimension 2 + return torch.narrow(tensor, 2, actual_start, actual_len) elif dims == 4: - return torch.narrow(tensor, 0, 0, target_len) + # [Frames, Channels, Height, Width] -> Slice dimension 0 + return torch.narrow(tensor, 0, actual_start, actual_len) elif dims == 3: - return torch.narrow(tensor, 0, 0, target_len) + # [Frames, Height, Width] -> Slice dimension 0 + return torch.narrow(tensor, 0, actual_start, actual_len) except Exception as e: - # Extreme fallback if narrow fails + # Fallback if narrow fails if dims == 5: - return tensor[:, :, :target_len] + return tensor[:, :, actual_start : actual_start + actual_len] elif dims == 4: - return tensor[:target_len] + return tensor[actual_start : actual_start + actual_len] elif dims == 3: - return tensor[:target_len] + return tensor[actual_start : actual_start + actual_len] return tensor + # Safely slice video samples if "samples" in new_latent: - new_latent["samples"] = safe_slice(new_latent["samples"], length) + new_latent["samples"] = safe_slice(new_latent["samples"], start, length) + # Safely slice video noise mask (if it exists) if "noise_mask" in new_latent: - new_latent["noise_mask"] = safe_slice(new_latent["noise_mask"], length) + new_latent["noise_mask"] = safe_slice(new_latent["noise_mask"], start, length) - return (new_latent,) \ No newline at end of file + return (new_latent,) + + +# Register the node with ComfyUI +NODE_CLASS_MAPPINGS = { + "CleanLatentSlice": CleanLatentSlice +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "CleanLatentSlice": "Clean Latent Slice" +} + +__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS'] \ No newline at end of file diff --git a/load_audio_ui.py b/load_audio_ui.py index eaa84dc..7488a0c 100644 --- a/load_audio_ui.py +++ b/load_audio_ui.py @@ -1,141 +1,141 @@ -import folder_paths -import os -import torch -import av - -def f32_pcm(wav: torch.Tensor) -> torch.Tensor: - """Convert audio to float 32 bits PCM format.""" - if wav.dtype.is_floating_point: - return wav - elif wav.dtype == torch.int16: - return wav.float() / (2 ** 15) - elif wav.dtype == torch.int32: - return wav.float() / (2 ** 31) - raise ValueError(f"Unsupported wav dtype: {wav.dtype}") - -def load_audio_file(filepath: str) -> tuple[torch.Tensor, int]: - """Uses the latest ComfyUI av-based decoding for maximum compatibility.""" - with av.open(filepath) as af: - if not af.streams.audio: - raise ValueError("No audio stream found in the file.") - - stream = af.streams.audio[0] - sr = stream.codec_context.sample_rate - n_channels = stream.channels - - frames = [] - for frame in af.decode(streams=stream.index): - buf = torch.from_numpy(frame.to_ndarray()) - if buf.shape[0] != n_channels: - buf = buf.view(-1, n_channels).t() - - frames.append(buf) - - if not frames: - raise ValueError("No audio frames decoded.") - - wav = torch.cat(frames, dim=1) - wav = f32_pcm(wav) - return wav, sr - - -class LoadAudioUI: - @classmethod - def INPUT_TYPES(s): - try: - files = folder_paths.get_filename_list("audio") - except: - files = [] - - if not files: - input_dir = folder_paths.get_input_directory() - if os.path.exists(input_dir): - all_files = [f for f in os.listdir(input_dir) if os.path.isfile(os.path.join(input_dir, f))] - try: - files = sorted(folder_paths.filter_files_content_types(all_files, ["audio", "video"])) - except: - files = sorted(all_files) - - if not files or len(files) == 0: - files = ["none"] - - return { - "required": { - "audio": (files, {"audio_upload": True}), # Moved to the top so it appears first - "start_time": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 100000.0, "step": 0.01}), - "end_time": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 100000.0, "step": 0.01}), - "duration": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 100000.0, "step": 0.01}), - }, - "optional": { - "audioUI": ("AUDIO_UI",) - } - } - - CATEGORY = "WhatDreamsCost" - RETURN_TYPES = ("AUDIO", "FLOAT") - RETURN_NAMES = ("audio", "duration") - FUNCTION = "load_audio" - - @classmethod - def VALIDATE_INPUTS(cls, audio, **kwargs): - # CRITICAL FIX: This bypasses the "Value not in list" error. - # By returning True, we tell ComfyUI to allow the 'audio' value even if it isn't in - # the current dropdown list. This allows the execution to reach load_audio(), - # where our fallback silence logic can handle the missing file gracefully. - return True - - def load_audio(self, audio, start_time, end_time, duration, **kwargs): - # Determine the annotated file path if a file is actually selected - # We wrap this in a try/except because get_annotated_filepath can fail if - # the input string is malformed or doesn't follow expected paths. - try: - audio_path = folder_paths.get_annotated_filepath(audio) if audio != "none" else "" - except: - audio_path = "" - - # --- FALLBACK LOGIC --- - # If the file is 'none' or doesn't exist on disk, provide 1 second of silence - if audio == "none" or not audio_path or not os.path.exists(audio_path): - missing_info = audio if audio != "none" else "None selected" - print(f"!!! [LoadAudioUI] Warning: Audio file '{missing_info}' not found. Outputting 1 second of silence.") - - sample_rate = 44100 - # 1 second of silence (stereo) -> shape [channels, time] - waveform = torch.zeros((2, 44100)) - else: - try: - waveform, sample_rate = load_audio_file(audio_path) - except Exception as e: - # If decoding fails for any reason, fallback to silence rather than crashing the workflow - print(f"!!! [LoadAudioUI] Error decoding {audio}: {e}. Falling back to silence.") - sample_rate = 44100 - waveform = torch.zeros((2, 44100)) - - # Convert seconds to frames - start_frame = int(start_time * sample_rate) - if end_time > 0: - end_frame = int(end_time * sample_rate) - # Ensure the end_frame does not exceed the actual audio length - end_frame = min(end_frame, waveform.shape[1]) - else: - # 0 defaults to the end of the file - end_frame = waveform.shape[1] - - # Ensure start frame stays within bounds and doesn't pass the end frame - start_frame = min(start_frame, end_frame) - - # Trim the waveform tensor -> shape: [channels, time] - trimmed_waveform = waveform[:, start_frame:end_frame] - - # Final safety check: if trimming resulted in zero length, give it a tiny bit of padding - # to prevent downstream nodes from crashing on empty tensors - if trimmed_waveform.shape[1] == 0: - trimmed_waveform = torch.zeros((waveform.shape[0], 1)) - - # Format for ComfyUI's standard AUDIO type: [batch, channels, time] - audio_output = {"waveform": trimmed_waveform.unsqueeze(0), "sample_rate": sample_rate} - - # Calculate the final trimmed duration in seconds as a float - final_duration = float(trimmed_waveform.shape[1] / sample_rate) - +import folder_paths +import os +import torch +import av + +def f32_pcm(wav: torch.Tensor) -> torch.Tensor: + """Convert audio to float 32 bits PCM format.""" + if wav.dtype.is_floating_point: + return wav + elif wav.dtype == torch.int16: + return wav.float() / (2 ** 15) + elif wav.dtype == torch.int32: + return wav.float() / (2 ** 31) + raise ValueError(f"Unsupported wav dtype: {wav.dtype}") + +def load_audio_file(filepath: str) -> tuple[torch.Tensor, int]: + """Uses the latest ComfyUI av-based decoding for maximum compatibility.""" + with av.open(filepath) as af: + if not af.streams.audio: + raise ValueError("No audio stream found in the file.") + + stream = af.streams.audio[0] + sr = stream.codec_context.sample_rate + n_channels = stream.channels + + frames = [] + for frame in af.decode(streams=stream.index): + buf = torch.from_numpy(frame.to_ndarray()) + if buf.shape[0] != n_channels: + buf = buf.view(-1, n_channels).t() + + frames.append(buf) + + if not frames: + raise ValueError("No audio frames decoded.") + + wav = torch.cat(frames, dim=1) + wav = f32_pcm(wav) + return wav, sr + + +class LoadAudioUI: + @classmethod + def INPUT_TYPES(s): + try: + files = folder_paths.get_filename_list("audio") + except: + files = [] + + if not files: + input_dir = folder_paths.get_input_directory() + if os.path.exists(input_dir): + all_files = [f for f in os.listdir(input_dir) if os.path.isfile(os.path.join(input_dir, f))] + try: + files = sorted(folder_paths.filter_files_content_types(all_files, ["audio", "video"])) + except: + files = sorted(all_files) + + if not files or len(files) == 0: + files = ["none"] + + return { + "required": { + "audio": (files, {"audio_upload": True}), # Moved to the top so it appears first + "start_time": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 100000.0, "step": 0.01}), + "end_time": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 100000.0, "step": 0.01}), + "duration": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 100000.0, "step": 0.01}), + }, + "optional": { + "audioUI": ("AUDIO_UI",) + } + } + + CATEGORY = "WhatDreamsCost" + RETURN_TYPES = ("AUDIO", "FLOAT") + RETURN_NAMES = ("audio", "duration") + FUNCTION = "load_audio" + + @classmethod + def VALIDATE_INPUTS(cls, audio, **kwargs): + # CRITICAL FIX: This bypasses the "Value not in list" error. + # By returning True, we tell ComfyUI to allow the 'audio' value even if it isn't in + # the current dropdown list. This allows the execution to reach load_audio(), + # where our fallback silence logic can handle the missing file gracefully. + return True + + def load_audio(self, audio, start_time, end_time, duration, **kwargs): + # Determine the annotated file path if a file is actually selected + # We wrap this in a try/except because get_annotated_filepath can fail if + # the input string is malformed or doesn't follow expected paths. + try: + audio_path = folder_paths.get_annotated_filepath(audio) if audio != "none" else "" + except: + audio_path = "" + + # --- FALLBACK LOGIC --- + # If the file is 'none' or doesn't exist on disk, provide 1 second of silence + if audio == "none" or not audio_path or not os.path.exists(audio_path): + missing_info = audio if audio != "none" else "None selected" + print(f"!!! [LoadAudioUI] Warning: Audio file '{missing_info}' not found. Outputting 1 second of silence.") + + sample_rate = 44100 + # 1 second of silence (stereo) -> shape [channels, time] + waveform = torch.zeros((2, 44100)) + else: + try: + waveform, sample_rate = load_audio_file(audio_path) + except Exception as e: + # If decoding fails for any reason, fallback to silence rather than crashing the workflow + print(f"!!! [LoadAudioUI] Error decoding {audio}: {e}. Falling back to silence.") + sample_rate = 44100 + waveform = torch.zeros((2, 44100)) + + # Convert seconds to frames + start_frame = int(start_time * sample_rate) + if end_time > 0: + end_frame = int(end_time * sample_rate) + # Ensure the end_frame does not exceed the actual audio length + end_frame = min(end_frame, waveform.shape[1]) + else: + # 0 defaults to the end of the file + end_frame = waveform.shape[1] + + # Ensure start frame stays within bounds and doesn't pass the end frame + start_frame = min(start_frame, end_frame) + + # Trim the waveform tensor -> shape: [channels, time] + trimmed_waveform = waveform[:, start_frame:end_frame] + + # Final safety check: if trimming resulted in zero length, give it a tiny bit of padding + # to prevent downstream nodes from crashing on empty tensors + if trimmed_waveform.shape[1] == 0: + trimmed_waveform = torch.zeros((waveform.shape[0], 1)) + + # Format for ComfyUI's standard AUDIO type: [batch, channels, time] + audio_output = {"waveform": trimmed_waveform.unsqueeze(0), "sample_rate": sample_rate} + + # Calculate the final trimmed duration in seconds as a float + final_duration = float(trimmed_waveform.shape[1] / sample_rate) + return (audio_output, final_duration) \ No newline at end of file diff --git a/load_video_ui.py b/load_video_ui.py index a67d3f3..fc1801c 100644 --- a/load_video_ui.py +++ b/load_video_ui.py @@ -1,412 +1,412 @@ -import os -import torch -import numpy as np -import folder_paths -import av -from server import PromptServer -from aiohttp import web -import comfy.utils - -# Custom API route to serve video files from anywhere on the user's system for the frontend preview -@PromptServer.instance.routes.get("/video_ui_custom_view") -async def custom_view(request): - file_path = request.query.get("filename", "") - if os.path.exists(file_path) and os.path.isfile(file_path): - return web.FileResponse(file_path) - return web.Response(status=404, text="File not found") - -# Custom API route for Chunked Uploads to bypass the 413 Payload Too Large error -@PromptServer.instance.routes.post("/video_ui_upload_chunk") -async def upload_chunk(request): - post = await request.post() - file = post.get("file") - filename = post.get("filename") - chunk_index = int(post.get("chunk_index")) - total_chunks = int(post.get("total_chunks")) - - upload_dir = folder_paths.get_input_directory() - file_path = os.path.join(upload_dir, filename) - - # Append to file if it's not the first chunk, otherwise write new - mode = "ab" if chunk_index > 0 else "wb" - with open(file_path, mode) as f: - f.write(file.file.read()) - - if chunk_index == total_chunks - 1: - return web.json_response({"name": filename}) - return web.json_response({"status": "ok"}) - - -class LoadVideoUI: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "video": ("STRING", {"default": ""}), - "start_time": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 100000.0, "step": 0.01}), - "end_time": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 100000.0, "step": 0.01}), - "duration": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 100000.0, "step": 0.01}), - "start_frame": ("INT", {"default": 0, "min": 0, "max": 10000000, "step": 1}), - "end_frame": ("INT", {"default": 0, "min": 0, "max": 10000000, "step": 1}), - "duration_frames": ("INT", {"default": 0, "min": 0, "max": 10000000, "step": 1}), - "resize_method": (["maintain aspect ratio", "stretch to fit", "pad", "crop"], {"default": "maintain aspect ratio"}), - "custom_width": ("INT", {"default": 0, "min": 0, "max": 100000, "step": 8, "tooltip": "Custom width. 0 means original width."}), - "custom_height": ("INT", {"default": 0, "min": 0, "max": 100000, "step": 8, "tooltip": "Custom height. 0 means original height."}), - "frame_rate": ("INT", {"default": 24, "min": 1, "max": 120, "step": 1, "tooltip": "Force the video to a specific frame rate for extraction."}), - "display_mode": (["seconds", "frames"], {"default": "seconds"}), - "crop_x": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}), - "crop_y": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}), - "crop_w": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}), - "crop_h": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}), - } - } - - RETURN_TYPES = ("IMAGE", "AUDIO", "FLOAT", "INT") - RETURN_NAMES = ("images", "audio", "duration", "frame_count") - FUNCTION = "load_video" - CATEGORY = "WhatDreamsCost" - - def load_video(self, video, frame_rate, display_mode, start_time, end_time, duration, start_frame, end_frame, duration_frames, custom_width=0, custom_height=0, resize_method="maintain aspect ratio", crop_x=0.0, crop_y=0.0, crop_w=1.0, crop_h=1.0, **kwargs): - if not video: - # Return blank defaults if no video is loaded - empty_image = torch.zeros((1, 512, 512, 3), dtype=torch.float32) - empty_audio = {"waveform": torch.zeros((1, 1, 44100)), "sample_rate": 44100} - return (empty_image, empty_audio, 0.0, 0) - - # 1. Resolve path using ComfyUI standard paths or Absolute Path - video_path = video # Try exact/absolute path first - if not os.path.exists(video_path): - video_path_annotated = folder_paths.get_annotated_filepath(video) - if os.path.exists(video_path_annotated): - video_path = video_path_annotated - else: - video_path_input = os.path.join(folder_paths.get_input_directory(), video) - if os.path.exists(video_path_input): - video_path = video_path_input - else: - raise FileNotFoundError(f"Video file not found: {video}") - - # Open container to read streams and metadata - container = av.open(video_path) - - # Determine video stream and duration - video_stream = container.streams.video[0] if len(container.streams.video) > 0 else None - video_duration = 0 - if video_stream and video_stream.duration and video_stream.time_base: - video_duration = float(video_stream.duration * video_stream.time_base) - - orig_w = video_stream.codec_context.width if video_stream else 512 - orig_h = video_stream.codec_context.height if video_stream else 512 - - # Determine correct colorspace and color range for PyAV conversion to prevent color shift - try: - from av.video.reformatter import Colorspace, ColorRange - # Improve fallback heuristic to check both dimensions (e.g. 720x1280 vertical video is HD) - fallback_cs = Colorspace.ITU709 if max(orig_w, orig_h) >= 720 else Colorspace.ITU601 - fallback_cr = ColorRange.MPEG - dst_range = ColorRange.JPEG # RGB should always be full range - except ImportError: - fallback_cs = "itu709" if max(orig_w, orig_h) >= 720 else "itu601" - fallback_cr = "mpeg" - dst_range = "jpeg" - - src_colorspace = fallback_cs - src_color_range = fallback_cr - - if video_stream and video_stream.codec_context: - cc = video_stream.codec_context - - c_space = getattr(cc, 'colorspace', getattr(cc, 'color_space', None)) - if c_space and hasattr(c_space, 'name') and c_space.name != "UNSPECIFIED": - src_colorspace = c_space - elif c_space and isinstance(c_space, str) and "unspecified" not in c_space.lower(): - src_colorspace = c_space - - c_range = getattr(cc, 'color_range', None) - if c_range and hasattr(c_range, 'name') and c_range.name != "UNSPECIFIED": - src_color_range = c_range - elif c_range and isinstance(c_range, str) and "unspecified" not in c_range.lower(): - src_color_range = c_range - - target_w = custom_width if custom_width > 0 else orig_w - target_h = custom_height if custom_height > 0 else orig_h - - target_w = target_w - (target_w % 2) - target_h = target_h - (target_h % 2) - - # Calculate manual crop from interactive UI first - manual_crop_left = int(orig_w * crop_x) - manual_crop_top = int(orig_h * crop_y) - manual_crop_right = orig_w - int(orig_w * (crop_x + crop_w)) - manual_crop_bottom = orig_h - int(orig_h * (crop_y + crop_h)) - - # Ensure we don't crop more than the image - manual_crop_left = max(0, min(manual_crop_left, orig_w - 1)) - manual_crop_top = max(0, min(manual_crop_top, orig_h - 1)) - manual_crop_right = max(0, min(manual_crop_right, orig_w - manual_crop_left - 1)) - manual_crop_bottom = max(0, min(manual_crop_bottom, orig_h - manual_crop_top - 1)) - - # After manual crop, the new original dimensions are: - cropped_orig_w = orig_w - manual_crop_left - manual_crop_right - cropped_orig_h = orig_h - manual_crop_top - manual_crop_bottom - - # If no custom width/height is provided, use the cropped original dimensions - if custom_width == 0: - target_w = cropped_orig_w - target_w = target_w - (target_w % 2) - if custom_height == 0: - target_h = cropped_orig_h - target_h = target_h - (target_h % 2) - - scale_w, scale_h = target_w, target_h - pad_left = pad_right = pad_top = pad_bottom = 0 - crop_left = crop_right = crop_top = crop_bottom = 0 - - if custom_width > 0 or custom_height > 0: - if resize_method == "maintain aspect ratio" or resize_method == "pad": - ratio = min(target_w / cropped_orig_w, target_h / cropped_orig_h) - scale_w = int(cropped_orig_w * ratio) - scale_h = int(cropped_orig_h * ratio) - scale_w = scale_w - (scale_w % 2) - scale_h = scale_h - (scale_h % 2) - - if resize_method == "pad": - pad_x = target_w - scale_w - pad_y = target_h - scale_h - pad_left = pad_x // 2 - pad_right = pad_x - pad_left - pad_top = pad_y // 2 - pad_bottom = pad_y - pad_top - else: - target_w, target_h = scale_w, scale_h - - elif resize_method == "crop": - ratio = max(target_w / cropped_orig_w, target_h / cropped_orig_h) - scale_w = int(cropped_orig_w * ratio) - scale_h = int(cropped_orig_h * ratio) - scale_w = scale_w - (scale_w % 2) - scale_h = scale_h - (scale_h % 2) - - crop_x = scale_w - target_w - crop_y = scale_h - target_h - crop_left = crop_x // 2 - crop_right = crop_x - crop_left - crop_top = crop_y // 2 - crop_bottom = crop_y - crop_top - - elif resize_method == "stretch to fit": - scale_w, scale_h = target_w, target_h - - # Determine exact bounds based on frontend mode - if display_mode == "frames": - fr = float(frame_rate) if frame_rate > 0 else 24.0 - actual_start_time = float(start_frame) / fr - actual_end_time = float(end_frame) / fr if (end_frame > 0 and end_frame > start_frame) else video_duration - else: - actual_start_time = start_time - actual_end_time = end_time if (end_time > 0 and end_time > start_time) else video_duration - - if actual_end_time <= 0: - actual_end_time = float('inf') # Fallback if duration is unknown - - # 2. Extract Video Frames (PyAV) - frames = [] - image_tensor = None - frames_loaded = 0 - - if video_stream: - video_stream.thread_type = "AUTO" # Enable multithreaded decoding - - # Efficiently seek backwards to the nearest keyframe - if video_stream.time_base: - seek_pts = int(actual_start_time / float(video_stream.time_base)) - else: - seek_pts = int(actual_start_time * av.time_base) - - container.seek(seek_pts, stream=video_stream, backward=True) - - # Custom sampling to force specific framerate - frame_interval = 1.0 / float(frame_rate) if frame_rate > 0 else 1.0/24.0 - expected_target_time = actual_start_time - - # Pre-calculate expected frames - alloc_end_time = actual_end_time if actual_end_time != float('inf') else video_duration - expected_frames = 0 - if alloc_end_time > 0: - duration_to_extract = alloc_end_time - actual_start_time - if duration_to_extract > 0: - expected_frames = int(np.ceil(duration_to_extract / frame_interval)) + 2 - - pbar = comfy.utils.ProgressBar(expected_frames) if expected_frames > 0 else None - - for frame in container.decode(video_stream): - frame_time = frame.time - if frame_time is None: - frame_time = float(frame.pts * float(video_stream.time_base)) if frame.pts and video_stream.time_base else 0.0 - - if frame_time < actual_start_time: - continue - - # Add a slight buffer (interval) to ensure we evaluate the boundary correctly - if frame_time > actual_end_time + frame_interval: - break - - # Fix PyAV color shift by forcing proper colorspace and range conversion. - # Omit dst_colorspace so swscale defaults naturally for RGB output - # (passing it can cause the YUV matrix to be applied incorrectly). - try: - frame = frame.reformat( - format="rgb24", - src_colorspace=src_colorspace, - src_color_range=src_color_range, - dst_color_range=dst_range - ) - frame_rgb = frame.to_ndarray(format='rgb24') - except Exception as e: - # Fallback: if explicit color reformat fails, use PyAV's default conversion - print(f"[LoadVideoUI] Color reformat failed, using default: {e}") - frame_rgb = frame.to_ndarray(format='rgb24') - - # Apply interactive crop first - if manual_crop_left > 0 or manual_crop_top > 0 or manual_crop_right > 0 or manual_crop_bottom > 0: - frame_rgb = frame_rgb[manual_crop_top:orig_h-manual_crop_bottom, manual_crop_left:orig_w-manual_crop_right, :] - - # Now resize to the scaled dimensions - if scale_w != cropped_orig_w or scale_h != cropped_orig_h: - import cv2 - frame_rgb = cv2.resize(frame_rgb, (scale_w, scale_h), interpolation=cv2.INTER_AREA) - - if crop_left > 0 or crop_top > 0 or crop_right > 0 or crop_bottom > 0: - frame_rgb = frame_rgb[crop_top:scale_h-crop_bottom, crop_left:scale_w-crop_right, :] - if pad_left > 0 or pad_top > 0 or pad_right > 0 or pad_bottom > 0: - frame_rgb = np.pad(frame_rgb, ((pad_top, pad_bottom), (pad_left, pad_right), (0, 0)), mode='constant', constant_values=0) - - # Duplicate or skip frames perfectly based on timestamps to meet forced framerate. - # FIX: Use strictly less than (<) for actual_end_time to prevent the loop from fetching an extra +1 frame - # at the exact boundary of the duration slice! - while expected_target_time <= frame_time and expected_target_time < actual_end_time - 1e-5: - if image_tensor is None and expected_frames > 0: - # First frame: allocate the tensor - height, width = frame_rgb.shape[:2] - alloc_frames = expected_frames + 50 # Add generous buffer to prevent reallocation - try: - image_tensor = torch.zeros((alloc_frames, height, width, 3), dtype=torch.float32) - except Exception as e: - print(f"[LoadVideoUI] Pre-allocation failed, falling back to list: {e}") - expected_frames = 0 # Disable pre-allocation - - if image_tensor is not None: - # Check bounds (just in case) - if frames_loaded >= image_tensor.shape[0]: - # Extend tensor if we underestimated - extension = torch.zeros((50, image_tensor.shape[1], image_tensor.shape[2], 3), dtype=torch.float32) - image_tensor = torch.cat((image_tensor, extension), dim=0) - - # Insert frame with minimal memory copy directly to tensor - image_tensor[frames_loaded] = torch.from_numpy(frame_rgb).float().div_(255.0) - frames_loaded += 1 - else: - # Fallback list append if pre-allocation failed - frames.append(frame_rgb) - - if pbar: - pbar.update(1) - - expected_target_time += frame_interval - - # Convert frames to ComfyUI Image standard format [N, H, W, C], float32, range 0.0-1.0 - if image_tensor is not None: - if frames_loaded > 0: - image_tensor = image_tensor[:frames_loaded] - else: - image_tensor = torch.zeros((1, 512, 512, 3), dtype=torch.float32) - elif len(frames) > 0: - frames_np = np.array(frames, dtype=np.float32) / 255.0 - image_tensor = torch.from_numpy(frames_np) - else: - # Fallback for an empty slice - image_tensor = torch.zeros((1, 512, 512, 3), dtype=torch.float32) - - # 3. Extract Audio (PyAV) - audio_dict = {"waveform": torch.zeros((1, 1, 44100)), "sample_rate": 44100} # Default empty audio - - if len(container.streams.audio) > 0: - try: - audio_stream = container.streams.audio[0] - audio_stream.thread_type = "AUTO" - sample_rate = getattr(audio_stream, 'rate', 44100) or 44100 - - # We must seek again on the container specifically for the audio stream - if audio_stream.time_base: - seek_pts = int(actual_start_time / float(audio_stream.time_base)) - else: - seek_pts = int(actual_start_time * av.time_base) - - container.seek(seek_pts, stream=audio_stream, backward=True) - - # Resample to standard float planar format (fltp) - resampler = av.AudioResampler(format='fltp') - - audio_data = [] - first_frame_time = None - - for frame in container.decode(audio_stream): - frame_time = frame.time - if frame_time is None: - frame_time = float(frame.pts * float(audio_stream.time_base)) if frame.pts and audio_stream.time_base else 0.0 - - # Give a small 1-second buffer to ensure we catch end frames - if frame_time > actual_end_time + 1.0: - break - - if first_frame_time is None: - first_frame_time = frame_time - - resampled_frames = resampler.resample(frame) - for r_frame in resampled_frames: - audio_data.append(r_frame.to_ndarray()) - - if audio_data: - # Concatenate all frames horizontally along the sample axis - waveform_np = np.concatenate(audio_data, axis=1) - waveform = torch.from_numpy(waveform_np).float() - - if first_frame_time is None: - first_frame_time = 0.0 - - # Calculate exact slice points to trim precisely - offset_sec = max(0.0, actual_start_time - first_frame_time) - start_sample = int(offset_sec * sample_rate) - - duration_sec_audio = actual_end_time - actual_start_time - end_sample = start_sample + int(duration_sec_audio * sample_rate) - - # Trim array bounds properly - if end_sample > start_sample: - waveform = waveform[:, start_sample:end_sample] - else: - waveform = waveform[:, start_sample:] - - # Expand to ComfyUI Audio standard [batch_size, channels, samples] - waveform = waveform.unsqueeze(0) - audio_dict = {"waveform": waveform, "sample_rate": sample_rate} - - except Exception as e: - # Catch gracefully without breaking the pipeline execution - print(f"[LoadVideoUI] Audio track extraction skipped or failed: {e}") - - # Always close container to free up system memory lock - container.close() - - # Output accurate final duration in seconds - final_duration_sec = float(max(0.0, actual_end_time - actual_start_time)) - - # Accurately output the true number of extracted frames - # (Using the shape of the array provides exact 1:1 parity with the timeline's math) - frame_count = image_tensor.shape[0] if (frames_loaded > 0 or len(frames) > 0) else 0 - if frame_count == 0 and final_duration_sec > 0: - # Fallback estimation only if PyAV completely failed to decode a valid chunk - calc_fr = float(frame_rate) if frame_rate > 0 else 24.0 - frame_count = int(np.floor(final_duration_sec * calc_fr)) - +import os +import torch +import numpy as np +import folder_paths +import av +from server import PromptServer +from aiohttp import web +import comfy.utils + +# Custom API route to serve video files from anywhere on the user's system for the frontend preview +@PromptServer.instance.routes.get("/video_ui_custom_view") +async def custom_view(request): + file_path = request.query.get("filename", "") + if os.path.exists(file_path) and os.path.isfile(file_path): + return web.FileResponse(file_path) + return web.Response(status=404, text="File not found") + +# Custom API route for Chunked Uploads to bypass the 413 Payload Too Large error +@PromptServer.instance.routes.post("/video_ui_upload_chunk") +async def upload_chunk(request): + post = await request.post() + file = post.get("file") + filename = post.get("filename") + chunk_index = int(post.get("chunk_index")) + total_chunks = int(post.get("total_chunks")) + + upload_dir = folder_paths.get_input_directory() + file_path = os.path.join(upload_dir, filename) + + # Append to file if it's not the first chunk, otherwise write new + mode = "ab" if chunk_index > 0 else "wb" + with open(file_path, mode) as f: + f.write(file.file.read()) + + if chunk_index == total_chunks - 1: + return web.json_response({"name": filename}) + return web.json_response({"status": "ok"}) + + +class LoadVideoUI: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "video": ("STRING", {"default": ""}), + "start_time": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 100000.0, "step": 0.01}), + "end_time": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 100000.0, "step": 0.01}), + "duration": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 100000.0, "step": 0.01}), + "start_frame": ("INT", {"default": 0, "min": 0, "max": 10000000, "step": 1}), + "end_frame": ("INT", {"default": 0, "min": 0, "max": 10000000, "step": 1}), + "duration_frames": ("INT", {"default": 0, "min": 0, "max": 10000000, "step": 1}), + "resize_method": (["maintain aspect ratio", "stretch to fit", "pad", "crop"], {"default": "maintain aspect ratio"}), + "custom_width": ("INT", {"default": 0, "min": 0, "max": 100000, "step": 8, "tooltip": "Custom width. 0 means original width."}), + "custom_height": ("INT", {"default": 0, "min": 0, "max": 100000, "step": 8, "tooltip": "Custom height. 0 means original height."}), + "frame_rate": ("INT", {"default": 24, "min": 1, "max": 120, "step": 1, "tooltip": "Force the video to a specific frame rate for extraction."}), + "display_mode": (["seconds", "frames"], {"default": "seconds"}), + "crop_x": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}), + "crop_y": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}), + "crop_w": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}), + "crop_h": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}), + } + } + + RETURN_TYPES = ("IMAGE", "AUDIO", "FLOAT", "INT") + RETURN_NAMES = ("images", "audio", "duration", "frame_count") + FUNCTION = "load_video" + CATEGORY = "WhatDreamsCost" + + def load_video(self, video, frame_rate, display_mode, start_time, end_time, duration, start_frame, end_frame, duration_frames, custom_width=0, custom_height=0, resize_method="maintain aspect ratio", crop_x=0.0, crop_y=0.0, crop_w=1.0, crop_h=1.0, **kwargs): + if not video: + # Return blank defaults if no video is loaded + empty_image = torch.zeros((1, 512, 512, 3), dtype=torch.float32) + empty_audio = {"waveform": torch.zeros((1, 1, 44100)), "sample_rate": 44100} + return (empty_image, empty_audio, 0.0, 0) + + # 1. Resolve path using ComfyUI standard paths or Absolute Path + video_path = video # Try exact/absolute path first + if not os.path.exists(video_path): + video_path_annotated = folder_paths.get_annotated_filepath(video) + if os.path.exists(video_path_annotated): + video_path = video_path_annotated + else: + video_path_input = os.path.join(folder_paths.get_input_directory(), video) + if os.path.exists(video_path_input): + video_path = video_path_input + else: + raise FileNotFoundError(f"Video file not found: {video}") + + # Open container to read streams and metadata + container = av.open(video_path) + + # Determine video stream and duration + video_stream = container.streams.video[0] if len(container.streams.video) > 0 else None + video_duration = 0 + if video_stream and video_stream.duration and video_stream.time_base: + video_duration = float(video_stream.duration * video_stream.time_base) + + orig_w = video_stream.codec_context.width if video_stream else 512 + orig_h = video_stream.codec_context.height if video_stream else 512 + + # Determine correct colorspace and color range for PyAV conversion to prevent color shift + try: + from av.video.reformatter import Colorspace, ColorRange + # Improve fallback heuristic to check both dimensions (e.g. 720x1280 vertical video is HD) + fallback_cs = Colorspace.ITU709 if max(orig_w, orig_h) >= 720 else Colorspace.ITU601 + fallback_cr = ColorRange.MPEG + dst_range = ColorRange.JPEG # RGB should always be full range + except ImportError: + fallback_cs = "itu709" if max(orig_w, orig_h) >= 720 else "itu601" + fallback_cr = "mpeg" + dst_range = "jpeg" + + src_colorspace = fallback_cs + src_color_range = fallback_cr + + if video_stream and video_stream.codec_context: + cc = video_stream.codec_context + + c_space = getattr(cc, 'colorspace', getattr(cc, 'color_space', None)) + if c_space and hasattr(c_space, 'name') and c_space.name != "UNSPECIFIED": + src_colorspace = c_space + elif c_space and isinstance(c_space, str) and "unspecified" not in c_space.lower(): + src_colorspace = c_space + + c_range = getattr(cc, 'color_range', None) + if c_range and hasattr(c_range, 'name') and c_range.name != "UNSPECIFIED": + src_color_range = c_range + elif c_range and isinstance(c_range, str) and "unspecified" not in c_range.lower(): + src_color_range = c_range + + target_w = custom_width if custom_width > 0 else orig_w + target_h = custom_height if custom_height > 0 else orig_h + + target_w = target_w - (target_w % 2) + target_h = target_h - (target_h % 2) + + # Calculate manual crop from interactive UI first + manual_crop_left = int(orig_w * crop_x) + manual_crop_top = int(orig_h * crop_y) + manual_crop_right = orig_w - int(orig_w * (crop_x + crop_w)) + manual_crop_bottom = orig_h - int(orig_h * (crop_y + crop_h)) + + # Ensure we don't crop more than the image + manual_crop_left = max(0, min(manual_crop_left, orig_w - 1)) + manual_crop_top = max(0, min(manual_crop_top, orig_h - 1)) + manual_crop_right = max(0, min(manual_crop_right, orig_w - manual_crop_left - 1)) + manual_crop_bottom = max(0, min(manual_crop_bottom, orig_h - manual_crop_top - 1)) + + # After manual crop, the new original dimensions are: + cropped_orig_w = orig_w - manual_crop_left - manual_crop_right + cropped_orig_h = orig_h - manual_crop_top - manual_crop_bottom + + # If no custom width/height is provided, use the cropped original dimensions + if custom_width == 0: + target_w = cropped_orig_w + target_w = target_w - (target_w % 2) + if custom_height == 0: + target_h = cropped_orig_h + target_h = target_h - (target_h % 2) + + scale_w, scale_h = target_w, target_h + pad_left = pad_right = pad_top = pad_bottom = 0 + crop_left = crop_right = crop_top = crop_bottom = 0 + + if custom_width > 0 or custom_height > 0: + if resize_method == "maintain aspect ratio" or resize_method == "pad": + ratio = min(target_w / cropped_orig_w, target_h / cropped_orig_h) + scale_w = int(cropped_orig_w * ratio) + scale_h = int(cropped_orig_h * ratio) + scale_w = scale_w - (scale_w % 2) + scale_h = scale_h - (scale_h % 2) + + if resize_method == "pad": + pad_x = target_w - scale_w + pad_y = target_h - scale_h + pad_left = pad_x // 2 + pad_right = pad_x - pad_left + pad_top = pad_y // 2 + pad_bottom = pad_y - pad_top + else: + target_w, target_h = scale_w, scale_h + + elif resize_method == "crop": + ratio = max(target_w / cropped_orig_w, target_h / cropped_orig_h) + scale_w = int(cropped_orig_w * ratio) + scale_h = int(cropped_orig_h * ratio) + scale_w = scale_w - (scale_w % 2) + scale_h = scale_h - (scale_h % 2) + + crop_x = scale_w - target_w + crop_y = scale_h - target_h + crop_left = crop_x // 2 + crop_right = crop_x - crop_left + crop_top = crop_y // 2 + crop_bottom = crop_y - crop_top + + elif resize_method == "stretch to fit": + scale_w, scale_h = target_w, target_h + + # Determine exact bounds based on frontend mode + if display_mode == "frames": + fr = float(frame_rate) if frame_rate > 0 else 24.0 + actual_start_time = float(start_frame) / fr + actual_end_time = float(end_frame) / fr if (end_frame > 0 and end_frame > start_frame) else video_duration + else: + actual_start_time = start_time + actual_end_time = end_time if (end_time > 0 and end_time > start_time) else video_duration + + if actual_end_time <= 0: + actual_end_time = float('inf') # Fallback if duration is unknown + + # 2. Extract Video Frames (PyAV) + frames = [] + image_tensor = None + frames_loaded = 0 + + if video_stream: + video_stream.thread_type = "AUTO" # Enable multithreaded decoding + + # Efficiently seek backwards to the nearest keyframe + if video_stream.time_base: + seek_pts = int(actual_start_time / float(video_stream.time_base)) + else: + seek_pts = int(actual_start_time * av.time_base) + + container.seek(seek_pts, stream=video_stream, backward=True) + + # Custom sampling to force specific framerate + frame_interval = 1.0 / float(frame_rate) if frame_rate > 0 else 1.0/24.0 + expected_target_time = actual_start_time + + # Pre-calculate expected frames + alloc_end_time = actual_end_time if actual_end_time != float('inf') else video_duration + expected_frames = 0 + if alloc_end_time > 0: + duration_to_extract = alloc_end_time - actual_start_time + if duration_to_extract > 0: + expected_frames = int(np.ceil(duration_to_extract / frame_interval)) + 2 + + pbar = comfy.utils.ProgressBar(expected_frames) if expected_frames > 0 else None + + for frame in container.decode(video_stream): + frame_time = frame.time + if frame_time is None: + frame_time = float(frame.pts * float(video_stream.time_base)) if frame.pts and video_stream.time_base else 0.0 + + if frame_time < actual_start_time: + continue + + # Add a slight buffer (interval) to ensure we evaluate the boundary correctly + if frame_time > actual_end_time + frame_interval: + break + + # Fix PyAV color shift by forcing proper colorspace and range conversion. + # Omit dst_colorspace so swscale defaults naturally for RGB output + # (passing it can cause the YUV matrix to be applied incorrectly). + try: + frame = frame.reformat( + format="rgb24", + src_colorspace=src_colorspace, + src_color_range=src_color_range, + dst_color_range=dst_range + ) + frame_rgb = frame.to_ndarray(format='rgb24') + except Exception as e: + # Fallback: if explicit color reformat fails, use PyAV's default conversion + print(f"[LoadVideoUI] Color reformat failed, using default: {e}") + frame_rgb = frame.to_ndarray(format='rgb24') + + # Apply interactive crop first + if manual_crop_left > 0 or manual_crop_top > 0 or manual_crop_right > 0 or manual_crop_bottom > 0: + frame_rgb = frame_rgb[manual_crop_top:orig_h-manual_crop_bottom, manual_crop_left:orig_w-manual_crop_right, :] + + # Now resize to the scaled dimensions + if scale_w != cropped_orig_w or scale_h != cropped_orig_h: + import cv2 + frame_rgb = cv2.resize(frame_rgb, (scale_w, scale_h), interpolation=cv2.INTER_AREA) + + if crop_left > 0 or crop_top > 0 or crop_right > 0 or crop_bottom > 0: + frame_rgb = frame_rgb[crop_top:scale_h-crop_bottom, crop_left:scale_w-crop_right, :] + if pad_left > 0 or pad_top > 0 or pad_right > 0 or pad_bottom > 0: + frame_rgb = np.pad(frame_rgb, ((pad_top, pad_bottom), (pad_left, pad_right), (0, 0)), mode='constant', constant_values=0) + + # Duplicate or skip frames perfectly based on timestamps to meet forced framerate. + # FIX: Use strictly less than (<) for actual_end_time to prevent the loop from fetching an extra +1 frame + # at the exact boundary of the duration slice! + while expected_target_time <= frame_time and expected_target_time < actual_end_time - 1e-5: + if image_tensor is None and expected_frames > 0: + # First frame: allocate the tensor + height, width = frame_rgb.shape[:2] + alloc_frames = expected_frames + 50 # Add generous buffer to prevent reallocation + try: + image_tensor = torch.zeros((alloc_frames, height, width, 3), dtype=torch.float32) + except Exception as e: + print(f"[LoadVideoUI] Pre-allocation failed, falling back to list: {e}") + expected_frames = 0 # Disable pre-allocation + + if image_tensor is not None: + # Check bounds (just in case) + if frames_loaded >= image_tensor.shape[0]: + # Extend tensor if we underestimated + extension = torch.zeros((50, image_tensor.shape[1], image_tensor.shape[2], 3), dtype=torch.float32) + image_tensor = torch.cat((image_tensor, extension), dim=0) + + # Insert frame with minimal memory copy directly to tensor + image_tensor[frames_loaded] = torch.from_numpy(frame_rgb).float().div_(255.0) + frames_loaded += 1 + else: + # Fallback list append if pre-allocation failed + frames.append(frame_rgb) + + if pbar: + pbar.update(1) + + expected_target_time += frame_interval + + # Convert frames to ComfyUI Image standard format [N, H, W, C], float32, range 0.0-1.0 + if image_tensor is not None: + if frames_loaded > 0: + image_tensor = image_tensor[:frames_loaded] + else: + image_tensor = torch.zeros((1, 512, 512, 3), dtype=torch.float32) + elif len(frames) > 0: + frames_np = np.array(frames, dtype=np.float32) / 255.0 + image_tensor = torch.from_numpy(frames_np) + else: + # Fallback for an empty slice + image_tensor = torch.zeros((1, 512, 512, 3), dtype=torch.float32) + + # 3. Extract Audio (PyAV) + audio_dict = {"waveform": torch.zeros((1, 1, 44100)), "sample_rate": 44100} # Default empty audio + + if len(container.streams.audio) > 0: + try: + audio_stream = container.streams.audio[0] + audio_stream.thread_type = "AUTO" + sample_rate = getattr(audio_stream, 'rate', 44100) or 44100 + + # We must seek again on the container specifically for the audio stream + if audio_stream.time_base: + seek_pts = int(actual_start_time / float(audio_stream.time_base)) + else: + seek_pts = int(actual_start_time * av.time_base) + + container.seek(seek_pts, stream=audio_stream, backward=True) + + # Resample to standard float planar format (fltp) + resampler = av.AudioResampler(format='fltp') + + audio_data = [] + first_frame_time = None + + for frame in container.decode(audio_stream): + frame_time = frame.time + if frame_time is None: + frame_time = float(frame.pts * float(audio_stream.time_base)) if frame.pts and audio_stream.time_base else 0.0 + + # Give a small 1-second buffer to ensure we catch end frames + if frame_time > actual_end_time + 1.0: + break + + if first_frame_time is None: + first_frame_time = frame_time + + resampled_frames = resampler.resample(frame) + for r_frame in resampled_frames: + audio_data.append(r_frame.to_ndarray()) + + if audio_data: + # Concatenate all frames horizontally along the sample axis + waveform_np = np.concatenate(audio_data, axis=1) + waveform = torch.from_numpy(waveform_np).float() + + if first_frame_time is None: + first_frame_time = 0.0 + + # Calculate exact slice points to trim precisely + offset_sec = max(0.0, actual_start_time - first_frame_time) + start_sample = int(offset_sec * sample_rate) + + duration_sec_audio = actual_end_time - actual_start_time + end_sample = start_sample + int(duration_sec_audio * sample_rate) + + # Trim array bounds properly + if end_sample > start_sample: + waveform = waveform[:, start_sample:end_sample] + else: + waveform = waveform[:, start_sample:] + + # Expand to ComfyUI Audio standard [batch_size, channels, samples] + waveform = waveform.unsqueeze(0) + audio_dict = {"waveform": waveform, "sample_rate": sample_rate} + + except Exception as e: + # Catch gracefully without breaking the pipeline execution + print(f"[LoadVideoUI] Audio track extraction skipped or failed: {e}") + + # Always close container to free up system memory lock + container.close() + + # Output accurate final duration in seconds + final_duration_sec = float(max(0.0, actual_end_time - actual_start_time)) + + # Accurately output the true number of extracted frames + # (Using the shape of the array provides exact 1:1 parity with the timeline's math) + frame_count = image_tensor.shape[0] if (frames_loaded > 0 or len(frames) > 0) else 0 + if frame_count == 0 and final_duration_sec > 0: + # Fallback estimation only if PyAV completely failed to decode a valid chunk + calc_fr = float(frame_rate) if frame_rate > 0 else 24.0 + frame_count = int(np.floor(final_duration_sec * calc_fr)) + return (image_tensor, audio_dict, final_duration_sec, frame_count) \ No newline at end of file diff --git a/ltx_director.py b/ltx_director.py index c2f47be..2b13a3b 100644 --- a/ltx_director.py +++ b/ltx_director.py @@ -1,829 +1,878 @@ -# --- START OF FILE ltx_director.py --- - -import logging -import json -import base64 -import io as _io -import math -import time - -import numpy as np -import torch -import av -from PIL import Image - -import os -import folder_paths -import comfy.model_management - -from comfy_api.latest import io - -from .prompt_relay import ( - get_raw_tokenizer, - map_token_indices, - build_segments, - create_mask_fn, - distribute_segment_lengths, -) - -from .patches import detect_model_type, apply_patches - -log = logging.getLogger(__name__) - -# Custom socket type shared with LTXSequencer -GuideData = io.Custom("GUIDE_DATA") - - -def _format_timeline_to_text(global_prompt, duration_frames, frame_rate, epsilon, - custom_width, custom_height, resize_method, - timeline_data, local_prompts, segment_lengths, guide_strength): - lines = [] - lines.append("LTX Director Timeline Export") - lines.append("============================") - lines.append("") - lines.append("=== Global Parameters ===") - lines.append(f"Global Prompt:\n{global_prompt}\n") - - fr = float(frame_rate) if frame_rate else 24.0 - if fr <= 0: fr = 24.0 - - lines.append(f"Duration: {duration_frames} frames ({(duration_frames / fr):.2f}s @ {fr} FPS)") - lines.append(f"Epsilon (Penalty Decay): {epsilon}") - - if custom_width > 0 or custom_height > 0: - lines.append(f"Target Dimensions: {custom_width}x{custom_height} (Resize Method: {resize_method})") - else: - lines.append("Target Dimensions: Auto (Based on first image)") - - lines.append("") - lines.append("=== Timeline Segments ===") - - # --- 1. Text Prompts (Extracted from direct inputs) --- - locals_list = [p.strip() for p in local_prompts.split("|")] if local_prompts else [] - lengths_list = [l.strip() for l in segment_lengths.split(",")] if segment_lengths else [] - - if locals_list and any(locals_list): - lines.append("\n--- Text Prompts ---") - current_frame = 0.0 - for i, prompt in enumerate(locals_list): - try: - len_f = float(lengths_list[i]) if i < len(lengths_list) and lengths_list[i] else 0.0 - except ValueError: - len_f = 0.0 - - start_f = current_frame - end_f = start_f + len_f - - start_s = start_f / fr - end_s = end_f / fr - len_s = len_f / fr - - lines.append(f"\n[Prompt {i+1}]") - lines.append(f"Time: {start_s:.2f}s - {end_s:.2f}s (Duration: {len_s:.2f}s)") - lines.append(f"Frames: {start_f:.1f} - {end_f:.1f} (Length: {len_f:.1f})") - lines.append(f"Prompt:\n{prompt}") - lines.append("-" * 40) - - current_frame += len_f - - # --- 2. Images & Audio (Extracted from JSON) --- - try: - tdata = json.loads(timeline_data) if timeline_data else {} - segs = tdata.get("segments", []) - - img_segs = [s for s in segs if s.get("type", "image") == "image"] - img_segs.sort(key=lambda s: float(s.get("start", 0))) - if img_segs: - lines.append("\n--- Image Guides ---") - strengths = [float(x.strip()) for x in guide_strength.split(",")] if guide_strength and guide_strength.strip() else [] - for i, seg in enumerate(img_segs): - start_f = float(seg.get("start", 0)) - start_s = start_f / fr - strength = strengths[i] if i < len(strengths) else 1.0 - - lines.append(f"\n[Image {i+1}]") - lines.append(f"Time Inserted: {start_s:.2f}s (Frame {start_f:.1f})") - lines.append(f"Guide Strength: {strength}") - lines.append("-" * 40) - - audio_segs = [s for s in segs if s.get("type") == "audio"] - audio_segs.sort(key=lambda s: float(s.get("start", 0))) - if audio_segs: - lines.append("\n--- Audio Segments ---") - for i, seg in enumerate(audio_segs): - start_f = float(seg.get("start", 0)) - len_f = float(seg.get("length", 0)) - start_s = start_f / fr - len_s = len_f / fr - file_name = seg.get("fileName", "Unknown") - lines.append(f"\n[Audio {i+1}] {file_name}") - lines.append(f"Time: {start_s:.2f}s (Frame {start_f:.1f}) | Duration: {len_s:.2f}s") - lines.append("-" * 40) - - except Exception as e: - lines.append(f"\n[Note: Could not parse detailed timeline JSON for images/audio. Error: {e}]") - - return "\n".join(lines) - - -# Register API endpoint for instant export from the JS UI if needed -try: - import server - from aiohttp import web - - @server.PromptServer.instance.routes.post("/ltx_director/export_timeline") - async def export_timeline_endpoint(request): - try: - data = await request.json() - global_prompt = data.get("global_prompt", "") - timeline_data = data.get("timeline_data", "{}") - duration_frames = int(data.get("duration_frames", 0)) - frame_rate = float(data.get("frame_rate", 24)) - epsilon = float(data.get("epsilon", 0.001)) - custom_width = int(data.get("custom_width", 0)) - custom_height = int(data.get("custom_height", 0)) - resize_method = data.get("resize_method", "maintain aspect ratio") - local_prompts = data.get("local_prompts", "") - segment_lengths = data.get("segment_lengths", "") - guide_strength = data.get("guide_strength", "") - - formatted_text = _format_timeline_to_text( - global_prompt, duration_frames, frame_rate, epsilon, - custom_width, custom_height, resize_method, - timeline_data, local_prompts, segment_lengths, guide_strength - ) - - out_dir = folder_paths.get_output_directory() - filename = f"ltx_director_prompts_{int(time.time())}.txt" - filepath = os.path.join(out_dir, filename) - - with open(filepath, "w", encoding="utf-8") as f: - f.write(formatted_text) - - return web.json_response({ - "status": "success", - "filepath": filepath, - "filename": filename, - "content": formatted_text - }) - except Exception as e: - log.error(f"[PromptRelay] Export timeline endpoint error: {e}") - return web.json_response({"status": "error", "message": str(e)}, status=500) -except Exception as e: - log.warning(f"[PromptRelay] Could not register /ltx_director/export_timeline endpoint: {e}") - - -def _load_image_tensor(seg: dict) -> torch.Tensor: - """Decode an image from the ComfyUI input folder (if imageFile provided) or fallback to base64 - to a ComfyUI-style image tensor of shape [1, H, W, 3], float32 in [0, 1].""" - if seg.get("imageFile"): - file_path = os.path.join(folder_paths.get_input_directory(), seg["imageFile"]) - if os.path.exists(file_path): - img = Image.open(file_path).convert("RGB") - arr = np.array(img, dtype=np.float32) / 255.0 - return torch.from_numpy(arr).unsqueeze(0) - - b64_str = seg.get("imageB64", "") - if not b64_str or b64_str.startswith("/view?"): - return torch.zeros((1, 512, 512, 3), dtype=torch.float32) - - if "," in b64_str: - b64_str = b64_str.split(",", 1)[1] - - try: - img_bytes = base64.b64decode(b64_str) - img = Image.open(_io.BytesIO(img_bytes)).convert("RGB") - arr = np.array(img, dtype=np.float32) / 255.0 - return torch.from_numpy(arr).unsqueeze(0) - except: - return torch.zeros((1, 512, 512, 3), dtype=torch.float32) - - -def _resize_image(tensor: torch.Tensor, target_w: int, target_h: int, method: str, divisible_by: int) -> torch.Tensor: - """Resize a [1, H, W, 3] float32 tensor to target dimensions using the given method, - then snap the final dimensions to be divisible by `divisible_by`.""" - from PIL import Image as _PilImage - import torchvision.transforms.functional as TF - - def snap(val, div): - return max(div, (val // div) * div) - - tw = snap(target_w, divisible_by) - th = snap(target_h, divisible_by) - - img_np = (tensor[0].cpu().numpy() * 255.0).clip(0, 255).astype(np.uint8) - pil = _PilImage.fromarray(img_np) - src_w, src_h = pil.size - - if method == "stretch to fit": - resized = pil.resize((tw, th), _PilImage.LANCZOS) - - elif method == "maintain aspect ratio": - ratio = min(tw / src_w, th / src_h) - new_w = int(src_w * ratio) - new_h = int(src_h * ratio) - new_w = snap(new_w, divisible_by) - new_h = snap(new_h, divisible_by) - resized = pil.resize((new_w, new_h), _PilImage.LANCZOS) - - elif method == "pad": - ratio = min(tw / src_w, th / src_h) - new_w = snap(int(src_w * ratio), divisible_by) - new_h = snap(int(src_h * ratio), divisible_by) - inner = pil.resize((new_w, new_h), _PilImage.LANCZOS) - resized = _PilImage.new("RGB", (tw, th), (0, 0, 0)) - resized.paste(inner, ((tw - new_w) // 2, (th - new_h) // 2)) - - elif method == "crop": - ratio = max(tw / src_w, th / src_h) - new_w = int(src_w * ratio) - new_h = int(src_h * ratio) - inner = pil.resize((new_w, new_h), _PilImage.LANCZOS) - left = (new_w - tw) // 2 - top = (new_h - th) // 2 - resized = inner.crop((left, top, left + tw, top + th)) - - else: - resized = pil.resize((tw, th), _PilImage.LANCZOS) - - arr = np.array(resized, dtype=np.float32) / 255.0 - return torch.from_numpy(arr).unsqueeze(0) - - -def _compress_image(tensor: torch.Tensor, crf: int) -> torch.Tensor: - """Apply H.264 compression artefacts to a [1, H, W, 3] float32 tensor (ComfyUI image format). - crf=0 means no compression. Uses PyAV to encode/decode a single frame in-memory.""" - if crf == 0: - return tensor - img = tensor[0] # [H, W, 3] - # Dimensions must be even for H.264 - h = (img.shape[0] // 2) * 2 - w = (img.shape[1] // 2) * 2 - img_np = (img[:h, :w] * 255.0).byte().cpu().numpy() # uint8 [H, W, 3] - - try: - buf = _io.BytesIO() - container = av.open(buf, mode="w", format="mp4") - stream = container.add_stream("libx264", rate=1) - stream.width = w - stream.height = h - stream.pix_fmt = "yuv420p" - stream.options = {"crf": str(crf), "preset": "ultrafast"} - frame = av.VideoFrame.from_ndarray(img_np, format="rgb24") - for pkt in stream.encode(frame): - container.mux(pkt) - for pkt in stream.encode(None): - container.mux(pkt) - container.close() - - buf.seek(0) - container_r = av.open(buf, mode="r") - decoded = None - for frame_r in container_r.decode(video=0): - decoded = frame_r.to_ndarray(format="rgb24") # [H, W, 3] - break - container_r.close() - - if decoded is None: - return tensor - arr = torch.from_numpy(decoded.astype(np.float32) / 255.0).to(tensor.device, tensor.dtype) - # Re-embed into original tensor shape (may have been cropped by even-rounding) - out = tensor.clone() - out[0, :h, :w] = arr - return out - except Exception as e: - log.warning("[PromptRelay] img_compression encode/decode failed: %s", e) - return tensor - - -def _build_combined_audio(timeline_data_str: str, duration_frames: int, frame_rate: float) -> dict: - """Parses timeline JSON, loads/trims audio directly from memory using PyAV, - and aligns to a global timeline yielding ComfyUI's format. - Output length explicitly mimics the timeline's duration_frames length.""" - target_sr = 44100 - total_samples = max(1, int(math.ceil(duration_frames / frame_rate * target_sr))) - empty_audio = {"waveform": torch.zeros((1, 2, total_samples), dtype=torch.float32), "sample_rate": target_sr} - - if not timeline_data_str: - return empty_audio - - try: - data = json.loads(timeline_data_str) - audio_segs = data.get("audioSegments", []) - except Exception: - return empty_audio - - if not audio_segs: - return empty_audio - - out_waveform = torch.zeros((2, total_samples), dtype=torch.float32) - - for seg in audio_segs: - buffer = None - if seg.get("audioFile"): - file_path = os.path.join(folder_paths.get_input_directory(), seg["audioFile"]) - if os.path.exists(file_path): - with open(file_path, "rb") as f: - buffer = _io.BytesIO(f.read()) - - if not buffer and seg.get("audioB64"): - b64 = seg.get("audioB64") - if "," in b64: - b64 = b64.split(",", 1)[1] - try: - audio_bytes = base64.b64decode(b64) - buffer = _io.BytesIO(audio_bytes) - except: - pass - - if not buffer: - continue - - try: - clip_frames = [] - - # Use PyAV to decode directly from memory buffer - with av.open(buffer) as container: - stream = container.streams.audio[0] - - # Setup resampler to ensure output is 44.1kHz, Stereo, Float32 Planar - resampler = av.AudioResampler( - format='fltp', - layout='stereo', - rate=target_sr, - ) - - for frame in container.decode(stream): - for resampled_frame in resampler.resample(frame): - # to_ndarray() on fltp gives shape (channels, samples) - arr = resampled_frame.to_ndarray() - clip_frames.append(torch.from_numpy(arr)) - - # Flush the resampler to get any remaining samples - for resampled_frame in resampler.resample(None): - arr = resampled_frame.to_ndarray() - clip_frames.append(torch.from_numpy(arr)) - - if not clip_frames: - continue - - # Concatenate all frame blocks along the samples dimension (dim 1) - waveform = torch.cat(clip_frames, dim=1) # Shape: [2, total_clip_samples] - - # Calculate interactive trim boundaries - trim_start_frames = float(seg.get("trimStart", 0)) - length_frames = float(seg.get("length", 1)) - start_frames = float(seg.get("start", 0)) - - start_sample_src = int(trim_start_frames / frame_rate * target_sr) - length_samples = int(length_frames / frame_rate * target_sr) - end_sample_src = start_sample_src + length_samples - - if start_sample_src < 0: start_sample_src = 0 - if end_sample_src > waveform.shape[1]: - end_sample_src = waveform.shape[1] - - actual_length = end_sample_src - start_sample_src - if actual_length <= 0: continue - - # Extract the correct segment of the audio - clip_waveform = waveform[:, start_sample_src:end_sample_src] - - # Position onto the timeline - start_sample_dst = int(start_frames / frame_rate * target_sr) - - if start_sample_dst >= out_waveform.shape[1]: - continue - - end_sample_dst = start_sample_dst + actual_length - - # Clip any trailing overflow so we don't index past the timeline bounds - if end_sample_dst > out_waveform.shape[1]: - actual_length = out_waveform.shape[1] - start_sample_dst - clip_waveform = clip_waveform[:, :actual_length] - end_sample_dst = start_sample_dst + actual_length - - if actual_length <= 0: - continue - - # Additive composite (allows clips overlapping to sum together naturally) - out_waveform[:, start_sample_dst:end_sample_dst] += clip_waveform - - except Exception as e: - log.warning("[PromptRelay] Audio process error for segment %s: %s", seg.get("fileName"), e) - continue - - return {"waveform": out_waveform.unsqueeze(0), "sample_rate": target_sr} - - -def _convert_to_latent_lengths(pixel_lengths, temporal_stride, latent_frames): - """Convert pixel-space segment lengths to integer latent-space lengths using the - largest-remainder method. Targets the full `latent_frames` when the pixel sum looks - like full coverage (within one stride of latent_frames * stride). Otherwise targets - round(total_pixel / temporal_stride) so partial-coverage timelines stay partial. - """ - if not pixel_lengths: - return [] - total_pixel = sum(pixel_lengths) - if total_pixel <= 0: - return [1] * len(pixel_lengths) - - naive_total = max(1, round(total_pixel / temporal_stride)) - target_total = min(latent_frames, naive_total) - # Within one frame of full → user clearly intended full coverage; pin to latent_frames. - if target_total >= latent_frames - 1: - target_total = latent_frames - - exact = [p * target_total / total_pixel for p in pixel_lengths] - result = [int(e) for e in exact] - diff = target_total - sum(result) - if diff > 0: - order = sorted(range(len(exact)), key=lambda i: -(exact[i] - int(exact[i]))) - for k in range(diff): - result[order[k % len(order)]] += 1 - - # Ensure every segment has ≥ 1 latent frame (steal from the largest if needed). - for i in range(len(result)): - if result[i] < 1: - max_idx = max(range(len(result)), key=lambda j: result[j]) - if result[max_idx] > 1: - result[max_idx] -= 1 - result[i] = 1 - - return result - - -def _encode_relay(model, clip, latent, global_prompt, local_prompts, segment_lengths, epsilon): - for name, val in (("global_prompt", global_prompt), - ("local_prompts", local_prompts), - ("segment_lengths", segment_lengths)): - if val is None: - raise ValueError( - f"PromptRelay: '{name}' arrived as None. " - "Likely causes: a stale workflow JSON saved with null, the timeline " - "editor's web extension failing to load, or an upstream node returning None. " - "Set the field to an empty string or fix the upstream connection." - ) - - # Split prompts but do NOT filter out empty ones yet, so we can detect them - locals_list = [p.strip() for p in local_prompts.split("|")] - - # Check if any specific segment is empty - for p in locals_list: - if not p: - raise ValueError("There is a segment on the timeline missing a prompt!") - - if not locals_list or (len(locals_list) == 1 and not locals_list[0]): - raise ValueError("At least one local prompt is required.") - - arch, patch_size, temporal_stride = detect_model_type(model) - - samples = latent["samples"] - latent_frames = samples.shape[2] - tokens_per_frame = (samples.shape[3] // patch_size[1]) * (samples.shape[4] // patch_size[2]) - - parsed_lengths = None - if segment_lengths.strip(): - pixel_lengths = [int(float(x.strip())) for x in segment_lengths.split(",") if x.strip()] - parsed_lengths = _convert_to_latent_lengths(pixel_lengths, temporal_stride, latent_frames) - - raw_tokenizer = get_raw_tokenizer(clip) - full_prompt, token_ranges = map_token_indices(raw_tokenizer, global_prompt, locals_list) - - log.info("[PromptRelay] Global: tokens [0:%d] (%d tokens)", token_ranges[0][0], token_ranges[0][0]) - for i, (s, e) in enumerate(token_ranges): - log.info("[PromptRelay] Segment %d: tokens [%d:%d] (%d tokens)", i, s, e, e - s) - - conditioning = clip.encode_from_tokens_scheduled(clip.tokenize(full_prompt)) - - effective_lengths = distribute_segment_lengths(len(locals_list), latent_frames, parsed_lengths) - - log.info( - "[PromptRelay] Latent: %d frames, %d tokens/frame, segments: %s", - latent_frames, tokens_per_frame, effective_lengths, - ) - - q_token_idx = build_segments(token_ranges, effective_lengths, epsilon, None) - mask_fn = create_mask_fn(q_token_idx, tokens_per_frame, latent_frames) - - patched = model.clone() - apply_patches(patched, arch, mask_fn) - - return patched, conditioning - - -class LTXDirector(io.ComfyNode): - """WYSIWYG timeline variant — segments and lengths come from a visual editor in the node UI.""" - - @classmethod - def define_schema(cls): - return io.Schema( - node_id="LTXDirector", - display_name="LTX Director", - category="WhatDreamsCost", - description=( - "Same as Prompt Relay Encode, but local prompts and segment lengths are edited " - "visually as draggable blocks on a timeline. The duration_frames input only sets the " - "timeline scale (pixel space) — actual frame count is still read from the latent." - ), - inputs=[ - io.Model.Input("model"), - io.Clip.Input("clip"), - io.Vae.Input("audio_vae", optional=True, tooltip="Optional. Connect an Audio VAE to generate audio latents."), - io.Latent.Input("optional_latent", optional=True, tooltip="Optional. Connect a latent to override the auto-generated one."), - io.String.Input("global_prompt", multiline=True, default="", tooltip="Conditions the entire video. Anchors persistent characters, objects, and scene context."), - io.Int.Input("duration_frames", default=120, min=1, max=10000, step=1, tooltip="Total timeline length in pixel-space frames. Used by the editor for visual scale only."), - io.Float.Input("duration_seconds", default=5.0, min=0.1, max=1000.0, step=0.01, tooltip="Total timeline duration in seconds (computed/synced from frames)."), - io.String.Input("timeline_data", default="", tooltip="JSON state of the timeline editor (auto-managed; do not edit by hand)."), - io.Boolean.Input("use_custom_audio", default=False, tooltip="Toggle between using timeline audio (ON) and generating audio from scratch (OFF)."), - io.String.Input("local_prompts", multiline=True, default="", tooltip="Auto-populated from the timeline editor."), - io.String.Input("segment_lengths", default="", tooltip="Auto-populated from the timeline editor (pixel-space frame counts)."), - io.Float.Input("epsilon", default=0.001, min=0.0001, max=0.99, step=0.0001, tooltip="Penalty decay parameter. Values below ~0.1 all produce sharp boundaries (paper default 0.001). For softer transitions, try 0.5 or higher."), - io.Float.Input("frame_rate", default=24.0, min=1.0, max=240.0, step=1.0, tooltip="Frames per second — only affects how time is displayed in the timeline editor when time_units is set to 'seconds'."), - io.Combo.Input("display_mode", options=["frames", "seconds"], default="seconds", tooltip="Display the ruler, segment ranges, length input, and total in frames or seconds. Internal storage is always pixel-space frames."), - io.String.Input("guide_strength", default="", tooltip="Auto-populated from the timeline editor (comma-separated guide strengths for image segments)."), - io.Int.Input("custom_width", default=0, min=0, max=8192, step=1, tooltip="Target output width for all image segments. Set to 0 to use the original image width."), - io.Int.Input("custom_height", default=0, min=0, max=8192, step=1, tooltip="Target output height for all image segments. Set to 0 to use the original image height."), - io.Combo.Input("resize_method", options=["maintain aspect ratio", "stretch to fit", "pad", "crop"], default="maintain aspect ratio", tooltip="How to resize image segments to fit the target dimensions."), - io.Int.Input("divisible_by", default=32, min=1, max=256, step=1, tooltip="Snap the final output image dimensions to be divisible by this number (e.g. 32 for LTX)."), - io.Int.Input("img_compression", default=18, min=0, max=100, step=1, tooltip="H.264 CRF compression to apply to each guide image. 0 = no compression, higher = more artefacts."), - io.Boolean.Input("save_prompts_to_file", default=False, optional=True, tooltip="Save the timeline prompts and parameters to a text file in your ComfyUI output directory during execution."), - io.Image.Input("reference_image", optional=True, tooltip="Invisible character sheet/style reference. Can also be a Batch of images. Automatically appended to the sequence out-of-bounds."), - io.Image.Input("reference_image_2", optional=True, tooltip="Second optional reference image."), - io.Image.Input("reference_image_3", optional=True, tooltip="Third optional reference image."), - io.Float.Input("reference_strength", default=1.0, min=0.0, max=5.0, step=0.05, optional=True, tooltip="Guide strength for the reference images."), - ], - outputs=[ - io.Model.Output(display_name="model"), - io.Conditioning.Output(display_name="positive"), - io.Latent.Output(display_name="video_latent", tooltip="Auto-generated LTXV empty latent (only populated when no latent is connected)."), - io.Latent.Output(display_name="audio_latent", tooltip="Auto-generated audio latent (uses custom audio if enabled)."), - GuideData.Output(display_name="guide_data"), - io.Float.Output(display_name="frame_rate", tooltip="The frame rate used for the timeline."), - io.Audio.Output(display_name="combined_audio", tooltip="Combined timeline audio layout."), - io.Int.Output(display_name="clean_latent_frames", tooltip="Plug this into a CleanLatentSlice node 'length' to effortlessly cut off the invisible reference image."), - io.Int.Output(display_name="clean_pixel_frames", tooltip="Plug this into your Video Combine 'frame_load_cap' to effortlessly cut off the invisible reference image."), - ], - ) - - @classmethod - def execute(cls, model, clip, global_prompt, duration_frames, duration_seconds, - timeline_data, local_prompts, segment_lengths, guide_strength="", epsilon=1e-3, - frame_rate=24.0, display_mode="seconds", - custom_width=768, custom_height=512, resize_method="maintain aspect ratio", - divisible_by=32, img_compression=0, audio_vae=None, optional_latent=None, - use_custom_audio=False, save_prompts_to_file=False, - reference_image=None, reference_image_2=None, reference_image_3=None, reference_strength=1.0) -> io.NodeOutput: - - # --- Calculate Clean Output Bounds First --- - clean_pixel_frames = duration_frames + 1 - clean_latent_frames = ((clean_pixel_frames - 1) // 8) + 1 - - # --- Build guide_data from image segments FIRST (to derive output dimensions) --- - guide_data = {"images": [], "insert_frames": [], "strengths": [], "frame_rate": float(frame_rate)} - derived_w, derived_h = custom_width, custom_height - - # Consolidate all reference inputs into a flat list, unraveling any image batches. - refs_to_process = [] - for ref_input in (reference_image, reference_image_2, reference_image_3): - if ref_input is not None: - for i in range(ref_input.shape[0]): - refs_to_process.append(ref_input[i:i+1]) - - try: - tdata = json.loads(timeline_data) if timeline_data else {} - img_segs = [ - s for s in tdata.get("segments", []) - if s.get("type", "image") == "image" - and (s.get("imageFile") or s.get("imageB64")) - and int(s.get("start", 0)) < duration_frames # exclude segments fully outside duration - ] - img_segs.sort(key=lambda s: s["start"]) - - strengths = [] - if guide_strength.strip(): - strengths = [float(x.strip()) for x in guide_strength.split(",") if x.strip()] - - for idx, seg in enumerate(img_segs): - tensor = _load_image_tensor(seg) - - # Apply resize - src_h, src_w = tensor.shape[1], tensor.shape[2] - - def snap(val, div): - return max(div, (val // div) * div) - - if custom_width > 0 and custom_height > 0: - # Both dimensions set — apply selected resize_method (pad, crop, stretch, maintain AR) - tensor = _resize_image(tensor, custom_width, custom_height, resize_method, divisible_by) - elif custom_width > 0: - # Width only — scale height from AR, snap both, then resize to exact dimensions - tgt_w = snap(custom_width, divisible_by) - tgt_h = snap(int(src_h * tgt_w / src_w), divisible_by) - tensor = _resize_image(tensor, tgt_w, tgt_h, "stretch to fit", divisible_by) - elif custom_height > 0: - # Height only — scale width from AR, snap both, then resize to exact dimensions - tgt_h = snap(custom_height, divisible_by) - tgt_w = snap(int(src_w * tgt_h / src_h), divisible_by) - tensor = _resize_image(tensor, tgt_w, tgt_h, "stretch to fit", divisible_by) - else: - # Both zero — keep original dimensions, just snap to divisible_by - tensor = _resize_image(tensor, src_w, src_h, "maintain aspect ratio", divisible_by) - - - # Apply compression - if img_compression > 0: - tensor = _compress_image(tensor, img_compression) - - # Record dimensions of the first processed image for latent generation - if idx == 0: - derived_h = tensor.shape[1] - derived_w = tensor.shape[2] - - strength = strengths[idx] if idx < len(strengths) else 1.0 - guide_data["images"].append(tensor) - guide_data["insert_frames"].append(int(seg["start"])) - guide_data["strengths"].append(float(strength)) - - # If no images were loaded from the timeline, create a dummy image at strength 0 - # to prevent artifacts in text-to-video mode. - if not guide_data["images"] and not refs_to_process: - w = derived_w if derived_w > 0 else 768 - h = derived_h if derived_h > 0 else 512 - w = (w // 32) * 32 - h = (h // 32) * 32 - - dummy_image = torch.zeros((1, h, w, 3), dtype=torch.float32) - guide_data["images"].append(dummy_image) - guide_data["insert_frames"].append(0) - guide_data["strengths"].append(0.0) - - derived_w = w - derived_h = h - except Exception as e: - log.warning("[PromptRelay] Could not build guide_data: %s", e) - - # --- Handle Reference Image Injection --- - if refs_to_process: - if optional_latent is not None: - log.warning("[PromptRelay] You connected reference images AND an external 'optional_latent'. Make sure your custom latent is long enough to fit the appended reference frames!") - - for i, single_ref in enumerate(refs_to_process): - src_h, src_w = single_ref.shape[1], single_ref.shape[2] - def snap(val, div): return max(div, (val // div) * div) - - if custom_width > 0 and custom_height > 0: - ref_tensor = _resize_image(single_ref, custom_width, custom_height, resize_method, divisible_by) - elif custom_width > 0: - tgt_w = snap(custom_width, divisible_by) - tgt_h = snap(int(src_h * tgt_w / src_w), divisible_by) - ref_tensor = _resize_image(single_ref, tgt_w, tgt_h, "stretch to fit", divisible_by) - elif custom_height > 0: - tgt_h = snap(custom_height, divisible_by) - tgt_w = snap(int(src_w * tgt_h / src_h), divisible_by) - ref_tensor = _resize_image(single_ref, tgt_w, tgt_h, "stretch to fit", divisible_by) - else: - ref_tensor = _resize_image(single_ref, src_w, src_h, "maintain aspect ratio", divisible_by) - - if img_compression > 0: - ref_tensor = _compress_image(ref_tensor, img_compression) - - guide_data["images"].append(ref_tensor) - - # Insert safely in the "hidden" padded latent blocks - # We place each ref 8 frames apart so they each get their own pure latent block. - insert_point = (clean_latent_frames + i) * 8 - guide_data["insert_frames"].append(insert_point) - guide_data["strengths"].append(float(reference_strength)) - - # Record dimensions for empty latent generation from the FIRST reference image if none exist - if derived_w <= 0 or derived_h <= 0: - derived_w = ref_tensor.shape[2] - derived_h = ref_tensor.shape[1] - - # --- Auto-generate LTXV latent --- - total_latents = clean_latent_frames + len(refs_to_process) - ltxv_length = ((total_latents - 1) * 8) + 1 # Map back to pixel frames for the empty latent logic - - if optional_latent is None: - latent_w = max(32, (derived_w // 32) * 32) - latent_h = max(32, (derived_h // 32) * 32) - - samples = torch.zeros( - [1, 128, total_latents, latent_h // 32, latent_w // 32], - device=comfy.model_management.intermediate_device(), - ) - latent = {"samples": samples} - log.info( - "[PromptRelay] Auto-generated LTXV latent: %dx%d, %d pixel frames (%d latent frames, %d refs hidden)", - latent_w, latent_h, ltxv_length, total_latents, len(refs_to_process) - ) - else: - latent = optional_latent - - patched, conditioning = _encode_relay( - model, clip, latent, global_prompt, local_prompts, segment_lengths, epsilon, - ) - - # --- Build Audio Output --- - audio_out = _build_combined_audio(timeline_data, ltxv_length, float(frame_rate)) - - # --- Audio Latent Generation --- - audio_latent = {} - - if audio_vae is not None: - def get_empty_latent(): - # Support both raw AudioVAE objects and ComfyUI VAE wrappers. - inner = getattr(audio_vae, "first_stage_model", audio_vae) - z_channels = audio_vae.latent_channels - audio_freq = inner.latent_frequency_bins - num_audio_latents = inner.num_of_latents_from_frames(ltxv_length, float(frame_rate)) - audio_latents = torch.zeros( - (1, z_channels, num_audio_latents, audio_freq), - device=comfy.model_management.intermediate_device(), - ) - return {"samples": audio_latents, "type": "audio"} - - if use_custom_audio: - try: - if audio_out is not None: - waveform = audio_out["waveform"] - if waveform.ndim == 2: - waveform = waveform.unsqueeze(0) - if waveform.ndim != 3: - raise ValueError(f"Expected custom audio waveform with 2 or 3 dims, got shape {tuple(waveform.shape)}") - - if hasattr(audio_vae, "first_stage_model"): - latent_samples = audio_vae.encode(waveform.movedim(1, -1)) - else: - latent_samples = audio_vae.encode({ - "waveform": waveform, - "sample_rate": audio_out["sample_rate"], - }) - - if latent_samples.numel() == 0: - raise ValueError("Encoded audio latent is empty (0 elements).") - - mask = torch.full( - (1, latent_samples.shape[-2], latent_samples.shape[-1]), - 0.0, - dtype=torch.float32, - device=comfy.model_management.intermediate_device() - ) - - audio_latent = { - "samples": latent_samples, - "type": "audio", - "noise_mask": mask.reshape((-1, 1, mask.shape[-2], mask.shape[-1])) - } - log.info("[PromptRelay] Generated custom audio latent with noise mask (value=0.0).") - else: - raise ValueError("No audio waveform to encode.") - except Exception as e: - log.error("[PromptRelay] Failed to generate custom audio latent: %s", e) - raise e - else: - try: - audio_latent = get_empty_latent() - log.info("[PromptRelay] Auto-generated empty audio latent.") - except Exception as e: - log.error("[PromptRelay] Could not generate empty audio latent: %s", e) - raise e - - # --- Save formatted Prompts logic --- - if save_prompts_to_file: - try: - formatted_text = _format_timeline_to_text( - global_prompt, duration_frames, float(frame_rate), epsilon, - custom_width, custom_height, resize_method, - timeline_data, local_prompts, segment_lengths, guide_strength - ) - out_dir = folder_paths.get_output_directory() - filename = f"ltx_director_prompts_{int(time.time())}.txt" - filepath = os.path.join(out_dir, filename) - with open(filepath, "w", encoding="utf-8") as f: - f.write(formatted_text) - log.info(f"[PromptRelay] Saved timeline prompts to {filepath}") - except Exception as e: - log.warning(f"[PromptRelay] Failed to save prompts to txt: {e}") - - return io.NodeOutput( - patched, - conditioning, - latent, - audio_latent, - guide_data, - float(frame_rate), - audio_out, - clean_latent_frames, - clean_pixel_frames - ) - - -NODE_CLASS_MAPPINGS = { - "LTXDirector": LTXDirector, -} - -NODE_DISPLAY_NAME_MAPPINGS = { - "PromptRelayEncodeTimeline": "Prompt Relay Encode (Timeline)", +# --- START OF FILE ltx_director.py --- + +import logging +import json +import base64 +import io as _io +import math +import time + +import numpy as np +import torch +import av +from PIL import Image + +import os +import folder_paths +import comfy.model_management + +from comfy_api.latest import io + +from .prompt_relay import ( + get_raw_tokenizer, + map_token_indices, + build_segments, + create_mask_fn, + distribute_segment_lengths, +) + +from .patches import detect_model_type, apply_patches + +log = logging.getLogger(__name__) + +# Custom socket type shared with LTXSequencer +GuideData = io.Custom("GUIDE_DATA") + + +def _preprocess_prompts_with_characters(global_prompt, local_prompts, char1="", char2="", char3=""): + """Invisibly swaps out @character1/@char1 tags with their high-fidelity VLM descriptions.""" + gp = global_prompt + char1 = char1 if char1 else "" + char2 = char2 if char2 else "" + char3 = char3 if char3 else "" + + # Process Global Prompt + for tag in ["@character1", "@char1"]: + if tag in gp: + gp = gp.replace(tag, char1) + for tag in ["@character2", "@char2"]: + if tag in gp: + gp = gp.replace(tag, char2) + for tag in ["@character3", "@char3"]: + if tag in gp: + gp = gp.replace(tag, char3) + + # Process Local Timeline Prompts + locals_list = [p.strip() for p in local_prompts.split("|")] if local_prompts else [] + processed_locals = [] + for lp in locals_list: + for tag in ["@character1", "@char1"]: + if tag in lp: + lp = lp.replace(tag, char1) + for tag in ["@character2", "@char2"]: + if tag in lp: + lp = lp.replace(tag, char2) + for tag in ["@character3", "@char3"]: + if tag in lp: + lp = lp.replace(tag, char3) + processed_locals.append(lp) + + return gp, " | ".join(processed_locals) + + +def _format_timeline_to_text(global_prompt, duration_frames, frame_rate, epsilon, + custom_width, custom_height, resize_method, + timeline_data, local_prompts, segment_lengths, guide_strength): + lines = [] + lines.append("LTX Director Timeline Export") + lines.append("============================") + lines.append("") + lines.append("=== Global Parameters ===") + lines.append(f"Global Prompt:\n{global_prompt}\n") + + fr = float(frame_rate) if frame_rate else 24.0 + if fr <= 0: fr = 24.0 + + lines.append(f"Duration: {duration_frames} frames ({(duration_frames / fr):.2f}s @ {fr} FPS)") + lines.append(f"Epsilon (Penalty Decay): {epsilon}") + + if custom_width > 0 or custom_height > 0: + lines.append(f"Target Dimensions: {custom_width}x{custom_height} (Resize Method: {resize_method})") + else: + lines.append("Target Dimensions: Auto (Based on first image)") + + lines.append("") + lines.append("=== Timeline Segments ===") + + # --- 1. Text Prompts (Extracted from direct inputs) --- + locals_list = [p.strip() for p in local_prompts.split("|")] if local_prompts else [] + lengths_list = [l.strip() for l in segment_lengths.split(",")] if segment_lengths else [] + + if locals_list and any(locals_list): + lines.append("\n--- Text Prompts ---") + current_frame = 0.0 + for i, prompt in enumerate(locals_list): + try: + len_f = float(lengths_list[i]) if i < len(lengths_list) and lengths_list[i] else 0.0 + except ValueError: + len_f = 0.0 + + start_f = current_frame + end_f = start_f + len_f + + start_s = start_f / fr + end_s = end_f / fr + len_s = len_f / fr + + lines.append(f"\n[Prompt {i+1}]") + lines.append(f"Time: {start_s:.2f}s - {end_s:.2f}s (Duration: {len_s:.2f}s)") + lines.append(f"Frames: {start_f:.1f} - {end_f:.1f} (Length: {len_f:.1f})") + lines.append(f"Prompt:\n{prompt}") + lines.append("-" * 40) + + current_frame += len_f + + # --- 2. Images & Audio (Extracted from JSON) --- + try: + tdata = json.loads(timeline_data) if timeline_data else {} + segs = tdata.get("segments", []) + + img_segs = [s for s in segs if s.get("type", "image") == "image"] + img_segs.sort(key=lambda s: float(s.get("start", 0))) + if img_segs: + lines.append("\n--- Image Guides ---") + strengths = [float(x.strip()) for x in guide_strength.split(",")] if guide_strength and guide_strength.strip() else [] + for i, seg in enumerate(img_segs): + start_f = float(seg.get("start", 0)) + start_s = start_f / fr + strength = strengths[i] if i < len(strengths) else 1.0 + + lines.append(f"\n[Image {i+1}]") + lines.append(f"Time Inserted: {start_s:.2f}s (Frame {start_f:.1f})") + lines.append(f"Guide Strength: {strength}") + lines.append("-" * 40) + + audio_segs = [s for s in segs if s.get("type", "audio") == "audio"] + audio_segs.sort(key=lambda s: float(s.get("start", 0))) + if audio_segs: + lines.append("\n--- Audio Segments ---") + for i, seg in enumerate(audio_segs): + start_f = float(seg.get("start", 0)) + len_f = float(seg.get("length", 0)) + start_s = start_f / fr + len_s = len_f / fr + file_name = seg.get("fileName", "Unknown") + lines.append(f"\n[Audio {i+1}] {file_name}") + lines.append(f"Time: {start_s:.2f}s (Frame {start_f:.1f}) | Duration: {len_s:.2f}s") + lines.append("-" * 40) + + except Exception as e: + lines.append(f"\n[Note: Could not parse detailed timeline JSON for images/audio. Error: {e}]") + + return "\n".join(lines) + + +# Register API endpoint for instant export from the JS UI if needed +try: + import server + from aiohttp import web + + @server.PromptServer.instance.routes.post("/ltx_director/export_timeline") + async def export_timeline_endpoint(request): + try: + data = await request.json() + global_prompt = data.get("global_prompt", "") + timeline_data = data.get("timeline_data", "{}") + duration_frames = int(data.get("duration_frames", 0)) + frame_rate = float(data.get("frame_rate", 24)) + epsilon = float(data.get("epsilon", 0.001)) + custom_width = int(data.get("custom_width", 0)) + custom_height = int(data.get("custom_height", 0)) + resize_method = data.get("resize_method", "maintain aspect ratio") + local_prompts = data.get("local_prompts", "") + segment_lengths = data.get("segment_lengths", "") + guide_strength = data.get("guide_strength", "") + + formatted_text = _format_timeline_to_text( + global_prompt, duration_frames, frame_rate, epsilon, + custom_width, custom_height, resize_method, + timeline_data, local_prompts, segment_lengths, guide_strength + ) + + out_dir = folder_paths.get_output_directory() + filename = f"ltx_director_prompts_{int(time.time())}.txt" + filepath = os.path.join(out_dir, filename) + + with open(filepath, "w", encoding="utf-8") as f: + f.write(formatted_text) + + return web.json_response({ + "status": "success", + "filepath": filepath, + "filename": filename, + "content": formatted_text + }) + except Exception as e: + log.error(f"[PromptRelay] Export timeline endpoint error: {e}") + return web.json_response({"status": "error", "message": str(e)}, status=500) +except Exception as e: + log.warning(f"[PromptRelay] Could not register /ltx_director/export_timeline endpoint: {e}") + + +def _load_image_tensor(seg: dict) -> torch.Tensor: + """Decode an image from the ComfyUI input folder (if imageFile provided) or fallback to base64 + to a ComfyUI-style image tensor of shape [1, H, W, 3], float32 in [0, 1].""" + if seg.get("imageFile"): + file_path = os.path.join(folder_paths.get_input_directory(), seg["imageFile"]) + if os.path.exists(file_path): + img = Image.open(file_path).convert("RGB") + arr = np.array(img, dtype=np.float32) / 255.0 + return torch.from_numpy(arr).unsqueeze(0) + + b64_str = seg.get("imageB64", "") + if not b64_str or b64_str.startswith("/view?"): + return torch.zeros((1, 512, 512, 3), dtype=torch.float32) + + if "," in b64_str: + b64_str = b64_str.split(",", 1)[1] + + try: + img_bytes = base64.b64decode(b64_str) + img = Image.open(_io.BytesIO(img_bytes)).convert("RGB") + arr = np.array(img, dtype=np.float32) / 255.0 + return torch.from_numpy(arr).unsqueeze(0) + except: + return torch.zeros((1, 512, 512, 3), dtype=torch.float32) + + +def _resize_image(tensor: torch.Tensor, target_w: int, target_h: int, method: str, divisible_by: int) -> torch.Tensor: + """Resize a [1, H, W, 3] float32 tensor to target dimensions using the given method, + then snap the final dimensions to be divisible by `divisible_by`.""" + from PIL import Image as _PilImage + import torchvision.transforms.functional as TF + + def snap(val, div): + return max(div, (val // div) * div) + + tw = snap(target_w, divisible_by) + th = snap(target_h, divisible_by) + + img_np = (tensor[0].cpu().numpy() * 255.0).clip(0, 255).astype(np.uint8) + pil = _PilImage.fromarray(img_np) + src_w, src_h = pil.size + + if method == "stretch to fit": + resized = pil.resize((tw, th), _PilImage.LANCZOS) + + elif method == "maintain aspect ratio": + ratio = min(tw / src_w, th / src_h) + new_w = int(src_w * ratio) + new_h = int(src_h * ratio) + new_w = snap(new_w, divisible_by) + new_h = snap(new_h, divisible_by) + resized = pil.resize((new_w, new_h), _PilImage.LANCZOS) + + elif method == "pad": + ratio = min(tw / src_w, th / src_h) + new_w = snap(int(src_w * ratio), divisible_by) + new_h = snap(int(src_h * ratio), divisible_by) + inner = pil.resize((new_w, new_h), _PilImage.LANCZOS) + resized = _PilImage.new("RGB", (tw, th), (0, 0, 0)) + resized.paste(inner, ((tw - new_w) // 2, (th - new_h) // 2)) + + elif method == "crop": + ratio = max(tw / src_w, th / src_h) + new_w = int(src_w * ratio) + new_h = int(src_h * ratio) + inner = pil.resize((new_w, new_h), _PilImage.LANCZOS) + left = (new_w - tw) // 2 + top = (new_h - th) // 2 + resized = inner.crop((left, top, left + tw, top + th)) + + else: + resized = pil.resize((tw, th), _PilImage.LANCZOS) + + arr = np.array(resized, dtype=np.float32) / 255.0 + return torch.from_numpy(arr).unsqueeze(0) + + +def _compress_image(tensor: torch.Tensor, crf: int) -> torch.Tensor: + """Apply H.264 compression artefacts to a [1, H, W, 3] float32 tensor (ComfyUI image format). + crf=0 means no compression. Uses PyAV to encode/decode a single frame in-memory.""" + if crf == 0: + return tensor + img = tensor[0] # [H, W, 3] + # Dimensions must be even for H.264 + h = (img.shape[0] // 2) * 2 + w = (img.shape[1] // 2) * 2 + img_np = (img[:h, :w] * 255.0).byte().cpu().numpy() # uint8 [H, W, 3] + + try: + buf = _io.BytesIO() + container = av.open(buf, mode="w", format="mp4") + stream = container.add_stream("libx264", rate=1) + stream.width = w + stream.height = h + stream.pix_fmt = "yuv420p" + stream.options = {"crf": str(crf), "preset": "ultrafast"} + frame = av.VideoFrame.from_ndarray(img_np, format="rgb24") + for pkt in stream.encode(frame): + container.mux(pkt) + for pkt in stream.encode(None): + container.mux(pkt) + container.close() + + buf.seek(0) + container_r = av.open(buf, mode="r") + decoded = None + for frame_r in container_r.decode(video=0): + decoded = frame_r.to_ndarray(format="rgb24") # [H, W, 3] + break + container_r.close() + + if decoded is None: + return tensor + arr = torch.from_numpy(decoded.astype(np.float32) / 255.0).to(tensor.device, tensor.dtype) + # Re-embed into original tensor shape (may have been cropped by even-rounding) + out = tensor.clone() + out[0, :h, :w] = arr + return out + except Exception as e: + log.warning("[PromptRelay] img_compression encode/decode failed: %s", e) + return tensor + + +def _build_combined_audio(timeline_data_str: str, duration_frames: int, frame_rate: float) -> dict: + """Parses timeline JSON, loads/trims audio directly from memory using PyAV, + and aligns to a global timeline yielding ComfyUI's format. + Output length explicitly mimics the timeline's duration_frames length.""" + target_sr = 44100 + total_samples = max(1, int(math.ceil(duration_frames / frame_rate * target_sr))) + empty_audio = {"waveform": torch.zeros((1, 2, total_samples), dtype=torch.float32), "sample_rate": target_sr} + + if not timeline_data_str: + return empty_audio + + try: + data = json.loads(timeline_data_str) + audio_segs = data.get("audioSegments", []) + except Exception: + return empty_audio + + if not audio_segs: + return empty_audio + + out_waveform = torch.zeros((2, total_samples), dtype=torch.float32) + + for seg in audio_segs: + buffer = None + if seg.get("audioFile"): + file_path = os.path.join(folder_paths.get_input_directory(), seg["audioFile"]) + if os.path.exists(file_path): + with open(file_path, "rb") as f: + buffer = _io.BytesIO(f.read()) + + if not buffer and seg.get("audioB64"): + b64 = seg.get("audioB64") + if "," in b64: + b64 = b64.split(",", 1)[1] + try: + audio_bytes = base64.b64decode(b64) + buffer = _io.BytesIO(audio_bytes) + except: + pass + + if not buffer: + continue + + try: + clip_frames = [] + + # Use PyAV to decode directly from memory buffer + with av.open(buffer) as container: + stream = container.streams.audio[0] + + # Setup resampler to ensure output is 44.1kHz, Stereo, Float32 Planar + resampler = av.AudioResampler( + format='fltp', + layout='stereo', + rate=target_sr, + ) + + for frame in container.decode(stream): + for resampled_frame in resampler.resample(frame): + # to_ndarray() on fltp gives shape (channels, samples) + arr = resampled_frame.to_ndarray() + clip_frames.append(torch.from_numpy(arr)) + + # Flush the resampler to get any remaining samples + for resampled_frame in resampler.resample(None): + arr = resampled_frame.to_ndarray() + clip_frames.append(torch.from_numpy(arr)) + + if not clip_frames: + continue + + # Concatenate all frame blocks along the samples dimension (dim 1) + waveform = torch.cat(clip_frames, dim=1) # Shape: [2, total_clip_samples] + + # Calculate interactive trim boundaries + trim_start_frames = float(seg.get("trimStart", 0)) + length_frames = float(seg.get("length", 1)) + start_frames = float(seg.get("start", 0)) + + start_sample_src = int(trim_start_frames / frame_rate * target_sr) + length_samples = int(length_frames / frame_rate * target_sr) + end_sample_src = start_sample_src + length_samples + + if start_sample_src < 0: start_sample_src = 0 + if end_sample_src > waveform.shape[1]: + end_sample_src = waveform.shape[1] + + actual_length = end_sample_src - start_sample_src + if actual_length <= 0: continue + + # Extract the correct segment of the audio + clip_waveform = waveform[:, start_sample_src:end_sample_src] + + # Position onto the timeline + start_sample_dst = int(start_frames / frame_rate * target_sr) + + if start_sample_dst >= out_waveform.shape[1]: + continue + + end_sample_dst = start_sample_dst + actual_length + + # Clip any trailing overflow so we don't index past the timeline bounds + if end_sample_dst > out_waveform.shape[1]: + actual_length = out_waveform.shape[1] - start_sample_dst + clip_waveform = clip_waveform[:, :actual_length] + end_sample_dst = start_sample_dst + actual_length + + if actual_length <= 0: + continue + + # Additive composite (allows clips overlapping to sum together naturally) + out_waveform[:, start_sample_dst:end_sample_dst] += clip_waveform + + except Exception as e: + log.warning("[PromptRelay] Audio process error for segment %s: %s", seg.get("fileName"), e) + continue + + return {"waveform": out_waveform.unsqueeze(0), "sample_rate": target_sr} + + +def _convert_to_latent_lengths(pixel_lengths, temporal_stride, latent_frames): + """Convert pixel-space segment lengths to integer latent-space lengths using the + largest-remainder method. Targets the full `latent_frames` when the pixel sum looks + like full coverage (within one stride of latent_frames * stride). Otherwise targets + round(total_pixel / temporal_stride) so partial-coverage timelines stay partial. + """ + if not pixel_lengths: + return [] + total_pixel = sum(pixel_lengths) + if total_pixel <= 0: + return [1] * len(pixel_lengths) + + naive_total = max(1, round(total_pixel / temporal_stride)) + target_total = min(latent_frames, naive_total) + # Within one frame of full → user clearly intended full coverage; pin to latent_frames. + if target_total >= latent_frames - 1: + target_total = latent_frames + + exact = [p * target_total / total_pixel for p in pixel_lengths] + result = [int(e) for e in exact] + diff = target_total - sum(result) + if diff > 0: + order = sorted(range(len(exact)), key=lambda i: -(exact[i] - int(exact[i]))) + for k in range(diff): + result[order[k % len(order)]] += 1 + + # Ensure every segment has ≥ 1 latent frame (steal from the largest if needed). + for i in range(len(result)): + if result[i] < 1: + max_idx = max(range(len(result)), key=lambda j: result[j]) + if result[max_idx] > 1: + result[max_idx] -= 1 + result[i] = 1 + + return result + + +def _encode_relay(model, clip, latent, global_prompt, local_prompts, segment_lengths, epsilon): + for name, val in (("global_prompt", global_prompt), + ("local_prompts", local_prompts), + ("segment_lengths", segment_lengths)): + if val is None: + raise ValueError( + f"PromptRelay: '{name}' arrived as None. " + "Likely causes: a stale workflow JSON saved with null, the timeline " + "editor's web extension failing to load, or an upstream node returning None. " + "Set the field to an empty string or fix the upstream connection." + ) + + # Split prompts but do NOT filter out empty ones yet, so we can detect them + locals_list = [p.strip() for p in local_prompts.split("|")] + + # Check if any specific segment is empty + for p in locals_list: + if not p: + raise ValueError("There is a segment on the timeline missing a prompt!") + + if not locals_list or (len(locals_list) == 1 and not locals_list[0]): + raise ValueError("At least one local prompt is required.") + + arch, patch_size, temporal_stride = detect_model_type(model) + + samples = latent["samples"] + latent_frames = samples.shape[2] + tokens_per_frame = (samples.shape[3] // patch_size[1]) * (samples.shape[4] // patch_size[2]) + + parsed_lengths = None + if segment_lengths.strip(): + pixel_lengths = [int(float(x.strip())) for x in segment_lengths.split(",") if x.strip()] + parsed_lengths = _convert_to_latent_lengths(pixel_lengths, temporal_stride, latent_frames) + + raw_tokenizer = get_raw_tokenizer(clip) + full_prompt, token_ranges = map_token_indices(raw_tokenizer, global_prompt, locals_list) + + log.info("[PromptRelay] Global: tokens [0:%d] (%d tokens)", token_ranges[0][0], token_ranges[0][0]) + for i, (s, e) in enumerate(token_ranges): + log.info("[PromptRelay] Segment %d: tokens [%d:%d] (%d tokens)", i, s, e, e - s) + + conditioning = clip.encode_from_tokens_scheduled(clip.tokenize(full_prompt)) + + effective_lengths = distribute_segment_lengths(len(locals_list), latent_frames, parsed_lengths) + + log.info( + "[PromptRelay] Latent: %d frames, %d tokens/frame, segments: %s", + latent_frames, tokens_per_frame, effective_lengths, + ) + + q_token_idx = build_segments(token_ranges, effective_lengths, epsilon, None) + mask_fn = create_mask_fn(q_token_idx, tokens_per_frame, latent_frames) + + patched = model.clone() + apply_patches(patched, arch, mask_fn) + + return patched, conditioning + + +class LTXDirector(io.ComfyNode): + """WYSIWYG timeline variant — segments and lengths come from a visual editor in the node UI.""" + + @classmethod + def define_schema(cls): + return io.Schema( + node_id="LTXDirector", + display_name="LTX Director", + category="WhatDreamsCost", + description=( + "Same as Prompt Relay Encode, but local prompts and segment lengths are edited " + "visually as draggable blocks on a timeline. The duration_frames input only sets the " + "timeline scale (pixel space) — actual frame count is still read from the latent." + ), + inputs=[ + io.Model.Input("model"), + io.Clip.Input("clip"), + io.Vae.Input("audio_vae", optional=True, tooltip="Optional. Connect an Audio VAE to generate audio latents."), + io.Latent.Input("optional_latent", optional=True, tooltip="Optional. Connect a latent to override the auto-generated one."), + io.String.Input("global_prompt", multiline=True, default="", tooltip="Conditions the entire video. Anchors persistent characters, objects, and scene context."), + io.Int.Input("duration_frames", default=120, min=1, max=10000, step=1, tooltip="Total timeline length in pixel-space frames. Used by the editor for visual scale only."), + io.Float.Input("duration_seconds", default=5.0, min=0.1, max=1000.0, step=0.01, tooltip="Total timeline duration in seconds (computed/synced from frames)."), + io.String.Input("timeline_data", default="", tooltip="JSON state of the timeline editor (auto-managed; do not edit by hand)."), + io.Boolean.Input("use_custom_audio", default=False, tooltip="Toggle between using timeline audio (ON) and generating audio from scratch (OFF)."), + io.String.Input("local_prompts", multiline=True, default="", tooltip="Auto-populated from the timeline editor."), + io.String.Input("segment_lengths", default="", tooltip="Auto-populated from the timeline editor (pixel-space frame counts)."), + io.Float.Input("epsilon", default=0.001, min=0.0001, max=0.99, step=0.0001, tooltip="Penalty decay parameter. Values below ~0.1 all produce sharp boundaries (paper default 0.001). For softer transitions, try 0.5 or higher."), + io.Float.Input("frame_rate", default=24.0, min=1.0, max=240.0, step=1.0, tooltip="Frames per second — only affects how time is displayed in the timeline editor when time_units is set to 'seconds'."), + io.Combo.Input("display_mode", options=["frames", "seconds"], default="seconds", tooltip="Display the ruler, segment ranges, length input, and total in frames or seconds. Internal storage is always pixel-space frames."), + io.String.Input("guide_strength", default="", tooltip="Auto-populated from the timeline editor (comma-separated guide strengths for image segments)."), + io.Int.Input("custom_width", default=0, min=0, max=8192, step=1, tooltip="Target output width for all image segments. Set to 0 to use the original image width."), + io.Int.Input("custom_height", default=0, min=0, max=8192, step=1, tooltip="Target output height for all image segments. Set to 0 to use the original image height."), + io.Combo.Input("resize_method", options=["maintain aspect ratio", "stretch to fit", "pad", "crop"], default="maintain aspect ratio", tooltip="How to resize image segments to fit the target dimensions."), + io.Int.Input("divisible_by", default=32, min=1, max=256, step=1, tooltip="Snap the final output image dimensions to be divisible by this number (e.g. 32 for LTX)."), + io.Int.Input("img_compression", default=18, min=0, max=100, step=1, tooltip="H.264 CRF compression to apply to each guide image. 0 = no compression, higher = more artefacts."), + io.Boolean.Input("save_prompts_to_file", default=False, optional=True, tooltip="Save the timeline prompts and parameters to a text file in your ComfyUI output directory during execution."), + io.Image.Input("reference_image", optional=True, tooltip="Invisible character sheet/style reference. Can also be a Batch of images. Automatically appended to the sequence out-of-bounds."), + io.Image.Input("reference_image_2", optional=True, tooltip="Second optional reference image."), + io.Image.Input("reference_image_3", optional=True, tooltip="Third optional reference image."), + io.Float.Input("reference_strength", default=1.0, min=0.0, max=5.0, step=0.05, optional=True, tooltip="Guide strength for the reference images."), + + # New descriptive inputs for automatic under-the-hood character replacement + io.String.Input("char1_description", multiline=True, default="", optional=True, tooltip="Plug in a detailed description for @character1 / @char1. You can write it manually or connect a VLM/Caption node."), + io.String.Input("char2_description", multiline=True, default="", optional=True, tooltip="Plug in a detailed description for @character2 / @char2."), + io.String.Input("char3_description", multiline=True, default="", optional=True, tooltip="Plug in a detailed description for @character3 / @char3."), + ], + outputs=[ + io.Model.Output(display_name="model"), + io.Conditioning.Output(display_name="positive"), + io.Latent.Output(display_name="video_latent", tooltip="Auto-generated LTXV empty latent (only populated when no latent is connected)."), + io.Latent.Output(display_name="audio_latent", tooltip="Auto-generated audio latent (uses custom audio if enabled)."), + GuideData.Output(display_name="guide_data"), + io.Float.Output(display_name="frame_rate", tooltip="The frame rate used for the timeline."), + io.Audio.Output(display_name="combined_audio", tooltip="Combined timeline audio layout."), + io.Int.Output(display_name="clean_latent_frames", tooltip="Plug this into a CleanLatentSlice node 'length' to effortlessly cut off the invisible reference image."), + io.Int.Output(display_name="clean_pixel_frames", tooltip="Plug this into your Video Combine 'frame_load_cap' to effortlessly cut off the invisible reference image."), + ], + ) + + @classmethod + def execute(cls, model, clip, global_prompt, duration_frames, duration_seconds, + timeline_data, local_prompts, segment_lengths, guide_strength="", epsilon=1e-3, + frame_rate=24.0, display_mode="seconds", + custom_width=768, custom_height=512, resize_method="maintain aspect ratio", + divisible_by=32, img_compression=0, audio_vae=None, optional_latent=None, + use_custom_audio=False, save_prompts_to_file=False, + reference_image=None, reference_image_2=None, reference_image_3=None, reference_strength=1.0, + char1_description="", char2_description="", char3_description="") -> io.NodeOutput: + + # --- Calculate Clean Output Bounds First --- + clean_pixel_frames = duration_frames + 1 + clean_latent_frames = ((clean_pixel_frames - 1) // 8) + 1 + + # --- Build guide_data from image segments FIRST (to derive output dimensions) --- + guide_data = {"images": [], "insert_frames": [], "strengths": [], "frame_rate": float(frame_rate)} + derived_w, derived_h = custom_width, custom_height + + # Consolidate all reference inputs into a flat list, unraveling any image batches. + refs_to_process = [] + for ref_input in (reference_image, reference_image_2, reference_image_3): + if ref_input is not None: + for i in range(ref_input.shape[0]): + refs_to_process.append(ref_input[i:i+1]) + + try: + tdata = json.loads(timeline_data) if timeline_data else {} + img_segs = [ + s for s in tdata.get("segments", []) + if s.get("type", "image") == "image" + and (s.get("imageFile") or s.get("imageB64")) + and int(s.get("start", 0)) < duration_frames # exclude segments fully outside duration + ] + img_segs.sort(key=lambda s: s["start"]) + + strengths = [] + if guide_strength.strip(): + strengths = [float(x.strip()) for x in guide_strength.split(",") if x.strip()] + + for idx, seg in enumerate(img_segs): + tensor = _load_image_tensor(seg) + + # Apply resize + src_h, src_w = tensor.shape[1], tensor.shape[2] + + def snap(val, div): + return max(div, (val // div) * div) + + if custom_width > 0 and custom_height > 0: + # Both dimensions set — apply selected resize_method (pad, crop, stretch, maintain AR) + tensor = _resize_image(tensor, custom_width, custom_height, resize_method, divisible_by) + elif custom_width > 0: + # Width only — scale height from AR, snap both, then resize to exact dimensions + tgt_w = snap(custom_width, divisible_by) + tgt_h = snap(int(src_h * tgt_w / src_w), divisible_by) + tensor = _resize_image(tensor, tgt_w, tgt_h, "stretch to fit", divisible_by) + elif custom_height > 0: + # Height only — scale width from AR, snap both, then resize to exact dimensions + tgt_h = snap(custom_height, divisible_by) + tgt_w = snap(int(src_w * tgt_h / src_h), divisible_by) + tensor = _resize_image(tensor, tgt_w, tgt_h, "stretch to fit", divisible_by) + else: + # Both zero — keep original dimensions, just snap to divisible_by + tensor = _resize_image(tensor, src_w, src_h, "maintain aspect ratio", divisible_by) + + + # Apply compression + if img_compression > 0: + tensor = _compress_image(tensor, img_compression) + + # Record dimensions of the first processed image for latent generation + if idx == 0: + derived_h = tensor.shape[1] + derived_w = tensor.shape[2] + + strength = strengths[idx] if idx < len(strengths) else 1.0 + guide_data["images"].append(tensor) + + # Keep timeline images at their exact timeline frames (no shifting) + guide_data["insert_frames"].append(int(seg["start"])) + + guide_data["strengths"].append(float(strength)) + + # If no images were loaded from the timeline, create a dummy image at strength 0 + # to prevent artifacts in text-to-video mode. + if not guide_data["images"] and not refs_to_process: + w = derived_w if derived_w > 0 else 768 + h = derived_h if derived_h > 0 else 512 + w = (w // 32) * 32 + h = (h // 32) * 32 + + dummy_image = torch.zeros((1, h, w, 3), dtype=torch.float32) + guide_data["images"].append(dummy_image) + guide_data["insert_frames"].append(0) + guide_data["strengths"].append(0.0) + + derived_w = w + derived_h = h + except Exception as e: + log.warning("[PromptRelay] Could not build guide_data: %s", e) + + # --- Handle Reference Image Injection (End-Hiding) --- + if refs_to_process: + if optional_latent is not None: + log.warning("[PromptRelay] You connected reference images AND an external 'optional_latent'. Make sure your custom latent is long enough to fit the appended reference frames!") + + for i, single_ref in enumerate(refs_to_process): + src_h, src_w = single_ref.shape[1], single_ref.shape[2] + def snap(val, div): return max(div, (val // div) * div) + + if custom_width > 0 and custom_height > 0: + ref_tensor = _resize_image(single_ref, custom_width, custom_height, resize_method, divisible_by) + elif custom_width > 0: + tgt_w = snap(custom_width, divisible_by) + tgt_h = snap(int(src_h * tgt_w / src_w), divisible_by) + ref_tensor = _resize_image(single_ref, tgt_w, tgt_h, "stretch to fit", divisible_by) + elif custom_height > 0: + tgt_h = snap(custom_height, divisible_by) + tgt_w = snap(int(src_w * tgt_h / src_h), divisible_by) + ref_tensor = _resize_image(single_ref, tgt_w, tgt_h, "stretch to fit", divisible_by) + else: + ref_tensor = _resize_image(single_ref, src_w, src_h, "maintain aspect ratio", divisible_by) + + if img_compression > 0: + ref_tensor = _compress_image(ref_tensor, img_compression) + + guide_data["images"].append(ref_tensor) + + # Safely hide reference sheets at the very end of the video sequence + insert_point = (clean_latent_frames + i) * 8 + guide_data["insert_frames"].append(insert_point) + guide_data["strengths"].append(float(reference_strength)) + + # Record dimensions for empty latent generation from the FIRST reference image if none exist + if derived_w <= 0 or derived_h <= 0: + derived_w = ref_tensor.shape[2] + derived_h = ref_tensor.shape[1] + + # --- Auto-generate LTXV latent --- + total_latents = clean_latent_frames + len(refs_to_process) + ltxv_length = ((total_latents - 1) * 8) + 1 # Map back to pixel frames for the empty latent logic + + if optional_latent is None: + latent_w = max(32, (derived_w // 32) * 32) + latent_h = max(32, (derived_h // 32) * 32) + + samples = torch.zeros( + [1, 128, total_latents, latent_h // 32, latent_w // 32], + device=comfy.model_management.intermediate_device(), + ) + latent = {"samples": samples} + log.info( + "[PromptRelay] Auto-generated LTXV latent: %dx%d, %d pixel frames (%d latent frames, %d refs hidden)", + latent_w, latent_h, ltxv_length, total_latents, len(refs_to_process) + ) + else: + latent = optional_latent + + # --- Preprocess Prompts with Character Tags --- + processed_global, processed_local = _preprocess_prompts_with_characters( + global_prompt, local_prompts, char1_description, char2_description, char3_description + ) + + patched, conditioning = _encode_relay( + model, clip, latent, processed_global, processed_local, segment_lengths, epsilon, + ) + + # --- Build Audio Output --- + audio_out = _build_combined_audio(timeline_data, ltxv_length, float(frame_rate)) + + # --- Audio Latent Generation --- + audio_latent = {} + + if audio_vae is not None: + def get_empty_latent(): + # Support both raw AudioVAE objects and ComfyUI VAE wrappers. + inner = getattr(audio_vae, "first_stage_model", audio_vae) + z_channels = audio_vae.latent_channels + audio_freq = inner.latent_frequency_bins + num_audio_latents = inner.num_of_latents_from_frames(ltxv_length, float(frame_rate)) + audio_latents = torch.zeros( + (1, z_channels, num_audio_latents, audio_freq), + device=comfy.model_management.intermediate_device(), + ) + return {"samples": audio_latents, "type": "audio"} + + if use_custom_audio: + try: + if audio_out is not None: + waveform = audio_out["waveform"] + if waveform.ndim == 2: + waveform = waveform.unsqueeze(0) + if waveform.ndim != 3: + raise ValueError(f"Expected custom audio waveform with 2 or 3 dims, got shape {tuple(waveform.shape)}") + + if hasattr(audio_vae, "first_stage_model"): + latent_samples = audio_vae.encode(waveform.movedim(1, -1)) + else: + latent_samples = audio_vae.encode({ + "waveform": waveform, + "sample_rate": audio_out["sample_rate"], + }) + + if latent_samples.numel() == 0: + raise ValueError("Encoded audio latent is empty (0 elements).") + + mask = torch.full( + (1, latent_samples.shape[-2], latent_samples.shape[-1]), + 0.0, + dtype=torch.float32, + device=comfy.model_management.intermediate_device() + ) + + audio_latent = { + "samples": latent_samples, + "type": "audio", + "noise_mask": mask.reshape((-1, 1, mask.shape[-2], mask.shape[-1])) + } + log.info("[PromptRelay] Generated custom audio latent with noise mask (value=0.0).") + else: + raise ValueError("No audio waveform to encode.") + except Exception as e: + log.error("[PromptRelay] Failed to generate custom audio latent: %s", e) + raise e + else: + try: + audio_latent = get_empty_latent() + log.info("[PromptRelay] Auto-generated empty audio latent.") + except Exception as e: + log.error("[PromptRelay] Could not generate empty audio latent: %s", e) + raise e + + # --- Save formatted Prompts logic --- + if save_prompts_to_file: + try: + formatted_text = _format_timeline_to_text( + processed_global, duration_frames, float(frame_rate), epsilon, + custom_width, custom_height, resize_method, + timeline_data, processed_local, segment_lengths, guide_strength + ) + out_dir = folder_paths.get_output_directory() + filename = f"ltx_director_prompts_{int(time.time())}.txt" + filepath = os.path.join(out_dir, filename) + with open(filepath, "w", encoding="utf-8") as f: + f.write(formatted_text) + log.info(f"[PromptRelay] Saved timeline prompts to {filepath}") + except Exception as e: + log.warning(f"[PromptRelay] Failed to save prompts to txt: {e}") + + return io.NodeOutput( + patched, + conditioning, + latent, + audio_latent, + guide_data, + float(frame_rate), + audio_out, + clean_latent_frames, + clean_pixel_frames + ) + + +NODE_CLASS_MAPPINGS = { + "LTXDirector": LTXDirector, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "PromptRelayEncodeTimeline": "Prompt Relay Encode (Timeline)", } \ No newline at end of file diff --git a/ltx_director_guide.py b/ltx_director_guide.py index 7476792..58169a1 100644 --- a/ltx_director_guide.py +++ b/ltx_director_guide.py @@ -1,90 +1,90 @@ -from comfy_extras.nodes_lt import LTXVAddGuide -import torch -import comfy.utils -from comfy_api.latest import io -from .ltx_director import GuideData - - -class LTXDirectorGuide(LTXVAddGuide): - @classmethod - def define_schema(cls): - return io.Schema( - node_id="LTXDirectorGuide", - display_name="LTX Director Guide", - category="WhatDreamsCost", - description=( - "Applies guide images from a Prompt Relay Timeline node at the frame positions " - "and strengths defined on the timeline. Connect guide_data from the timeline node." - ), - inputs=[ - io.Conditioning.Input("positive", tooltip="Positive conditioning to add guide keyframe info to."), - io.Conditioning.Input("negative", tooltip="Negative conditioning to add guide keyframe info to."), - io.Vae.Input("vae", tooltip="Video VAE used to encode the guide images."), - io.Latent.Input("latent", tooltip="Video latent — guides are inserted into this latent."), - GuideData.Input("guide_data", tooltip="Guide data produced by Prompt Relay Encode (Timeline)."), - io.Float.Input("scale_by", default=1.0, min=0.01, max=8.0, step=0.01, tooltip="Scale the latent by this factor."), - io.Combo.Input("upscale_method", options=["nearest-exact", "bilinear", "area", "bicubic", "bislerp"], default="bicubic", tooltip="Method used to upscale/downscale the latent."), - ], - outputs=[ - io.Conditioning.Output(display_name="positive"), - io.Conditioning.Output(display_name="negative"), - io.Latent.Output(display_name="latent", tooltip="Video latent with guide frames applied."), - ], - ) - - @classmethod - def execute(cls, positive, negative, vae, latent, guide_data, scale_by=1.0, upscale_method="bicubic") -> io.NodeOutput: - scale_factors = vae.downscale_index_formula - - # Clone latents to avoid mutating upstream nodes - latent_image = latent["samples"].clone() - - if "noise_mask" in latent: - noise_mask = latent["noise_mask"].clone() - else: - batch, _, latent_frames, latent_height, latent_width = latent_image.shape - noise_mask = torch.ones( - (batch, 1, latent_frames, 1, 1), - dtype=torch.float32, - device=latent_image.device, - ) - - # Apply scale factor if not 1.0 - if scale_by != 1.0: - B, C, F, H, W = latent_image.shape - width = round(W * scale_by) - height = round(H * scale_by) - - # Reshape to 4D for common_upscale - latent_4d = latent_image.permute(0, 2, 1, 3, 4).reshape(B * F, C, H, W) - latent_resized_4d = comfy.utils.common_upscale(latent_4d, width, height, upscale_method, "disabled") - latent_image = latent_resized_4d.reshape(B, F, C, height, width).permute(0, 2, 1, 3, 4) - - # Also resize noise mask if it's not a broadcasted mask - if noise_mask.shape[-1] > 1 or noise_mask.shape[-2] > 1: - mask_4d = noise_mask.permute(0, 2, 1, 3, 4).reshape(B * F, 1, H, W) - mask_resized_4d = comfy.utils.common_upscale(mask_4d, width, height, upscale_method, "disabled") - noise_mask = mask_resized_4d.reshape(B, F, 1, height, width).permute(0, 2, 1, 3, 4) - - _, _, latent_length, latent_height, latent_width = latent_image.shape - - images = guide_data.get("images", []) - insert_frames = guide_data.get("insert_frames", []) - strengths = guide_data.get("strengths", []) - - for idx, img_tensor in enumerate(images): - f_idx = insert_frames[idx] if idx < len(insert_frames) else 0 - strength = strengths[idx] if idx < len(strengths) else 1.0 - - image_1, t = cls.encode(vae, latent_width, latent_height, img_tensor, scale_factors) - frame_idx, latent_idx = cls.get_latent_index(positive, latent_length, len(image_1), f_idx, scale_factors) - - assert latent_idx + t.shape[2] <= latent_length, ( - f"Guide image {idx + 1}: conditioning frames exceed the length of the latent sequence." - ) - - positive, negative, latent_image, noise_mask = cls.append_keyframe( - positive, negative, frame_idx, latent_image, noise_mask, t, strength, scale_factors, - ) - +from comfy_extras.nodes_lt import LTXVAddGuide +import torch +import comfy.utils +from comfy_api.latest import io +from .ltx_director import GuideData + + +class LTXDirectorGuide(LTXVAddGuide): + @classmethod + def define_schema(cls): + return io.Schema( + node_id="LTXDirectorGuide", + display_name="LTX Director Guide", + category="WhatDreamsCost", + description=( + "Applies guide images from a Prompt Relay Timeline node at the frame positions " + "and strengths defined on the timeline. Connect guide_data from the timeline node." + ), + inputs=[ + io.Conditioning.Input("positive", tooltip="Positive conditioning to add guide keyframe info to."), + io.Conditioning.Input("negative", tooltip="Negative conditioning to add guide keyframe info to."), + io.Vae.Input("vae", tooltip="Video VAE used to encode the guide images."), + io.Latent.Input("latent", tooltip="Video latent — guides are inserted into this latent."), + GuideData.Input("guide_data", tooltip="Guide data produced by Prompt Relay Encode (Timeline)."), + io.Float.Input("scale_by", default=1.0, min=0.01, max=8.0, step=0.01, tooltip="Scale the latent by this factor."), + io.Combo.Input("upscale_method", options=["nearest-exact", "bilinear", "area", "bicubic", "bislerp"], default="bicubic", tooltip="Method used to upscale/downscale the latent."), + ], + outputs=[ + io.Conditioning.Output(display_name="positive"), + io.Conditioning.Output(display_name="negative"), + io.Latent.Output(display_name="latent", tooltip="Video latent with guide frames applied."), + ], + ) + + @classmethod + def execute(cls, positive, negative, vae, latent, guide_data, scale_by=1.0, upscale_method="bicubic") -> io.NodeOutput: + scale_factors = vae.downscale_index_formula + + # Clone latents to avoid mutating upstream nodes + latent_image = latent["samples"].clone() + + if "noise_mask" in latent: + noise_mask = latent["noise_mask"].clone() + else: + batch, _, latent_frames, latent_height, latent_width = latent_image.shape + noise_mask = torch.ones( + (batch, 1, latent_frames, 1, 1), + dtype=torch.float32, + device=latent_image.device, + ) + + # Apply scale factor if not 1.0 + if scale_by != 1.0: + B, C, F, H, W = latent_image.shape + width = round(W * scale_by) + height = round(H * scale_by) + + # Reshape to 4D for common_upscale + latent_4d = latent_image.permute(0, 2, 1, 3, 4).reshape(B * F, C, H, W) + latent_resized_4d = comfy.utils.common_upscale(latent_4d, width, height, upscale_method, "disabled") + latent_image = latent_resized_4d.reshape(B, F, C, height, width).permute(0, 2, 1, 3, 4) + + # Also resize noise mask if it's not a broadcasted mask + if noise_mask.shape[-1] > 1 or noise_mask.shape[-2] > 1: + mask_4d = noise_mask.permute(0, 2, 1, 3, 4).reshape(B * F, 1, H, W) + mask_resized_4d = comfy.utils.common_upscale(mask_4d, width, height, upscale_method, "disabled") + noise_mask = mask_resized_4d.reshape(B, F, 1, height, width).permute(0, 2, 1, 3, 4) + + _, _, latent_length, latent_height, latent_width = latent_image.shape + + images = guide_data.get("images", []) + insert_frames = guide_data.get("insert_frames", []) + strengths = guide_data.get("strengths", []) + + for idx, img_tensor in enumerate(images): + f_idx = insert_frames[idx] if idx < len(insert_frames) else 0 + strength = strengths[idx] if idx < len(strengths) else 1.0 + + image_1, t = cls.encode(vae, latent_width, latent_height, img_tensor, scale_factors) + frame_idx, latent_idx = cls.get_latent_index(positive, latent_length, len(image_1), f_idx, scale_factors) + + assert latent_idx + t.shape[2] <= latent_length, ( + f"Guide image {idx + 1}: conditioning frames exceed the length of the latent sequence." + ) + + positive, negative, latent_image, noise_mask = cls.append_keyframe( + positive, negative, frame_idx, latent_image, noise_mask, t, strength, scale_factors, + ) + return io.NodeOutput(positive, negative, {"samples": latent_image, "noise_mask": noise_mask}) \ No newline at end of file diff --git a/ltx_keyframer.py b/ltx_keyframer.py index a876293..6f22b1d 100644 --- a/ltx_keyframer.py +++ b/ltx_keyframer.py @@ -1,117 +1,117 @@ -import torch -import comfy.utils -from comfy_api.latest import io - -class LTXKeyframer(io.ComfyNode): - @classmethod - def define_schema(cls): - inputs = [ - io.Vae.Input("vae", tooltip="Video VAE used to encode the images"), - io.Latent.Input("latent", tooltip="Video latent to insert images into"), - io.Image.Input("multi_input", tooltip="Batched images from MultiImageLoader"), - ] - - inputs.append(io.Int.Input("num_images", default=1, min=0, max=50, step=1, display_name="images_loaded", tooltip="Select how many index/strength widgets to configure.")) - - for i in range(1, 51): # 1 to 50 images - inputs.extend([ - io.Int.Input( - f"insert_frame_{i}", - default=0, - min=-9999, - max=9999, - step=1, - tooltip=f"Frame insert_frame for image {i} (in pixel space).", - optional=True, - ), - io.Float.Input( - f"strength_{i}", - default=1.0, - min=0.0, - max=1.0, - step=0.01, - tooltip=f"Strength for image {i}.", - optional=True, - ), - ]) - - return io.Schema( - node_id="LTXKeyframer", - display_name="LTX Keyframer", - category="WhatDreamsCost", - description="Replaces video latent frames with the encoded input images. Number of widgets is dynamically configured.", - inputs=inputs, - outputs=[ - io.Latent.Output(display_name="latent", tooltip="The video latent with the images inserted and latent noise mask updated."), - ], - ) - - @classmethod - def execute(cls, vae, latent, multi_input, num_images, **kwargs) -> io.NodeOutput: - - samples = latent["samples"].clone() - scale_factors = vae.downscale_index_formula - _, height_scale_factor, width_scale_factor = scale_factors - - batch, _, latent_frames, latent_height, latent_width = samples.shape - width = latent_width * width_scale_factor - height = latent_height * height_scale_factor - - # Get existing noise mask if present, otherwise create new one - if "noise_mask" in latent: - conditioning_latent_frames_mask = latent["noise_mask"].clone() - else: - conditioning_latent_frames_mask = torch.ones( - (batch, 1, latent_frames, 1, 1), - dtype=torch.float32, - device=samples.device, - ) - - batch_size = multi_input.shape[0] if multi_input is not None else 0 - - # We process inputs up to num_images, extracting values from kwargs - for i in range(1, num_images + 1): - # Skip if this image index exceeds the batch - if i > batch_size: - continue - - image = multi_input[i-1:i] # Extract the single image frame from the batch - if image is None: - continue - - insert_frame = kwargs.get(f"insert_frame_{i}") - if insert_frame is None: - continue - strength = kwargs.get(f"strength_{i}", 1.0) - - if image.shape[1] != height or image.shape[2] != width: - pixels = comfy.utils.common_upscale(image.movedim(-1, 1), width, height, "bilinear", "center").movedim(1, -1) - else: - pixels = image - encode_pixels = pixels[:, :, :, :3] - t = vae.encode(encode_pixels) - - # Convert pixel frame insert_frame to latent insert_frame - time_scale_factor = scale_factors[0] - - # Handle negative indexing in pixel space - pixel_frame_count = (latent_frames - 1) * time_scale_factor + 1 - if insert_frame < 0: - insert_frame = pixel_frame_count + insert_frame - - # Convert to latent insert_frame - latent_idx = insert_frame // time_scale_factor - - # Clamp to valid range - latent_idx = max(0, min(latent_idx, latent_frames - 1)) - - # Calculate end insert_frame, ensuring we don't exceed latent_frames - end_index = min(latent_idx + t.shape[2], latent_frames) - - # Replace samples at the specified insert_frame range - samples[:, :, latent_idx:end_index] = t[:, :, :end_index - latent_idx] - - # Update mask at the specified insert_frame range - conditioning_latent_frames_mask[:, :, latent_idx:end_index] = 1.0 - strength - +import torch +import comfy.utils +from comfy_api.latest import io + +class LTXKeyframer(io.ComfyNode): + @classmethod + def define_schema(cls): + inputs = [ + io.Vae.Input("vae", tooltip="Video VAE used to encode the images"), + io.Latent.Input("latent", tooltip="Video latent to insert images into"), + io.Image.Input("multi_input", tooltip="Batched images from MultiImageLoader"), + ] + + inputs.append(io.Int.Input("num_images", default=1, min=0, max=50, step=1, display_name="images_loaded", tooltip="Select how many index/strength widgets to configure.")) + + for i in range(1, 51): # 1 to 50 images + inputs.extend([ + io.Int.Input( + f"insert_frame_{i}", + default=0, + min=-9999, + max=9999, + step=1, + tooltip=f"Frame insert_frame for image {i} (in pixel space).", + optional=True, + ), + io.Float.Input( + f"strength_{i}", + default=1.0, + min=0.0, + max=1.0, + step=0.01, + tooltip=f"Strength for image {i}.", + optional=True, + ), + ]) + + return io.Schema( + node_id="LTXKeyframer", + display_name="LTX Keyframer", + category="WhatDreamsCost", + description="Replaces video latent frames with the encoded input images. Number of widgets is dynamically configured.", + inputs=inputs, + outputs=[ + io.Latent.Output(display_name="latent", tooltip="The video latent with the images inserted and latent noise mask updated."), + ], + ) + + @classmethod + def execute(cls, vae, latent, multi_input, num_images, **kwargs) -> io.NodeOutput: + + samples = latent["samples"].clone() + scale_factors = vae.downscale_index_formula + _, height_scale_factor, width_scale_factor = scale_factors + + batch, _, latent_frames, latent_height, latent_width = samples.shape + width = latent_width * width_scale_factor + height = latent_height * height_scale_factor + + # Get existing noise mask if present, otherwise create new one + if "noise_mask" in latent: + conditioning_latent_frames_mask = latent["noise_mask"].clone() + else: + conditioning_latent_frames_mask = torch.ones( + (batch, 1, latent_frames, 1, 1), + dtype=torch.float32, + device=samples.device, + ) + + batch_size = multi_input.shape[0] if multi_input is not None else 0 + + # We process inputs up to num_images, extracting values from kwargs + for i in range(1, num_images + 1): + # Skip if this image index exceeds the batch + if i > batch_size: + continue + + image = multi_input[i-1:i] # Extract the single image frame from the batch + if image is None: + continue + + insert_frame = kwargs.get(f"insert_frame_{i}") + if insert_frame is None: + continue + strength = kwargs.get(f"strength_{i}", 1.0) + + if image.shape[1] != height or image.shape[2] != width: + pixels = comfy.utils.common_upscale(image.movedim(-1, 1), width, height, "bilinear", "center").movedim(1, -1) + else: + pixels = image + encode_pixels = pixels[:, :, :, :3] + t = vae.encode(encode_pixels) + + # Convert pixel frame insert_frame to latent insert_frame + time_scale_factor = scale_factors[0] + + # Handle negative indexing in pixel space + pixel_frame_count = (latent_frames - 1) * time_scale_factor + 1 + if insert_frame < 0: + insert_frame = pixel_frame_count + insert_frame + + # Convert to latent insert_frame + latent_idx = insert_frame // time_scale_factor + + # Clamp to valid range + latent_idx = max(0, min(latent_idx, latent_frames - 1)) + + # Calculate end insert_frame, ensuring we don't exceed latent_frames + end_index = min(latent_idx + t.shape[2], latent_frames) + + # Replace samples at the specified insert_frame range + samples[:, :, latent_idx:end_index] = t[:, :, :end_index - latent_idx] + + # Update mask at the specified insert_frame range + conditioning_latent_frames_mask[:, :, latent_idx:end_index] = 1.0 - strength + return io.NodeOutput({"samples": samples, "noise_mask": conditioning_latent_frames_mask}) \ No newline at end of file diff --git a/ltx_sequencer.py b/ltx_sequencer.py index 791c662..ee314a8 100644 --- a/ltx_sequencer.py +++ b/ltx_sequencer.py @@ -1,174 +1,174 @@ -import json -from comfy_extras.nodes_lt import get_noise_mask, LTXVAddGuide -import torch -import comfy.utils -from comfy_api.latest import io - -class LTXSequencer(LTXVAddGuide): - @classmethod - def define_schema(cls): - inputs = [ - io.Conditioning.Input("positive", tooltip="Positive conditioning to which guide keyframe info will be added"), - io.Conditioning.Input("negative", tooltip="Negative conditioning to which guide keyframe info will be added"), - io.Vae.Input("vae", tooltip="Video VAE used to encode the guide images"), - io.Latent.Input("latent", tooltip="Video latent, guides are added to the end of this latent"), - io.Image.Input("multi_input", tooltip="Batched images from MultiImageLoader"), - ] - - inputs.append(io.Int.Input("num_images", default=1, min=0, max=50, step=1, display_name="images_loaded", tooltip="Select how many index/strength widgets to configure.")) - - # New global settings widgets - inputs.append(io.Combo.Input("insert_mode", options=["frames", "seconds"], default="frames", tooltip="Select the method for determining insertion points.")) - inputs.append(io.Int.Input("frame_rate", default=24, min=1, max=120, step=1, tooltip="Video FPS (used for calculating second insertions).")) - - for i in range(1, 51): # 1 to 50 images - inputs.extend([ - io.Int.Input( - f"insert_frame_{i}", - default=0, - min=-9999, - max=9999, - step=1, - tooltip=f"Frame insert point for image {i} (in pixel space).", - optional=True, - ), - io.Float.Input( - f"insert_second_{i}", - default=0.0, - min=0.0, - max=9999.0, - step=0.1, - tooltip=f"Second insert point for image {i}.", - optional=True, - ), - io.Float.Input( - f"strength_{i}", - default=1.0, - min=0.0, - max=1.0, - step=0.01, - tooltip=f"Strength for image {i}.", - optional=True, - ), - ]) - - return io.Schema( - node_id="LTXSequencer", - display_name="LTX Sequencer", - category="WhatDreamsCost", - description="Add multiple guide images at specified frame indices or seconds. Auto-syncs to Prompt Relay invisibly via the positive wire.", - inputs=inputs, - outputs=[ - io.Conditioning.Output(display_name="positive"), - io.Conditioning.Output(display_name="negative"), - io.Latent.Output(display_name="latent", tooltip="Video latent with added guides"), - # ---> ADDED SYNC LOG OUTPUT <--- - io.String.Output(display_name="sync_log", tooltip="Outputs a readable text block showing the actual times used for each image"), - ], - ) - - @classmethod - def execute(cls, positive, negative, vae, latent, multi_input, num_images, **kwargs) -> io.NodeOutput: - scale_factors = vae.downscale_index_formula - - # Clone latents to avoid overwriting previous nodes' operations - latent_image = latent["samples"].clone() - - # Helper logic to fetch or generate a noise mask - if "noise_mask" in latent: - noise_mask = latent["noise_mask"].clone() - else: - batch, _, latent_frames, latent_height, latent_width = latent_image.shape - noise_mask = torch.ones( - (batch, 1, latent_frames, 1, 1), - dtype=torch.float32, - device=latent_image.device, - ) - - _, _, latent_length, latent_height, latent_width = latent_image.shape - batch_size = multi_input.shape[0] if multi_input is not None else 0 - - # Retrieve selected insertion settings - insert_mode = kwargs.get("insert_mode", "frames") - frame_rate = kwargs.get("frame_rate", 24) - - # ---> EXTRACT GHOST SYNC DATA <--- - timeline_data = None - if positive is not None and len(positive) > 0: - if "prompt_relay_timeline" in positive[0][1]: - try: - timeline_data = json.loads(positive[0][1]["prompt_relay_timeline"]) - except json.JSONDecodeError: - pass - - # Prepare the log text - sync_log_lines = [] - if timeline_data: - sync_log_lines.append("=== TIMELINE SYNC ENABLED ===") - sync_log_lines.append(f"Invisibly Synced via Positive Wire! Using {insert_mode.upper()}:") - else: - sync_log_lines.append("=== MANUAL MODE ===") - sync_log_lines.append(f"No timeline detected, or Sync turned OFF. Using manual UI inputs ({insert_mode}):") - - # Process inputs up to num_images, extracting dynamic frame/strength values from kwargs - for i in range(1, num_images + 1): - # Skip if this image index exceeds the batch - if i > batch_size: - sync_log_lines.append(f"Image #{i}: Skipped (No image loaded in batch)") - continue - - img = multi_input[i-1:i] # Extract the single image frame from the batch - if img is None: - continue - - f_idx = None - strength = kwargs.get(f"strength_{i}", 1.0) - - # 1. AUTO-SYNC - if timeline_data and "starts_frames" in timeline_data and (i - 1) < len(timeline_data["starts_frames"]): - display_frame = timeline_data["starts_frames"][i - 1] - display_sec = timeline_data["starts_seconds"][i - 1] - - if insert_mode == "frames": - f_idx = display_frame - sync_log_lines.append(f"-> Image #{i} (Strength: {strength}): Synced to Segment #{i} start @ {display_frame} frames") - elif insert_mode == "seconds": - f_idx = int(display_sec * frame_rate) - sync_log_lines.append(f"-> Image #{i} (Strength: {strength}): Synced to Segment #{i} start @ {display_sec:.2f} seconds (Frame {f_idx})") - - # 2. MANUAL FALLBACK - else: - if insert_mode == "frames": - f_idx = kwargs.get(f"insert_frame_{i}") - sync_log_lines.append(f"-> Image #{i} (Strength: {strength}): Manual input @ {f_idx} frames") - elif insert_mode == "seconds": - sec = kwargs.get(f"insert_second_{i}") - if sec is not None: - f_idx = int(sec * frame_rate) - sync_log_lines.append(f"-> Image #{i} (Strength: {strength}): Manual input @ {sec:.2f} seconds (Frame {f_idx})") - - if f_idx is None: - continue - - # Execution logic mirrored from LTXVAddGuideMulti - image_1, t = cls.encode(vae, latent_width, latent_height, img, scale_factors) - - frame_idx, latent_idx = cls.get_latent_index(positive, latent_length, len(image_1), f_idx, scale_factors) - assert latent_idx + t.shape[2] <= latent_length, "Conditioning frames exceed the length of the latent sequence." - - positive, negative, latent_image, noise_mask = cls.append_keyframe( - positive, - negative, - frame_idx, - latent_image, - noise_mask, - t, - strength, - scale_factors, - ) - - sync_log_str = "\n".join(sync_log_lines) - print(f"\n[LTX Sequencer]\n{sync_log_str}\n") - - # Returned the newly formatted sync log string at the end of NodeOutput +import json +from comfy_extras.nodes_lt import get_noise_mask, LTXVAddGuide +import torch +import comfy.utils +from comfy_api.latest import io + +class LTXSequencer(LTXVAddGuide): + @classmethod + def define_schema(cls): + inputs = [ + io.Conditioning.Input("positive", tooltip="Positive conditioning to which guide keyframe info will be added"), + io.Conditioning.Input("negative", tooltip="Negative conditioning to which guide keyframe info will be added"), + io.Vae.Input("vae", tooltip="Video VAE used to encode the guide images"), + io.Latent.Input("latent", tooltip="Video latent, guides are added to the end of this latent"), + io.Image.Input("multi_input", tooltip="Batched images from MultiImageLoader"), + ] + + inputs.append(io.Int.Input("num_images", default=1, min=0, max=50, step=1, display_name="images_loaded", tooltip="Select how many index/strength widgets to configure.")) + + # New global settings widgets + inputs.append(io.Combo.Input("insert_mode", options=["frames", "seconds"], default="frames", tooltip="Select the method for determining insertion points.")) + inputs.append(io.Int.Input("frame_rate", default=24, min=1, max=120, step=1, tooltip="Video FPS (used for calculating second insertions).")) + + for i in range(1, 51): # 1 to 50 images + inputs.extend([ + io.Int.Input( + f"insert_frame_{i}", + default=0, + min=-9999, + max=9999, + step=1, + tooltip=f"Frame insert point for image {i} (in pixel space).", + optional=True, + ), + io.Float.Input( + f"insert_second_{i}", + default=0.0, + min=0.0, + max=9999.0, + step=0.1, + tooltip=f"Second insert point for image {i}.", + optional=True, + ), + io.Float.Input( + f"strength_{i}", + default=1.0, + min=0.0, + max=1.0, + step=0.01, + tooltip=f"Strength for image {i}.", + optional=True, + ), + ]) + + return io.Schema( + node_id="LTXSequencer", + display_name="LTX Sequencer", + category="WhatDreamsCost", + description="Add multiple guide images at specified frame indices or seconds. Auto-syncs to Prompt Relay invisibly via the positive wire.", + inputs=inputs, + outputs=[ + io.Conditioning.Output(display_name="positive"), + io.Conditioning.Output(display_name="negative"), + io.Latent.Output(display_name="latent", tooltip="Video latent with added guides"), + # ---> ADDED SYNC LOG OUTPUT <--- + io.String.Output(display_name="sync_log", tooltip="Outputs a readable text block showing the actual times used for each image"), + ], + ) + + @classmethod + def execute(cls, positive, negative, vae, latent, multi_input, num_images, **kwargs) -> io.NodeOutput: + scale_factors = vae.downscale_index_formula + + # Clone latents to avoid overwriting previous nodes' operations + latent_image = latent["samples"].clone() + + # Helper logic to fetch or generate a noise mask + if "noise_mask" in latent: + noise_mask = latent["noise_mask"].clone() + else: + batch, _, latent_frames, latent_height, latent_width = latent_image.shape + noise_mask = torch.ones( + (batch, 1, latent_frames, 1, 1), + dtype=torch.float32, + device=latent_image.device, + ) + + _, _, latent_length, latent_height, latent_width = latent_image.shape + batch_size = multi_input.shape[0] if multi_input is not None else 0 + + # Retrieve selected insertion settings + insert_mode = kwargs.get("insert_mode", "frames") + frame_rate = kwargs.get("frame_rate", 24) + + # ---> EXTRACT GHOST SYNC DATA <--- + timeline_data = None + if positive is not None and len(positive) > 0: + if "prompt_relay_timeline" in positive[0][1]: + try: + timeline_data = json.loads(positive[0][1]["prompt_relay_timeline"]) + except json.JSONDecodeError: + pass + + # Prepare the log text + sync_log_lines = [] + if timeline_data: + sync_log_lines.append("=== TIMELINE SYNC ENABLED ===") + sync_log_lines.append(f"Invisibly Synced via Positive Wire! Using {insert_mode.upper()}:") + else: + sync_log_lines.append("=== MANUAL MODE ===") + sync_log_lines.append(f"No timeline detected, or Sync turned OFF. Using manual UI inputs ({insert_mode}):") + + # Process inputs up to num_images, extracting dynamic frame/strength values from kwargs + for i in range(1, num_images + 1): + # Skip if this image index exceeds the batch + if i > batch_size: + sync_log_lines.append(f"Image #{i}: Skipped (No image loaded in batch)") + continue + + img = multi_input[i-1:i] # Extract the single image frame from the batch + if img is None: + continue + + f_idx = None + strength = kwargs.get(f"strength_{i}", 1.0) + + # 1. AUTO-SYNC + if timeline_data and "starts_frames" in timeline_data and (i - 1) < len(timeline_data["starts_frames"]): + display_frame = timeline_data["starts_frames"][i - 1] + display_sec = timeline_data["starts_seconds"][i - 1] + + if insert_mode == "frames": + f_idx = display_frame + sync_log_lines.append(f"-> Image #{i} (Strength: {strength}): Synced to Segment #{i} start @ {display_frame} frames") + elif insert_mode == "seconds": + f_idx = int(display_sec * frame_rate) + sync_log_lines.append(f"-> Image #{i} (Strength: {strength}): Synced to Segment #{i} start @ {display_sec:.2f} seconds (Frame {f_idx})") + + # 2. MANUAL FALLBACK + else: + if insert_mode == "frames": + f_idx = kwargs.get(f"insert_frame_{i}") + sync_log_lines.append(f"-> Image #{i} (Strength: {strength}): Manual input @ {f_idx} frames") + elif insert_mode == "seconds": + sec = kwargs.get(f"insert_second_{i}") + if sec is not None: + f_idx = int(sec * frame_rate) + sync_log_lines.append(f"-> Image #{i} (Strength: {strength}): Manual input @ {sec:.2f} seconds (Frame {f_idx})") + + if f_idx is None: + continue + + # Execution logic mirrored from LTXVAddGuideMulti + image_1, t = cls.encode(vae, latent_width, latent_height, img, scale_factors) + + frame_idx, latent_idx = cls.get_latent_index(positive, latent_length, len(image_1), f_idx, scale_factors) + assert latent_idx + t.shape[2] <= latent_length, "Conditioning frames exceed the length of the latent sequence." + + positive, negative, latent_image, noise_mask = cls.append_keyframe( + positive, + negative, + frame_idx, + latent_image, + noise_mask, + t, + strength, + scale_factors, + ) + + sync_log_str = "\n".join(sync_log_lines) + print(f"\n[LTX Sequencer]\n{sync_log_str}\n") + + # Returned the newly formatted sync log string at the end of NodeOutput return io.NodeOutput(positive, negative, {"samples": latent_image, "noise_mask": noise_mask}, sync_log_str) \ No newline at end of file diff --git a/multi_image_loader.py b/multi_image_loader.py index a290894..cb1e99a 100644 --- a/multi_image_loader.py +++ b/multi_image_loader.py @@ -1,182 +1,182 @@ -import torch -import torch.nn.functional as F -import numpy as np -from PIL import Image, ImageOps -import os -import folder_paths -import io -import comfy.utils - -class MultiImageLoader: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "image_paths": ("STRING", {"default": "", "multiline": True}), - "width": ("INT", {"default": 0, "min": 0, "max": 8192, "step": 1}), - "height": ("INT", {"default": 0, "min": 0, "max": 8192, "step": 1}), - "interpolation": (["lanczos", "nearest", "bilinear", "bicubic", "area", "nearest-exact"],), - "resize_method": (["keep proportion", "stretch", "pad", "crop"],), - "multiple_of": ("INT", {"default": 32, "min": 0, "max": 512, "step": 1}), - "img_compression": ("INT", {"default": 18, "min": 0, "max": 100, "step": 1}), - }, - } - - # Added "IMAGE" at the beginning for multi_output + 50 individual outputs = 51 outputs - RETURN_TYPES = ("IMAGE",) * 51 - RETURN_NAMES = ("multi_output",) + tuple(f"image_{i+1}" for i in range(50)) - FUNCTION = "load_images" - CATEGORY = "WhatDreamsCost" - - def resize_image(self, image, width, height, resize_method="keep proportion", interpolation="nearest", multiple_of=0): - MAX_RESOLUTION = 8192 - _, oh, ow, _ = image.shape - x = y = x2 = y2 = 0 - pad_left = pad_right = pad_top = pad_bottom = 0 - - if multiple_of > 1: - width = width - (width % multiple_of) - height = height - (height % multiple_of) - - if resize_method == 'keep proportion' or resize_method == 'pad': - if width == 0 and oh < height: - width = MAX_RESOLUTION - elif width == 0 and oh >= height: - width = ow - - if height == 0 and ow < width: - height = MAX_RESOLUTION - elif height == 0 and ow >= width: - height = oh - - ratio = min(width / ow, height / oh) - new_width = round(ow * ratio) - new_height = round(oh * ratio) - - if resize_method == 'pad': - pad_left = (width - new_width) // 2 - pad_right = width - new_width - pad_left - pad_top = (height - new_height) // 2 - pad_bottom = height - new_height - pad_top - - width = new_width - height = new_height - - elif resize_method == 'crop': - width = width if width > 0 else ow - height = height if height > 0 else oh - - ratio = max(width / ow, height / oh) - new_width = round(ow * ratio) - new_height = round(oh * ratio) - x = (new_width - width) // 2 - y = (new_height - height) // 2 - x2 = x + width - y2 = y + height - if x2 > new_width: - x -= (x2 - new_width) - if x < 0: - x = 0 - if y2 > new_height: - y -= (y2 - new_height) - if y < 0: - y = 0 - width = new_width - height = new_height - - else: - width = width if width > 0 else ow - height = height if height > 0 else oh - - # Always apply resize logic - outputs = image.permute(0, 3, 1, 2) - - if interpolation == "lanczos": - outputs = comfy.utils.lanczos(outputs, width, height) - else: - outputs = F.interpolate(outputs, size=(height, width), mode=interpolation) - - if resize_method == 'pad': - if pad_left > 0 or pad_right > 0 or pad_top > 0 or pad_bottom > 0: - outputs = F.pad(outputs, (pad_left, pad_right, pad_top, pad_bottom), value=0) - - outputs = outputs.permute(0, 2, 3, 1) - - if resize_method == 'crop': - if x > 0 or y > 0 or x2 > 0 or y2 > 0: - outputs = outputs[:, y:y2, x:x2, :] - - if multiple_of > 1 and (outputs.shape[2] % multiple_of != 0 or outputs.shape[1] % multiple_of != 0): - width = outputs.shape[2] - height = outputs.shape[1] - x = (width % multiple_of) // 2 - y = (height % multiple_of) // 2 - x2 = width - ((width % multiple_of) - x) - y2 = height - ((height % multiple_of) - y) - outputs = outputs[:, y:y2, x:x2, :] - - outputs = torch.clamp(outputs, 0, 1) - - return outputs - - def load_images(self, image_paths, width, height, interpolation, resize_method, multiple_of, img_compression): - results = [] - valid_paths = [p.strip() for p in image_paths.split("\n") if p.strip()] - - for path in valid_paths: - try: - # Resolve full path - full_path = path - if not os.path.exists(full_path): - full_path = os.path.join(folder_paths.get_input_directory(), path) - - if not os.path.exists(full_path): - print(f"Warning: Image path not found: {path}") - continue - - # Load image - image = Image.open(full_path) - image = ImageOps.exif_transpose(image) - image = image.convert("RGB") - - # Convert to Torch Tensor to prepare for Advanced Resize Logic - image_np = np.array(image).astype(np.float32) / 255.0 - image_tensor = torch.from_numpy(image_np)[None,] - - # Apply Advanced Resize - image_tensor = self.resize_image(image_tensor, width, height, resize_method, interpolation, multiple_of) - - # Compression (Applied after resize to accurately maintain the effect) - if img_compression > 0: - img_np = (image_tensor[0].numpy() * 255).clip(0, 255).astype(np.uint8) - img_pil = Image.fromarray(img_np) - img_byte_arr = io.BytesIO() - img_pil.save(img_byte_arr, format="JPEG", quality=max(1, 100 - img_compression)) - img_pil = Image.open(img_byte_arr) - image_tensor = torch.from_numpy(np.array(img_pil).astype(np.float32) / 255.0)[None,] - - results.append(image_tensor) - except Exception as e: - print(f"Error loading {path}: {e}") - - # Combine all successfully loaded images into a single batched tensor for multi_output - if len(results) > 0: - # Safety Check: Advanced resize methods might output differently sized tensors (e.g., 'keep proportion') - first_shape = results[0].shape - all_same_shape = all(r.shape == first_shape for r in results) - - if all_same_shape: - multi_output = torch.cat(results, dim=0) - else: - print("MultiImageLoader Warning: Images have different dimensions due to resize settings. Cannot batch into multi_output. Outputting zero tensor for the batch, but individual output nodes will still work fine.") - multi_output = torch.zeros((1, 64, 64, 3)) - else: - # Fallback empty tensor if no valid paths - multi_output = torch.zeros((1, 64, 64, 3)) - results = [multi_output] - - # Pad individual outputs exactly to length 50 as defined in RETURN_TYPES - padded_results = results + [torch.zeros((1, 64, 64, 3))] * (50 - len(results)) - - # Return the multi batch output first, followed by the individual padded items +import torch +import torch.nn.functional as F +import numpy as np +from PIL import Image, ImageOps +import os +import folder_paths +import io +import comfy.utils + +class MultiImageLoader: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "image_paths": ("STRING", {"default": "", "multiline": True}), + "width": ("INT", {"default": 0, "min": 0, "max": 8192, "step": 1}), + "height": ("INT", {"default": 0, "min": 0, "max": 8192, "step": 1}), + "interpolation": (["lanczos", "nearest", "bilinear", "bicubic", "area", "nearest-exact"],), + "resize_method": (["keep proportion", "stretch", "pad", "crop"],), + "multiple_of": ("INT", {"default": 32, "min": 0, "max": 512, "step": 1}), + "img_compression": ("INT", {"default": 18, "min": 0, "max": 100, "step": 1}), + }, + } + + # Added "IMAGE" at the beginning for multi_output + 50 individual outputs = 51 outputs + RETURN_TYPES = ("IMAGE",) * 51 + RETURN_NAMES = ("multi_output",) + tuple(f"image_{i+1}" for i in range(50)) + FUNCTION = "load_images" + CATEGORY = "WhatDreamsCost" + + def resize_image(self, image, width, height, resize_method="keep proportion", interpolation="nearest", multiple_of=0): + MAX_RESOLUTION = 8192 + _, oh, ow, _ = image.shape + x = y = x2 = y2 = 0 + pad_left = pad_right = pad_top = pad_bottom = 0 + + if multiple_of > 1: + width = width - (width % multiple_of) + height = height - (height % multiple_of) + + if resize_method == 'keep proportion' or resize_method == 'pad': + if width == 0 and oh < height: + width = MAX_RESOLUTION + elif width == 0 and oh >= height: + width = ow + + if height == 0 and ow < width: + height = MAX_RESOLUTION + elif height == 0 and ow >= width: + height = oh + + ratio = min(width / ow, height / oh) + new_width = round(ow * ratio) + new_height = round(oh * ratio) + + if resize_method == 'pad': + pad_left = (width - new_width) // 2 + pad_right = width - new_width - pad_left + pad_top = (height - new_height) // 2 + pad_bottom = height - new_height - pad_top + + width = new_width + height = new_height + + elif resize_method == 'crop': + width = width if width > 0 else ow + height = height if height > 0 else oh + + ratio = max(width / ow, height / oh) + new_width = round(ow * ratio) + new_height = round(oh * ratio) + x = (new_width - width) // 2 + y = (new_height - height) // 2 + x2 = x + width + y2 = y + height + if x2 > new_width: + x -= (x2 - new_width) + if x < 0: + x = 0 + if y2 > new_height: + y -= (y2 - new_height) + if y < 0: + y = 0 + width = new_width + height = new_height + + else: + width = width if width > 0 else ow + height = height if height > 0 else oh + + # Always apply resize logic + outputs = image.permute(0, 3, 1, 2) + + if interpolation == "lanczos": + outputs = comfy.utils.lanczos(outputs, width, height) + else: + outputs = F.interpolate(outputs, size=(height, width), mode=interpolation) + + if resize_method == 'pad': + if pad_left > 0 or pad_right > 0 or pad_top > 0 or pad_bottom > 0: + outputs = F.pad(outputs, (pad_left, pad_right, pad_top, pad_bottom), value=0) + + outputs = outputs.permute(0, 2, 3, 1) + + if resize_method == 'crop': + if x > 0 or y > 0 or x2 > 0 or y2 > 0: + outputs = outputs[:, y:y2, x:x2, :] + + if multiple_of > 1 and (outputs.shape[2] % multiple_of != 0 or outputs.shape[1] % multiple_of != 0): + width = outputs.shape[2] + height = outputs.shape[1] + x = (width % multiple_of) // 2 + y = (height % multiple_of) // 2 + x2 = width - ((width % multiple_of) - x) + y2 = height - ((height % multiple_of) - y) + outputs = outputs[:, y:y2, x:x2, :] + + outputs = torch.clamp(outputs, 0, 1) + + return outputs + + def load_images(self, image_paths, width, height, interpolation, resize_method, multiple_of, img_compression): + results = [] + valid_paths = [p.strip() for p in image_paths.split("\n") if p.strip()] + + for path in valid_paths: + try: + # Resolve full path + full_path = path + if not os.path.exists(full_path): + full_path = os.path.join(folder_paths.get_input_directory(), path) + + if not os.path.exists(full_path): + print(f"Warning: Image path not found: {path}") + continue + + # Load image + image = Image.open(full_path) + image = ImageOps.exif_transpose(image) + image = image.convert("RGB") + + # Convert to Torch Tensor to prepare for Advanced Resize Logic + image_np = np.array(image).astype(np.float32) / 255.0 + image_tensor = torch.from_numpy(image_np)[None,] + + # Apply Advanced Resize + image_tensor = self.resize_image(image_tensor, width, height, resize_method, interpolation, multiple_of) + + # Compression (Applied after resize to accurately maintain the effect) + if img_compression > 0: + img_np = (image_tensor[0].numpy() * 255).clip(0, 255).astype(np.uint8) + img_pil = Image.fromarray(img_np) + img_byte_arr = io.BytesIO() + img_pil.save(img_byte_arr, format="JPEG", quality=max(1, 100 - img_compression)) + img_pil = Image.open(img_byte_arr) + image_tensor = torch.from_numpy(np.array(img_pil).astype(np.float32) / 255.0)[None,] + + results.append(image_tensor) + except Exception as e: + print(f"Error loading {path}: {e}") + + # Combine all successfully loaded images into a single batched tensor for multi_output + if len(results) > 0: + # Safety Check: Advanced resize methods might output differently sized tensors (e.g., 'keep proportion') + first_shape = results[0].shape + all_same_shape = all(r.shape == first_shape for r in results) + + if all_same_shape: + multi_output = torch.cat(results, dim=0) + else: + print("MultiImageLoader Warning: Images have different dimensions due to resize settings. Cannot batch into multi_output. Outputting zero tensor for the batch, but individual output nodes will still work fine.") + multi_output = torch.zeros((1, 64, 64, 3)) + else: + # Fallback empty tensor if no valid paths + multi_output = torch.zeros((1, 64, 64, 3)) + results = [multi_output] + + # Pad individual outputs exactly to length 50 as defined in RETURN_TYPES + padded_results = results + [torch.zeros((1, 64, 64, 3))] * (50 - len(results)) + + # Return the multi batch output first, followed by the individual padded items return (multi_output, *padded_results[:50]) \ No newline at end of file diff --git a/patches.py b/patches.py index 1404146..862d094 100644 --- a/patches.py +++ b/patches.py @@ -1,167 +1,167 @@ -import types -import torch -import comfy.ldm.modules.attention - - -def _masked_attention(q, k, v, heads, mask, transformer_options={}, **kwargs): - # Bypass wrap_attn (sage/etc may ignore masks) by calling attention_pytorch directly. - return comfy.ldm.modules.attention.attention_pytorch( - q, k, v, heads, mask=mask, - _inside_attn_wrapper=True, - transformer_options=transformer_options, - **kwargs, - ) - - -def _wan_t2v_forward(self, mask_fn, x, context, transformer_options={}, **kwargs): - q = self.norm_q(self.q(x)) - k = self.norm_k(self.k(context)) - v = self.v(context) - - mask = mask_fn(q, k, transformer_options) - if mask is not None: - x = _masked_attention(q, k, v, heads=self.num_heads, mask=mask, - transformer_options=transformer_options) - else: - x = comfy.ldm.modules.attention.optimized_attention( - q, k, v, heads=self.num_heads, transformer_options=transformer_options, - ) - return self.o(x) - - -def _wan_i2v_forward(self, mask_fn, x, context, context_img_len, transformer_options={}, **kwargs): - context_img = context[:, :context_img_len] - context_text = context[:, context_img_len:] - - q = self.norm_q(self.q(x)) - - k_img = self.norm_k_img(self.k_img(context_img)) - v_img = self.v_img(context_img) - img_x = comfy.ldm.modules.attention.optimized_attention( - q, k_img, v_img, heads=self.num_heads, transformer_options=transformer_options, - ) - - k = self.norm_k(self.k(context_text)) - v = self.v(context_text) - - mask = mask_fn(q, k, transformer_options) - if mask is not None: - x = _masked_attention(q, k, v, heads=self.num_heads, mask=mask, - transformer_options=transformer_options) - else: - x = comfy.ldm.modules.attention.optimized_attention( - q, k, v, heads=self.num_heads, transformer_options=transformer_options, - ) - - return self.o(x + img_x) - - -def _ltx_forward(self, mask_fn, x, context=None, mask=None, pe=None, k_pe=None, transformer_options={}): - from comfy.ldm.lightricks.model import apply_rotary_emb - - is_self_attn = context is None - context = x if is_self_attn else context - - q = self.q_norm(self.to_q(x)) - k = self.k_norm(self.to_k(context)) - v = self.to_v(context) - - if pe is not None: - q = apply_rotary_emb(q, pe) - k = apply_rotary_emb(k, pe if k_pe is None else k_pe) - - if not is_self_attn: - temporal_mask = mask_fn(q, k, transformer_options) - if temporal_mask is not None: - mask = temporal_mask if mask is None else mask + temporal_mask - - if mask is None: - out = comfy.ldm.modules.attention.optimized_attention( - q, k, v, self.heads, attn_precision=self.attn_precision, - transformer_options=transformer_options, - ) - else: - out = _masked_attention(q, k, v, self.heads, mask=mask, - attn_precision=self.attn_precision, - transformer_options=transformer_options) - - if self.to_gate_logits is not None: - gate_logits = self.to_gate_logits(x) - b, t, _ = out.shape - out = out.view(b, t, self.heads, self.dim_head) - out = out * (2.0 * torch.sigmoid(gate_logits)).unsqueeze(-1) - out = out.view(b, t, self.heads * self.dim_head) - - return self.to_out(out) - - -class _CrossAttnPatch: - """Descriptor that binds (impl, mask_fn) as a method onto a cross-attn module.""" - - def __init__(self, impl, mask_fn): - self.impl = impl - self.mask_fn = mask_fn - - def __get__(self, obj, objtype=None): - impl, mask_fn = self.impl, self.mask_fn - - def wrapped(self_module, *args, **kwargs): - return impl(self_module, mask_fn, *args, **kwargs) - - return types.MethodType(wrapped, obj) - - -def detect_model_type(model): - """Return (arch, patch_size, temporal_stride) for latent geometry. - - temporal_stride is the VAE's pixel→latent temporal compression factor, - used to convert user-facing pixel frame counts to latent frames. - """ - diff_model = model.model.diffusion_model - - if hasattr(diff_model, "patch_size") and not hasattr(diff_model, "patchifier"): - return "wan", tuple(diff_model.patch_size), 4 - - if hasattr(diff_model, "patchifier"): - return "ltx", (1, 1, 1), int(diff_model.vae_scale_factors[0]) - - raise ValueError( - f"Unsupported model type: {type(diff_model).__name__}. " - f"Currently supports Wan and LTX models." - ) - - -def _check_unpatched(model_clone, key): - if key in getattr(model_clone, "object_patches", {}): - raise RuntimeError( - f"PromptRelay: cross-attention forward at '{key}' is already patched by " - "another node (e.g. KJNodes NAG). Stacking is not supported — remove the " - "conflicting node." - ) - - -def apply_patches(model_clone, arch, mask_fn): - diffusion_model = model_clone.get_model_object("diffusion_model") - - if arch == "wan": - from comfy.ldm.wan.model import WanI2VCrossAttention - for idx, block in enumerate(diffusion_model.blocks): - key = f"diffusion_model.blocks.{idx}.cross_attn.forward" - _check_unpatched(model_clone, key) - cross_attn = block.cross_attn - impl = _wan_i2v_forward if isinstance(cross_attn, WanI2VCrossAttention) else _wan_t2v_forward - model_clone.add_object_patch(key, _CrossAttnPatch(impl, mask_fn).__get__(cross_attn, cross_attn.__class__)) - return - - if arch == "ltx": - for idx, block in enumerate(diffusion_model.transformer_blocks): - for attr in ("attn2", "audio_attn2"): - module = getattr(block, attr, None) - if module is None: - continue - key = f"diffusion_model.transformer_blocks.{idx}.{attr}.forward" - _check_unpatched(model_clone, key) - model_clone.add_object_patch(key, _CrossAttnPatch(_ltx_forward, mask_fn).__get__(module, module.__class__)) - return - - raise ValueError(f"Unknown model arch: {arch}") +import types +import torch +import comfy.ldm.modules.attention + + +def _masked_attention(q, k, v, heads, mask, transformer_options={}, **kwargs): + # Bypass wrap_attn (sage/etc may ignore masks) by calling attention_pytorch directly. + return comfy.ldm.modules.attention.attention_pytorch( + q, k, v, heads, mask=mask, + _inside_attn_wrapper=True, + transformer_options=transformer_options, + **kwargs, + ) + + +def _wan_t2v_forward(self, mask_fn, x, context, transformer_options={}, **kwargs): + q = self.norm_q(self.q(x)) + k = self.norm_k(self.k(context)) + v = self.v(context) + + mask = mask_fn(q, k, transformer_options) + if mask is not None: + x = _masked_attention(q, k, v, heads=self.num_heads, mask=mask, + transformer_options=transformer_options) + else: + x = comfy.ldm.modules.attention.optimized_attention( + q, k, v, heads=self.num_heads, transformer_options=transformer_options, + ) + return self.o(x) + + +def _wan_i2v_forward(self, mask_fn, x, context, context_img_len, transformer_options={}, **kwargs): + context_img = context[:, :context_img_len] + context_text = context[:, context_img_len:] + + q = self.norm_q(self.q(x)) + + k_img = self.norm_k_img(self.k_img(context_img)) + v_img = self.v_img(context_img) + img_x = comfy.ldm.modules.attention.optimized_attention( + q, k_img, v_img, heads=self.num_heads, transformer_options=transformer_options, + ) + + k = self.norm_k(self.k(context_text)) + v = self.v(context_text) + + mask = mask_fn(q, k, transformer_options) + if mask is not None: + x = _masked_attention(q, k, v, heads=self.num_heads, mask=mask, + transformer_options=transformer_options) + else: + x = comfy.ldm.modules.attention.optimized_attention( + q, k, v, heads=self.num_heads, transformer_options=transformer_options, + ) + + return self.o(x + img_x) + + +def _ltx_forward(self, mask_fn, x, context=None, mask=None, pe=None, k_pe=None, transformer_options={}): + from comfy.ldm.lightricks.model import apply_rotary_emb + + is_self_attn = context is None + context = x if is_self_attn else context + + q = self.q_norm(self.to_q(x)) + k = self.k_norm(self.to_k(context)) + v = self.to_v(context) + + if pe is not None: + q = apply_rotary_emb(q, pe) + k = apply_rotary_emb(k, pe if k_pe is None else k_pe) + + if not is_self_attn: + temporal_mask = mask_fn(q, k, transformer_options) + if temporal_mask is not None: + mask = temporal_mask if mask is None else mask + temporal_mask + + if mask is None: + out = comfy.ldm.modules.attention.optimized_attention( + q, k, v, self.heads, attn_precision=self.attn_precision, + transformer_options=transformer_options, + ) + else: + out = _masked_attention(q, k, v, self.heads, mask=mask, + attn_precision=self.attn_precision, + transformer_options=transformer_options) + + if self.to_gate_logits is not None: + gate_logits = self.to_gate_logits(x) + b, t, _ = out.shape + out = out.view(b, t, self.heads, self.dim_head) + out = out * (2.0 * torch.sigmoid(gate_logits)).unsqueeze(-1) + out = out.view(b, t, self.heads * self.dim_head) + + return self.to_out(out) + + +class _CrossAttnPatch: + """Descriptor that binds (impl, mask_fn) as a method onto a cross-attn module.""" + + def __init__(self, impl, mask_fn): + self.impl = impl + self.mask_fn = mask_fn + + def __get__(self, obj, objtype=None): + impl, mask_fn = self.impl, self.mask_fn + + def wrapped(self_module, *args, **kwargs): + return impl(self_module, mask_fn, *args, **kwargs) + + return types.MethodType(wrapped, obj) + + +def detect_model_type(model): + """Return (arch, patch_size, temporal_stride) for latent geometry. + + temporal_stride is the VAE's pixel→latent temporal compression factor, + used to convert user-facing pixel frame counts to latent frames. + """ + diff_model = model.model.diffusion_model + + if hasattr(diff_model, "patch_size") and not hasattr(diff_model, "patchifier"): + return "wan", tuple(diff_model.patch_size), 4 + + if hasattr(diff_model, "patchifier"): + return "ltx", (1, 1, 1), int(diff_model.vae_scale_factors[0]) + + raise ValueError( + f"Unsupported model type: {type(diff_model).__name__}. " + f"Currently supports Wan and LTX models." + ) + + +def _check_unpatched(model_clone, key): + if key in getattr(model_clone, "object_patches", {}): + raise RuntimeError( + f"PromptRelay: cross-attention forward at '{key}' is already patched by " + "another node (e.g. KJNodes NAG). Stacking is not supported — remove the " + "conflicting node." + ) + + +def apply_patches(model_clone, arch, mask_fn): + diffusion_model = model_clone.get_model_object("diffusion_model") + + if arch == "wan": + from comfy.ldm.wan.model import WanI2VCrossAttention + for idx, block in enumerate(diffusion_model.blocks): + key = f"diffusion_model.blocks.{idx}.cross_attn.forward" + _check_unpatched(model_clone, key) + cross_attn = block.cross_attn + impl = _wan_i2v_forward if isinstance(cross_attn, WanI2VCrossAttention) else _wan_t2v_forward + model_clone.add_object_patch(key, _CrossAttnPatch(impl, mask_fn).__get__(cross_attn, cross_attn.__class__)) + return + + if arch == "ltx": + for idx, block in enumerate(diffusion_model.transformer_blocks): + for attr in ("attn2", "audio_attn2"): + module = getattr(block, attr, None) + if module is None: + continue + key = f"diffusion_model.transformer_blocks.{idx}.{attr}.forward" + _check_unpatched(model_clone, key) + model_clone.add_object_patch(key, _CrossAttnPatch(_ltx_forward, mask_fn).__get__(module, module.__class__)) + return + + raise ValueError(f"Unknown model arch: {arch}") diff --git a/prompt_relay.py b/prompt_relay.py index adfffe0..ebe2750 100644 --- a/prompt_relay.py +++ b/prompt_relay.py @@ -1,201 +1,201 @@ -import logging -import math -import torch - -log = logging.getLogger(__name__) - - -def build_temporal_cost(q_token_idx, Lq, Lk, device, dtype, tokens_per_frame): - """Gaussian penalty matrix [Lq, Lk] for video cross-attention (integer frame indexing).""" - offset = torch.zeros(Lq, Lk, device=device, dtype=dtype) - query_frames = torch.arange(Lq, device=device, dtype=torch.long) // tokens_per_frame - - for seg in q_token_idx: - local = seg["local_token_idx"].to(device=device) - d = (query_frames.float()[:, None] - seg["midpoint"]).abs() - strength = seg.get("strength", 1.0) - cost = strength * (torch.relu(d - seg["window"]) ** 2) / (2 * seg["sigma"] ** 2) - offset[:, local] = cost.to(offset.dtype) - - return offset - - -def build_temporal_cost_scaled(q_token_idx, Lq, Lk, device, dtype, latent_frames): - """Penalty matrix for queries that don't map to integer frames (e.g. LTXAV audio tokens).""" - offset = torch.zeros(Lq, Lk, device=device, dtype=dtype) - query_frames = torch.arange(Lq, device=device, dtype=torch.float32) * latent_frames / Lq - - for seg in q_token_idx: - local = seg["local_token_idx"].to(device=device) - d = (query_frames[:, None] - seg["midpoint"]).abs() - sigma_a = seg.get("sigma_audio", seg["sigma"]) - window_a = seg.get("window_audio", seg["window"]) - strength_a = seg.get("strength_audio", 1.0) - cost = strength_a * (torch.relu(d - window_a) ** 2) / (2 * sigma_a ** 2) - offset[:, local] = cost.to(offset.dtype) - - return offset - - -def create_mask_fn(q_token_idx, fallback_tokens_per_frame, latent_frames): - """Closure: mask_fn(q, k, transformer_options) -> additive mask or None.""" - cache = {} - max_token_idx = max(int(seg["local_token_idx"].max().item()) for seg in q_token_idx) + 1 - - def mask_fn(q, k, transformer_options): - Lq, Lk = q.shape[1], k.shape[1] - - if Lq == Lk: - return None - - # Only apply on conditional pass — not unconditional (negative prompt) - cond_or_uncond = transformer_options.get("cond_or_uncond", []) - if 1 in cond_or_uncond and 0 not in cond_or_uncond: - return None - - grid_sizes = transformer_options.get("grid_sizes", None) - video_tpf = int(grid_sizes[1]) * int(grid_sizes[2]) if grid_sizes is not None else fallback_tokens_per_frame - video_lq = latent_frames * video_tpf - - # Skip cross-modal attention — text keys are padded to a fixed length ≥ max_token_idx and != video_lq - if Lk == video_lq or Lk < max_token_idx: - return None - - mode = "video" if Lq == video_lq else "scaled" - - key = (Lq, Lk, mode, q.device) - if key not in cache: - if mode == "video": - cost = build_temporal_cost(q_token_idx, Lq, Lk, q.device, q.dtype, video_tpf) - else: - cost = build_temporal_cost_scaled(q_token_idx, Lq, Lk, q.device, q.dtype, latent_frames) - log.info( - "[PromptRelay] Built penalty matrix (%s): Lq=%d, Lk=%d, nonzero=%d/%d", - mode, Lq, Lk, (cost > 0).sum().item(), cost.numel(), - ) - cache[key] = -cost - - return cache[key].to(q.dtype) - - return mask_fn - - -def build_segments(token_ranges, segment_lengths, epsilon=1e-3, relay_options=None): - """Per-segment metadata for the temporal penalty. - - relay_options (optional dict) overrides per-stream knobs: - video_strength, video_window_scale, - audio_epsilon, audio_strength, audio_window_scale - Audio knobs only affect architectures whose cross-attention takes the scaled - (non-integer-frame) path — currently LTX audio_attn2. - """ - # Paper uses constant sigma = 1/ln(1/epsilon) regardless of segment length - sigma = 1.0 / math.log(1.0 / epsilon) if 0 < epsilon < 1 else 0.1448 - - opts = relay_options or {} - v_strength = opts.get("video_strength", 1.0) - v_window_scale = opts.get("video_window_scale", 1.0) - a_epsilon = opts.get("audio_epsilon") - a_strength = opts.get("audio_strength", 1.0) - a_window_scale = opts.get("audio_window_scale", 1.0) - - if a_epsilon is not None and 0 < a_epsilon < 1: - sigma_audio = 1.0 / math.log(1.0 / a_epsilon) - else: - sigma_audio = sigma - - if relay_options: - log.info( - "[PromptRelay] Advanced options active — video: strength=%.3f window_scale=%.3f | " - "audio: epsilon=%s strength=%.3f window_scale=%.3f", - v_strength, v_window_scale, - f"{a_epsilon:.4f}" if a_epsilon is not None else "inherit", - a_strength, a_window_scale, - ) - - q_token_idx = [] - frame_cursor = 0 - - for (tok_start, tok_end), L in zip(token_ranges, segment_lengths): - if L <= 0: - frame_cursor += L - continue - midpoint = (2 * frame_cursor + L) // 2 - base_window = max(L // 2 - 2, 0) - q_token_idx.append({ - "local_token_idx": torch.arange(tok_start, tok_end), - "midpoint": midpoint, - "window": max(base_window * v_window_scale, 0.0), - "sigma": sigma, - "strength": v_strength, - "window_audio": max(base_window * a_window_scale, 0.0), - "sigma_audio": sigma_audio, - "strength_audio": a_strength, - }) - frame_cursor += L - - return q_token_idx - - -def get_raw_tokenizer(clip): - """Extract the raw SPiece/HF tokenizer from a ComfyUI CLIP object.""" - tokenizer_wrapper = clip.tokenizer - for attr_name in dir(tokenizer_wrapper): - if attr_name.startswith("_"): - continue - inner = getattr(tokenizer_wrapper, attr_name, None) - if inner is not None and hasattr(inner, "tokenizer"): - return inner.tokenizer - - raise RuntimeError( - f"Could not find raw tokenizer on CLIP object. " - f"Known attributes: {[a for a in dir(tokenizer_wrapper) if not a.startswith('_')]}" - ) - - -def map_token_indices(raw_tokenizer, global_prompt, local_prompts): - """Tokenize global + space-prefixed locals; return (full_prompt, per-local token ranges). - - Uses incremental tokenization to avoid SentencePiece context-dependency issues. - """ - prefixed_locals = [" " + lp for lp in local_prompts] - full_prompt = global_prompt + "".join(prefixed_locals) - has_eos = getattr(raw_tokenizer, "add_eos", False) - eos_adj = 1 if has_eos else 0 - - prev_len = len(raw_tokenizer(global_prompt)["input_ids"]) - eos_adj - token_ranges = [] - built = global_prompt - - for plp in prefixed_locals: - built += plp - cur_len = len(raw_tokenizer(built)["input_ids"]) - eos_adj - if cur_len <= prev_len: - raise ValueError(f"Local prompt produced no tokens: '{plp.strip()}'") - token_ranges.append((prev_len, cur_len)) - prev_len = cur_len - - return full_prompt, token_ranges - - -def distribute_segment_lengths(num_segments, latent_frames, specified_lengths=None): - """Validate or auto-distribute segment frame counts, capped to fit within latent_frames.""" - if specified_lengths: - if len(specified_lengths) != num_segments: - raise ValueError( - f"Number of segment_lengths ({len(specified_lengths)}) " - f"must match number of local prompts ({num_segments})" - ) - lengths = specified_lengths - else: - # ceil division — matches reference implementation - step = -(-latent_frames // num_segments) - lengths = [step] * num_segments - - effective = [] - cursor = 0 - for L in lengths: - end = min(cursor + L, latent_frames) - effective.append(max(end - cursor, 0)) - cursor = end - return effective +import logging +import math +import torch + +log = logging.getLogger(__name__) + + +def build_temporal_cost(q_token_idx, Lq, Lk, device, dtype, tokens_per_frame): + """Gaussian penalty matrix [Lq, Lk] for video cross-attention (integer frame indexing).""" + offset = torch.zeros(Lq, Lk, device=device, dtype=dtype) + query_frames = torch.arange(Lq, device=device, dtype=torch.long) // tokens_per_frame + + for seg in q_token_idx: + local = seg["local_token_idx"].to(device=device) + d = (query_frames.float()[:, None] - seg["midpoint"]).abs() + strength = seg.get("strength", 1.0) + cost = strength * (torch.relu(d - seg["window"]) ** 2) / (2 * seg["sigma"] ** 2) + offset[:, local] = cost.to(offset.dtype) + + return offset + + +def build_temporal_cost_scaled(q_token_idx, Lq, Lk, device, dtype, latent_frames): + """Penalty matrix for queries that don't map to integer frames (e.g. LTXAV audio tokens).""" + offset = torch.zeros(Lq, Lk, device=device, dtype=dtype) + query_frames = torch.arange(Lq, device=device, dtype=torch.float32) * latent_frames / Lq + + for seg in q_token_idx: + local = seg["local_token_idx"].to(device=device) + d = (query_frames[:, None] - seg["midpoint"]).abs() + sigma_a = seg.get("sigma_audio", seg["sigma"]) + window_a = seg.get("window_audio", seg["window"]) + strength_a = seg.get("strength_audio", 1.0) + cost = strength_a * (torch.relu(d - window_a) ** 2) / (2 * sigma_a ** 2) + offset[:, local] = cost.to(offset.dtype) + + return offset + + +def create_mask_fn(q_token_idx, fallback_tokens_per_frame, latent_frames): + """Closure: mask_fn(q, k, transformer_options) -> additive mask or None.""" + cache = {} + max_token_idx = max(int(seg["local_token_idx"].max().item()) for seg in q_token_idx) + 1 + + def mask_fn(q, k, transformer_options): + Lq, Lk = q.shape[1], k.shape[1] + + if Lq == Lk: + return None + + # Only apply on conditional pass — not unconditional (negative prompt) + cond_or_uncond = transformer_options.get("cond_or_uncond", []) + if 1 in cond_or_uncond and 0 not in cond_or_uncond: + return None + + grid_sizes = transformer_options.get("grid_sizes", None) + video_tpf = int(grid_sizes[1]) * int(grid_sizes[2]) if grid_sizes is not None else fallback_tokens_per_frame + video_lq = latent_frames * video_tpf + + # Skip cross-modal attention — text keys are padded to a fixed length ≥ max_token_idx and != video_lq + if Lk == video_lq or Lk < max_token_idx: + return None + + mode = "video" if Lq == video_lq else "scaled" + + key = (Lq, Lk, mode, q.device) + if key not in cache: + if mode == "video": + cost = build_temporal_cost(q_token_idx, Lq, Lk, q.device, q.dtype, video_tpf) + else: + cost = build_temporal_cost_scaled(q_token_idx, Lq, Lk, q.device, q.dtype, latent_frames) + log.info( + "[PromptRelay] Built penalty matrix (%s): Lq=%d, Lk=%d, nonzero=%d/%d", + mode, Lq, Lk, (cost > 0).sum().item(), cost.numel(), + ) + cache[key] = -cost + + return cache[key].to(q.dtype) + + return mask_fn + + +def build_segments(token_ranges, segment_lengths, epsilon=1e-3, relay_options=None): + """Per-segment metadata for the temporal penalty. + + relay_options (optional dict) overrides per-stream knobs: + video_strength, video_window_scale, + audio_epsilon, audio_strength, audio_window_scale + Audio knobs only affect architectures whose cross-attention takes the scaled + (non-integer-frame) path — currently LTX audio_attn2. + """ + # Paper uses constant sigma = 1/ln(1/epsilon) regardless of segment length + sigma = 1.0 / math.log(1.0 / epsilon) if 0 < epsilon < 1 else 0.1448 + + opts = relay_options or {} + v_strength = opts.get("video_strength", 1.0) + v_window_scale = opts.get("video_window_scale", 1.0) + a_epsilon = opts.get("audio_epsilon") + a_strength = opts.get("audio_strength", 1.0) + a_window_scale = opts.get("audio_window_scale", 1.0) + + if a_epsilon is not None and 0 < a_epsilon < 1: + sigma_audio = 1.0 / math.log(1.0 / a_epsilon) + else: + sigma_audio = sigma + + if relay_options: + log.info( + "[PromptRelay] Advanced options active — video: strength=%.3f window_scale=%.3f | " + "audio: epsilon=%s strength=%.3f window_scale=%.3f", + v_strength, v_window_scale, + f"{a_epsilon:.4f}" if a_epsilon is not None else "inherit", + a_strength, a_window_scale, + ) + + q_token_idx = [] + frame_cursor = 0 + + for (tok_start, tok_end), L in zip(token_ranges, segment_lengths): + if L <= 0: + frame_cursor += L + continue + midpoint = (2 * frame_cursor + L) // 2 + base_window = max(L // 2 - 2, 0) + q_token_idx.append({ + "local_token_idx": torch.arange(tok_start, tok_end), + "midpoint": midpoint, + "window": max(base_window * v_window_scale, 0.0), + "sigma": sigma, + "strength": v_strength, + "window_audio": max(base_window * a_window_scale, 0.0), + "sigma_audio": sigma_audio, + "strength_audio": a_strength, + }) + frame_cursor += L + + return q_token_idx + + +def get_raw_tokenizer(clip): + """Extract the raw SPiece/HF tokenizer from a ComfyUI CLIP object.""" + tokenizer_wrapper = clip.tokenizer + for attr_name in dir(tokenizer_wrapper): + if attr_name.startswith("_"): + continue + inner = getattr(tokenizer_wrapper, attr_name, None) + if inner is not None and hasattr(inner, "tokenizer"): + return inner.tokenizer + + raise RuntimeError( + f"Could not find raw tokenizer on CLIP object. " + f"Known attributes: {[a for a in dir(tokenizer_wrapper) if not a.startswith('_')]}" + ) + + +def map_token_indices(raw_tokenizer, global_prompt, local_prompts): + """Tokenize global + space-prefixed locals; return (full_prompt, per-local token ranges). + + Uses incremental tokenization to avoid SentencePiece context-dependency issues. + """ + prefixed_locals = [" " + lp for lp in local_prompts] + full_prompt = global_prompt + "".join(prefixed_locals) + has_eos = getattr(raw_tokenizer, "add_eos", False) + eos_adj = 1 if has_eos else 0 + + prev_len = len(raw_tokenizer(global_prompt)["input_ids"]) - eos_adj + token_ranges = [] + built = global_prompt + + for plp in prefixed_locals: + built += plp + cur_len = len(raw_tokenizer(built)["input_ids"]) - eos_adj + if cur_len <= prev_len: + raise ValueError(f"Local prompt produced no tokens: '{plp.strip()}'") + token_ranges.append((prev_len, cur_len)) + prev_len = cur_len + + return full_prompt, token_ranges + + +def distribute_segment_lengths(num_segments, latent_frames, specified_lengths=None): + """Validate or auto-distribute segment frame counts, capped to fit within latent_frames.""" + if specified_lengths: + if len(specified_lengths) != num_segments: + raise ValueError( + f"Number of segment_lengths ({len(specified_lengths)}) " + f"must match number of local prompts ({num_segments})" + ) + lengths = specified_lengths + else: + # ceil division — matches reference implementation + step = -(-latent_frames // num_segments) + lengths = [step] * num_segments + + effective = [] + cursor = 0 + for L in lengths: + end = min(cursor + L, latent_frames) + effective.append(max(end - cursor, 0)) + cursor = end + return effective diff --git a/pyproject.toml b/pyproject.toml index c0459a0..b8ce9f1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,36 +1,36 @@ -[project] -name = "WhatDreamsCost-ComfyUI" -description = "A variety of custom ComfyUI nodes and workflows for creatives." -version = "1.3.9" -license = {file = "LICENSE"} -# classifiers = [ -# # For OS-independent nodes (works on all operating systems) -# "Operating System :: OS Independent", -# -# # OR for OS-specific nodes, specify the supported systems: -# "Operating System :: Microsoft :: Windows", # Windows specific -# "Operating System :: POSIX :: Linux", # Linux specific -# "Operating System :: MacOS", # macOS specific -# -# # GPU Accelerator support. Pick the ones that are supported by your extension. -# "Environment :: GPU :: NVIDIA CUDA", # NVIDIA CUDA support -# "Environment :: GPU :: AMD ROCm", # AMD ROCm support -# "Environment :: GPU :: Intel Arc", # Intel Arc support -# "Environment :: NPU :: Huawei Ascend", # Huawei Ascend support -# "Environment :: GPU :: Apple Metal", # Apple Metal support -# ] - - -[project.urls] -Repository = "https://github.com/WhatDreamsCost/WhatDreamsCost-ComfyUI" -# Used by Comfy Registry https://registry.comfy.org -Documentation = "https://github.com/WhatDreamsCost/WhatDreamsCost-ComfyUI/wiki" -"Bug Tracker" = "https://github.com/WhatDreamsCost/WhatDreamsCost-ComfyUI/issues" - -[tool.comfy] -PublisherId = "whatdreamscost" -DisplayName = "WhatDreamsCost-ComfyUI" -Icon = "https://raw.githubusercontent.com/WhatDreamsCost/MediaSyncer/refs/heads/main/Icon.png" -includes = [] -# "requires-comfyui" = ">=1.0.0" # ComfyUI version compatibility - +[project] +name = "WhatDreamsCost-ComfyUI" +description = "A variety of custom ComfyUI nodes and workflows for creatives." +version = "1.3.9" +license = {file = "LICENSE"} +# classifiers = [ +# # For OS-independent nodes (works on all operating systems) +# "Operating System :: OS Independent", +# +# # OR for OS-specific nodes, specify the supported systems: +# "Operating System :: Microsoft :: Windows", # Windows specific +# "Operating System :: POSIX :: Linux", # Linux specific +# "Operating System :: MacOS", # macOS specific +# +# # GPU Accelerator support. Pick the ones that are supported by your extension. +# "Environment :: GPU :: NVIDIA CUDA", # NVIDIA CUDA support +# "Environment :: GPU :: AMD ROCm", # AMD ROCm support +# "Environment :: GPU :: Intel Arc", # Intel Arc support +# "Environment :: NPU :: Huawei Ascend", # Huawei Ascend support +# "Environment :: GPU :: Apple Metal", # Apple Metal support +# ] + + +[project.urls] +Repository = "https://github.com/WhatDreamsCost/WhatDreamsCost-ComfyUI" +# Used by Comfy Registry https://registry.comfy.org +Documentation = "https://github.com/WhatDreamsCost/WhatDreamsCost-ComfyUI/wiki" +"Bug Tracker" = "https://github.com/WhatDreamsCost/WhatDreamsCost-ComfyUI/issues" + +[tool.comfy] +PublisherId = "whatdreamscost" +DisplayName = "WhatDreamsCost-ComfyUI" +Icon = "https://raw.githubusercontent.com/WhatDreamsCost/MediaSyncer/refs/heads/main/Icon.png" +includes = [] +# "requires-comfyui" = ">=1.0.0" # ComfyUI version compatibility + diff --git a/speech_length_calculator.py b/speech_length_calculator.py index 81edcf6..8f41b78 100644 --- a/speech_length_calculator.py +++ b/speech_length_calculator.py @@ -1,51 +1,51 @@ -import re -import math - -class SpeechLengthCalculator: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "text": ("STRING", {"multiline": True, "default": 'Enter your script here. "Make sure to put spoken words inside quotes!"'}), - "fps": ("INT", {"default": 24, "min": 1, "max": 120, "step": 1}), - "additional_time": ("FLOAT", {"default": 0.0, "min": 0.0, "step": 0.1}), - }, - "optional": { - "text_input": ("STRING", {"forceInput": True}), - } - } - - # Added "STRING" to RETURN_TYPES - RETURN_TYPES = ("INT", "INT", "INT", "STRING") - # Added "text" to RETURN_NAMES - RETURN_NAMES = ("slow_frame_count", "average_frame_count", "fast_frame_count", "text") - FUNCTION = "calculate_speech" - CATEGORY = "WhatDreamsCost" - - def calculate_speech(self, text, fps, additional_time=0.0, text_input=None): - # Prioritize the connected text_input if provided, otherwise fallback to the text widget - active_text = text_input if (text_input is not None and isinstance(text_input, str) and text_input.strip() != "") else text - - # Regex to find words inside double quotes, single quotes, or smart quotes - matches = re.findall(r'"([^"]*)"|\'([^\']*)\'|“([^”]*)”|‘([^’]*)’', active_text) - - # Extract matches, handling all possible captured groups from the regex - quoted_text = " ".join([next((g for g in m if g), "") for m in matches]) - - # Split by whitespace to get words and count them - words = quoted_text.split() - word_count = len(words) - - def calc_frames(wpm): - if word_count == 0 and additional_time == 0: - return 0 - minutes = word_count / wpm - seconds = (minutes * 60) + additional_time - return math.ceil(seconds * fps) - - slow_frames = calc_frames(100) - avg_frames = calc_frames(130) - fast_frames = calc_frames(160) - - # Added active_text as the 4th returned value +import re +import math + +class SpeechLengthCalculator: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "text": ("STRING", {"multiline": True, "default": 'Enter your script here. "Make sure to put spoken words inside quotes!"'}), + "fps": ("INT", {"default": 24, "min": 1, "max": 120, "step": 1}), + "additional_time": ("FLOAT", {"default": 0.0, "min": 0.0, "step": 0.1}), + }, + "optional": { + "text_input": ("STRING", {"forceInput": True}), + } + } + + # Added "STRING" to RETURN_TYPES + RETURN_TYPES = ("INT", "INT", "INT", "STRING") + # Added "text" to RETURN_NAMES + RETURN_NAMES = ("slow_frame_count", "average_frame_count", "fast_frame_count", "text") + FUNCTION = "calculate_speech" + CATEGORY = "WhatDreamsCost" + + def calculate_speech(self, text, fps, additional_time=0.0, text_input=None): + # Prioritize the connected text_input if provided, otherwise fallback to the text widget + active_text = text_input if (text_input is not None and isinstance(text_input, str) and text_input.strip() != "") else text + + # Regex to find words inside double quotes, single quotes, or smart quotes + matches = re.findall(r'"([^"]*)"|\'([^\']*)\'|“([^”]*)”|‘([^’]*)’', active_text) + + # Extract matches, handling all possible captured groups from the regex + quoted_text = " ".join([next((g for g in m if g), "") for m in matches]) + + # Split by whitespace to get words and count them + words = quoted_text.split() + word_count = len(words) + + def calc_frames(wpm): + if word_count == 0 and additional_time == 0: + return 0 + minutes = word_count / wpm + seconds = (minutes * 60) + additional_time + return math.ceil(seconds * fps) + + slow_frames = calc_frames(100) + avg_frames = calc_frames(130) + fast_frames = calc_frames(160) + + # Added active_text as the 4th returned value return (slow_frames, avg_frames, fast_frames, active_text) \ No newline at end of file