How to add a navigation button to this app

My previous post - this is a follow up: Is there a way to open an interactive text editor in streamlit or using streamlit components?

  • This is a transcription labelling app and I want to seamlessly navigate to the next file by adding a button and navigating to a previous file would be a HUGE plus.
  • Once I press the button, the updated transcript will be saved to a file - that code I haven’t added yet. I tried adding the add_selectbox in a infinite while loop - it doesn’t move forward to the next file. Without the - the code moves to the next file erratically.
  • I want to also SAVE my state and whenever I run the app next - It starts from my previously left file.
        def RenderFile(element, TranscriptPath,WavsPath):
               TranscriptFile = os.path.join(TranscriptPath,element.split("\\")[-1].replace(".wav",".txt"))    
                with open(TranscriptFile,'r', encoding='utf-8') as f:
                  data = f.read()
                key = element.split("\\")[-1]
                st.subheader("Name of File = " + key)
                st.audio(open(element, 'rb').read(), format='audio/wav')
                content = st_ace(
                value = data,
                theme=st.sidebar.selectbox("Theme.", options=THEMES, key=key),
                font_size=st.sidebar.slider("Font size.", 5, 24, 24, key=key),
                tab_size=st.sidebar.slider("Tab size.", 1, 8, 4, key=key),
                show_gutter=st.sidebar.checkbox("Show gutter.", value=True, key=key),
                show_print_margin=st.sidebar.checkbox("Show print margin.", value=True,key=key),
                wrap=st.sidebar.checkbox("Wrap enabled.", value=True,key=key),
                key=key
                )
                add_selectbox = st.sidebar.selectbox(
                "Are you done with the file?",
                ("No", "Yes"), key=key)
                st.title(content)
                while(add_selectbox!= "Yes"):
                     i = 0
                     i = i+1
                     if add_selectbox == "Yes":
                         break
                if add_selectbox == "Yes":
                    st.warning("Loading next file")   
                    return
        def main():
            st.sidebar.title("Labeling Data")
            st.title("Transcription Labeling App")
            TranscriptPath =  # PATH TO THE TRANSCRIPTS
            WavsPath =    # PATH TO THE WAVS + *  
            WavsList = glob.glob(WavsPath)[0:5]
            session_state = SessionState.get(CurrentIndex=0, AudioList = WavsList)
            if session_state.CurrentIndex == len(session_state.AudioList):
                st.warning("DONE!")
                st.stop()
            RenderFile(session_state.AudioList[session_state.CurrentIndex], TranscriptPath, WavsPath)
             `indent preformatted text by 4 spaces`
            session_state.CurrentIndex = session_state.CurrentIndex + 1    

@andfanilo

Bump

Hi,

Reworked your code a bit, this should work at least if you don’t exit your tab. This is Streamlit >= 0.65.

SessionState.py
# https://gist.github.com/FranzDiebold/898396a6be785d9b5ca6f3706ef9b0bc
"""Hack to add per-session state to Streamlit.

Works for Streamlit >= v0.65

Usage
-----

>>> import SessionState
>>>
>>> session_state = SessionState.get(user_name='', favorite_color='black')
>>> session_state.user_name
''
>>> session_state.user_name = 'Mary'
>>> session_state.favorite_color
'black'

Since you set user_name above, next time your script runs this will be the
result:
>>> session_state = get(user_name='', favorite_color='black')
>>> session_state.user_name
'Mary'

"""

import streamlit.report_thread as ReportThread
from streamlit.server.server import Server


class SessionState():
    """SessionState: Add per-session state to Streamlit."""
    def __init__(self, **kwargs):
        """A new SessionState object.

        Parameters
        ----------
        **kwargs : any
            Default values for the session state.

        Example
        -------
        >>> session_state = SessionState(user_name='', favorite_color='black')
        >>> session_state.user_name = 'Mary'
        ''
        >>> session_state.favorite_color
        'black'

        """
        for key, val in kwargs.items():
            setattr(self, key, val)


