Skip to content

Fix for reading certain Ogg files on Linux via file-like objects.#436

Merged
psobot merged 2 commits intomasterfrom
psobot/fix-errno
Oct 1, 2025
Merged

Fix for reading certain Ogg files on Linux via file-like objects.#436
psobot merged 2 commits intomasterfrom
psobot/fix-errno

Conversation

@psobot
Copy link
Copy Markdown
Member

@psobot psobot commented Sep 29, 2025

This PR fixes a subtle bug where certain Ogg Vorbis files can fail to decode when passed to Pedalboard via Python file-like objects, particularly on Linux.

The bug stems from an interaction between three layers of code:

  1. When Pedalboard reads audio from a Python file-like object (i.e.: a network stream, custom wrapper, etc), it calls Python methods like read(), seek(), and tell().
  2. These Python objects can internally call other native C/C++ code that sets the thread-local errno variable as part of normal operation, but this error is not enough to cause the Python wrapper(s) to throw exceptions.
  3. The libvorbis decoder (specifically in vorbisfile.c) checks errno after I/O operations to detect errors:
    errno=0;  // Clear errno before the read
    // ... perform read operation ...
    if(bytes==0 && errno)return(-1);  // Treat errno!=0 as an error

This results in valid Ogg Vorbis files failing to parse, as - if that last libvorbis read returns 0 bytes, but some other code set errno - then the entire read call fails. This can be spurred on by appending a couple null bytes to the end of the file, which I do in test here.

@psobot psobot requested review from hyperc54 and tdhopper September 29, 2025 23:43
@psobot psobot added the bug Something isn't working label Sep 29, 2025
af.write(np.random.rand(2, 44100))

# Add some random garbage past the end of the file to trigger the bug:
buf.write(b"\x00\x00")
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This was the only reliable way I could find to repro the bug in test, apart from including a proprietary audio file as part of the tests. Trailing null bytes should not prevent the file from being parsed.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

If it's supposed to trigger the bug but now this PR fixed it, should we actually test this with a simple

    with pedalboard.io.AudioFile(buf) as af:  # <- the buffer with garbage in it.. which should now succeed parsing  without any change
        assert af.samplerate == 44100  

and we can keep the other assert with a different but perfectly fine buffer

    with pedalboard.io.AudioFile(ErrnoTriggeringFileLike(ok_buffer_made_not_ok_with_wrapper_class)) as af:
        assert af.samplerate == 44100

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Not sure I fully follow - the code here does fail on Linux if I comment out the errno = 0 fix in the code under test.

Copy link
Copy Markdown
Collaborator

@hyperc54 hyperc54 Oct 2, 2025

Choose a reason for hiding this comment

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

the code here does fail on Linux if I comment out the errno = 0 fix in the code under test.

This makes sense 👍

What I was challenging is this part:

    # Add some random garbage past the end of the file to trigger the bug:
    buf.write(b"\x00\x00")

I would argue you don't need to add random garbage because the test buffer is wrapped in ErrnoTriggeringFileLike which would trigger the bug in any case with any buffer, clean or not. (If I understand the class properly)

I was then suggesting to actually test without the ErrnoTriggeringFileLike and an unhealthy buffer :)

not important though!

Copy link
Copy Markdown
Collaborator

@hyperc54 hyperc54 left a comment

Choose a reason for hiding this comment

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

Great dig and explanation, thank you

af.write(np.random.rand(2, 44100))

# Add some random garbage past the end of the file to trigger the bug:
buf.write(b"\x00\x00")
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

If it's supposed to trigger the bug but now this PR fixed it, should we actually test this with a simple

    with pedalboard.io.AudioFile(buf) as af:  # <- the buffer with garbage in it.. which should now succeed parsing  without any change
        assert af.samplerate == 44100  

and we can keep the other assert with a different but perfectly fine buffer

    with pedalboard.io.AudioFile(ErrnoTriggeringFileLike(ok_buffer_made_not_ok_with_wrapper_class)) as af:
        assert af.samplerate == 44100

@psobot psobot merged commit ab36dbb into master Oct 1, 2025
36 checks passed
@psobot psobot deleted the psobot/fix-errno branch October 1, 2025 18:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants