Search code examples
pythonpython-3.xkivylagkivy-language

Kivy unresponsive and unable to change text in a textbox


So this is a part of my code, and in theory, it should be working well because I could not replicate the problem anywhere else.

The problem is that while the print(string) works very well and prints in order, when I try to make a kivy interface and instead print the results on to a textbox, the interface works for a moment, then goes all laggy af then Window tells me that the program is unresponsive. But if I look at the print results, they are working just fine.

Also when I wait for the program to finish, when it finishes, it it spits out all the text in one big heap and then finishes.

So is there any way to make textbox print out results in the same order as a normal print would work?

ex)

normal print: print('hello')

textbox print: self.work.text += '\nhello'

P.S. If you need to know the functions that is used in this code, please ask

Python code

import kivy
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.textinput import TextInput
from kivy.uix.button import Button
from kivy.uix.widget import Widget
from kivy.properties import ObjectProperty
from kivy.core.window import Window
from kivy.uix.togglebutton import ToggleButton
Window.clearcolor = (1, 1, 1, 1)
from kivy.uix.popup import Popup
from datetime import date
import requests
from bs4 import BeautifulSoup
import sys
import urllib.request
import urllib.parse
import re
import os
import time
from pytube import YouTube
from pydub import AudioSegment
AudioSegment.converter = "/FFmpeg/bin/ffmpeg.exe"
import eyed3

############################### functions #########################################

just some functions that work

################################################################################################################
class MyGrid(Widget):
    # url = ObjectProperty(None)
    # mod = ObjectProperty(None)
    # submit = ObjectProperty(None)

    def submit(self):

        self.work.text='starting...'

        if self.mod.state=='down':
            info=get_name_list(self.url.text,'single')
            print(info)
            self.high_audio(info)
        
        if self.mod.state=='normal':
            info=get_name_list(self.url.text,'playlist')
            print(info)
            self.high_audio(info)
        
        

    def high_audio(self,song_album_artist_list):

        for item in song_album_artist_list:
            song = str(item[0])
            artist_name = str(item[1])
            album = str(item[2])
            
            print(song+" : "+artist_name+" : "+album)
            self.work.text += song+" : "+artist_name+" : "+album
            print('............................')

            youtube_url=get_top_url(song,artist_name)

            # print(youtube_url)

            download_high_audio(youtube_url,song)

            convert(youtube_url,song)


            self.work.text += 'converting finished, applying metadata...'

            song_file = eyed3.load(output_path+"/"+song+".mp3")
            song_file.tag.artist = artist_name
            song_file.tag.album = album

            self.work.text += 'searching music...'

            art=get_music_inf(song,artist_name,album)

            if art=='':
                art=get_album_art_high(song,artist_name,album)

            if art=='':
                art=get_album_art_low(song,artist_name,album)
            
            if art=='':
                self.work.text += '****************************No artwork availiable****************************'
            
            self.work.text+=art

            try:
                response = requests.get(art)
                imagedata = response.content
                song_file.tag.images.set(3,imagedata,"image/png",u"None")
                song_file.tag.save()
            except requests.exceptions.MissingSchema:
                pass


            
            song_file.tag.save()

            self.work.text+='done'




class MyApp(App):
    def build(self):
        return MyGrid()



if __name__ == "__main__":
    MyApp().run()

Kivy code


<RoundButton@Button>:
    background_color: 0,0,0,0
    canvas.before:
        Color:
            rgba: (.7,.7,.7,0.7) if self.state=='normal' else (0.82,0.96,0.92,1)
        RoundedRectangle:
            pos: self.pos
            size: self.size
            radius: [10,]

<RoundToggleButton@ToggleButton>:
    background_color: 0,0,0,0
    canvas.before:
        Color:
            rgba: (.7,.7,.7,0.7) if self.state=='normal' else (0.82,0.96,0.92,1)
        RoundedRectangle:
            pos: self.pos
            size: self.size
            radius: [10,]

<MyTextInput@TextInput>:
    background_normal: "textinput.png"
    background_color: (0.82,0.96,0.92,1) if self.focus else (1,1,1,0.5)

<MyGrid>:

    url:url
    mod:mod
    work:work

    FloatLayout:

        size: root.width, root.height

        
        MyTextInput:
            id: url
            pos_hint: {"x": 0.25, "top":0.85}
            size_hint: 0.5,0.1
            text:"Paste url"

        RoundButton:
            text: "submit"
            on_press: root.submit()
            pos_hint: {"x":0.4, "top":0.7}
            size_hint: 0.2,0.1

        RoundToggleButton:
            id: mod
            text: 'Playlist' if self.state=='normal' else 'Single'
            pos_hint: {"x":0.4, "top":0.45}
            size_hint: 0.2,0.1
        
        MyTextInput:
            id:work
            text:''
            pos_hint: {"x": 0.25,"top":0.3}
            size_hint: 0.5,0.25

Can somebody please tell me what the problem is? I wasted almost a day trying to figure it out :(


Solution

  • When you run print(), what actually happens is the text gets output by the program as a stream of bytes, and then some other program (i.e. your terminal) can inspect that stream and display it as characters. It doesn't matter what else the Python program does, because that byte stream is external to the rest of its control flow.

    When you draw your text with a gui, the model is different - setting the text of the TextInput tells it what to do, but the actual rendering of the text is also part of the program's flow.

    When you run lots of code all at once, you block that flow, so no Kivy code for updating the gui runs between your function starting and all the code finishing so the function returns.

    The solution is to not block the gui. Either split your function into chunks, each of which calls the next one using Clock.schedule_once, or run your long-running function call in a thread.