1

I am trying to use gradio for a web-based gif editor. The user uploads a gif and then performs various manipulations on it.

My problem is that once the gif is uploaded as input, the resulting object is just a static image of the first GIF frame. To be clear, the web gui will display the animated gif, but the underlying code object is not a gif. When I open a GIF in PIL, it is loaded as a multiframe image that you can perform .seek on to advance frames. In Gradio, the image uploads as a simple Image object, whereas pure Pillow will upload a gif as a gif plugin object.

I can't find any resources that address this, as it seems most are focused on static images and AI in Gradio right now.

I have explained the issue. The desired outcome I am looking for is a way to get the actual uploaded gif as a multi-frame image object in my python script.

Here comes my code. Problem arises in 'z_gif_handler.py' when I attempt to get the n_frame property of the PIL image. Traceback included at bottom:

main.py

from imageEditUI.main_webui import *


# Press the green button in the gutter to run the script.
if __name__ == '__main__':
    app()

main_webui.py

import gradio as gr
import numpy as np
from imageEditUI.z_image_editing import *
from imageEditUI.z_gif_handler import *


def app():
    def sepia_image(input_img):
        # choose: sepia, bw, or negative (so far)
        return color_filters(input_img, 'sepia')

    def flip_image(x):
        return np.fliplr(x)

    def rembg_image(input_img, rgb_high, rgb_low):
        rgb_high = rgb_high.lstrip('#')
        rgb_low = rgb_low.lstrip('#')
        high = tuple(int(rgb_high[i:i + 2], 16) for i in (0, 2, 4))
        low = tuple(int(rgb_low[i:i + 2], 16) for i in (0, 2, 4))
        return remColor(input_img, high, low)

    def rembg_gif(input_img, rgb_high, rgb_low):
        rgb_high = rgb_high.lstrip('#')
        rgb_low = rgb_low.lstrip('#')
        high = tuple(int(rgb_high[i:i + 2], 16) for i in (0, 2, 4))
        low = tuple(int(rgb_low[i:i + 2], 16) for i in (0, 2, 4))
        return master(input_img, high, low)

    def floodFill_ui(image_input_remBGCol):
        return floodFill(image_input_remBGCol)

    def gif_rembgcolor(image_input_remBGCol, high, low):
        print(type(image_input_remBGCol))
        return gif_main(image_input_remBGCol, high, low)

    with gr.Blocks() as demo:
        gr.Markdown("Select your edits below:")
        # set output image height var:
        output_height = 400
        with gr.Tab("GIF"):
            with gr.Row():
                gif_input = gr.Image(type='pil', interactive=True)
                gif_output = gr.Image(height=output_height)
            with gr.Row():
                high_gif = gr.ColorPicker(label="Choose high color")
                low_gif = gr.ColorPicker(label="Choose low color")
            gif_button = gr.Button("Remove Background Color")
            gif_button2 = gr.Button("Remove Background Color v2")
        with gr.Tab("Color"):
            with gr.Row():
                image_input_remBGCol = gr.Image()
                image_output_remBGCol = gr.Image(height=output_height)
            with gr.Row():
                rgb_high = gr.ColorPicker(label="Choose high color")
                rgb_low = gr.ColorPicker(label="Choose low color")
            remBGCol_button = gr.Button("Remove Background Color")
            selectArea_button = gr.Button("Select area of image")
        with gr.Tab("Flip Image"):
            # filter: FLIP
            with gr.Row():
                image_input = gr.Image()
                image_output = gr.Image(height=output_height)
            image_button = gr.Button("Flip")

        remBGCol_button.click(rembg_image,
                              inputs=[image_input_remBGCol, rgb_high, rgb_low],
                              outputs=image_output_remBGCol)
        selectArea_button.click(floodFill_ui, inputs=image_input_remBGCol, outputs=image_output_remBGCol)
        image_button.click(flip_image, inputs=image_input, outputs=image_output)
        gif_button.click(rembg_gif, inputs=[gif_input, high_gif, low_gif], outputs=gif_output)
        gif_button2.click(gif_rembgcolor, inputs=[gif_input, high_gif, low_gif], outputs=gif_output)
    demo.launch()

