Skip to content

Instantly share code, notes, and snippets.

@yodaluca23
Last active March 21, 2025 10:24
Show Gist options
  • Select an option

  • Save yodaluca23/82ab1129e12f39e30c8e760a8c853c1f to your computer and use it in GitHub Desktop.

Select an option

Save yodaluca23/82ab1129e12f39e30c8e760a8c853c1f to your computer and use it in GitHub Desktop.

Revisions

  1. yodaluca23 revised this gist Feb 26, 2025. 1 changed file with 103 additions and 70 deletions.
    173 changes: 103 additions & 70 deletions BeautifulLyricsSaveLyrics.py
    Original file line number Diff line number Diff line change
    @@ -3,63 +3,98 @@
    import json
    import re
    from bs4 import BeautifulSoup
    from mutagen import File as MutagenFile
    from mutagen.mp4 import MP4

    # Function to load configuration from BLconfig.txt
    # --- Configuration for A2 extension and gap text ---
    def load_config():
    if os.path.exists('BLconfig.txt'):
    with open('BLconfig.txt', 'r', encoding='utf-8') as config_file:
    config = json.load(config_file)
    return config.get('naming_format'), config.get('useA2'), config.get('gapText')
    return None, None, None
    return config.get('useA2'), config.get('gapText')
    return None, None

    # Function to save configuration to BLconfig.txt
    def save_config(naming_format, useA2, gapText):
    def save_config(useA2, gapText):
    with open('BLconfig.txt', 'w', encoding='utf-8') as config_file:
    json.dump({'naming_format': naming_format, 'useA2': useA2, 'gapText': gapText}, config_file, ensure_ascii=False)
    json.dump({'useA2': useA2, 'gapText': gapText}, config_file, ensure_ascii=False)

    # Load naming format and useA2 from BLconfig.txt if it exists
    naming_format, useA2, gapText = load_config()

    if not naming_format:
    naming_format = input("Enter the naming format (use %A for artist and %T for title): ")
    useA2, gapText = load_config()
    if useA2 is None or gapText is None:
    useA2 = input("Should use A2 extension (Enhanced LRC format) if available? (yes/no): ").strip().lower() == 'yes'
    gapText = input('What text should be displayed for instrumental sections. (Enter "MusicNote" for a music note ♪): ').strip()
    if gapText.lower().replace(' ', '') == "musicnote":
    gapText = "♪"
    save_config(naming_format, useA2, gapText)
    save_config(useA2, gapText)

    # Ask the user if they want to override existing files
    override_existing = input("Do you want to override existing files? (yes/no): ").strip().lower() == 'yes'

    # List of supported file extensions
    supported_extensions = [
    ".3gp", ".aa", ".aac", ".aax", ".act", ".aiff", ".alac", ".amr", ".ape", ".au", ".awb", ".dss",
    ".dvf", ".flac", ".gsm", ".iklax", ".ivs", ".m4a", ".m4b", ".m4p", ".mmf", ".movpkg", ".mp3",
    ".mpc", ".msv", ".nmf", ".ogg", ".oga", ".mogg", ".opus", ".ra", ".rm", ".raw", ".rf64",
    ".sln", ".tta", ".voc", ".vox", ".wav", ".wma", ".wv", ".webm", ".8svx", ".cda"
    supported_extensions = supported_extensions = [
    ".asf",
    ".wma",
    ".flac",
    ".mp4",
    ".m4a",
    ".ape",
    ".mp3",
    ".mpc",
    ".opus",
    ".oga",
    ".spx",
    ".ogv",
    ".ogg",
    ".tta",
    ".wv",
    ".ofr",
    ".aiff",
    ".aif"
    ]

    def extract_artist_and_song(filename, naming_format):
    naming_format = naming_format + "."
    placeholders = {
    '%A': '(?P<artist>.+?)',
    '%T': '(?P<title>.+?)'
    }
    escaped_format = re.escape(naming_format)
    for placeholder, pattern in placeholders.items():
    escaped_format = escaped_format.replace(re.escape(placeholder), pattern)
    pattern = re.compile(escaped_format)
    match = pattern.match(filename)
    if match:
    artist = match.group('artist')
    title = match.group('title')
    return artist.strip(), title.strip()
    else:
    filename = filename.split('.')[0]
    print(f"The filename '{filename}' does not match the naming format '{naming_format}'")
    artist = "unknown_artist"
    title = "unknown_title"
    return artist.strip(), title.strip()
    def get_metadata(filepath):
    """
    Extracts artist and title metadata from an audio file.
    Supports common audio formats and m4a (MP4) files.
    """
    try:
    # For m4a files, use MP4-specific tags.
    if filepath.lower().endswith('.m4a'):
    audio = MP4(filepath)
    tags = audio.tags
    # The MP4 tags for artist and title are typically stored with these keys:
    artist = tags.get('\xa9ART', [None])[0]
    title = tags.get('\xa9nam', [None])[0]
    else:
    audio = MutagenFile(filepath)
    if audio is None or not audio.tags:
    print(f"Could not read metadata from {filepath}")
    return None, None
    tags = audio.tags
    artist = None
    title = None
    # Try common tag keys for MP3 and similar files.
    if 'TPE1' in tags:
    artist = tags['TPE1'].text[0]
    elif 'artist' in tags:
    artist = tags['artist'][0] if isinstance(tags['artist'], list) else tags['artist']

    if 'TIT2' in tags:
    title = tags['TIT2'].text[0]
    elif 'title' in tags:
    title = tags['title'][0] if isinstance(tags['title'], list) else tags['title']

    # Fallback: iterate over all tags
    if not artist or not title:
    for key, value in tags.items():
    key_lower = key.lower()
    if not artist and "artist" in key_lower:
    artist = value[0] if isinstance(value, list) else value
    if not title and "title" in key_lower:
    title = value[0] if isinstance(value, list) else value

    return (artist.strip() if artist else None), (title.strip() if title else None)
    except Exception as e:
    print(f"Error reading metadata from {filepath}: {e}")
    return None, None

    def get_bearer_token():
    fetch_url = "https://open.spotify.com"
    @@ -97,7 +132,7 @@ def search_spotify(artist, song, token):
    def fetch_lyrics(track_id, token):
    url = f'https://beautiful-lyrics.socalifornian.live/lyrics/{track_id}'
    headers = {
    'authorization': 'Bearer {token}'
    'Authorization': f'Bearer {token}'
    }
    response = requests.get(url, headers=headers)
    if response.status_code == 200 and response.headers.get('content-length') != '0':
    @@ -134,7 +169,7 @@ def add_empty_timestamp_if_gap(start_time, gapText):
    lyrics.append(f"[{timestamp}] {line.strip()}")
    prev_end_time = item['EndTime']
    if 'Background' in item:
    print("This song has Background with Type Line, I was not able to find this in testing so I don't know the structure, please report this song, so I may add support for it.\n https://gist.github.com/yodaluca23/82ab1129e12f39e30c8e760a8c853c1f")
    print("This song has Background with Type Line, please report this song for further support.")
    elif data['Type'] == 'Syllable':
    if useA2:
    for item in data['Content']:
    @@ -144,20 +179,17 @@ def add_empty_timestamp_if_gap(start_time, gapText):
    syllables = item['Lead']['Syllables']
    line = ''
    timestamp = convert_to_lrc_timestamp(start_time)
    previous_is_part_of_word = False # Initialize to False or appropriate default value
    previous_is_part_of_word = False
    for syllable in syllables:
    syllable_text = syllable['Text']
    syllable_timestamp = convert_to_lrc_timestamp(syllable['StartTime'])

    if previous_is_part_of_word:
    line += f"{syllable_text}"
    else:
    line += f" <{syllable_timestamp}> {syllable_text}"
    # Update the previous_is_part_of_word for the next iteration
    previous_is_part_of_word = syllable['IsPartOfWord']
    lyrics.append(f"[{timestamp}]{line.strip()}")
    prev_end_time = item['Lead']['EndTime']

    if 'Background' in item:
    for bg in item['Background']:
    start_time = bg['StartTime']
    @@ -217,36 +249,37 @@ def save_lyrics(lrc_filename, lyrics_body, is_time_synced, filename):
    with open(lrc_filename, 'w', encoding='utf-8') as lrc_file:
    lrc_file.write("\n".join(lyrics_body))
    if is_time_synced:
    filename = filename.split('.')[0]
    print(f"Saved time-synced lyrics for {filename}")
    base = os.path.splitext(filename)[0]
    print(f"Saved time-synced lyrics for \'{base}\'")
    else:
    filename = filename.split('.')[0]
    print(f"Saved non-time-synced lyrics for {filename}")
    base = os.path.splitext(filename)[0]
    print(f"Saved non-time-synced lyrics for \'{base}\'")

    def main():
    token = get_bearer_token()
    for item in os.listdir('.'):
    if any(item.endswith(ext) for ext in supported_extensions):
    artist, title = extract_artist_and_song(item, naming_format)
    if artist and title:
    lrc_filename = os.path.splitext(item)[0] + '.lrc'
    if not override_existing and os.path.exists(lrc_filename):
    item = item.split('.')[0]
    print(f"Lyrics for {item} already exist, skipping")
    continue
    try:
    track_id = search_spotify(artist, title, token)
    data = fetch_lyrics(track_id, token)
    if data:
    lyrics = parse_lyrics(data, useA2, gapText)
    save_lyrics(lrc_filename, lyrics, True, item)
    else:
    print(f"No lyrics found for {item}")
    except Exception as e:
    print(f"Could not save lyrics for {item}: {e}")
    else:
    item = item.split('.')[0]
    print(f"Could not extract artist and title from {item}")
    if any(item.lower().endswith(ext) for ext in supported_extensions):
    artist, title = get_metadata(item)
    if not artist or not title:
    print(f"Could not extract metadata (artist/title) from '{item}', skipping.")
    continue

    lrc_filename = os.path.splitext(item)[0] + '.lrc'
    if not override_existing and os.path.exists(lrc_filename):
    base = os.path.splitext(item)[0]
    print(f"Lyrics for '{base}' already exist, skipping")
    continue
    try:
    track_id = search_spotify(artist, title, token)
    data = fetch_lyrics(track_id, token)
    if data:
    lyrics = parse_lyrics(data, useA2, gapText)
    save_lyrics(lrc_filename, lyrics, True, item)
    else:
    print(f"No lyrics found for '{item}'")
    except Exception as e:
    print(f"Could not save lyrics for '{item}': {e}")
    print()

    if __name__ == "__main__":
    main()
  2. yodaluca23 revised this gist Jan 18, 2025. 1 changed file with 12 additions and 8 deletions.
    20 changes: 12 additions & 8 deletions BeautifulLyricsSaveLyrics.py
    Original file line number Diff line number Diff line change
    @@ -7,15 +7,15 @@
    # Function to load configuration from BLconfig.txt
    def load_config():
    if os.path.exists('BLconfig.txt'):
    with open('BLconfig.txt', 'r') as config_file:
    with open('BLconfig.txt', 'r', encoding='utf-8') as config_file:
    config = json.load(config_file)
    return config.get('naming_format'), config.get('useA2'), config.get('gapText')
    return None, None, None

    # Function to save configuration to BLconfig.txt
    def save_config(naming_format, useA2, gapText):
    with open('BLconfig.txt', 'w') as config_file:
    json.dump({'naming_format': naming_format, 'useA2': useA2, 'gapText': gapText}, config_file)
    with open('BLconfig.txt', 'w', encoding='utf-8') as config_file:
    json.dump({'naming_format': naming_format, 'useA2': useA2, 'gapText': gapText}, config_file, ensure_ascii=False)

    # Load naming format and useA2 from BLconfig.txt if it exists
    naming_format, useA2, gapText = load_config()
    @@ -74,7 +74,7 @@ def get_bearer_token():
    return access_token

    def search_spotify(artist, song, token):
    url = f'https://api.spotify.com/v1/search?query=artist%3A+{artist}+track%3A+{song}&type=track&offset=0&limit=1'
    url = f'https://api.spotify.com/v1/search?q=artist%3A{artist}%20track%3A{song}&type=track'
    headers = {
    'Authorization': f'Bearer {token}'
    }
    @@ -94,10 +94,10 @@ def search_spotify(artist, song, token):
    else:
    raise Exception(f"Spotify API request failed with status code {response.status_code}")

    def fetch_lyrics(track_id):
    def fetch_lyrics(track_id, token):
    url = f'https://beautiful-lyrics.socalifornian.live/lyrics/{track_id}'
    headers = {
    'authorization': 'Bearer litterallyAnythingCanGoHereItJustTakesItLOL'
    'authorization': 'Bearer {token}'
    }
    response = requests.get(url, headers=headers)
    if response.status_code == 200 and response.headers.get('content-length') != '0':
    @@ -207,10 +207,14 @@ def add_empty_timestamp_if_gap(start_time, gapText):
    timestamp = convert_to_lrc_timestamp(start_time)
    lyrics.append(f"[{timestamp}] ({line.rstrip()})")
    prev_end_time = bg['EndTime']
    elif data['Type'] == 'Static':
    print("The following song is not compatible with LRC, continuing with static Lyrics.")
    for item in data['Lines']:
    lyrics.append(item['Text'])
    return lyrics

    def save_lyrics(lrc_filename, lyrics_body, is_time_synced, filename):
    with open(lrc_filename, 'w') as lrc_file:
    with open(lrc_filename, 'w', encoding='utf-8') as lrc_file:
    lrc_file.write("\n".join(lyrics_body))
    if is_time_synced:
    filename = filename.split('.')[0]
    @@ -232,7 +236,7 @@ def main():
    continue
    try:
    track_id = search_spotify(artist, title, token)
    data = fetch_lyrics(track_id)
    data = fetch_lyrics(track_id, token)
    if data:
    lyrics = parse_lyrics(data, useA2, gapText)
    save_lyrics(lrc_filename, lyrics, True, item)
  3. yodaluca23 revised this gist Jul 21, 2024. 1 changed file with 27 additions and 21 deletions.
    48 changes: 27 additions & 21 deletions BeautifulLyricsSaveLyrics.py
    Original file line number Diff line number Diff line change
    @@ -4,26 +4,29 @@
    import re
    from bs4 import BeautifulSoup

    # Function to load configuration from config.txt
    # Function to load configuration from BLconfig.txt
    def load_config():
    if os.path.exists('config.txt'):
    with open('config.txt', 'r') as config_file:
    if os.path.exists('BLconfig.txt'):
    with open('BLconfig.txt', 'r') as config_file:
    config = json.load(config_file)
    return config.get('naming_format'), config.get('useA2')
    return None, None
    return config.get('naming_format'), config.get('useA2'), config.get('gapText')
    return None, None, None

    # Function to save configuration to config.txt
    def save_config(naming_format, useA2):
    with open('config.txt', 'w') as config_file:
    json.dump({'naming_format': naming_format, 'useA2': useA2}, config_file)
    # Function to save configuration to BLconfig.txt
    def save_config(naming_format, useA2, gapText):
    with open('BLconfig.txt', 'w') as config_file:
    json.dump({'naming_format': naming_format, 'useA2': useA2, 'gapText': gapText}, config_file)

    # Load naming format and useA2 from config.txt if it exists
    naming_format, useA2 = load_config()
    # Load naming format and useA2 from BLconfig.txt if it exists
    naming_format, useA2, gapText = load_config()

    if not naming_format:
    naming_format = input("Enter the naming format (use %A for artist and %T for title): ")
    useA2 = input("Should use A2 extension (Enhanced LRC format) if available? (yes/no): ").strip().lower() == 'yes'
    save_config(naming_format, useA2)
    gapText = input('What text should be displayed for instrumental sections. (Enter "MusicNote" for a music note ♪): ').strip()
    if gapText.lower().replace(' ', '') == "musicnote":
    gapText = "♪"
    save_config(naming_format, useA2, gapText)

    # Ask the user if they want to override existing files
    override_existing = input("Do you want to override existing files? (yes/no): ").strip().lower() == 'yes'
    @@ -106,14 +109,17 @@ def convert_to_lrc_timestamp(timestamp):
    seconds = timestamp % 60
    return f"{minutes:02}:{seconds:05.2f}"

    def parse_lyrics(data, useA2):
    def parse_lyrics(data, useA2, gapText):
    lyrics = []
    prev_end_time = 0 # Initialize previous end time to zero

    def add_empty_timestamp_if_gap(start_time):
    def add_empty_timestamp_if_gap(start_time, gapText):
    nonlocal prev_end_time
    if start_time - prev_end_time > 5:
    empty_timestamp = f"[{convert_to_lrc_timestamp(prev_end_time)}]"
    if gapText == '':
    empty_timestamp = f"[{convert_to_lrc_timestamp(prev_end_time)}]"
    else:
    empty_timestamp = f"[{convert_to_lrc_timestamp(prev_end_time)}] {gapText}"
    lyrics.append(empty_timestamp)

    if data['Type'] == 'Line':
    @@ -122,7 +128,7 @@ def add_empty_timestamp_if_gap(start_time):
    for item in data['Content']:
    if item['Type'] == 'Vocal':
    start_time = item['StartTime']
    add_empty_timestamp_if_gap(start_time)
    add_empty_timestamp_if_gap(start_time, gapText)
    line = item['Text']
    timestamp = convert_to_lrc_timestamp(start_time)
    lyrics.append(f"[{timestamp}] {line.strip()}")
    @@ -134,7 +140,7 @@ def add_empty_timestamp_if_gap(start_time):
    for item in data['Content']:
    if item['Type'] == 'Vocal':
    start_time = item['Lead']['StartTime']
    add_empty_timestamp_if_gap(start_time)
    add_empty_timestamp_if_gap(start_time, gapText)
    syllables = item['Lead']['Syllables']
    line = ''
    timestamp = convert_to_lrc_timestamp(start_time)
    @@ -155,7 +161,7 @@ def add_empty_timestamp_if_gap(start_time):
    if 'Background' in item:
    for bg in item['Background']:
    start_time = bg['StartTime']
    add_empty_timestamp_if_gap(start_time)
    add_empty_timestamp_if_gap(start_time, gapText)
    syllables = bg['Syllables']
    line = ''
    timestamp = convert_to_lrc_timestamp(start_time)
    @@ -182,7 +188,7 @@ def add_empty_timestamp_if_gap(start_time):
    for item in data['Content']:
    if item['Type'] == 'Vocal':
    start_time = item['Lead']['StartTime']
    add_empty_timestamp_if_gap(start_time)
    add_empty_timestamp_if_gap(start_time, gapText)
    line = ''.join([
    f"{syllable['Text']}{' ' if not syllable['IsPartOfWord'] else ''}"
    for syllable in item['Lead']['Syllables']
    @@ -193,7 +199,7 @@ def add_empty_timestamp_if_gap(start_time):
    if 'Background' in item:
    for bg in item['Background']:
    start_time = bg['StartTime']
    add_empty_timestamp_if_gap(start_time)
    add_empty_timestamp_if_gap(start_time, gapText)
    line = ''.join([
    f"{syllable['Text']}{' ' if not syllable['IsPartOfWord'] else ''}"
    for syllable in bg['Syllables']
    @@ -228,7 +234,7 @@ def main():
    track_id = search_spotify(artist, title, token)
    data = fetch_lyrics(track_id)
    if data:
    lyrics = parse_lyrics(data, useA2)
    lyrics = parse_lyrics(data, useA2, gapText)
    save_lyrics(lrc_filename, lyrics, True, item)
    else:
    print(f"No lyrics found for {item}")
  4. yodaluca23 revised this gist Jul 19, 2024. No changes.
  5. yodaluca23 revised this gist Jul 19, 2024. 1 changed file with 8 additions and 3 deletions.
    11 changes: 8 additions & 3 deletions BeautifulLyricsSaveLyrics.py
    Original file line number Diff line number Diff line change
    @@ -104,7 +104,7 @@ def fetch_lyrics(track_id):
    def convert_to_lrc_timestamp(timestamp):
    minutes = int(timestamp // 60)
    seconds = timestamp % 60
    return f"{minutes}:{seconds:05.2f}"
    return f"{minutes:02}:{seconds:05.2f}"

    def parse_lyrics(data, useA2):
    lyrics = []
    @@ -138,15 +138,20 @@ def add_empty_timestamp_if_gap(start_time):
    syllables = item['Lead']['Syllables']
    line = ''
    timestamp = convert_to_lrc_timestamp(start_time)
    previous_is_part_of_word = False # Initialize to False or appropriate default value
    for syllable in syllables:
    syllable_text = syllable['Text']
    syllable_timestamp = convert_to_lrc_timestamp(syllable['StartTime'])
    if syllable['IsPartOfWord']:
    line += f" {syllable_text}"

    if previous_is_part_of_word:
    line += f"{syllable_text}"
    else:
    line += f" <{syllable_timestamp}> {syllable_text}"
    # Update the previous_is_part_of_word for the next iteration
    previous_is_part_of_word = syllable['IsPartOfWord']
    lyrics.append(f"[{timestamp}]{line.strip()}")
    prev_end_time = item['Lead']['EndTime']

    if 'Background' in item:
    for bg in item['Background']:
    start_time = bg['StartTime']
  6. yodaluca23 revised this gist Jul 19, 2024. 1 changed file with 33 additions and 15 deletions.
    48 changes: 33 additions & 15 deletions BeautifulLyricsSaveLyrics.py
    Original file line number Diff line number Diff line change
    @@ -108,38 +108,52 @@ def convert_to_lrc_timestamp(timestamp):

    def parse_lyrics(data, useA2):
    lyrics = []
    prev_end_time = 0 # Initialize previous end time to zero

    def add_empty_timestamp_if_gap(start_time):
    nonlocal prev_end_time
    if start_time - prev_end_time > 5:
    empty_timestamp = f"[{convert_to_lrc_timestamp(prev_end_time)}]"
    lyrics.append(empty_timestamp)

    if data['Type'] == 'Line':
    if useA2:
    print("The following song is not compatible with A2 extension (Enhanced LRC format), continuing with standard LRC")
    for item in data['Content']:
    if item['Type'] == 'Vocal':
    start_time = item['StartTime']
    add_empty_timestamp_if_gap(start_time)
    line = item['Text']
    timestamp = convert_to_lrc_timestamp(item['StartTime'])
    line = f"[{timestamp}] " + line
    lyrics.append(line.strip())
    timestamp = convert_to_lrc_timestamp(start_time)
    lyrics.append(f"[{timestamp}] {line.strip()}")
    prev_end_time = item['EndTime']
    if 'Background' in item:
    print("This song has Background with Type Line, I was not able to find this in testing so I don't know the structure, please report this song, so I may add support for it.\n https://gist.github.com/yodaluca23/82ab1129e12f39e30c8e760a8c853c1f")
    elif data['Type'] == 'Syllable':
    if useA2:
    for item in data['Content']:
    if item['Type'] == 'Vocal':
    start_time = item['Lead']['StartTime']
    add_empty_timestamp_if_gap(start_time)
    syllables = item['Lead']['Syllables']
    line = ''
    timestamp = convert_to_lrc_timestamp(item['Lead']['StartTime'])
    timestamp = convert_to_lrc_timestamp(start_time)
    for syllable in syllables:
    syllable_text = syllable['Text']
    syllable_timestamp = convert_to_lrc_timestamp(syllable['StartTime'])
    if syllable['IsPartOfWord']:
    line += f" {syllable_text}"
    else:
    line += f" <{syllable_timestamp}> {syllable_text}"
    line = f"[{timestamp}]" + line
    lyrics.append(line.strip())
    lyrics.append(f"[{timestamp}]{line.strip()}")
    prev_end_time = item['Lead']['EndTime']
    if 'Background' in item:
    for bg in item['Background']:
    start_time = bg['StartTime']
    add_empty_timestamp_if_gap(start_time)
    syllables = bg['Syllables']
    line = ''
    timestamp = convert_to_lrc_timestamp(bg['StartTime'])
    timestamp = convert_to_lrc_timestamp(start_time)
    for index, syllable in enumerate(syllables):
    syllable_text = syllable['Text']
    syllable_timestamp = convert_to_lrc_timestamp(syllable['StartTime'])
    @@ -157,27 +171,31 @@ def parse_lyrics(data, useA2):
    line += f" <{syllable_timestamp}> {syllable_text})"
    else:
    line += f" <{syllable_timestamp}> {syllable_text}"
    line = f"[{timestamp}]" + line
    lyrics.append(line.strip())
    lyrics.append(f"[{timestamp}]{line.strip()}")
    prev_end_time = bg['EndTime']
    else:
    for item in data['Content']:
    if item['Type'] == 'Vocal':
    start_time = item['Lead']['StartTime']
    add_empty_timestamp_if_gap(start_time)
    line = ''.join([
    f"{syllable['Text']}{' ' if not syllable['IsPartOfWord'] else ''}"
    for syllable in item['Lead']['Syllables']
    ])
    timestamp = item['Lead']['StartTime']
    line = f"[{convert_to_lrc_timestamp(timestamp)}]" + f" {line}"
    lyrics.append(line.strip())
    timestamp = convert_to_lrc_timestamp(start_time)
    lyrics.append(f"[{timestamp}] {line.strip()}")
    prev_end_time = item['Lead']['EndTime']
    if 'Background' in item:
    for bg in item['Background']:
    start_time = bg['StartTime']
    add_empty_timestamp_if_gap(start_time)
    line = ''.join([
    f"{syllable['Text']}{' ' if not syllable['IsPartOfWord'] else ''}"
    for syllable in bg['Syllables']
    ])
    timestamp = item['Background'][0]['StartTime']
    line = f"[{convert_to_lrc_timestamp(timestamp)}]" + f" ({line.rstrip()})"
    lyrics.append(line.strip())
    timestamp = convert_to_lrc_timestamp(start_time)
    lyrics.append(f"[{timestamp}] ({line.rstrip()})")
    prev_end_time = bg['EndTime']
    return lyrics

    def save_lyrics(lrc_filename, lyrics_body, is_time_synced, filename):
  7. yodaluca23 revised this gist Jul 19, 2024. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions BeautifulLyricsSaveLyrics.py
    Original file line number Diff line number Diff line change
    @@ -130,7 +130,7 @@ def parse_lyrics(data, useA2):
    syllable_text = syllable['Text']
    syllable_timestamp = convert_to_lrc_timestamp(syllable['StartTime'])
    if syllable['IsPartOfWord']:
    line += syllable_text
    line += f" {syllable_text}"
    else:
    line += f" <{syllable_timestamp}> {syllable_text}"
    line = f"[{timestamp}]" + line
    @@ -149,7 +149,7 @@ def parse_lyrics(data, useA2):
    elif index == len(syllables) - 1:
    line += f"{syllable_text})"
    else:
    line += syllable_text
    line += f" {syllable_text}"
    else:
    if index == 0:
    line += f" <{syllable_timestamp}> ({syllable_text}"
  8. yodaluca23 revised this gist Jul 19, 2024. 1 changed file with 2 additions and 0 deletions.
    2 changes: 2 additions & 0 deletions BeautifulLyricsSaveLyrics.py
    Original file line number Diff line number Diff line change
    @@ -109,6 +109,8 @@ def convert_to_lrc_timestamp(timestamp):
    def parse_lyrics(data, useA2):
    lyrics = []
    if data['Type'] == 'Line':
    if useA2:
    print("The following song is not compatible with A2 extension (Enhanced LRC format), continuing with standard LRC")
    for item in data['Content']:
    if item['Type'] == 'Vocal':
    line = item['Text']
  9. yodaluca23 revised this gist Jul 19, 2024. 1 changed file with 28 additions and 25 deletions.
    53 changes: 28 additions & 25 deletions BeautifulLyricsSaveLyrics.py
    Original file line number Diff line number Diff line change
    @@ -70,8 +70,7 @@ def get_bearer_token():
    access_token = tokens['accessToken']
    return access_token

    def search_spotify(artist, song):
    token = get_bearer_token()
    def search_spotify(artist, song, token):
    url = f'https://api.spotify.com/v1/search?query=artist%3A+{artist}+track%3A+{song}&type=track&offset=0&limit=1'
    headers = {
    'Authorization': f'Bearer {token}'
    @@ -117,7 +116,7 @@ def parse_lyrics(data, useA2):
    line = f"[{timestamp}] " + line
    lyrics.append(line.strip())
    if 'Background' in item:
    print("This song has Background with Type Line, I was not able to find this in testing so I don't know the structure, please report this song, so I may add support for it.")
    print("This song has Background with Type Line, I was not able to find this in testing so I don't know the structure, please report this song, so I may add support for it.\n https://gist.github.com/yodaluca23/82ab1129e12f39e30c8e760a8c853c1f")
    elif data['Type'] == 'Syllable':
    if useA2:
    for item in data['Content']:
    @@ -189,26 +188,30 @@ def save_lyrics(lrc_filename, lyrics_body, is_time_synced, filename):
    filename = filename.split('.')[0]
    print(f"Saved non-time-synced lyrics for {filename}")

    # Scan the current directory for supported audio files
    for item in os.listdir('.'):
    if any(item.endswith(ext) for ext in supported_extensions):
    artist, title = extract_artist_and_song(item, naming_format)
    if artist and title:
    lrc_filename = os.path.splitext(item)[0] + '.lrc'
    if not override_existing and os.path.exists(lrc_filename):
    def main():
    token = get_bearer_token()
    for item in os.listdir('.'):
    if any(item.endswith(ext) for ext in supported_extensions):
    artist, title = extract_artist_and_song(item, naming_format)
    if artist and title:
    lrc_filename = os.path.splitext(item)[0] + '.lrc'
    if not override_existing and os.path.exists(lrc_filename):
    item = item.split('.')[0]
    print(f"Lyrics for {item} already exist, skipping")
    continue
    try:
    track_id = search_spotify(artist, title, token)
    data = fetch_lyrics(track_id)
    if data:
    lyrics = parse_lyrics(data, useA2)
    save_lyrics(lrc_filename, lyrics, True, item)
    else:
    print(f"No lyrics found for {item}")
    except Exception as e:
    print(f"Could not save lyrics for {item}: {e}")
    else:
    item = item.split('.')[0]
    print(f"Lyrics for {item} already exist, skipping")
    continue
    try:
    track_id = search_spotify(artist, title)
    data = fetch_lyrics(track_id)
    if data:
    lyrics = parse_lyrics(data, useA2)
    save_lyrics(lrc_filename, lyrics, True, item)
    else:
    print(f"No lyrics found for {item}")
    except Exception as e:
    print(f"Could not save lyrics for {item}: {e}")
    else:
    item = item.split('.')[0]
    print(f"Could not extract artist and title from {item}")
    print(f"Could not extract artist and title from {item}")

    if __name__ == "__main__":
    main()
  10. yodaluca23 created this gist Jul 19, 2024.
    214 changes: 214 additions & 0 deletions BeautifulLyricsSaveLyrics.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,214 @@
    import os
    import requests
    import json
    import re
    from bs4 import BeautifulSoup

    # Function to load configuration from config.txt
    def load_config():
    if os.path.exists('config.txt'):
    with open('config.txt', 'r') as config_file:
    config = json.load(config_file)
    return config.get('naming_format'), config.get('useA2')
    return None, None

    # Function to save configuration to config.txt
    def save_config(naming_format, useA2):
    with open('config.txt', 'w') as config_file:
    json.dump({'naming_format': naming_format, 'useA2': useA2}, config_file)

    # Load naming format and useA2 from config.txt if it exists
    naming_format, useA2 = load_config()

    if not naming_format:
    naming_format = input("Enter the naming format (use %A for artist and %T for title): ")
    useA2 = input("Should use A2 extension (Enhanced LRC format) if available? (yes/no): ").strip().lower() == 'yes'
    save_config(naming_format, useA2)

    # Ask the user if they want to override existing files
    override_existing = input("Do you want to override existing files? (yes/no): ").strip().lower() == 'yes'

    # List of supported file extensions
    supported_extensions = [
    ".3gp", ".aa", ".aac", ".aax", ".act", ".aiff", ".alac", ".amr", ".ape", ".au", ".awb", ".dss",
    ".dvf", ".flac", ".gsm", ".iklax", ".ivs", ".m4a", ".m4b", ".m4p", ".mmf", ".movpkg", ".mp3",
    ".mpc", ".msv", ".nmf", ".ogg", ".oga", ".mogg", ".opus", ".ra", ".rm", ".raw", ".rf64",
    ".sln", ".tta", ".voc", ".vox", ".wav", ".wma", ".wv", ".webm", ".8svx", ".cda"
    ]

    def extract_artist_and_song(filename, naming_format):
    naming_format = naming_format + "."
    placeholders = {
    '%A': '(?P<artist>.+?)',
    '%T': '(?P<title>.+?)'
    }
    escaped_format = re.escape(naming_format)
    for placeholder, pattern in placeholders.items():
    escaped_format = escaped_format.replace(re.escape(placeholder), pattern)
    pattern = re.compile(escaped_format)
    match = pattern.match(filename)
    if match:
    artist = match.group('artist')
    title = match.group('title')
    return artist.strip(), title.strip()
    else:
    filename = filename.split('.')[0]
    print(f"The filename '{filename}' does not match the naming format '{naming_format}'")
    artist = "unknown_artist"
    title = "unknown_title"
    return artist.strip(), title.strip()

    def get_bearer_token():
    fetch_url = "https://open.spotify.com"
    response = requests.get(fetch_url)
    response.raise_for_status()
    html_content = response.text
    soup = BeautifulSoup(html_content, 'html.parser')
    session_element = soup.find(id="session")
    session_html = session_element.get_text()
    tokens = json.loads(session_html)
    access_token = tokens['accessToken']
    return access_token

    def search_spotify(artist, song):
    token = get_bearer_token()
    url = f'https://api.spotify.com/v1/search?query=artist%3A+{artist}+track%3A+{song}&type=track&offset=0&limit=1'
    headers = {
    'Authorization': f'Bearer {token}'
    }
    response = requests.get(url, headers=headers)
    if response.status_code == 200:
    data = response.json()
    if data['tracks']['items']:
    href = data['tracks']['items'][0]['href']
    match = re.search(r'tracks/([a-zA-Z0-9]+)', href)
    if match:
    song_id = match.group(1)
    return song_id
    else:
    raise ValueError("Song ID not found in the href.")
    else:
    raise ValueError("No tracks found for the given artist and song.")
    else:
    raise Exception(f"Spotify API request failed with status code {response.status_code}")

    def fetch_lyrics(track_id):
    url = f'https://beautiful-lyrics.socalifornian.live/lyrics/{track_id}'
    headers = {
    'authorization': 'Bearer litterallyAnythingCanGoHereItJustTakesItLOL'
    }
    response = requests.get(url, headers=headers)
    if response.status_code == 200 and response.headers.get('content-length') != '0':
    return response.json()
    return None

    def convert_to_lrc_timestamp(timestamp):
    minutes = int(timestamp // 60)
    seconds = timestamp % 60
    return f"{minutes}:{seconds:05.2f}"

    def parse_lyrics(data, useA2):
    lyrics = []
    if data['Type'] == 'Line':
    for item in data['Content']:
    if item['Type'] == 'Vocal':
    line = item['Text']
    timestamp = convert_to_lrc_timestamp(item['StartTime'])
    line = f"[{timestamp}] " + line
    lyrics.append(line.strip())
    if 'Background' in item:
    print("This song has Background with Type Line, I was not able to find this in testing so I don't know the structure, please report this song, so I may add support for it.")
    elif data['Type'] == 'Syllable':
    if useA2:
    for item in data['Content']:
    if item['Type'] == 'Vocal':
    syllables = item['Lead']['Syllables']
    line = ''
    timestamp = convert_to_lrc_timestamp(item['Lead']['StartTime'])
    for syllable in syllables:
    syllable_text = syllable['Text']
    syllable_timestamp = convert_to_lrc_timestamp(syllable['StartTime'])
    if syllable['IsPartOfWord']:
    line += syllable_text
    else:
    line += f" <{syllable_timestamp}> {syllable_text}"
    line = f"[{timestamp}]" + line
    lyrics.append(line.strip())
    if 'Background' in item:
    for bg in item['Background']:
    syllables = bg['Syllables']
    line = ''
    timestamp = convert_to_lrc_timestamp(bg['StartTime'])
    for index, syllable in enumerate(syllables):
    syllable_text = syllable['Text']
    syllable_timestamp = convert_to_lrc_timestamp(syllable['StartTime'])
    if syllable['IsPartOfWord']:
    if index == 0:
    line += f"({syllable_text}"
    elif index == len(syllables) - 1:
    line += f"{syllable_text})"
    else:
    line += syllable_text
    else:
    if index == 0:
    line += f" <{syllable_timestamp}> ({syllable_text}"
    elif index == len(syllables) - 1:
    line += f" <{syllable_timestamp}> {syllable_text})"
    else:
    line += f" <{syllable_timestamp}> {syllable_text}"
    line = f"[{timestamp}]" + line
    lyrics.append(line.strip())
    else:
    for item in data['Content']:
    if item['Type'] == 'Vocal':
    line = ''.join([
    f"{syllable['Text']}{' ' if not syllable['IsPartOfWord'] else ''}"
    for syllable in item['Lead']['Syllables']
    ])
    timestamp = item['Lead']['StartTime']
    line = f"[{convert_to_lrc_timestamp(timestamp)}]" + f" {line}"
    lyrics.append(line.strip())
    if 'Background' in item:
    for bg in item['Background']:
    line = ''.join([
    f"{syllable['Text']}{' ' if not syllable['IsPartOfWord'] else ''}"
    for syllable in bg['Syllables']
    ])
    timestamp = item['Background'][0]['StartTime']
    line = f"[{convert_to_lrc_timestamp(timestamp)}]" + f" ({line.rstrip()})"
    lyrics.append(line.strip())
    return lyrics

    def save_lyrics(lrc_filename, lyrics_body, is_time_synced, filename):
    with open(lrc_filename, 'w') as lrc_file:
    lrc_file.write("\n".join(lyrics_body))
    if is_time_synced:
    filename = filename.split('.')[0]
    print(f"Saved time-synced lyrics for {filename}")
    else:
    filename = filename.split('.')[0]
    print(f"Saved non-time-synced lyrics for {filename}")

    # Scan the current directory for supported audio files
    for item in os.listdir('.'):
    if any(item.endswith(ext) for ext in supported_extensions):
    artist, title = extract_artist_and_song(item, naming_format)
    if artist and title:
    lrc_filename = os.path.splitext(item)[0] + '.lrc'
    if not override_existing and os.path.exists(lrc_filename):
    item = item.split('.')[0]
    print(f"Lyrics for {item} already exist, skipping")
    continue
    try:
    track_id = search_spotify(artist, title)
    data = fetch_lyrics(track_id)
    if data:
    lyrics = parse_lyrics(data, useA2)
    save_lyrics(lrc_filename, lyrics, True, item)
    else:
    print(f"No lyrics found for {item}")
    except Exception as e:
    print(f"Could not save lyrics for {item}: {e}")
    else:
    item = item.split('.')[0]
    print(f"Could not extract artist and title from {item}")