Ren'Py [resolved]Store .webp images while ignoring animated .webp images?

Tribe

Member
Game Developer
May 5, 2021
226
492
I need a way to filter out animated .webp images from .webp images...
Of course, animated webp files are not compatible with renpy, so I figured the best way to do that is to return whether or not the file itself is compatible.
My assumption was that I could use renpy.loadable() but, as it turns out, animated webp files are very loadable... up until the point that the game crashes.

I need to make this work:
Python:
the_list = []
for f in renpy.list_files():
    n, e = os.path.splitext ( os.path.basename( f ) )
    if not e.lower() in [ ".jpg", ".jpeg", ".png", ".webp" ]: continue
    if willNotCrashYourGame( f ) # need help here
        the_list.append( f )
I need to replace willNotCrashYourGame( f ) with something that will not crash my game.
Thanks again.
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,971
16,228
I need to replace willNotCrashYourGame( f ) with something that will not crash my game.
Strictly speaking, you can't, because Ren'Py will catch the exception way before it reach your code.

Therefore all you can do is to works with the file header, to discriminate between static images and animations. Since it's for a single file format, no need to use a module for this. Normally, this should do it:
Forget about this, no, really, there where nothing here... The code is and have always been in this post that appear later in the thread due to some hole in the space-time continuum.

According to the , the ILEXAR flags should be in this order, but I would have preferred to validate this before.


And then your code become:
Python:
    if not webpIsAnimated( f )
        the_list.append( f )
Since you discriminate due to the file extension before the test, you can safely assume that if a file is not in WEBP format, it's still an image and should be supported by Ren'Py.


Edit: Replaced the code by a link to the definitive and fully working version.
 
Last edited:

Tribe

Member
Game Developer
May 5, 2021
226
492
Just use webm for animated stuff?
I hear ya, but this for players who wish to add images/animations to my game.
It promises simple drop&go functionality but I didn't account for webp images slipping through the cracks.
I missed it cause it'll be a cold day in hell before I use webp. :LOL:
Thanks again for your help the other day in my other post!

Strictly speaking, you can't, because Ren'Py will catch the exception way before it reach your code.

Therefore all you can do is to works with the file header, to discriminate between static images and animations. Since it's for a single file format, no need to use a module for this. Normally, this should do it:
Python:
init python:

    # FOR Python 3.x/Ren'Py 8.x
    # Return None if unknown WEBP format or not WEBP format at all.
    # Return True if Animated WEBP.
    # return False if static WEBP.
    def webpIsAnimated( file ):

        # Assume that /file/ is a relative path, adapt depending on the entry value.
        FH = open( config.gamedir + "/images/" + file, "br" )
        fileHeader = FH.read( 17 )
        FH.close()

        if   fileHeader[:4]    != b"RIFF": return None
        elif fileHeader[8:12]  != b"WEBP": return None
        elif fileHeader[12:16] == b"VP8 ": return False
        elif fileHeader[12:16] == b"VP8L": return False
        elif fileHeader[12:16] != b"VP8X": return None
        return int( fileHeader[17] ) & 64 == 64
/!\ This works fine with static WEBP, both lossy and lossless, but I don't have animated WEBP file to validate the last step. /!\

According to the , the ILEXAR flags should be in this order, but I would have preferred to validate this before.


And then your code become:
Python:
    if not webpIsAnimated( f )
        the_list.append( f )
Since you discriminate due to the file extension before the test, you can safely assume that if a file is not in WEBP format, it's still an image and should be supported by Ren'Py.
As always, thank you for this, Anne. I've done my best to stay within my current capabilities for this project, but the last thing I wanted to do was limit the players' flexibility any further by telling them that webp is no longer accepted in order to reduce frustration.
 

Tribe

Member
Game Developer
May 5, 2021
226
492
Strictly speaking, you can't, because Ren'Py will catch the exception way before it reach your code.

Therefore all you can do is to works with the file header, to discriminate between static images and animations. Since it's for a single file format, no need to use a module for this. Normally, this should do it:
Python:
init python:

    # FOR Python 3.x/Ren'Py 8.x
    # Return None if unknown WEBP format or not WEBP format at all.
    # Return True if Animated WEBP.
    # return False if static WEBP.
    def webpIsAnimated( file ):

        # Assume that /file/ is a relative path, adapt depending on the entry value.
        FH = open( config.gamedir + "/images/" + file, "br" )
        fileHeader = FH.read( 17 )
        FH.close()

        if   fileHeader[:4]    != b"RIFF": return None
        elif fileHeader[8:12]  != b"WEBP": return None
        elif fileHeader[12:16] == b"VP8 ": return False
        elif fileHeader[12:16] == b"VP8L": return False
        elif fileHeader[12:16] != b"VP8X": return None
        return int( fileHeader[17] ) & 64 == 64
/!\ This works fine with static WEBP, both lossy and lossless, but I don't have animated WEBP file to validate the last step. /!\

According to the , the ILEXAR flags should be in this order, but I would have preferred to validate this before.


And then your code become:
Python:
    if not webpIsAnimated( f )
        the_list.append( f )
Since you discriminate due to the file extension before the test, you can safely assume that if a file is not in WEBP format, it's still an image and should be supported by Ren'Py.
I'm getting: IndexError: index out of range for return int( fileHeader[17] ) & 64 == 64
I've tried making sense of that link and fooling around with it but I didn't get very far doing that.

Here is a webp and animated webp if needed. They're SFW, which made them really hard to come by.

View attachment webp_animated.webp View attachment webp_not_animated.webp
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,971
16,228
I'm getting: IndexError: index out of range for return int( fileHeader[17] ) & 64 == 64
Hmm. read 17, address index 17... Oops...