z_gif_handler.py

from PIL import Image, ImageFile
########################################################################################################################
# Manages the splitting of gifs into images, delivering individual images to the image editor
# module, and recompiling gifs before sending back to webui
########################################################################################################################


def split_gif(im):
    # loop through frames saving each to bytesio
    myImages = []
    i = 0
    for i in range(i, im.n_frames):
        im.seek(i)
        myImages.append(im)

    return myImages


def deliver_to_editor(gif_frames, tool_selection):
    if tool_selection == 'chroma':
        return


def gif_main(input_image, choice, high=(165, 253, 171), low=(1, 77, 0)):
    gif_frames = split_gif(input_image)
    gif_frames[0].save('assets/images/final.gif', save_all=True, append_images=gif_frames[1:], duration=100, loop=0)

Traceback error

C:\Users\Admin\PycharmProjects\ImageEditUI\venv\Scripts\python.exe C:\Users\Admin\PycharmProjects\ImageEditUI\imageEditUI\main.py 
Running on local URL:  http://127.0.0.1:7860

To create a public link, set `share=True` in `launch()`.
<class 'PIL.Image.Image'>
Traceback (most recent call last):
  File "C:\Users\Admin\PycharmProjects\ImageEditUI\venv\Lib\site-packages\gradio\routes.py", line 437, in run_predict
    output = await app.get_blocks().process_api(
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Admin\PycharmProjects\ImageEditUI\venv\Lib\site-packages\gradio\blocks.py", line 1352, in process_api
    result = await self.call_function(
             ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Admin\PycharmProjects\ImageEditUI\venv\Lib\site-packages\gradio\blocks.py", line 1077, in call_function
    prediction = await anyio.to_thread.run_sync(
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Admin\PycharmProjects\ImageEditUI\venv\Lib\site-packages\anyio\to_thread.py", line 33, in run_sync
    return await get_asynclib().run_sync_in_worker_thread(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Admin\PycharmProjects\ImageEditUI\venv\Lib\site-packages\anyio\_backends\_asyncio.py", line 877, in run_sync_in_worker_thread
    return await future
           ^^^^^^^^^^^^
  File "C:\Users\Admin\PycharmProjects\ImageEditUI\venv\Lib\site-packages\anyio\_backends\_asyncio.py", line 807, in run
    result = context.run(func, *args)
             ^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Admin\PycharmProjects\ImageEditUI\imageEditUI\main_webui.py", line 38, in gif_rembgcolor
    return gif_main(image_input_remBGCol, high, low)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Admin\PycharmProjects\ImageEditUI\imageEditUI\z_gif_handler.py", line 40, in gif_main
    gif_frames = split_gif(input_image)
                 ^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Admin\PycharmProjects\ImageEditUI\imageEditUI\z_gif_handler.py", line 21, in split_gif
    for i in range(i, im.n_frames):
                      ^^^^^^^^^^^
  File "C:\Users\Admin\PycharmProjects\ImageEditUI\venv\Lib\site-packages\PIL\Image.py", line 528, in __getattr__
    raise AttributeError(name)
AttributeError: n_frames
  • Are you sure you're getting a GIF at all? A brief excursion through the code seems to indicate that it converts downloads to PNG internally. It may be that gradio is simply not the right framework for your task. – Tim Roberts Jun 23 '23 at 03:11
  • I think I am not getting a gif. I think it’s as you say where it is assigned as a single image. A type() function shows it is pil.image.image, where in a normal pillow module I would expect something like pil.gif_plugin or something similar. I really want gradio to work because of its simplicity and I am a little surprised gif support is not apparently available. – Logan Price Jun 23 '23 at 03:25
  • Simple frameworks are great for simple tasks. Your task is not simple at all. You need more control. I suspect you're in for some learning curve with Qt or wxPython. – Tim Roberts Jun 23 '23 at 03:39
  • @TimRoberts thanks for your candor. I'm looking at the gr.File() class as a way to upload a gif. If that fails, I will probably start looking at a different UI package. – Logan Price Jun 23 '23 at 13:23

0 Answers0