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

Support reading from stdin, writing to stdout in HEIC2PNG #5

Open
wants to merge 9 commits into
base: main
Choose a base branch
from

Conversation

smontanaro
Copy link

Here's a first cut at my proposed changes. I don't do Windows (Mac & Linux–so far only tested on Mac), so you'll have to take care of seeing if things still work there. I will check to make sure it works on Linux.

I wouldn't assume I have this all correct. We should have a bit of back-and-forth in the review/comment section of the PR to make sure I haven't unwittingly broken something. At any rate, the current tests do pass.

@NatLee NatLee self-requested a review February 6, 2025 18:49
@NatLee NatLee added the enhancement New feature or request label Feb 6, 2025
eprint('Please report this issue with the full traceback.')
eprint('-> https://github.com/NatLee/HEIC2PNG/issues')

def eprint(*args, file=sys.stderr, **kwds):
Copy link
Owner

Choose a reason for hiding this comment

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

We could consider moving this function to execute before the function of cli.

Copy link
Author

Choose a reason for hiding this comment

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

Not sure what you're suggesting, move the definition of eprint elsewhere, or the various checks for problems.

Copy link
Owner

@NatLee NatLee Feb 10, 2025

Choose a reason for hiding this comment

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

This is not a problem; however, I recommend positioning the eprint function before the CLI function.


parser = argparse.ArgumentParser(description="Convert HEIC images to PNG.")
parser.add_argument("-i", "--input_path", required=True, help="Path to the input HEIC image.")
Copy link
Owner

Choose a reason for hiding this comment

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

Could you clarify the rationale behind removing the required flag?

Copy link
Author

Choose a reason for hiding this comment

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

If the input comes from sys.stdin, there's not going to be an explicit input file, so requiring one doesn't make a ton of sense.

I'm not going to hold it up as best practice, but I have another package on GitHub, csvprogs which goes the other way. I had always supported using stdin and stdout for IO, only supporting filled in a hit-or-miss fashion. It has grown organically though, so different tools were organized differently. In a fit of virtual housecleaning, I tackled this inconsistency. I was able to come up with a relatively clean way to support both styles of consuming input and generating output (see the openio function and its usage).

Copy link
Owner

Choose a reason for hiding this comment

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

Understood. I believe it is necessary to add a check within the CLI function to verify whether input_path is None. Without this validation, the program might fail to locate the file and become unresponsive.

open(outputfile, "rb") as outp):
# I don't really know how to compare images. If we assume that PIL
# does its thing properly, and PNG files will always be larger than
# their corresponding HEIC files, hopefully that's good enough.
Copy link
Owner

Choose a reason for hiding this comment

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

I think this should be sufficient for comparing these two different images.

# TODO: Not sure this is correct, there's probably a better mapping
# between image file extensions and file formats. For example, see the
# output of `python -m PIL`.
self.image.save(output_path, format=extension.replace(".", "").upper())
Copy link
Owner

Choose a reason for hiding this comment

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

We could refer to this link to retrieve a list of supported formats with PIL: https://stackoverflow.com/a/71114152

Copy link
Owner

Choose a reason for hiding this comment

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

I tested it in Python 3.12, but I'm unsure if this function works across all versions of PIL.

>>> from PIL import Image
>>> exts = Image.registered_extensions()
>>> supported_extensions = {ex for ex, f in exts.items() if f in Image.OPEN}
>>> supported_extensions
{'.wmf', '.icns', '.gif', '.grib', '.pnm', '.ftu', '.jpe', '.rgb', '.dib', '.tif', '.jfif', '.emf', '.jpc', '.pxr', '.ppm', '.dds', '.fit', '.icb', '.cur', '.pcx', '.bmp', '.h5', '.pfm', '.qoi', '.dcx', '.apng', '.iim', '.pbm', '.png', '.jpx', '.bw', '.psd', '.pcd', '.sgi', '.vda', '.im', '.mpeg', '.bufr', '.mpg', '.rgba', '.xpm', '.fli', '.fits', '.ps', '.flc', '.gbr', '.msp', '.j2c', '.jpg', '.tga', '.ico', '.xbm', '.pgm', '.j2k', '.jpf', '.jpeg', '.vst', '.hdf', '.ras', '.tiff', '.ftc', '.jp2', '.eps', '.webp', '.blp'}

Copy link
Author

Choose a reason for hiding this comment

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

Would be nice if there was a mapping from file extensions to formats. I think ultimately, that's what really matters.

Copy link
Owner

Choose a reason for hiding this comment

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

I believe we can select a few from within and create a map like this:

class HEIC2PNG:
  FORMAT_MAPPING: Dict[str, str] = {
      '.png': 'PNG',
      '.jpg': 'JPEG',
      '.jpeg': 'JPEG',
      '.webp': 'WEBP',
      '.bmp': 'BMP',
      '.gif': 'GIF',
      '.tiff': 'TIFF',
  }
  def __init__(...):
    ...

We can make adjustments at this point.

self.image.save(output_path, format=extension.replace(".", "").upper())

This line can be adjusted using the following approach: the variable image_format is assigned based on the file extension mapping (self.FORMAT_MAPPING[ext]), and the image is subsequently saved to the specified output_path in the determined format.

The snippet appears as follows:

...
ext = extension.lower()
if ext not in self.FORMAT_MAPPING:
    supported_formats = ', '.join(self.FORMAT_MAPPING.keys())
    raise ValueError(f"Unsupported format: {extension}. Supported formats are: {supported_formats}")

image_format = self.FORMAT_MAPPING[ext]
self.image.save(output_path, format=image_format)
...

Copy link
Owner

Choose a reason for hiding this comment

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

This is just a method I came up with on short notice. It may not be the best approach, but feel free to use it as a reference.


# TODO: Skip this step if the output format isn't PNG or pngquant can't
# be found?
Copy link
Owner

Choose a reason for hiding this comment

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

Does the other format specify parameters like quality or compression ratio? If not, we can skip this check.

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

Successfully merging this pull request may close these issues.

Should be able to read from stdin, write to stdout, messages must not go to stdout
2 participants