It should read: return int( fileHeader[16] ) & 64 == 64
After test, it's in fact return int( fileHeader[16] ) & 2 == 2

I don't know why I thought there were a typo and corrected the index when copying the code here :cautious:


Edit:
Ok, now that I have a file to test it, I've in fact read the flags in the wrong order.
 
Last edited:
  • Yay, update!
Reactions: Tribe

Tribe

Member
Game Developer
May 5, 2021
226
492
Hmm. read 17, address index 17... Oops...

It should read: return int( fileHeader[16] ) & 64 == 64

I don't know why I thought there were a typo and corrected the index when copying the code here :cautious:
Nice! I thought it was way more complex than that :cry:

Unfortunately, it's still letting the animated .webps through
Exception: Could not load image "Image.webp": error('Failed to decode WEBP')

here is the modified code:

Python:
the_list = []
for f in renpy.list_files():
    if not f.startswith( "Your Images/" ): continue
    n, e = os.path.splitext(os.path.basename( f ) )
    if not e.lower() in [ ".jpg", ".jpeg", ".png", ".webp", ".webm", ".ogv", ".avi", ".mpg", ".mpeg" ]: continue
    if e.lower() == ".webp":
        if webpIsAnimated( f ): continue
    the_list.append( f )

init_python:
    def webpIsAnimated( file ):
        FH = open( config.gamedir + "/" + file, "br" )
        fileHeader = FH.read( 17 )
        FH.close()

        if   fileHeader[:4]    != b"RIFF": return None
        elif fileHeader[8:12]  != b"WEBP": return None
        elif fileHeader[12:16] == b"VP8 ": return False
        elif fileHeader[12:16] == b"VP8L": return False
        elif fileHeader[12:16] != b"VP8X": return None
        return int( fileHeader[16] ) & 64 == 64
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,971
16,228
Unfortunately, it's still letting the animated .webps through
Because you were too fast. I updated the post after doing a test run with the animated WEBP you provided. It happened that I read the flag order backward.
 
  • Like
Reactions: Tribe

Tribe

Member
Game Developer
May 5, 2021
226
492
Because you were too fast. I updated the post after doing a test run with the animated WEBP you provided. It happened that I read the flag order backward.
:LOL: I saw the change in the original post just as you posted this.

Ok so... Now that I've applied the change to return int( fileHeader[16] ) & 2 == 2, the static image is being filtered out too, or at least the static image I attached earlier. (maybe it's related to the image itself?)
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,971
16,228
Now that I've applied the change to return int( fileHeader[16] ) & 2 == 2, the static image is being filtered out too, or at least the static image I attached earlier.
Hmm, it shouldn't be.

It's a bit late, but I'll take a look at the headers tomorrow and try to have more materials for more in depth tests.
 
  • Star-struck
Reactions: Tribe

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,971
16,228
Ok so...

[my life]
I was at work, looking alternatively to my code and to the format specification. Lost in my perplex thoughts, when a young (and necessarily arrogant prick who don't respect his elders) co-worker came. He took a quick look at my code and said (with a mandatory assassin tone in order to prove the superiority of his brain over my starting dementia) "it should be 20, not 16".
Face to my reaction (that without a single doubt he misread as a totally lack of understanding face to the most obvious fact that a human ever had to face in human history) he pointed to the line with the condition, adding, "Here, it should be 20, because chunkheader include the FourCC and the chunksize" (note the emphasis in the "and", that he pronounced slowly, with the condescension we have for a one year old that, clearly, still haven't understood that dirt isn't something one should eat). Then he left (with the clear conviction that my place should be in a hospice for the elderly and not as his team manager) not even bothering to listen to my "aaaaahhhhh".
[/my life]

So, yeah, I may be a bit to old to understand modern format specification... The revised code is:
Python:
    # FOR Python 3.x/Ren'Py 8.x
    # Return True if Animated WEBP
    # return False if static WEBP
    # Return None if not WEBP or invalid/unknown WEBP
    # https://developers.google.com/speed/webp/docs/riff_container?hl=en
    def webpIsAnimated( file ):
        FH = open( config.gamedir + "/images/" + file, "br" )
        fileHeader = FH.read( 21 )
        FH.close()

        if   fileHeader[:4]    != b"RIFF": return None
        # file size minus 8 - uint32
        elif fileHeader[8:12]  != b"WEBP": return None
        elif fileHeader[12:16] == b"VP8 ": return False
        elif fileHeader[12:16] == b"VP8L": return False
        elif fileHeader[12:16] != b"VP8X": return None
        # chunk size - uint32
        return int( fileHeader[20] ) & 2 == 2
And I tested it with a mix of 200 WEBP images, animated and not, lossy and lossless, new and old format. All results are according to the effective format.


So, now that this is done, I'll ask for a raise, I've some beers to pay to someone (who surely lost the few respect it still had for me)...
 
  • Haha
Reactions: Meushi

Tribe

Member
Game Developer
May 5, 2021
226
492
And it works beautifully!
You are just a wonderful human being, that's all that's to it!
I'll buy you a yacht when my games are recognized for their potential and become a success!
Thank you for seeing this through. This means I don't have to explain why I've reduced the file types accepted by my game when, in reality, I can't trust the community to ensure that their webp files aren't animated and send me crash reports. This meant so much to me, anne O'nymous, thank you!
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,971
16,228
You are just a wonderful human being, that's all that's to it!
I'll show this to my young co-worker, in hope that it will change what he now think about me ;)

More seriously, even if I struggled a bit, and wasn't at my best to the eyes of someone who's in fact way nicer that I presented him, it was a pleasure. It force me to works on things I don't usually works with, what help me stay sharp and deserve my paycheck. And, in top of this, it help someone, so it's all benefits.