0
import pylrc
import sys
import time
import vlc
import pathlib
import signal
import argparse
import tkinter
# Check that the user has specified the .lrc file
if (len(sys.argv) != 2):
  exit(0)

parser = argparse.ArgumentParser()
parser.add_argument('input_lrc', help='')
args = parser.parse_args()
# Parse the .lrc file using pylrc
fp = open(args.input_lrc,'r') 
lrc_string = ''.join(fp.readlines())
fp.close()

window = tkinter.Tk()
window.title("Scores")
window.geometry("800x1000")

# Creating our text widget.
sample_text = tkinter.Entry(window)
sample_text.pack()

subs = pylrc.parse(lrc_string)

# Generate the .mp3 filename
# an alternative is https://stackoverflow.com/questions/678236/how-to-get-the-filename-without-the-extension-from-a-path-in-python
# filename = pathlib.PurePosixPath(sys.argv[1]).stem
filename = args.input_lrc.split('\\')[-1].split('.')[0]
mp3_file = filename + '.mp3'

def SongFinished(event):
    global song_has_finished
    print("Event reports - finished")
    song_has_finished = True
    # Show the cursor
    sys.stdout.write("\033[?25h")

song_has_finished = False

# Prepare VLC
instance = vlc.Instance()
player = instance.media_player_new()
media = instance.media_new_path(mp3_file) #Your audio file here
player.set_media(media)
events = player.event_manager()
events.event_attach(vlc.EventType.MediaPlayerEndReached, SongFinished)

# handle ctrl-c
def sigint_handler(signum, frame):
    player.stop()
    # Show cursor
    sys.stdout.write("\033[?25h")
    exit(0)

signal.signal(signal.SIGINT, sigint_handler)

# Start playing the song
print('Playing "' + subs.title + '" by "' + subs.artist + '"')
player.play()

# Hide the cursor
sys.stdout.write("\033[?25l")

line = 0
num_lines = len(subs)
line_printed = False

# wait for the song to finish
while song_has_finished == False:
    sec = player.get_time() / 1000

    # should we show the next lyrics?
    if line+1 == num_lines or sec < subs[line+1].time:
        # make sure that we only show the lyric once
        if line_printed == False:
            print("\r" + subs[line].text.rstrip() + " " * (60 - len(subs[line].text)), end='', flush=True)
            
            sample_text.insert(0,"\r" + subs[line].text.rstrip() + " " * (60 - len(subs[line].text)))
            # Creating the function to set the text
            # with the help of button
            
            


            line_printed = True
    else:
        line += 1
        line_printed = False

    # try to reduce the CPU usage a bit...
    time.sleep(0.1)
    window.mainloop()

So as shown above I have created an textbox to show the lyrics. The timed lyric have been created and saved as lrc. It works when the tkinter interface is not in use and shows the output as needed in cmd.

I have shifted the position of the mainloop and the windows to get different output. The output I need is the printing of the timed lyric according to time in the tkinter interface without no user intervention. (Press of a button etc)

  • Avoid using while loop in a tkinter application. Also calling `window.mainloop()` inside the while loop is an issue because it will block the while loop. – acw1668 Jun 19 '23 at 10:33
  • I need the loop inorder to get the lyrics of the song line by line. Is there anything that can be done in such a case? – themadhatter Jun 19 '23 at 10:34
  • Since you're using Tk, you're not really in control of the main loop and order of execution; use `.after()` to schedule your lyric-showing function to be repeatedly called e.g. every 0.1 seconds, and update the lyric shown in there. – AKX Jun 19 '23 at 10:41

1 Answers1

0

Maybe something like this (I can't test this since I don't have any LRC files at hand) – the idea is that window.after() schedules Tk to call the lyric function every 0.1 seconds.

I also took the liberty of simplifying your argument parsing and so on, and using finally: to clean up the player instead of a sigint handler.

import argparse
import pathlib
import sys
import tkinter

import pylrc
import vlc

parser = argparse.ArgumentParser()
parser.add_argument("input_lrc", required=True)
args = parser.parse_args()
lrc_path = pathlib.Path(args.input_lrc)
mp3_path = lrc_path.with_suffix(".mp3")
subs = pylrc.parse(lrc_path.read_text())

window = tkinter.Tk()
window.title("Scores")
window.geometry("800x1000")

sample_text = tkinter.Entry(window)
sample_text.pack()

# Prepare VLC
instance = vlc.Instance()
player = instance.media_player_new()
media = instance.media_new_path(str(mp3_path))
player.set_media(media)
events = player.event_manager()
events.event_attach(vlc.EventType.MediaPlayerEndReached, lambda: window.destroy())

# Start playing the song
print('Playing "' + subs.title + '" by "' + subs.artist + '"')
player.play()

last_time_printed = 0


def update_lyrics():
    global last_time_printed
    sec = player.get_time() / 1000
    next_lyric = next((s for s in subs if last_time_printed <= s.time < sec), None)
    if next_lyric and last_time_printed < next_lyric.time:
        sample_text.insert(0, "\r" + next_lyric.text.ljust(60))
        last_time_printed = next_lyric.time
    window.after(100, update_lyrics)  # Schedule next call


try:
    sys.stdout.write("\033[?25l")  # Hide cursor
    window.after(100, update_lyrics)  # Schedule first update call
    window.mainloop()
finally:
    sys.stdout.write("\033[?25h")  # Show cursor
    player.stop()
AKX
  • 152,115
  • 15
  • 115
  • 172
  • Hello, so I tried it out. Initially these were the error that appeared. `events.event_attach(vlc.EventType.MediaPlayerEndReached, lambda: window.destroy()) raise VLCException("%s required: %r" % ('argument', callback))` `parser.add_argument("input_lrc", required=True) TypeError: 'required' is an invalid argument for positionals` Thses were the errors that appeared. Once I fixed that I got an output but the bug was that timing of the lyrics in text file and the outputed printing of the lyrics were completely different. Id there any chance you might know why? – themadhatter Jun 19 '23 at 11:13
  • Also if possible could you explain this part? next_lyric = next((s for s in subs if s.time >= sec), None) if next_lyric and last_time_printed < next_lyric.time: sample_text.insert(0, "\r" + next_lyric.text.ljust(60)) last_time_printed = next_lyric.time Thankyou for your help and I am extremely grateful for it. – themadhatter Jun 19 '23 at 11:14
  • Oops, there's actually a bug in the `next` thing – it should of course be `if s.time <= sec` (e.g. get the first current-or-past lyric). – AKX Jun 19 '23 at 11:25
  • Earlier it changed but now it remains the same showing only the first line of lrc – themadhatter Jun 19 '23 at 16:24
  • @themadhatter Sorry, shouldn't have written this answer when dead tired. Fixed the `next` expression (hopefully) - the idea is to find the next unprinted lyric. – AKX Jun 20 '23 at 05:25
  • 1
    So the value of last_time_printed was not changing so I made this change `next_lyric = next((s for s in subs if last_time_printed < s.time < sec), None)` and it works now. Thanks for the help. – themadhatter Jun 20 '23 at 07:14