def get(**kwargs):
    """Gets a SessionState object for the current session.

    Creates a new object if necessary.

    Parameters
    ----------
    **kwargs : any
        Default values you want to add to the session state, if we're creating a
        new one.

    Example
    -------
    >>> session_state = get(user_name='', favorite_color='black')
    >>> session_state.user_name
    ''
    >>> session_state.user_name = 'Mary'
    >>> session_state.favorite_color
    'black'

    Since you set user_name above, next time your script runs this will be the
    result:
    >>> session_state = get(user_name='', favorite_color='black')
    >>> session_state.user_name
    'Mary'

    """
    # Hack to get the session object from Streamlit.

    session_id = ReportThread.get_report_ctx().session_id
    session_info = Server.get_current()._get_session_info(session_id)

    if session_info is None:
        raise RuntimeError('Could not get Streamlit session object.')

    this_session = session_info.session

    # Got the session object! Now let's attach some state into it.

    if not hasattr(this_session, '_custom_session_state'):
        this_session._custom_session_state = SessionState(**kwargs)

    return this_session._custom_session_state


def sync():
    session_id = ReportThread.get_report_ctx().session_id
    session_info = Server.get_current()._get_session_info(session_id)

    if session_info is None:
        raise RuntimeError('Could not get Streamlit session object.')

    this_session = session_info.session
    this_session.request_rerun()


__all__ = ['get', 'sync'] 
app.py
import glob
import SessionState
import streamlit as st


def render_file(wav_path, transcript_path):
    st.markdown(f"Currently rendering audio {wav_path}")

    if st.sidebar.button("Go to next file"):
        # write streamlit ace transcript to disk
        SessionState.get().current_index += 1
        SessionState.sync()


def main():
    st.title("Transcription Labeling App")
    st.sidebar.title("Labeling Data")

    transcript_path = "data/transcripts"
    wavs_path = "data/wav/*"
    wavs_list = glob.glob(wavs_path)[0:5]  # glob is ordered

    session_state = SessionState.get(current_index=0)

    if session_state.current_index == len(wavs_list):
        st.warning("DONE!")
        st.stop()

    render_file(wavs_list[session_state.current_index], transcript_path)


if __name__ == "__main__":
    main()

If you want to save your state for if you exit your tab and come back, the dirty fast option is you’ll need to store the current_index somewhere (like on disk) and when rerunning the app initialize current_index with value in file or disk, else 0.

Thank you so much!!

Also this might be a caching issue or something but sometimes when I load the audio file it gives this error

2020-09-16 18:10:21.019 MediaFileManager: Missing file 0670b43871441c5fbebcbf9e65ebffa45c52316da627bf67bcf88992

The file is present - is it a cache miss or something else?

So the issue is - it deletes the files after a session is run - any work around?

Hmmm are you able to produce a minimal example? I generally see the MediaFileManager problem on images, first time I see this on audio files.

Ah, actually someone else experienced this : https://github.com/streamlit/streamlit/issues/1294#issuecomment-688904800. Would you be able to post your code there so it can be reproduced? Thanks! In the meantime you could try this workaround.

(actually @randyzwitch should we rename the GH issue or build a new one to account for disappearing refs of audio too with images ?)

Fanilo

I should disable cache for the page?
Is that the solution you are referring to?

Hmmm so everytime you use st.image or st.audio, the image/audio file is put in a MediaFileManager and then Streamlit asks for a reference to the image/audio through it. Whenever you session reruns (basically when you press next), the MediaFileManager considers the file is not needed anymore and kills its reference, hence the missing file.

So, thinking out loud, that may be because of my SessionState.sync() which asks for a rerun after an immediate rerun, maybe on first rerun Streamlit builds a reference to your new audio file, then I call rerun through the sync and maybe Streamlit kills the reference to the next audio, and then you get a missing file… :confused: does the bug happen if you remove the SessionState.sync() part ?

Could you write your code with the audio part here?

I will check but the issue persisted before in my previous code as well.
One dirty solution would be adding a reload button and re-rendering st.audio - is it possible I can render in place of the existing component or sth?

Managed to add all the state and everything - runs pretty smoothly and uses pickles to get state from.

1 Like