Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Set output default to subfolder of current date "%Y-%m-%d" to help better organize output. #3402

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

andrewtvuong
Copy link

@andrewtvuong andrewtvuong commented May 4, 2024

The default behavior currently saves output to ./output as ComfyUI_XXXXX_.png, XXXXX being file counter. With prefix defaulted to ComfyUI. A thousand runs over a thousand days would all be sequential in one folder making it difficult to organize runs.

With discussion and contribution from @asagi4 to use ISO date format "%Y-%m-%d" and @shawnington way of toggling for default behavior.

Proposing a new default behavior where when toggled on for the SaveImage node, will use default subfolder with current date in "%Y-%m-%d" format, retaining behavior of saving ./output/"%Y-%m-%d"/prefix_XXXXX_.png. When toggled off will retain original default behavior of ./output/prefix_XXXXX_.png.

Looking like this:
➜ output git:(master) ✗ ls
2024-05-03 2024-04-18 output_images_will_be_put_here
➜ output git:(master) ✗ cd 2024-05-03
➜ 2024-05-03 git:(master) ✗ ls
ComfyUI_00001
.png ComfyUI_00019_.png ComfyUI_00037_.png ComfyUI_00055_.png

Simple change but helps organization for default.

Thanks

@asagi4
Copy link
Contributor

asagi4 commented May 4, 2024

I think changing the node's default filename prefix to %date:yyyy-MM-dd%/ComfyUI would be a better way to do this (someone else needs to test if it works on Windows). I agree the current default isn't very good for organization.

If you do this, please don't put the month first. That's just a dumb way of formatting dates.

@shawnington
Copy link
Contributor

Seems like localization should be used given differences in date-time formats globally.

@asagi4
Copy link
Contributor

asagi4 commented May 5, 2024

@shawnington I think localization would make things way too complex for the problem that it would solve. YYYY-MM-DD is the ISO standard and it also has the nice property of sorting correctly in alphabetical order, so you can easily sort your folders by time.

@andrewtvuong
Copy link
Author

@shawnington I think localization would make things way too complex for the problem that it would solve. YYYY-MM-DD is the ISO standard and it also has the nice property of sorting correctly in alphabetical order, so you can easily sort your folders by time.

I agree with YYYY-MM-DD would work best for sort. I'll send a revision

@shawnington
Copy link
Contributor

shawnington commented May 5, 2024

@shawnington I think localization would make things way too complex for the problem that it would solve. YYYY-MM-DD is the ISO standard and it also has the nice property of sorting correctly in alphabetical order, so you can easily sort your folders by time.

if os.normpath is called on on the date before os.join to create nested directories in the manner that something like Light Room organizes photos, ISO YYYY-MM-DD is definitely the preferred way to create a nested date structure for organizing.

However it appears to be creating individual folders for each date with the format, so it may still be preferable to create them in the format that is familiar to the user based on locality, as the intended purpose seems to be to aid quickly find images by date.

Fair point about sorting though.

@andrewtvuong andrewtvuong changed the title Set output default to subfolder of current date "%m-%d-%Y" to help better organize output. Set output default to subfolder of current date "%Y-%m-%d" to help better organize output. May 5, 2024
@shawnington
Copy link
Contributor

shawnington commented May 5, 2024

The more I think about it, this implementation needs quite a bit of work.

It discards subfolders specified in the SaveImage node in the format <sub_folder>/<file_prefix> by replacing this line

subfolder = os.path.dirname(os.path.normpath(filename_prefix))

with one that forces placement into a date folder while discarding the user specified subfolder.

subfolder = current_date

but leaving

filename = os.path.basename(os.path.normpath(filename_prefix))

because os.path.basename is called, this discards, any specified subfolder, and only leaves the filename which will be placed in the date folder even if a subfolder has been specified.

as overwriting the subfolder with the date, user specified folder are not returned to the SaveImage node in nodes.py as the date is joined into the path with out the user specified subfolder via:

full_output_folder = os.path.join(output_dir, subfolder)

and return to the SaveImage node where the image is saved via:

img.save(os.path.join(full_output_folder, file), pnginfo=metadata, compress_level=self.compress_level)

I do not believe discarding user specified subfolders is preferred behavior.

It seem there should be a check for user specified subfolders and perhaps default to place them there instead of in the date based folder if they are specified.

Perhaps modification of the SaveImage node to have an enable disable option to place the user specified subfolder inside the date folder, or in the base directory is a needed part of a full solution.

@asagi4
Copy link
Contributor

asagi4 commented May 5, 2024

Perhaps modification of the SaveImage node to have an enable disable option to place the user specified subfolder inside the date folder, or in the base directory is a needed part of a full solution.

I think just changing the default value of file_prefix in the nodes to include the directory would solve the most common usecase. Anyone who doesn't want date directories can just delete the directory part or otherwise modify it.

I think having it in as a default is better than not having it. It makes the format string discoverable and defaults to a bit more organized output folder.

@shawnington
Copy link
Contributor

shawnington commented May 5, 2024

Perhaps modification of the SaveImage node to have an enable disable option to place the user specified subfolder inside the date folder, or in the base directory is a needed part of a full solution.

I think just changing the default value of file_prefix in the nodes to include the directory would solve the most common usecase. Anyone who doesn't want date directories can just delete the directory part or otherwise modify it.

I think having it in as a default is better than not having it. It makes the format string discoverable and defaults to a bit more organized output folder.

I think this is a palatable solution, but perhaps difficult to implement due to the way nodes are constructed.

the date would need obtained in the init of SaveImage in nodes.py, not in folder_paths.py

I am however not sure if format strings can be used in the INPUT_TYPES constructor to inject the date as part of the default value.

Perhaps something like this.

class SaveImage:
    def __init__(self):
        self.output_dir = folder_paths.get_output_directory()
        self.type = "output"
        self.prefix_append = ""
        self.compress_level = 4
        self.current_date = time.strftime("%Y-%m-%d")

    @classmethod
    def INPUT_TYPES(s):
        return {"required": 
                    {"images": ("IMAGE", ),
                     "filename_prefix": ("STRING", {"default": f"{s.current_date}"/ComfyUI"})},
                "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
                }

Again, I am not sure if a variable can be injected into the INPUT_TYPES constructor, and I am not sure the current_date would be accessible via s, since s is an instance of cls in a class method, so this might not be working code which is why I suggest an enable/disable date option for placing subfolder inside or outside of the date folder.

@andrewtvuong
Copy link
Author

andrewtvuong commented May 5, 2024

Certainly don't intend on overriding user specified subfolders and that was my mistake, I was trying to set the default folder to be current date yyyy-mm-dd if none specified, so default, rather than defaulting to just output/.

Maybe we can check for filename_prefix if None, then replace with current_date, else no-op to preserve user specified. I imagine most new users would be better served with some organization than none. Then they can move onto non default, user specified dir.

    current_date = time.strftime("%Y-%m-%d")
    filename_prefix = compute_vars(filename_prefix, image_width, image_height)

    if filename_prefix:
        subfolder = os.path.dirname(os.path.normpath(filename_prefix))
    else:
        subfolder = current_date
    filename = os.path.basename(os.path.normpath(filename_prefix))

    full_output_folder = os.path.join(output_dir, subfolder)
    

How's that sound @shawnington @asagi4

@shawnington
Copy link
Contributor

shawnington commented May 5, 2024

Certainly don't intend on overriding user specified subfolders and that was my mistake, I was trying to set the default folder to be current date yyyy-mm-dd if none specified, so default, rather than defaulting to just output/.

Maybe we can check for filename_prefix if None, then replace with current_date, else no-op to preserve user specified. I imagine most new users would be better served with some organization than none. Then they can move onto non default, user specified dir.

How's that sound @shawnington @asagi4

That seems okay to me.

Id personally also want an enable disable toggle added to the SaveImage node by a simple name such as "organize by date" which can be enable by default, to allow users that wish to specify a custom folder, to be able to have it placed inside the date folder.

It just seems if this functionality is added, it should not exclude users who wish to specify a subfolder for the image to go into.

That would seem to require changing the folder_paths.py to either return the constituent path elements or state of the organize by date option, so that joining can be done properly.

My preferred default behavior for your suggested change:

  • organize by date option: default = enabled
  • no user path specified -> into date folder
  • user path specified but date folder enabled -> place user specified folder in date folder
  • user path specified but place in date folder disabled -> place in default directory in specified folder

@andrewtvuong
Copy link
Author

That seems okay to me.

Id personally also want an enable disable toggle added to the SaveImage node by a simple name such as "organize by date" which can be enable by default, to allow users that wish to specify a custom folder, to be able to have it placed inside the date folder.

I added the minimal change for defaulting to current date instead of none.

I do like the toggle idea, brings more visibility, but I think that can built upon minimal change. We can follow up with it.

Thanks for yalls feedback.

@shawnington
Copy link
Contributor

Certainly don't intend on overriding user specified subfolders and that was my mistake, I was trying to set the default folder to be current date yyyy-mm-dd if none specified, so default, rather than defaulting to just output/.

Maybe we can check for filename_prefix if None, then replace with current_date, else no-op to preserve user specified. I imagine most new users would be better served with some organization than none. Then they can move onto non default, user specified dir.

    current_date = time.strftime("%Y-%m-%d")
    filename_prefix = compute_vars(filename_prefix, image_width, image_height)

    if filename_prefix:
        subfolder = os.path.dirname(os.path.normpath(filename_prefix))
    else:
        subfolder = current_date
    filename = os.path.basename(os.path.normpath(filename_prefix))

    full_output_folder = os.path.join(output_dir, subfolder)
    

How's that sound @shawnington @asagi4

This if statement would always return true, as filename_prefix has a default value, you would need to do the os.path.dirname first as the splits out the user defined subfolder, and check based on that

@asagi4
Copy link
Contributor

asagi4 commented May 5, 2024

The filename prefix will never be None because the nodes all have defaults for it.
What I'm talking about it just doing this:

index 50deb29..bf7f44b 100644
--- a/nodes.py
+++ b/nodes.py
@@ -1387,7 +1387,7 @@ class SaveImage:
     def INPUT_TYPES(s):
         return {"required": 
                     {"images": ("IMAGE", ),
-                     "filename_prefix": ("STRING", {"default": "ComfyUI"})},
+                     "filename_prefix": ("STRING", {"default": "%date:yyyy-MM-dd%/ComfyUI"})},
                 "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
                 }

and the same for other nodes that save files.

@shawnington
Copy link
Contributor

shawnington commented May 5, 2024

The filename prefix will never be None because the nodes all have defaults for it. What I'm talking about it just doing this:

index 50deb29..bf7f44b 100644
--- a/nodes.py
+++ b/nodes.py
@@ -1387,7 +1387,7 @@ class SaveImage:
     def INPUT_TYPES(s):
         return {"required": 
                     {"images": ("IMAGE", ),
-                     "filename_prefix": ("STRING", {"default": "ComfyUI"})},
+                     "filename_prefix": ("STRING", {"default": "%date:yyyy-MM-dd%/ComfyUI"})},
                 "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
                 }

and the same for other nodes that save files.

That is a simple way to do it if it works, Im still not sure on if injecting the date into the INPUT_TYPES breaks anything, did you test this to see if it works?

It can also be simply done with an intuitive toggle like this.

@classmethod
    def INPUT_TYPES(s):
        # format the string to include variable self.type
        return {"required": 
                    {"images": ("IMAGE", ),
                     "filename_prefix": ("STRING", {"default": "ComfyUI"}),
                     "organize_by_date": ("BOOLEAN", {"default": True, "label_on": "enable", "label_off": "disable"}),},
                "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
                }

    RETURN_TYPES = ()
    FUNCTION = "save_images"

    OUTPUT_NODE = True

    CATEGORY = "image"

    def save_images(self, images, filename_prefix="ComfyUI", organize_by_date=True, prompt=None, extra_pnginfo=None):
        if organize_by_date:
            filname_prefix = time.strftime("%Y-%m-%d") + "/" + filename_prefix

I find this method preferable because it doesn't crowd the text box, or leave an opportunity to make errors when deleting the date or inserting a subfolder.

Just tested locally, no changes are required to folder_paths.py

It just simply prepend the date folder to the front of the file_prefix if the option is enabled.

I think all 3 solutions we have come up with are reasonable ways to tackle the problem.

@andrewtvuong
Copy link
Author

andrewtvuong commented May 5, 2024

Pesky Windows '\' :( does this impact anything if the change is done through nodes.py?

Feel free to push your updates yall. I'm good either way, much better my initial commit lol.

        if organize_by_date:
            filname_prefix = time.strftime("%Y-%m-%d") + "/" + filename_prefix

Typo in var name

@shawnington
Copy link
Contributor

shawnington commented May 5, 2024

Pesky Windows \ :( does this impact anything if the change is done through nodes.py?

The slash direction shouldn't matter, as the os.path.normpath(filename_prefix) in the folder_path.py should change the slashes to be compatible with what ever OS is it is running on.

Someone on a different platform would need to cross check and verify that this is indeed the case.

@shawnington
Copy link
Contributor

Pesky Windows '' :( does this impact anything if the change is done through nodes.py?

Feel free to push your updates yall. I'm good either way, much better my initial commit lol.

        if organize_by_date:
            filname_prefix = time.strftime("%Y-%m-%d") + "/" + filename_prefix

Typo in var name

Yep, I typed the code into the comment before I retyped it into the local version, but feel free to add it to your pull request, no need to have multiple requests open for the same thing.

Copy link
Contributor

@shawnington shawnington left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

248 if statements always returns true.

Consider doing subfolder = os.path.dirname(os.path.normpth(filename_prefix) and checking if subfolder != none or length != 0 not sure if the methods returns none or an empty string without checking the documents for os.

@andrewtvuong
Copy link
Author

andrewtvuong commented May 6, 2024

@shawnington Latest commit used your changes to nodes.py #3402 (comment) works great!

Just tested. I did not know how to do the toggling before, but it was a clean and good change. Thanks!

@shawnington
Copy link
Contributor

@shawnington Latest commit used your changes to nodes.py #3402 (comment) works great!

Just tested. I did not know how to do the toggling before, but it was a clean and good change. Thanks!

If it's not merged into the main branch, you should offer it as a custom node, seems like a worthwhile effort. It a good idea, and Id use it.

You can also expand the options to separate by year month or day, and changing the format from YYYY-MM-DD to YYYY/MM/DD etc, for the different options. The folder_path.py handles the separations as long as a slash is included in the file_prefix path, it will be a new level of nesting.

Code looks good to me.

Copy link
Contributor

@shawnington shawnington left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested the latest commit, works as expected. Improves on the current node without changing legacy functionality that might be relied on by other nodes/workflows.

@andrewtvuong
Copy link
Author

Hi @asagi4 @shawnington @comfyanonymous just updated the original body with the final changes proposed. Thanks for the discussions, contributions, and review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants