0

Let's say I have a python script which reads all the images in a folder and resizes them. The script works all by his own, it takes in two arguments - the input folder and an output folder. To have a visual response of the progress I'm using a progressbar which is printed out to the console/terminal.

resize.py:

import argparse
import fnmatch
import os
import PIL
from PIL import Image
from progress.bar import Bar

parser = argparse.ArgumentParser(description='Photo resizer.')
parser.add_argument('input_folder', nargs='?', default='', help="Input folder")
parser.add_argument('export_folder', nargs='?', default='', help="Output folder")
args = parser.parse_args()

if args.input_folder:
    input_folder = args.input_folder
if args.export_folder:
    export_resized_folder = args.export_folder

NEW_SIZE = 2000

inputFiles = []
for root, dirnames, filenames in os.walk(input_folder):
    for filename in fnmatch.filter(filenames, '*.jpg'):
        inputFiles.append(os.path.join(root, filename))

bar = Bar("Processing photos", max=len(inputFiles), check_tty=False)
for photo in inputFiles:
    filename = os.path.basename(photo)
    im = Image.open(photo)
    im_width, im_height = im.size
    if im_width > im_height:
        new_width = NEW_SIZE
        new_height = int(NEW_SIZE * im_height / im_width)
    else:
        new_height = NEW_SIZE
        new_width = int(NEW_SIZE * im_width / im_height)
    new_size = (new_width, new_height)
    im_resized = im.resize(new_size, resample=PIL.Image.Resampling.LANCZOS)
    im_resized.save(os.path.join(export_resized_folder, filename), quality=70)
    bar.next()
bar.finish()

Now I have an another script (main_gui.py) which does some batch processing and one of the jobs is to resize the images. This script provides a simple GUI. When it comes to resizing the images, I use subprocess Popen to execute the script and pass in the input and output folders as args.

So in main_gui.py I start the subprocess:

script_path = "resize.py"
process = subprocess.Popen(["python", script_path, INPUT_FOLDER, OUTPUT_FOLDER], universal_newlines=True, stdout=subprocess.PIPE)

Now I'd like to see the progress in the GUI also. I don't know if I'm doing it correctly (It is a high probability that not, this is just the first thing that came to my mind)...
So in resize.py along with the progressbar I print out information about my progress and then read it in the main_gui.py and based on that information I update a tkinter progressbar.

In resize.py:

bar = Bar("Processing photos", max=len(inputFiles), check_tty=False)
print("**TOTAL** " + str(len(inputFiles)))
...
progressCounter = 1
for photo in inputFiles:
   ...
   bar.next()
   print("**PROGRESS** " + str(progressCounter)) 
   progressCounter += 1
   ...

I read these values in main_gui.py

process = subprocess.Popen(["python", script_path], universal_newlines=True, stdout=subprocess.PIPE)

while process.poll() is None:
    data = process.stdout.readline().strip()
    print(data)
    if "**TOTAL** " in data:
        total = int(data.replace("**TOTAL** ", ""))
        progressbarWidget['maximum'] = total
    if "**PROGRESS** " in data and self.GUI:
        progressCounter = int(data.replace("**PROGRESS** ", ""))
        progressbarWidget['value'] = progressCounter
        progressbarWidget.update_idletasks()

And at this point I'd like in my resize.py check if it is run by itself or by the subprocess, so I don't have the unnecessary print statements.

I tried pass in an env value as Charles suggested in the comments, but couldn't get it done

12
  • 2
    You're always a subprocess of something. If you want to look up what it is, that's what PPID is for. Commented Jun 14, 2022 at 19:22
  • 2
    That said, what's the real-world use case for making the distinction? For a lot of purposes it's more appropriate to check whether stdout is connected to a TTY, for example. Commented Jun 14, 2022 at 19:23
  • 2
    Another approach (there are plenty of options!) is to use an environment variable to signal to your subprocess that it should behave in the manner appropriate to a given usage mode. Commented Jun 14, 2022 at 19:25
  • Can you please post some code of one of these options? Commented Jun 14, 2022 at 19:55
  • 1
    I'd need to understand why you're doing this to know which option is best. That's why I asked. Commented Jun 14, 2022 at 20:31

1 Answer 1

2

Trying to detect your parent process is an unnecessary amount of magic for this use case. Making it explicit with an optional argument will let others writing their own GUIs (potentially in non-Python languages) get the machine-readable status output without needing to try to fool the detection.

parser = argparse.ArgumentParser(description='Photo resizer.')
parser.add_argument('--progress', choices=('none', 'human', 'machine-readable'), default='none',
                    help="Should a progress bar be written to stderr in a human-readable form, to stdout in a machine-readable form, or not at all?")
parser.add_argument('input_folder', nargs='?', default='', help="Input folder")
parser.add_argument('export_folder', nargs='?', default='', help="Output folder")
args = parser.parse_args()

...and then later...

if args.progress == 'machine-readable':
  pass # TODO: Write your progress messages for the programmatic consumer to stdout here
elif args.progress == 'human':
  pass # TODO: Write your progress bar for a human reader to stderr here

while on the GUI side, adding --progress=human to the argument list:

process = subprocess.Popen([sys.executable, script_path, '--progress=human'],
                           universal_newlines=True, stdout=subprocess.PIPE)
Sign up to request clarification or add additional context in comments.

1 Comment

This is way more simple and nicer than what I tried to do. Haven't thought this way. Thank you!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.